ラベル ソフトウェア&プログラム の投稿を表示しています。 すべての投稿を表示
ラベル ソフトウェア&プログラム の投稿を表示しています。 すべての投稿を表示

2025年12月30日火曜日

Nvidia ビデオカードしか画面出力能力の無いデスクトップに LMDE7 をセットアップ

はい。実はあれから3日程でできていたのですが、その後に自動マウントしたドライブを Steam のドライブとして認識させるところでだいぶ躓きまして。更新していませんでした。

LMDE7 に純正ビデオドライバを入れると起動時に CUI になってしまう問題
はい。これは LightDM というログイン画面(ディスプレイマネージャ)が原因です。
で、これを GDM3 というソフトに置き換えれば起動するようになります。

…まあ、これ自体はどこにでも書かれている内容なのですが、なんかコマンドラインが大好きな人の記事が多いのですよねえ…というわけでコマンドラインを最低限に留めた備忘録です。

0.とりあえずクリーンインストール
インストール後のアップデートはひとまずやっておく。
※この時点では Nvidia の純正ドライバーの代わりに nouveau が入っているため普通に起動できる。

1.GDM3 をインストール
「ソフトウェアマネージャー」から Gdm3 をインストールする。
インストールする際に、Gdm3 と LightDM のどちらをデフォルトにするか聞かれるので Gdm3 を選択。

2.起動画面を確認
こんな感じの起動画面になっていたら OK

3.LightDM をアンインストール
「ソフトウェアマネージャー」から LightDM をアンインストールする。
やらなくても良いが万が一にも起動させないようにしたいので念の為。

4.ドライバインストール
ひとまず、コマンドラインで lsmod を打ってみて nouveau がビデオドライバーに使われている事を確認しておきましょう。
その後、「ソフトウェアマネージャー」から Nvidia-settings をインストールする。

5.確認
再起動が無事にできて、 lsmod を流したあとにビデオドライバーから nouveau が消えて nvidia 系のドライバーが入っていれば成功です。

散々つまった Steam のドライブ追加
Steam にローカルドライブ以外の追加記憶装置にゲームをインストールする場合、
設定→ストレージを選択した後、デフォルトでローカルドライブとなっている項目の横のを押してタブを開き「ドライブを追加」を選択します。
「ドライブを追加」という名前自体が罠で、実際はフォルダーを選択するのですが…
このフォルダ、ユーザーが所有権を持っていて入出力及び、実行可能属性を持っていないと何回追加の操作をしても追加されません。

自動起動の失敗談
Linux の自動マウントの仕方はいくらでもネット上にあるので詳しくは書きませんが、私は自動マウントの設定ファイル /etc/fstab の追加を以下のように書きました

UUID=XXXX /media/games ext4 defaults 0 2

最終的にはこれで正解だったのですが、何度も失敗しまして。
まず、凡ミスが

default

とパラメータを記述して起動しなくなりました(最後の s が抜けている)。うーんせめてマウント不良で起動だけはしてくれないものか。また、パラメータを user にしても駄目でした。これは実行不可能属性がつくため…なのですが、名前から動作が想像つかなさすぎる…

あとは、追加したドライブの直下にゲームで使用するフォルダを作り、 chown で所有者を root からユーザーに変え、親フォルダを右クリックして root 権限で入り直して、ゲームで使用するフォルダを右クリック→プロパティー→パーミッションからすべてのアクセス権を持たせれば追加が可能になるはずです。

…うーん長かった。

ちなみに
追加した後で GGST をインストールしてみましたが…うーん?別段 Ryzen7 8700G と大して変わらないですね(セルレンダリングのアウトラインがノイジーなのはスペックゆえかと思っていましたが別に変わらなかったですね)。レイトレーシングがシビアに使われるようなゲームじゃないと変化ないのでしょうか?
とりあえず、そこそこのヘビー級ゲームでも遊べる環境はできましたが…そんなゲーム遊ぶ時間もお金も無いという。いつか役に立つ日がくるのですかね?

2025年11月3日月曜日

超シンプルテキストエディタを作ってみた

コピペもアンドゥもできず、なにかあってもダイアログすら表示しません。
セーブとロードのみできますが、拡張子は .txt のみ。

ソースここから

## TextEditor ##

import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput

from kivy.uix.floatlayout import FloatLayout
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup

from kivy.uix.filechooser import FileChooserIconView
from kivy.uix.filechooser import FileChooserListView

import japanize_kivy

import os
from os.path import join,isdir

pathDef = ("/home/")
SUFFIX = (".txt","*.txt")

#class DirectoryChooser(FileChooserListView):
class DirectoryChooser(FileChooserIconView):
    def is_dir(self,directory,filename):
        return isdir(join(directory,filename))

# [ファイル選択画面へ]
class BtnTransferLoadFile(Button):
    def on_release(self):
        self.parent.DrawLoadFile()

# [ディレクトリ選択/ファイル名決定画面へ]
class BtnTransferSaveFile(Button):
    def on_release(self):
        self.parent.DrawSaveFile()

# [ファイル読み込み]親ウィンドウ全削除→ファイルを読み込んで基本画面
class BtnLoadFile(Button):
    def on_release(self):
        strPath = '\n'.join(self.parent.fc.selection)

        if os.path.exists(strPath):
            with open(strPath,'r') as f:
                self.parent.txEdit.text = f.read()
                self.parent.parent.lFile.text=strPath

        self.parent.DrawBase()

# [ファイル書き込み]親ウィンドウ全削除→ファイルを書き込んで基本画面
class BtnSaveFile(Button):
    def on_release(self):
        while self.parent.txFile.text != "":
            strPath = self.parent.dc.path+os.sep+self.parent.txFile.text+SUFFIX[0]

            with open(strPath,'w') as f:
                outputtext = f.write(self.parent.txEdit.text)
                self.parent.parent.lFile.text = strPath
            break
            
        self.parent.DrawBase()
        
# [基本画面へ]親ウィンドウ全削除→基本画面
class BtnQuit(Button):
    def on_release(self):
        self.parent.DrawBase()
        
# メインフレームの中にあるウィンドウ
class SubDisplay(BoxLayout):
    def __init__(self,**kwargs):
        super(SubDisplay,self).__init__(**kwargs)
        #self.fc=FileChooserListView()
        self.fc=FileChooserIconView()
        self.fc.size_hint_y=7
        self.fc.filters=[SUFFIX[1]]

        self.dc=DirectoryChooser()
        self.dc.size_hint_y=6
        self.dc.filters=[self.dc.is_dir]
        
        self.btQuit=BtnQuit(text='QUIT',size_hint_y=1)
        
        self.txFile =TextInput(text="defaultName",size_hint_y=1,multiline=False)
        self.txEdit =TextInput(text="",size_hint_y=4,multiline=True)
        
    # テキストを表示する基本画面
    def DrawBase(self):
        self.clear_widgets()
        self.orientation='vertical'
               
        b1 =BtnTransferLoadFile(text='LoadFile',size_hint_y=1)
        b2 =BtnTransferSaveFile(text='SaveFile',size_hint_y=1)
        self.add_widget(self.txEdit)
        self.add_widget(b1)
        self.add_widget(b2)

    # ファイルロード画面
    def DrawLoadFile(self):
        self.clear_widgets()
        self.orientation='vertical'

        b1=BtnLoadFile(text='LoadFile',size_hint_y=1)
        
        self.fc.path=os.path.dirname(self.parent.lFile.text)
        self.add_widget(self.fc)
        self.add_widget(b1)
        self.add_widget(self.btQuit)

    # ファイルセーブ画面
    def DrawSaveFile(self):
        self.clear_widgets()
        self.orientation='vertical'
        
        base = os.path.split(self.parent.lFile.text)
        
        self.txFile.text = os.path.splitext(base[1])[0]
        self.dc.path=base[0]

        b1=BtnSaveFile(text='SaveFile',size_hint_y=1)
        
        self.add_widget(self.txFile)
        self.add_widget(self.dc)
        self.add_widget(b1)
        self.add_widget(self.btQuit)

# メインフレーム画面初期化にしか使われない
class Display(BoxLayout):
    orientation='vertical'
    def __init__(self,**kwargs):
        super(Display,self).__init__(**kwargs)
        self.lFile = Label(text=pathDef,size_hint_y=1)
        self.boxSub = SubDisplay(size_hint_y=8)
        self.add_widget(self.lFile)
        self.add_widget(self.boxSub)
        self.boxSub.DrawBase()
        
class TextEditorApp(App):
    def build(self):
        layout = Display()
        return layout

if __name__=='__main__':
    TextEditorApp().run()


ソースここまで

あまり解説する事はありませんが
何年か前に載せたテキスト読み込みプログラムを改造して作りました。
又、kivy の日本語化ライブラリ japanize_kivy も使っています。世の中便利になっているんですね、作者の方に感謝です。
一応 Linux 上で動作確認しているのですが、環境に合わせてデフォルトのディレクトリパスを書き替えてください。

pathDef = ("/home/")

の内容を変えればOKのハズです。

で、なんでこんなモン作ったのか?ですが、ここ最近しょっちゅうアキバに買い物に行ってまして、行くたびに買い物メモを紙に書いて持って行ってたのです。
いや、スマホで良くね?と思ったのですが、私のスマホ、メモ帳入って無かったのですよ。

で、メモ帳アプリを物色していたら「無料版では広告が…」いや、メモ読むのに広告表示されるってなんやねん、となりまして。
それなら自分で作るかと思って作った…のですが、kivy でアプリ作るのも何年かぶりで10日以上かかりました。そして、作ってから気づいたのですよね、いつぞやのアップデートからアンドロイドって野良アプリから内部ファイルの読み書きできなくなっていなかったっけ?と。
仮に buildozer でコンパイルできたとしても、これアンドロイドでは使えないのかもしれませんね。

2025年10月22日水曜日

LMDE7 β版 がリリースされまして

これの元である Debian13 も Mate 版をこっそり試したりはしていたのですが、やっぱり Debian は合わないなあという事で断念していたのですが、LMDE7 β版が出たので再トライ。たしかコレまでのβ版はそのままアップデートしていれば正式版になったハズなので、このまま入れても問題無いだろう…と。

まず問題になったのはどのマシンに入れるのか?
つい先日 Windows10 のサポートが切れた LenovoYoga310 …と思ったのですが、中に入っているデータのバックアップと Celeron 3050 という非力過ぎるマシンでは有意義な結果は出ないだろうとの事で断念。
じゃあ intel N100 辺りのミニPCを中古で買うかとアキバに行ったのですが良いマシンが見つからず…ふと気づいたのですが…だいぶ前に買って CPU を i7 3770 知る人ぞ知るサンディーおじさんの至宝 コレ第三世代だから Ivy でしたね)に換装した富士通 Esprimo が放置されてたなと。

性能を調べると N100 とほぼ互角。Core i7 の 4C/8T が Atom の末裔の 4C/4T に追いつかれるとか凄い時代になったもんだと思うべきか。2011 2012リリースの CPU が未だに現役で使える事を驚くべきか。

タッチドライバ問題
さておき、このデスクトップ、実は骨董品(18年前に購入したと思われる)の15インチ感圧式LCDに繋がってまして。
普通にインストールしたら、タッチセンサが誤動作していました。これは毎回起こる事で、キャリブレーションを治す…
というか、設定を書いた 99-calibration.conf を /etc/X11/xorg.conf.d/ に放り込めば良い。

…で、xinput_calibrator をかけたら…出力の書式が間違ってる。
コレ、5年以上前からある問題で、xinput-calibrator のインストールをやり直すのですが…
出力されたものがこちら
これをそのまま使うと、今度はタッチパネルは動くがマウスが暴走する。

原因は見慣れないコイツ。

これを古い記述法に描き直すとちゃんと動く。
…うーん、次期 Mint 大丈夫だろうか?

Linux と言えば
自称独裁者のリーナスおじさんが又キレたってニュースがちょっと前に出てました。正直、いつもの事…なのですが。
このおじさんを怒らせた「糞コード」。実はAIで書かれたコードらしく、「そんなんいらんやろ」というリンクがアホほど貼られていた為に怒ったとか。
…コードをAIに書かせるのも限度はあると思いますが、成果物のレビューすらできない人間がOSを開発しているとか流石に世も末で、おじさんが怒るのも当たり前だと思います。

2029年に「コンピュータが人間の知性を追い越す」≒シンギュラリティが起こると予測されていますが、人類の知性が退化して後押ししているのだとしたら未来は暗いと思わざるをえません。

2025年10月14日火曜日

シェイプキーとアーマチュアを関連付けて筋肉を表現してみたい3

なんと、一応動作する所までできました。
という訳で、いきなりソースコード

import bpy
import math

class B2S_data:
    def __init__(self):
        self.bone : bpy_types.PoseBone = None
        self.length = 0.0

        # direct full
        self.shape_df : bpy.types.ShapeKey = None
        # direct 1/2
        self.shape_dh : bpy.types.ShapeKey = None
        # direct 1/3
        self.shape_dt : bpy.types.ShapeKey = None
        # direct 1/4
        self.shape_dq : bpy.types.ShapeKey = None

        # inverse full
        self.shape_if : bpy.types.ShapeKey = None
        # inverse 1/2
        self.shape_ih : bpy.types.ShapeKey = None
        # inverse 1/3
        self.shape_it : bpy.types.ShapeKey = None
        # inverse 1/4
        self.shape_iq : bpy.types.ShapeKey = None

# Shape Prefix
preShape = ("b2sdf","b2sdh","b2sdt","b2sdq","b2sif","b2sih","b2sit","b2siq")

while bpy.context.mode == 'OBJECT':
    
    scene = bpy.context.scene
    
    frame_backup = scene.frame_current
    frame_start  = scene.frame_start
    frame_end    = scene.frame_end
    frame_step   = scene.frame_step

    for obj in bpy.context.selected_objects:
        if obj.type != 'MESH':
            continue

        # find allocation bone
        arm = obj.find_armature()
        if not arm:
            continue
        
        shape_keys = bpy.data.meshes[obj.name].shape_keys
        if not shape_keys:
            continue
        
        # make work table

        listB2S = []
        
        for bone in arm.pose.bones:
            if bone.name[:3] != "b2s":
                continue
            
            mydata = B2S_data()
            bFindShape = False

            mydata.bone = bone
            mydata.length = arm.data.bones[bone.name].length

            # find shapes
            for i in range(0,8,1):
                nameShape = preShape[i]+bone.name[3:]
                
                if shape_keys.key_blocks.find(nameShape) == -1:
                    continue
                shape = shape_keys.key_blocks[nameShape]

                match i:
                    case 0:
                        mydata.shape_df = shape
                    case 1:
                        mydata.shape_dh = shape
                    case 2:
                        mydata.shape_dt = shape
                    case 3:
                        mydata.shape_dq = shape
                    case 4:
                        mydata.shape_if = shape
                    case 5:
                        mydata.shape_ih = shape
                    case 6:
                        mydata.shape_it = shape
                    case 7:
                        mydata.shape_iq = shape
            
                bFindShape = True
            
            if not bFindShape:
                continue
            listB2S.append(mydata)
            
        # frame loop
        for frInsert in range(frame_start,frame_end+frame_step,frame_step):
            scene.frame_set(frInsert)

            for work in listB2S:
                vec = work.bone.head - work.bone.tail
                
                base_rate = math.sqrt(vec.x*vec.x + vec.y+vec.y + vec.z*vec.z) / work.length
                if base_rate > 1.0:
                    base_rate = 1.0
                
                # insert key
                if work.shape_df:
                    work.shape_df.value = base_rate
                    work.shape_df.keyframe_insert("value",frame=frInsert)
                if work.shape_dh:
                    work.shape_dh.value = base_rate/2.0
                    work.shape_dh.keyframe_insert("value",frame=frInsert)
                if work.shape_dt:
                    work.shape_dt.value = base_rate/3.0
                    work.shape_dt.keyframe_insert("value",frame=frInsert)
                if work.shape_dq:
                    work.shape_dq.value = base_rate/4.0
                    work.shape_dq.keyframe_insert("value",frame=frInsert)
                if work.shape_if:
                    work.shape_if.value = (1.0-base_rate)
                    work.shape_if.keyframe_insert("value",frame=frInsert)
                if work.shape_ih:
                    work.shape_ih.value = (1.0-base_rate)/2.0
                    work.shape_ih.keyframe_insert("value",frame=frInsert)
                if work.shape_it:
                    work.shape_it.value = (1.0-base_rate)/3.0
                    work.shape_it.keyframe_insert("value",frame=frInsert)
                if work.shape_iq:
                    work.shape_iq.value = (1.0-base_rate)/4.0
                    work.shape_iq.keyframe_insert("value",frame=frInsert)
                
        scene.frame_set(frame_backup)
        
    break

コードここまで

…どちらかというと、Python に慣れていないことが露呈しまくっているコードだと思います。そもそも私は現役時代は C++ だったのでここの知識をベースに作成しています。故に差異があるととたんに躓くのですよね。
では、解説…というか、躓いた部分の言い訳を列挙していきます。

class B2S_data:
構造体が無いので class を使うというのは知っていたのですが、メンバーを直接読み書きする形で利用しています。正直行儀が悪いのは解っているのですが、こうでもしないと使えません。
  • 8個の shape を持ったクラスをノードとしたリストを組んでいますが
まず、ボーンとシェイプキーを関連付けるために名前の接頭語(プレフィクス)にルールを設けます。
最初に両者とも最初の3文字を b2s としています。これは from Bone to Shape からとっています。
次に shape の4文字目は d もしくは i これは direct inverse の頭文字で正作用、逆作用を意味します。
筋肉は引っ張る動きしかできないため、たとえば肘を曲げる筋肉があれば、肘を伸ばす筋肉が別途必要になります。
屈筋、伸筋と言いますが、肘ならば上腕二頭筋が屈筋、上腕三頭筋が伸筋になります。
肘を曲げると延びる係数ボーンを観測するなら、屈筋は正作用、伸筋は逆作用(つまり曲げる程しぼむ)すれば良い訳です。
で、shape の5文字目は f h t q となっておりそれぞれ 1/1 1/2 1/3 1/4 の反映率で分けています。
肘のように1方向のみに曲げる関節の方が稀で大抵は筋肉と関節は複雑に関係している場合が多く、例えば背筋をパンプアップさせようとしたら背骨を逸らした上で両腕も後ろに動かす必要があります。
このような場合に、同じ形状のシェイプキーを複数用意し、半分ずつ影響させる…といった動作を想定して作りました。
まとめると、1本のボーンに対して正作用、逆作用のそれぞれ4種の反映率の最大8個のシェイプキーに影響させる為のデータを保存するためのクラスという事になります。

# Shape Prefix
今回始めてタプルを使っていますが、配列っぽく使える define 文程度の認識で使っていますがこれで良かったのでしょうか?

# make work table より前の部分
これは第二回で行っていた処理と同じなので解説は割愛します。

# make work table
先述の B2S_data クラスのリストを作ります。ボーン名に b2s プレフィクスを持つボーン(係数用ボーン)抽出し、発見できたら同プレフィクスを持つシェイプキー(係数に影響されるシェイプキー)を探して登録していきます。
尚、[]がC言語で言う所の配列ではなく、VC++ の CList のようなもの…ということは何年か前にビットマップ表示プログラムを書いた際にハマったので覚えていましたが、未だにこのリストのスライス等の扱いが曖昧です。

今回はじめて match 文を使っていますが、これは Python3 から導入されたそうです。まんまC言語の switch 文と同じ使い方をしていますが、case には条件文をそれぞれ入れられる…のですが、調べてみるとこの条件文の書き方がだいぶ怪しい感じで…次期 Python で又大幅変更があるんじゃないかと危惧するのは私だけでしょうか?
Python の下位互換性を信用できないのは私だけでは無い(2から3への移行のグダグダを知っているので)とは思いますが、今後に不安を感じる文だなと感じました。

# frame loop
各フレームでのボーンの影響度を計算して、各シェイプキーのキーフレームを打っていきます。
尚、シェイプキーを打っている処理そのものは

keyframe_insert("value",frame=frInsert)

なのですが、「Pythonツールチップを表示」機能を使って、シェイプキーのキーフレーム追加部分をマウスオーバーしても「プロパティのアニメーション」と表示されるだけ(ver4.51時点)で Python のコードは表示されず、結構ハマりました。
特に、メンバー変数の value に値を入れた後に、keyframe_insert では "value" と文字列で指定して追加するというやり方は直感で思いつくものでは無いと思います。

プログラムを通して
B2S_data クラスにある shape_xx メンバーを shape[] 等のリスト型にすればソースコードが大分短くなる…というのは重々承知しています。
ただ、こう書かないと意味を忘れた時に困るだろうと思って可読性を優先しました。

係数ボーンの係数は
if base_rate > 1.0:
    base_rate = 1.0
とある通り、1以下(100%以下)に制限されています。又、この係数はスカラ量を元にしていますので、必ず正の値になります。
ボーンが短くなる限界を突き抜けてベクトルが逆(マイナス方向)に動いた場合でもスカラ量を取っているため正の値が増える事になります。


実際に動かしてみる
まず第一回でやっていたなんちゃってデータに対して実行してみます。
すると、当然大量のキーが打たれまして…
こんな感じに。第一回はシェイプキーを開始と終了の線形補完で動かしていたので今回の方がより生物的な動きをしている…のですが正直そんなに変わらないですね。

次に伸筋を作ってみる
伸筋(上腕三頭筋)を作ってみます。
肘の曲げの係数を取るボーンを b2s_hizi
屈筋のシェイプキーを b2sdf_hizi
伸筋のシェイプキーを b2sif_hizi
と命名しています。命名の規約は前述の class B2S_data の解説の通り。
※ところで hizi ってなんだよ、hiji じゃないのかよ、と作ってから気づきました。平たい顔族にローマ字は難しいのです。
で、こちらは単体ではこんな感じに動きます。

いざ実行
とりあえず、正作用、逆作用の同時処理はできているようです。

作ってみた感想
…いやあ、半年かかって出来なかった物が一週間で出来てしまいました。脳が活性化しているって大事なんだなあと思いましたね。
これにて筋肉の造形はシェイプキーに任せる事が可能になりました。
※以前作っていたモデルの背面。肩口に筋肉を表現する為のボーンが密集している。
通常のリギングと筋肉のパンプアップを分けて作る事が出来る上に動作の破綻部分の調整にもシェイプキーを活用できるので相当効率が上がりそうです。
ちなみにはじめて顔以外の全身モデルを作った際は2016年9月から2019年10月までという実に3年もの時間がかかりました。
こういった物を作っておかないと、モデルが出来る前に自分の寿命が終わりかねないのですよねえ。
又、この時期は働きながらモデリングやプログラムができる環境にありましたが、直近2年はそういった環境にありませんでした。
日本のトップすらライフワークバランスを棄てる昨今ですが、ライフに重点を置いた生活がしたいなあとしみじみ感じますね。


2025年10月11日土曜日

シェイプキーとアーマチュアを関連付けて筋肉を表現してみたい2

退職を伝えた日は仏滅だったのですが、吉日だったそうです。ひと粒万倍日とかだそうで…生涯初めて1年で2回退職しましたが、これは3回目もあるんでしょうか?
その会社の事務の方から電話がかかってきましたがひたすら謝られてしまいました。いや、今どきこんな昭和みたいな会社あるんやなあとは思いましたが、特に謝られるような事は…私と同日に入社1週間で辞めた方とは揉めたんでしょうか?
定期的に人事がえらいことになっているのかもしれません。
又、丁度国税調査をやっていましたが…確かに調査対象の日程は就業してましたが回答する時点では退職しているんですよね…これ、情勢と合わないのでは?

さておき、今日も元気にプログラムです
とりあえず、ボーン名決め打ち(test_bone)で伸縮割合を出すプログラムを書いていきます。最初に考えるべきは、このプラグインの主体ですが、アーマチュアとシェイプキーを割り当てられたメッシュオブジェクトが主体になります。なので、プラグインはオブジェクトモードで起動する事にします。

コード開始

import bpy
import math

while bpy.context.mode == 'OBJECT':

    scene = bpy.context.scene
    
    frame_backup = scene.frame_current
    frame_start  = scene.frame_start
    frame_end    = scene.frame_end
    frame_step   = scene.frame_step

    for obj in bpy.context.selected_objects:
        if obj.type != 'MESH':
            continue

        # find allocation bone
        arm = obj.find_armature()
        if not arm:
            continue
        
        # find allocation shape
        shape_keys = bpy.data.meshes[obj.name].shape_keys
        if not shape_keys:
            continue

        bone = arm.pose.bones["test_bone"
        length = arm.data.bones["test_bone"].length
        
        # frame loop
        for frame in range(frame_start,frame_end+frame_step,frame_step):
            scene.frame_set(frame)
            
            vec = bone.head - bone.tail
            scalar = math.sqrt(vec.x*vec.x + vec.y+vec.y + vec.z*vec.z)
            print("frame/",frame,":length/",scalar / length)
            
        scene.frame_set(frame_backup)
    break

コードここまで

順を追って解説していきます。
最初は現フレームと開始/終了フレーム等を拾っています。

次に選択されたオブジェクトを拾うループに入ります。
拾ったオブジェクトに対して、メッシュオブジェクトか?アーマチュアが割り当てられているか?シェイプキーが割り当てられているか?
を調べ、該当しなければ処理から除外します。

次にオブジェクトに割り当てられたアーマチュアの中から名前決め打ち(test_bone)でボーンを抜いてきますが…
arm.pose.bones と arm.data.bones の2系統からデータを引いてきています。
pose の方はフレーム変化に伴うボーンの移動等により値が変化しますが、data の方は変化しません。どちらにもボーンの先頭と末尾を示す head tail のメンバーがあるのですが data の方は値が変化せず、結構ハマりました。

# frame loop 以降は各フレームでのボーンの長さを求めて、元々のボーンの長さで割り比率を求めています
ココで解説すべきは…

scalar = math.sqrt(vec.x*vec.x + vec.y+vec.y + vec.z*vec.z)

二乗の和の平方根=スカラ量≒ベクトルの長さ≒ボーンの現在の長さ
いやあ、スカラ量を求める計算なんてゲーム業界辞めてから20年近くやってなかったですね。まあ法線を必要とするプラグイン開発をしょっちゅうやっていたので流石にすぐに出ましたけど、覚えているもんなんですねえ。

で、結果がこんな感じに。
順調に値が変わっているのが見て取れます。次はこれらを使ってシェイプキーを打つプログラムですかね。

2025年8月21日木曜日

身構えて居ない時にやってくると言うアレ

 就職決まって、就職前の4日間…本来はのんびり過ごす予定だったのですが…
NAS から警告音が鳴りまして
4台でRAID6 を構築していたのですが1台無くなってる。繋ぎ直しても数分で切断される事からコンデンサかサーマル系の回路が逝ったと判断。
じゃんぱらでやっとこ中古の Intel 製を見つけて1台購入して交換し環境構築して就寝したら3時間後に警告音で目が醒めました。

今度は別の SSD が落ちてやがる。
ちなみにコレまで使っていたものは2年前に購入したもの

結局翌日も買い物に行き同じ Intel SSD を追加で3枚換装しました。
ADATA から Intel なら性能はだいぶ上がるが新品から中古品になっているのに、23年に新品で3.4万だったものが中古で4.2万とは…。
実は現在アキバでは1.92tbの SSD はほとんど売られておらず 960GB 以下が主流です…というか SSD の主流が PCIe に移っている。
しかも23年当時はコロナ特需が止まった年で、増産していた会社がブレーキをかけられず市場に在庫がだぶついた時期でした。このため暴落していたんですよね。
(このため SSD 4台による NAS 構築に挑戦している)
この時期に大量にストックしていたテンバイヤーは儲かった…のでしょうか?食品と精密機械は素人から買うものではないと思いますが。

まあ最大の誤算は価格の高騰より2年程度で壊れた事なんですけどね。安物で RAID を組んだとはいえここまで早く壊れるとは思いませんでした。
結局復旧に就業前日までかかり優雅に休むという目論見は脆くも砕け散りました。

venv
以前面倒な物が増えていたので回避するために古いバージョンの linuxMint を使うことになったのですが…
思ったより随分とローテクだった
まさか隔離用にディレクトリを切ったらそのままそこに実行体を置いているだけだったとは。隔離した環境との行き来は activate deactivate のコマンドを…とあるけど、別に直接呼べば良い。

以下備忘録

インストール
最初にマネージャーからこちらをインストール。
これを入れておかないと venv のセットアップの際に pip が入らずに詰んでしまう。

python3 -m venv [環境名]

で、これを行うと通常

/home/[ユーザー名]/[環境名]

ディレクトリが作られて、更にこの下の /bin ディレクトリに python やら pip やらが作られる。
つまり…以前エラーが出ていた  pip install 等も

/home/[ユーザー名]/[環境名]/bin/pip install 〜

と打てばいいだけ。
また geany と MS Code の変更点は以下の通り。
※私の環境名ではユーザー名が shin 環境名が venv なので各自読み替えてください。

Code


Geany
※上記2点を書き換え

いくつかのパソコンの OS 入れ替えと環境やり直しですね。Mint21.3 の環境を渡しちゃった甥っ子にはちょっと悪いことをしたかもですが、まあ勉強用だし27年4月まで持てば良いので致し方なし。

ともあれ、なんで半年暇があってこんな土壇場で理解できたんだか…ただ、人間時間が惜しくならないと行動しませんよね。
実は Blender もひとつ大きな事に気づいたのですが…それは次回にいたしましょう。


2025年7月22日火曜日

2度程アキバに行きまして

 中二の甥っ子がゲーム制作業をやりたいとか言ったそうで、無碍にする訳にもいかんかなと思いまして。
盆に実家に帰省してくると解っているので、それまでに以前作った Pyxel の教本の勉強ができる環境でも用意してやろうと思いたちました。

Latitude3190
3月に移転した秋葉原のジャンク屋のパレットアキバに行きまして一つ前の世代の Latitude3190 (8800円)を買ってきました。
※ 3120 の前の世代が 3190 ってのがややこしい
CPU の違いはほとんど誤差なのですが、SSD が SATA なのが結構キツイですね。インストール作業に倍以上の時間がかかります。
ちなみにこの個体はスペースキーに異常があって押し辛かったです。キートップを弄ったらある程度打ちやすくはなりましたが、今度はクリック音がおかしくなりました。
ただ、バッテリーはほぼ残っており、筐体もほぼ無傷、ディスプレーもきれいな物でした。
察するにキーボード不良で返品されたものを部品取り用に保存、そのまま保守期限(一般的には8年)が切れて放出したのではないでしょうか?
学生用パソコンなので在庫も豊富でしょうし。

UX311U
で、こいつは7日に購入したのですが、18日に再訪。
購入した際にどうしても気になった機種があって18日に再度パレットアキバに行って買ってきたのがコレ。
UX331U
価格は17800円。
こいつは正にジャンクでして、キーボードはそれなりに動くのですが、ゴム足欠損。
※写真は100円ショップで買ったゴム足を貼っています

天板凹みあり、バックライトに一部明るすぎる部分が有り、バッテリー満充電で6割といった代物でした。ただ、コレ MX150 というモバイル版 GT1030 が乗っているんですよね。ちなみに VRAM は 2GB。
ひとまず Windows11 のまま運用。というのも Excel 2019 がとうとうサポート切れになりまして、次の Office を買うにしてもどのパソコンに入れようか?と。早く職が決まるようなら型落ちの N150 搭載パソコンでも新品で買って、Office をバンドルしてもらうのですが、状況が悪ければコレに乗せるのもありかなと。
ちなみに Excel 2019 を入れていたノートは2コアのセレロンであり、そもそも Windows11 の要件を満たさない Windows10 マシンのため10月以降の使用が厳しくなります。
…そんなものよりいい加減 RogFlow 使えよって話ではあるのですが。

※8/16 追記
100円ショップで買ってきた補修用壁紙を天板に貼ってみました。
微妙に失敗しているのですが、一応印刷加工職人(復帰)ですのでこれくらいは出来るのですよハイ。
又、分解してみてた所、過去に一度改修した跡がありまして、ファンにグリスを塗り直した痕跡があった他、本来SATA SSDが入っている所、PCIe3.0 のSSDが入っていたりしました。
現状ではこのまま Office マシンになりそうです。

水を貰う
購入の特典で水を貰ったのですが
ガチで助かった
とんでもない猛暑で死にかかっていたので。
ちなみに市販品にお手製シールを貼った物です、念のため。

選挙
18日に秋葉に行った帰りに駅前で岡本まやが選挙演説しててびっくり。
なんでそんな微妙な政党(マイルドな表現)からと思ったのですが、冒頭の部分で(駅前だったので聞こえたのですよ)インボイス制について言いたいことがあったとの事。
確かに後輩たちの生活がかかっているので筋は通っています。
このままだと実家が太いか二世以外の声優(含む俳優等タレント業)のデビューは厳しいでしょうしね。
ただまあ、こんだけアニメやらゲームやら出番が多い割に一部の声優に仕事が振られすぎている業界の方が問題なのでは?と、思わなくもないです。もうちょい均等に仕事振れよと。
その受け皿が∨チューバー等なのかもしれませんが、アレも問題の多い業界っぽいですし、だれかが解決に乗り出さないといけないタイミングなのかもしれません。

環境構築
さてさて、私が以前作った環境と同じものを Latitude3190 で組んだのですが…アレ?動かない?
私の動作している環境と見比べると…

…Ubuntu 系ではあり得ない Python を起動しようとして落ちている。現にコマンドプロンプトから Python3 で直接呼ぶと駆動する。
ビルドコマンドを直接編集しようかと思ったのですが、良く解らないのでいろいろ悩んだのですが…
標準のソフトウェアマネージャから落としてくる 1.102.1 を使うとこの現象が発生し、自力で 1.102.0 の deb ファイルを拾ってきてインストールするとこの問題は起きない。
Microsoft は動作検証もしないで公式リポジトリにソフト上げてるんかいな。Ubuntu で直ぐに発覚するようなバグを deb パッケージで上げるとかアホなの?
それとも何かの嫌がらせなのか?

次は VOICEVOX
お勧めはインストーラーからのインストールとなっていますが、CPU版のフルサイズファイルを落としてきて展開しないと動作しない事が多いです。
で、落としてきたのですが…こいつも動作しない。
で、まさか…と思ってデグレード。
0.23.0は起動するが0.24.2は起動しない
またかよ。最近はソフトを動作検証しないでアップデートするのが流行っているんですかね?

2025年6月18日水曜日

本当に久々の Blender スクリプト

いやー本当にギリギリまで追い込まれないと知的な行動ができない子なんですよ、自分。
以前ちょこっとだけ、顔のモデリングをはじめました…と書いていたのですが…。

瞳を作成
瞳を作成するに当たって通常どうやってモデリングするでしょうか?
一般的にはUV球にテクスチャを描いて貼り付けると思います。
又、黒目のように円一周の模様が同じパーツなら、切り分けたピザの様に一部だけテクスチャを描いて、ぐるっと一周コピーして使用するのでは無いでしょうか?
が、残念ながら私はテクスチャを描く技術がありません。
普通ならココで絵を描く技術を磨くところですが、残念ながら私にはやる気と時間が(年齢的にも)ありません。
よって、全て細かいメッシュで表現しようとしているのですが、流石にまるまる全てをモデリングすると労力がかかりすぎます。

そこで眼球の右下部分だけをモデリングして、それをコピー接合していきます。
…左上に当たる座標に3Dカーソルを当てる→複製→縦横にマイナス拡縮を行って反転→自動吸着を行う…とやっていてけば確かに手動でも可能ですが、流石に面倒すぎる。

※ぶちゃけた事を書けば
眼球を作ると言うより、幾何学模様があるカラーコンタクトを大量に作って重ねるという感覚の方が近く、これら(カラーコンタクト)を1個作る毎に毎回この処理をやってるのは面倒すぎるのです。

そこで、手動操作を「スクリプト作成」で書き出して繋ぎ合わせたのが下のコードになります

コード開始

import bpy
import bmesh

while bpy.context.mode == 'EDIT_MESH':

    bFirst=True
    min_x=0.0
    min_z=0.0
    max_x=0.0
    max_z=0.0

    #### get center point ####

    me = bpy.context.object.data
    bm = bmesh.from_edit_mesh(me)

    for v in bm.verts:
        if v.select == False:
            continue
        if bFirst:
            min_x=v.co.x
            max_x=v.co.x
            min_z=v.co.z
            max_z=v.co.z
            bFirst=False
        if min_x>v.co.x:
            min_x=v.co.x
        if max_x<v.co.x:
            max_x=v.co.x
        if min_z>v.co.z:
            min_z=v.co.z
        if max_z<v.co.z:
            max_z=v.co.z

    bm.free()

    if bFirst == True:
        break

    center_x = min_x
    center_z = max_z
    width = max_x-min_x
    height = max_z-min_z

    #### dupricate ####

    old_pivot = bpy.context.scene.tool_settings.transform_pivot_point
    bpy.context.scene.tool_settings.transform_pivot_point = 'MEDIAN_POINT'

    old_merge = bpy.context.scene.tool_settings.use_mesh_automerge

    #### 1 ####
    bpy.context.scene.tool_settings.use_mesh_automerge = False
    bpy.ops.mesh.duplicate_move(
        MESH_OT_duplicate={"mode":1}, 
        TRANSFORM_OT_translate={"value":(-width, 0, 0)}
    )
    bpy.context.scene.tool_settings.use_mesh_automerge = True
    bpy.ops.transform.resize(value=(-1, 1, 1))
    bpy.ops.mesh.flip_normals()

    #### 2 ####
    bpy.context.scene.tool_settings.use_mesh_automerge = False
    bpy.ops.mesh.duplicate_move(
        MESH_OT_duplicate={"mode":1}, 
        TRANSFORM_OT_translate={"value":(0, 0, height)}
    )
    bpy.context.scene.tool_settings.use_mesh_automerge = True
    bpy.ops.transform.resize(value=(1, 1, -1))
    bpy.ops.mesh.flip_normals()
        
    #### 3 ####
    bpy.context.scene.tool_settings.use_mesh_automerge = False
    bpy.ops.mesh.duplicate_move(
        MESH_OT_duplicate={"mode":1}, 
        TRANSFORM_OT_translate={"value":(width, 0, 0)}
    )
    bpy.context.scene.tool_settings.use_mesh_automerge = True
    bpy.ops.transform.resize(value=(-1, 1, 1))
    bpy.ops.mesh.flip_normals()

    bpy.context.scene.tool_settings.transform_pivot_point = old_pivot
    bpy.context.scene.tool_settings.use_mesh_automerge = old_merge
    
    break

コード終わり

一応解説

#### get center point ####
バウンディングボックスの算出

#### dupricate ####
ぐるっとコピー。コピーして反転しての繰り返し。反転する前に頂点同士がくっつくと不味いので、自動頂点吸着のフラグ操作が忙しくなっている。

はい、大分久しぶりでした。こんなんでも1週間くらいかかったような…
いや、コードが動き始めてからは2日くらいでしたが調べるのに時間がかかりました。

※8/11コード修正
1/4のパーツを反転しながらコピーして駆処理なのですが、反転しているので面法線が逆になる事を失念していました。このためコピーする度に面法線を反転させる処理を追加しました。

速度を出すなら bmesh で全ての処理を完結させるべきだとは解るのですが、瞳に使う頂点、辺、面、法線…を全て網羅するコードを無事駆動させる自信が無かったので、「早い手動」でいいかなと。
あと、今回はじめて「スクリプト作成」を見ながらスクリプトを作ったのですが…フラグを弄ろうとして、わざわざ設定したのに反映されない場面が多々ありました。
なんでフラグ設定に従ってくれなかったのか?おかげでどのフラグがどういった意味を持っているのか判らないと言う部分がありまして、結局は動く分だけでどうにかしたのが今回のコードになります。

また、今回のスクリプトですが、例によって開発に使った機材は…以前購入した Latitude3120 です。Celeron N5100 マシン。
何年か前にスクリプトを書いた際にも書きましたが、こういう物は気付いた時に枕元で作るのが一番進みます。スペックではないのですよ。

まあ、久々に趣味で頭を使ったのでそれなりに満足ではあります。今後はコレを使ってモデリングを進めねば。

2025年4月17日木曜日

プログラムが難航しているのですが

 休業して2か月と少しが過ぎましたが、当時プログラムとモデリングを再開しようとしていました。Blender の方はバージョン3系辺りからのレイヤーの扱いをだましだましやってきたのがついに限界がきまして0から学び直す必要が出たのですが、プログラムの方は…
こういった本が近所の古本市場に売られてまして…
コレ、3月に買ったのですが実は1月販売の本なのですよ、なぜゆえ2か月で売られてしまったのかな?と。
著者の方は経歴を見ると元コナミの小島監督チームのプログラマーさんのようで、自身の作ったフリーゲームライブラリを使ったプログラム方法を記した本…らしいです。
実は既に読み進めていて、最初のチャプターまで来ているのですが、いわゆるハローワールドの次が。

ゲームプログラムを改造して、キャラを無敵にしようぜ

というもの。
変数や配列の概念を教えるでもなく、いきなりコレ。
教育現場からすれば破天荒な内容ですが、40年ほど昔なら割とあるあるな学習方法です。
というのも、ネットが電話回線通信で「草の根」とか言われていた時代、フリーゲームはプログラミング雑誌への投稿が主戦場であり、雑誌に印字されたソースコードを自分のパソコンに打ち込む事がゲームプログラムの一歩でした。で、自分のパソコンへ一字一句間違わず効率よく打ち込むために構文を覚えたりしていきます。
そして、間違わずに打ち込めたらゲームで遊べる訳ですが…その次にやる事と言ったら、ゲームの改造。変数に代入される値を変えて、スピードを2倍にしたり、アイテムの数を増やしたり…で、どの部分のソースがどういった動きを司っているのか判ったら、次は条件分岐をいじり…といった順でプログラムを覚えたものです。
※追記
最近思い出したのですが、20年ほど前に NintendoDS のソフト開発をやった際「任天堂簡単マニュアル」という物がありまして、実際に動くスーパーマリオみたいなソフトのサンプルソースコードが付属されており、これを改造しながら開発手段を覚えてくれ…みたいなスタンスでしたね。結局この方法が一番なのですよ。

なので、ゲームプログラマ育成的にはコレだわな、という内容で良書に見えます。
流石に2か月弱で読破したとは思えない綺麗さだったのですが、どうして売られたのか。
…恐らく、ライブラリインストールが pip だったのでそこで躓いたのかな…と。
ウィンドウズユーザーだと msi 形式以外に不慣れなユーザーが多いですし。

この教本には Windows と Mac のインストール方法が載っていたのですが、私は当然のように Linux で進めていきました。
…はい。
たしか、前の会社に入る前、23年の8月までは見かけなかったのですが…退職後にプログラムを再勉強し始めたら pip install する度に見かけるんですよねこのエラー。
実はこの本を買う以前 pyautogui を導入する際も同じ問題が起こっていて、何度も環境を作り直していました。

しまいには面倒になって環境構築練習用のマシンを買いました。
Latitude3120
学生向け教育用パソコン。3月11日に川越のじゃんぱらで1.4万円で購入。
CPU Celeron5100 / メモリ 4GB / ストレージ PCIe3.0x4 接続の128GB SSD。
2Dのゲームを作る分には十分だし、ストレージが早いのでOSインストールのやり直しがしやすい。
問題は…
11.6インチモニタだが、ベゼル幅がとっても広く、筐体サイズは13インチ級。
BIOS画面がやや癖がある。
キーボードのタイプ感がかなり悪い。
右クリックボタンが無い。

…本気でプログラミングをしたければ、キーボードとマウスの接続をした方が良いかもしれませんが、最低限は揃っている感じ。
以前はこういった作業は EliteBook 2760p でやっていたのですが、流石にバッテリーが壊れた13年前のパソコンはそろそろ眠らせてやろうかと(経年劣化でタッチパネルのボタンが溶けちゃったんですよね)。もちろん、他にもいくつかノートパソコンは買いましたが、近年は GPU 搭載だったり UMPC だったりとインストールのやり直しには向かないのでパス。

話戻って、エラーメッセージの意味
アメリカ語がさっぱりな自分がグーグル先生に翻訳して貰ったらざっくりと

外部環境のソフトをインストールしようとしています

とりあえず apt を使ってインストールできるかを試してみろ
.deb 形式以外のパッケージをインストールしたければ venv を使って仮想環境を用意してそっちにインストールして使え
Python アプリなら pipx install を使ってみろ

最悪 --break-system-packages オプションでインストールできるかもしれないが、OSが壊れても知らんぞ

と、言ってるらしいです。

で、一番確実なのは venv を使って仮想環境を用意して~なのですが、それだと物が出来ても他のパソコンで動かすたびにそのパソコンでも仮想環境を作るのか?
となりまして、他を試したのですが…
まず apt install はそもそも存在しないから無理。pipx はやってみた所「いや、私の仕事じゃないから、pip さんの所でインストールしてみたら?」とエラーを吐かれて、たらいまわしにされました。
ダメもとで --break-system-packages を使ったらインストールできたのですが、動作しません。OSが壊れたかは確認できませんでしたがあまり良く無いような。

…で、ふと pip 系のソフト等を見渡すとほとんどが Ubuntu22.04 対応なのに気づきまして。
確実に言える事は Ubuntu24.04 ベースがダメって事。
Debian ベースの LMDE6 を最初に試しましたが、コレもダメでした。ついで LMDE5 で試したらすんなりインストールできましたが、今度は Latitude3120 のタッチパッドが反応しなくなったのでアウト。
最終的には Ubuntu22.04 ベースの LinuxMint 21.3 をインストールする事で環境が安定しました。デグレードですが、一応2927年まではサポートしているようですし、コレで良いかなと。

ネタバレすると
先ほどの Latitude3120 の写真に写っているのは Visual Studio Code 上の Pyxel の動作画面なんですよね。ちなみにこのマシンには VOICEVOX と Blender4.4 そして pyautogui がインストールされていたりします。
まあ、VOICEVOX は短文を喋らせるだけでも待たされますし「動く」ってだけで実用速度とは言えませんが、実験マシンとしては十分働いています。
ここからどれだけ Python のプログラム能力を回復できますやら…

2024年11月25日月曜日

Linux Mint + Steam Play では GGST は動かない?

 というわけで、以前 Ryzen7 8700G の入った Linux マシンを組んだのですが。
私の手持ちのソフトでは最重量になると思われる GGST をインストールしてみまして、動作させてみました。
一応、メモリの電圧を 1.42V 駆動周波数を 6400Mhz まで上げています。
これ以上電圧を上げるとマザボが警告してくるのと、周波数を次の 6600Mhz まで上げると起動しなくなったんですよね…
やはり雑にやると定格の10%以上は厳しいようです。

さておき、トレーニングモードしかやっていませんが、とりあえず待機モーションを眺めている分にはコマ落ちが確認できますが、デフォルトの設定(ほとんどの設定が「高」になっているもの)で割と問題なく動作しています。
コマ落ちが出る以上、対人対戦はお勧めしませんが、ソロでストーリーをやる分には…

  • …と、思ったのですが
何回やっても20分程のプレーでフリーズするんですよね。
別に熱暴走ではなさそうなのですが、確認した限りでは1スレッドのみ100%使用になっているので、なんかしらのバグっぽいです。
これが、ゲーム側の問題なのか、 SteamPlay の問題なのかは判りませんが、現時点ではこのゲームはプレーできないと考えた方が良いと思われます。

  • Linux で Steam は未成熟
うーん、 Kenshi は SteamDeck でも遊べるのは知っているので動くと思いますが、微妙にしんどいですねえ。
セーブデータをクラウドで移動できる物は全てこいつに移動しようと思っていたので。
流石に一度クリアしたゲームを再度攻略したいとは思わないので、引き継げるものは引き継いでここに死蔵しようと思っていたのですが、ゲーム毎に動作検証するのは骨が折れそう。
ビデオボード無しでここまで動いたので未来は感じる事はできましたが、現状では安定とは遠い感じなので、もうちょい成熟までに時間がかかりそうですが…その頃にはAPUの新時代が来ていると思うですよね。

とどのつまり、世間の評価と同じ「時期尚早」って奴ですが、このマシンの組み合わせは今年のデスクトップ界では最も魅力があったと思うのも事実。
次は画像生成ですかね。

2024年1月6日土曜日

Nanote P8 に Manjaro Linux を入れた話の続き

さて、前回 Nanote に Manjaro Mate をインストールした所まで書きました。今回はその続きなのですが…

  • デフォで縦向きになる画面
まず、よく言われているように画面が縦向きになります。
これも解決策はネットの方々で
xrandr -o right
とコマンドを打ち込めと書かれています。
ちなみにデフォルトでは xrandr すらインストールされていないのでインストールする必要があります。
sudo pacman -S xrandr
2024/4/14 追記
パッケージマネージャーから
xorg-xrandr
を入れるのが現在の正解の模様…以前は入ったハズなのですが…

それは良いのですが、起動時に必ずこの回転コマンドを書き込むのは厄介。
で、大抵のサイトにはどこそこディレクトリのなんちゃらファイルに追記…と、あるのですが。
Xfce と MATE に関しては「自動起動するアプリ」を登録するアプリにコマンドを登録しておけば OK だったりします。
GUI だし編集も簡単なので便利。ログイン画面も回転させたい人は…がんばれ。

  • 日本語化
インストーラーで日本を選択していれば、日本語フォントがインストールされ、「表示は」日本語化されます。
しかし、日本語入力はできないので、ひとまず、
sudo pacman -S manjaro-asian-input-support-fcitx fcitx-mozc
このコマンドを流す方法が一番簡単でした。
なぜか他の方法だとインストールは出来るのですが、日本語入力できなかったんですよね。

  • タッチ入力
github のページに行きまして、
ファームウェアをダウンロードしてくるのですが…
なんというか、このページ、更新頻度が早すぎて正確な事が言えないんですよね。
一応、「RWC」の「NANOTE P8」の項目にある拡張子が「.fw」のファイルをダウンロードしてくれば良いハズです。
現在は silead_ts.fw になっているのですが、私がダウンロードした時は gsl1680-rwc-nanote-p8.fw って名前だったような。
で、コレを
/lib/firmware/silead/
ディレクトリにコピーします。
Xfce の場合はGUI上のディレクトリのアイコンを右クリックしてルート権限で開く旨を選択するだけですが、Mate ではなぜかその操作ができないので、ユーザーディレクトリ(デフォルトでコマンドラインで ls を入力して開かれるディレクトリ)にダウンロードしてきたファームウェアを置いて。
sudo cp silead_ts.fw /lib/firmware/silead/
てな感じでコピーしました。…うーん今後システムファイルの操作はずっとこうなんだろうか?
ちなみに、私のような初心者が「システムフォルダ内の設定ファイルを書き換えろ」とか言われて積むことが割とあります。解説ページとかを見ると、割と難解な CUI ベースのテキストエディタの vim で開いて編集しろと言うページが散見されますが、
sudo pluma [開きたいテキストベースのファイルのパス(/~/xxxx.conf 等)]
とでも入力すれば、標準のメモ帳で編集できるので無駄に苦しむ必要は無いです。

  • 話戻ってファームウェアをディレクトリ上に置けたら
sudo modprobe -r silead
sudo modprobe silead
とコマンドを流せば一応タッチスクリーンは反応するハズ。
modprobe はOSの外部部品を管理するコマンド、 -r は取り外しを行うオプション( remove ) silead はタッチスクリーンの処理を行う部品の事らしいので、要約すれば、これまで役に立っていなかったタッチスクリーンの部品を一旦切り離して繋ぎ直した事になるらしい。

  • 肝心な所でこける
本来、ココまでくればラストスパートなのですが。
音もなる、無線も繋がる、日本語も入力できる、タッチスクリーンが反応するとくれば後はタッチスクリーンのキャリブレーションだけ。
一般的には
xinput_calibrator
を使って調整してくるんですが…まず標準の pacman でダウンロードできない。ネットを見て github から落としてこいと書かれていたので、試してみたがコレもダメ。
なら先祖の Arch のリポジトリから落としてこようとしたのですが、これもダメ(仮に上手く行ってもサポート外)。
そもそも、 manjaro 上で標準的に落とせないのが不思議なんですが。

最後の最後で Arch 系の洗礼を受けてしまいましたね。
ここら辺も自力解決できないといけないOSなんです。
Nanote P8 自体はパッケージは面白いと思います。ただ、扱いと耐久性に難がある事に目をつむれば。その難あり機体に難ありOSを組み込んで苦労を楽しめる人向けの機材だと思いますね。画面は綺麗だし、現在のサウンドドライバで鳴らす限りは値段相応の音も出ます(少なくともネットで言われているような音が割れて聴きとれないという事は無い)。
動画再生程度なら難なくこなしますし、Blender4 もテクスチャ貼ったり、ライティングに凝らなければ割と動きます。

あと、この機体はハードウェア改造をする人もいますが…正直お勧めしません。中古でも2万円しますし、リスクとリターンが合わないと思います。確かにOSの起動や、パッケージマネージャの起動は遅いのですがそれ以外で気になるほど遅くは無いので、基盤にはんだ付けするリスクには見合わないと思いますよ。

2023年2月7日火曜日

Python+Kivy 備忘録 BMPファイルを読んでみる編

はい、前回テキストを読みましたが、今回はBMPファイルです。
といっても処理を簡単にしたかったので、24ビットBMPファイルしか読めません。

## load_bmp ##

from kivy.app import App
from kivy.uix.widget import Widget

from kivy.graphics import Color, Ellipse, Line, Rectangle
from kivy.core.window import Window

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput

from kivy.uix.floatlayout import FloatLayout
from kivy.factory import Factory
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup

import os
from os.path import join,isdir
import uuid
from datetime import datetime

from kivy.uix.filechooser import FileChooserIconView
from kivy.uix.filechooser import FileChooserListView

from kivy.clock import Clock

Window.clearcolor = (0,0,0,0)

class BtnChangeSelectFile(Button):
    def on_release(self):
        self.parent.DrawSelectFile()
        
class BtnSelectFile(Button):
    def on_release(self):
        self.parent.parent.lFile.text='\n'.join(self.parent.fc.selection)
        self.parent.DrawBase()
        
class BtnChangeBase(Button):
    def on_release(self):
        self.parent.DrawBase()

class RGB():
    def __init__(self,r,g,b):
        self.r = r
        self.g = g
        self.b = b
        
class Output(BoxLayout):
    def __init__(self,path,**kwargs):
        super(Output,self).__init__(**kwargs)

        outputtext = path
        header = []
        
        self.map_width = 0
        self.map_height = 0
        self.bitmap:RGB = []

        self.list_la =[]
        self.list_rc =[]

        while True :
            if not os.path.exists(path):
                break
            outputtext = "exists"
            
            size = os.path.getsize(path)
            if size < 54:
                outputtext = "header not found"
                break
                
                
            with open(path,'rb') as f: 
                while True :
                    header = f.read(54)
                    
                    if not ((header[0]==66)&(header[1]==77)):
                        outputtext = "this file is not bmp"
                        break
                    
                    filesize = int(header[2]+(header[3]<<8)+(header[4]<<16)+(header[5]<<24))
                    headersize = int(header[10]+(header[11]<<8)+(header[12]<<16)+(header[13]<<24))
                    colorbit = int(header[28]+(header[29]<<8))
                    w = int(header[18]+(header[19]<<8)+(header[20]<<16)+(header[21]<<24))
                    h = int(header[22]+(header[23]<<8)+(header[24]<<16)+(header[25]<<24))
                    
                    if headersize != 54:
                        outputtext = "this format not surpported"
                        break
                        
                    if colorbit != 24:
                        outputtext = "24 bit color only"
                        break
                        
                    ### read bitmap ###
                    hsize = int((filesize - headersize)/h)
                    d = f.read(filesize - headersize)
                    
                    for y in range(h):
                        for x in range(w):
                            idx = hsize * y + x * 3
                            self.bitmap.append(RGB(b = int(d[idx+0]),g = int(d[idx+1]),r = int(d[idx+2])))
                            
                    outputtext ="colorbit:"+str(colorbit)+"width:"+str(w)+"/height:"+str(h)
                    self.map_width = w
                    self.map_height = h

                    break
            break
        print(outputtext)
        self.text = outputtext

    def Draw(self):
        if not self.bitmap:
            return
            
        with self.canvas.before:
            # clear all rectangle
            for old in self.list_rc:
                self.canvas.before.children.remove(old)
            self.list_rc.clear()
            
            # clear all label
            for old in self.list_la:
                self.canvas.before.children.remove(old.canvas)
            self.list_la.clear()
            
            w = self.size[0]/self.map_width
            h = self.size[1]/self.map_height
            idx = 0
            for y in range(int(self.map_height)):
                for x in range(int(self.map_width)):
                    Color(self.bitmap[idx].r/255,self.bitmap[idx].g/255,self.bitmap[idx].b/255)
                    idx = idx+1
                    self.list_rc.append(Rectangle(pos=(self.right-self.size[0]+x*w,self.top-self.size[1]+y*h),size=(w,h)))

class SubDisplay(BoxLayout):
    def __init__(self,**kwargs):
        super(SubDisplay,self).__init__(**kwargs)
        self.fc=FileChooserListView()
        #self.fc=FileChooserIconView()
        self.fc.size_hint_y=7
        self.fc.filters=['*.bmp']
        self.mode = -1 # setup

        Clock.schedule_interval(self.update, 1.0/3.0)

    def update(self,dt):
        if self.mode == 0: # Base Mode
            self.main_disp.Draw()
            return
        
    def DrawBase(self):
        self.clear_widgets()
        self.orientation='vertical'
        
        self.main_disp =Output(path=self.parent.lFile.text,size_hint_y=4)
        b1 =BtnChangeSelectFile(text='SelectFile',size_hint_y=1)
        self.add_widget(self.main_disp)
        self.add_widget(b1)
        
        self.mode = 0 # Base
        
    def DrawSelectFile(self):
        self.clear_widgets()
        self.orientation='vertical'

        b2=BtnSelectFile(text='SelectFile',size_hint_y=1)
        b3=BtnChangeBase(text='QUIT',size_hint_y=1)
        
        self.add_widget(self.fc)
        self.add_widget(b2)
        self.add_widget(b3)
        
        self.mode = 1 # Select

class Display(BoxLayout):
    orientation='vertical'
    def __init__(self,**kwargs):
        super(Display,self).__init__(**kwargs)
        self.lFile = Label(text='No Selected',size_hint_y=1)
        self.boxSub = SubDisplay(size_hint_y=8)
        self.add_widget(self.lFile)
        self.add_widget(self.boxSub)
        self.boxSub.DrawBase()
        
class LoadBmpApp(App):
    def build(self):
        layout = Display()
        return layout

if __name__=='__main__':
    LoadBmpApp().run()

※毎度の事ですが、一部字を小さくしているので、コードはコピペして読む事を推奨します

  • キモは BoxLayout 派生クラスの Output クラス
解説するとしたらイニシャライズで行っている、BMPファイルの読み込みと Draw 関数でしょうか。
そこまで怪しい処理は組んでいないと思いますが、いくつか注意点を連ねます。

  •  hsize という変数はビットマップの横一列分のデータサイズです
ビットマップは横一列のデータサイズが4バイト区切りで無いといけないというルールがあります。しかし、24ビット=3バイトで1ドットを表現している都合上、画像サイズによっては4で割り切れない場合があります。
この場合、末端に穴埋め用のデータを詰めて4バイトで割れるように整えます(パディングとか言います)。
が、この計算が面倒だったので、単純に画像データサイズ(ファイルサイズ-ヘッダーサイズ)をビットマップの縦のドット数で割って求めています。

  • 各ドットの色の順
一般的に光の三原色はRGBと並べますが、ファイルにはBGRの順に並んで格納されているので取り出す際に注意が必要です。

  • 描画は1秒間に3回行っている
Output クラスの Draw メソッドは親の SubDisplay クラスから1/3秒ごとに呼ばれる事になっています。
これは add_widget 直後ではサイズが定かではない為、正しく描画ができなかった為です。

…で、動かしてみたのですが…
…動く…けど、無茶苦茶重い。
32x32ドットの画を1024個の Rectangle で秒間3回描画しているだけなのに異常に重い。
コレはなにかしらやらかしている予感がしますね。
本当は次はテクスチャフォーマットの読み込みをやりたかったのですが、改良編になりそうです。