2020年11月3日火曜日

Python+Kivy 地獄編 続・無意味なアプリを作って地獄の扉を開けてみる

 以前作った、任意の色で四角形のBMPファイルを作る無意味アプリ。コレを Python3 で実行すると動かない。
bytearay の扱いが変わったからだそうで、正直そんなものをホイホイ変えてくれるなと思ったのですが、仕方ないので修正…したんですけどね。
なんというか、 Python は C 言語に恨みでもあるんですかね?私はC及びC++の知識を下敷きに Python のコードを書いていますが、今回は完全に仇になりました。

コード

## make bmp ##

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.slider import Slider

from kivy.uix.floatlayout import FloatLayout

import os
from os.path import join,isdir

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

from ctypes import *

#class HEADER(Structure):
class HEADER(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        # BITMAPFILEHEADER (14byte)
        ('bfType', c_char * 2),
        ('bfSize', c_uint32),
        ('bfReserved1', c_uint16),
        ('bfReserved2', c_uint16),
        ('bfOffBits', c_uint32),
        # BITMAPINFOHEADER (40byte)
        ('biSize', c_uint32),
        ('biWidth', c_uint32),
        ('biHeight', c_uint32),
        ('biPlanes', c_uint16),
        ('biBitCount', c_uint16),
        ('biCompression', c_uint32),
        ('biSizeImage', c_uint32),
        ('biXPelsPerMeter', c_uint32),
        ('biYPelsPerMeter', c_uint32),
        ('biClrUsed', c_uint32),
        ('biClrImportant', c_uint32)]
    
### Input Directory
#class DirectoryChooser(FileChooserListView):
class DirectoryChooser(FileChooserIconView):
    def is_dir(self,directory,filename):
        self.parent.parent.t1.text = directory
        return isdir(join(directory,filename))

class BoxSelectFile(BoxLayout):
    orientation='vertical'
    def __init__(self,**kwargs):
        super(BoxSelectFile,self).__init__(**kwargs)
        self.dc =DirectoryChooser(size_hint_y=1)
        self.dc.filters=[self.dc.is_dir]
        self.add_widget(self.dc)

### Input FileName
class BtSave(Button):
    def on_press(self):
        self.parent.parent.MakeFile()

class BoxSaveFileName(BoxLayout):
    orientation='horizontal'
    def __init__(self,**kwargs):
        super(BoxSaveFileName,self).__init__(**kwargs)
        self.t1 = TextInput(text='',size_hint_x=5,multiline=False)
        self.l1 = Label(text='.bmp',size_hint_x=2)
        self.b1 = BtSave(text='CREATE',size_hint_x=3)

        self.add_widget(self.t1)
        self.add_widget(self.l1)
        self.add_widget(self.b1)

### Input Color Value
class SlParam(Slider):
    def on_touch_up(self,touch):
        self.parent.t.text = str(int(self.value))

class TxParam(TextInput):
    def on_text_validate(self):
        value = int(self.text)
        if value <= self.parent.max and value >= self.parent.min:
            self.parent.s.value = value
        else:
            self.text=str(int(self.parent.s.value))

class GridParam(GridLayout):
    def __init__(self,**kwargs):
        super(GridParam,self).__init__(**kwargs)
        self.SetUp()

    def SetUp(self):
        self.clear_widgets()

        self.cols=3

        self.min = 0
        self.max = 100

        self.l = Label(text='none',size_hint_x=1)
        self.s = SlParam(min=self.min, max=self.max, value=self.min,size_hint_x=7)
        self.t = TxParam(text=str(self.min),multiline=False,size_hint_x=2)

        self.add_widget(self.l)
        self.add_widget(self.s)
        self.add_widget(self.t)

    def SetName(self,name):
        self.l.text = name

    def GetValue(self):
        return int(self.parent.s.value)

    def SetRange(self,range_min,range_max):
        if range_max<range_min:
            self.min = range_max
            self.max = range_min
        else:
            self.min = range_min
            self.max = range_max

        self.s.min = self.min
        self.s.max = self.max
        self.s.value = self.min
        self.t.text=str(self.min)

### Pixel
class Pixel():
    def __init__(self):
        self.r = 0;
        self.g = 0;
        self.b = 0;

    def Set(r,g,b):
        self.r = r;
        self.g = g;
        self.b = b;

### BMP File cotrol
class BMPFileControl():
    def __init__(self):
        self.header = HEADER()
        # BITMAPFILEHEADER
        self.header.bfType=b"BM"
        self.header.bfSize=14+40+4
        self.header.bfReserved1=0
        self.header.bfReserved2=0
        self.header.bfOffBits=14+40
        # BITMAPINFOHEADER
        self.header.biSize=40
        self.header.biWidth=1
        self.header.biHeight=1
        self.header.biPlanes=0
        self.header.biBitCount=24
        self.header.biCompression=0
        self.header.biSizeImage=4
        self.header.biXPelsPerMeter=0
        self.header.biYPelsPerMeter=0
        self.header.biClrUsed=0
        self.header.biClrImportant=0

        self.image=bytearray(b'\x00\x00\x00\x00')

    def GetWidth(self):
        return int(self.header.biWidth)

    def GetHeight(self):
        return int(self.header.biHeight)

    def GetPadding(self):
        return int((4-(self.GetWidth()*3)%4)%4)

    def GetPixel(self,destPixel,x,y):
        if x < 0 or x >= self.GetWidth() or y < 0 or y >= self.GetHeight():
            return False
        linesize = GetWidth()*3+GetPadding()
        index = y * linesize + x*3
        destPixel.Set(self.image[index+2],self.image[index+1],self.image[index])
        return True

    def Fill(self,r,g,b):
        w = self.GetWidth()
        h = self.GetHeight()
        padding = self.GetPadding()

        self.image=bytearray()
        for y in range(h):
            for x in range(w):
                self.image+=c_int8(b)
                self.image+=c_int8(g)
                self.image+=c_int8(r)
            for p in range(padding):
                self.image+=b'\x00'

    def ReSize(self,w,h):
        if w <= 0 or h <= 0:
            return False

        self.header.biWidth = w
        self.header.biHeight = h

        imagesize = int((w*3+self.GetPadding())*h)
        filesize = 14+40+imagesize

        self.header.bfSize  =  filesize
        self.header.biSizeImage  =  imagesize
        self.Fill(0xff,0xff,0xff)

        return True

    def FileWrite(self,filepath):
        with open(filepath,'wb') as f:
            f.write(self.header)
            f.write(self.image)

    def MakeBMPFile(self,filepath,r,g,b):
        self.Fill(r,g,b)
        self.FileWrite(filepath)

class Display(BoxLayout):
    orientation='vertical'
    def __init__(self,**kwargs):
        super(Display,self).__init__(**kwargs)

        self.t1 = TextInput(text="",size_hint_y=1,multiline=False,readonly=True)
        self.add_widget(self.t1)

        self.boxSave = BoxSaveFileName(size_hint_y=1)
        self.add_widget(self.boxSave)

        self.boxFile = BoxSelectFile(size_hint_y=15)
        self.add_widget(self.boxFile)

        self.gridR = GridParam(size_hint_y=1)
        self.gridG = GridParam(size_hint_y=1)
        self.gridB = GridParam(size_hint_y=1)

        self.gridR.SetName('R')
        self.gridG.SetName('G')
        self.gridB.SetName('B')

        self.gridR.SetRange(0,255)
        self.gridG.SetRange(0,255)
        self.gridB.SetRange(255,0)

        self.add_widget(self.gridR)
        self.add_widget(self.gridG)
        self.add_widget(self.gridB)

    def MakeFile(self):
        txtFilepath=self.t1.text+'/'+self.boxSave.t1.text+self.boxSave.l1.text
        print(txtFilepath)

        bmp = BMPFileControl()
        if bmp.ReSize(15,15):
            bmp.MakeBMPFile(txtFilepath,int(self.gridR.s.value),int(self.gridG.s.value),int(self.gridB.s.value))

class MainApp(App):
    def build(self):
        layout = Display()
        return layout

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

LinuxMint20 AMD64+Python3 で動作を確認しています
※追記:MakeFile 関数の末尾の表示が崩れたので、ひとまず字を小さくしてみました
  • C言語の配列と Python のリストは違う
どっちも記述すると
table[100]
とかになりますがね。
例えばC言語で short 型で配列を作れば、2バイト毎の変数のブロックが出来て、バイナリファイルに出力しても同じものができるのですが…
Python の list はどうやらこんな形っぽい。
四半世紀前の情報科で勉強するような奴ですね、この構造は間に物を挟んだり並べ直したりする処理がしやすいですが、ノード同志がメモリ上のどこに有るのかは不明です。
私としては、連続するバイト配列を作ってバイナリ出力したいだけなんですが…
候補は array bytes bytearray とありまして、当初 array を使ったのですが

srcList=[c_byte(b),c_byte(g),c_byte(r)]
destList=array.array('B')
destList.fromlist(srcList)

等と書いていたのですが、最後まで型が違うと怒られまして、埒が明かないので取り扱いを辞めました。
次は bytes bytearray なんですが…。
2020年11月現在、現役エンジニアが解説する某サイトにて bytes をミュータブル、bytearray をイミュータブルと解説してますね。
大石ちゃんは田島さんをグーで殴って良いと思う。

という訳で、結局元通り、bytearray に帰ってきました。
BMPFileControl クラスの Fill 関数の中ですね…1バイトずつ追加…なんとも鈍重です。


  • ctypes.Struct を使ってみたのだが…
前回ヘッダ内容をいちいち1バイトずつ入力していましたが、流石に面倒になって、 Python で構造体を使おうとしたのですけどね…これもなぜか struct と ctypes.Strcutre があります。

前述の配列も実は array 系では無く、拡張の NumPy を使えという記事の方が圧倒的に多かったですが…なんで Python のデータの取り扱いってこんなにとっ散らかってるんでしょうかね?
バージョン間の互換性も吹っ飛んでるし、まともな団体が音頭をとっているとは思えません。C言語のK&Rのような(初代は何年か前に亡くなられましたが)団体は居ないんでしょうか。

話が逸れましたが、最初に struct …は仕様を見て使う事を辞めました。
メンバー変数毎のサイズを示す文字列を定義して最初に登録するのですが、ビットマップのヘッダは全部で16個の変数があり、可読性が悪すぎる。
という訳で ctypes.Structure を使って、構造体を定義し、バイナリ出力も出来た…のですが。

どうも正しく表示されない、バイナリエディタで確認したら最初の2バイトの'BM'の後にパディングが2バイト追加されてる…
勝手に4バイトアライメントされている
…余計な事するなと。バイナリファイルを出力するプログラマーで「勝手にパディングを挟んで欲しい」という層って現在多数派なんでしょうか?
ちなみに LinuxMint20 AMD64版 での出力ですが、環境によってアライメントのされ方は違うそうです。
この余計なお世話をとっ外す為には構造体定義の _fields_ の前に _pack_ でアライメントのバイト数を定義してやる必要があります。
これを1にすればアライメントが無いのと同意になるわけです。
ちなみにここら辺は日本語の記述がほとんど無くて海外のサイトで見つけてきたのですが、
_pack_ = True
等と記載されているサイトも有りましたが…この値に3とかを入れてみると3バイトアライメントになる事から、やっぱり bool では無いと思のですがどうなんでしょう?

…以上、Python って短い記述でいろいろできるけど、細かい所はとっ散らかってるなあと愚痴を言いながら書いたコードでした。

2020年10月30日金曜日

5回目のランクイン

 …実は6月も狙ったのですが、最終日に490番台になって案の定逃しまして、今回はしっかり周回もしたのですよ。
そもそも、そんなにイベントを真面目にやっていなかったので資材も余っていましたしね。
そんな訳でこの「深山」。
私としては、STGの1942に居たような居ないような?くらいの印象しか無いデス。
ワンチャン、ホーネットに搭載できないかと思って試しましたがダメでした。

で、もう一つはイタリアの駆逐艦砲。
こやつの存在意義は…
コレですね。普通に良い砲ではありますが。
多分、次のイベントでマエストラーレを大量に確保しておけば、改二になる際に持ってくるんじゃないでしょうか?



2020年10月29日木曜日

Blender でアクティブ頂点グループに影響を与えるボーンを探してみる

 リギングの調整をしていて思った事。ボーン増えすぎて頂点に影響を与えているボーンがどれか判らない。
そんな訳で、先日作ったスクリプトを改造して作ってみました。
編集モードでメッシュオブジェクトの頂点編集中に起動すると影響を与えるアーマチュアのボーンが選択状態になります。
一応2.7、2.8で動作は確認しましたが、相変わらず2.8の動作は怪しいです。

これで

こうなる

コード
# serch active vg bone
import bpy
if bpy.context.mode == 'EDIT_MESH':
    for obj in bpy.context.selected_objects:
        if obj.type != 'MESH':
            continue
        
        # vg use check
        name_armature=''
        for modifier in obj.modifiers:
            if modifier.type != 'ARMATURE':
                continue
            if not(modifier.use_vertex_groups):
                continue
            name_armature = modifier.object.name
            break
        
        if not(name_armature):
            continue
        active_vg = obj.vertex_groups.active
        obj_arm = bpy.data.objects[name_armature]
        arm = obj_arm.data
        
        # search bone loop
        for bone in arm.bones:
            if bone.name == active_vg.name:
                print(bone.name)
                bone.select=True
                break

特筆するところは
obj_arm = bpy.data.objects[name_armature]

for bone in arm.bones:
以下のループ処理ですね。
前者はアーマチュアオブジェクトを取得する際にテーブルに対して、モディファイアから取得したアーマチュアの名称をキーとして直接データを呼び出しているのに対して、後者は頂点グループ名と一致するボーンをループで探しています。
前者はモディファイアにアーマチュアを登録する際に実在する事が担保されているので、直接名前で呼び出しましたが、後者は頂点グループ名とボーン名が一致する保証は無い為、わざわざ一致するボーンが出るかを検索しています。

実はこれまで掲載してきたスクリプトは、皆2.7用アドオンに仕立て直したのですが、今後は2.8向けに修正しなくてはならないです。
その上で、リギング修正もしないと次の作業には入れない…近いようで遠いですねえ。

2020年10月26日月曜日

Blender で左右化したリギング済みオブジェクトを左側のみの状態に戻してみる

 前回、リギングがひとまず終わりまして、微調整し終わったらテクスチャ描き&2.9への移行へイクゾ―…と、思ったのですが。
私は左半分のみのモデルを作ってひたすらモデリング&リギングし、仕上げ工程で左右化しています(左右化)。
…が、ふと思った訳ですよ。左右化した後で問題が見つかったらどうするのか?と。
最後に片面で編集していたファイルまで戻ってやり直すというのが素直な手だとは思うのですが、流石に面倒過ぎる。
というわけで、左右化したオブジェクトを左片面に戻すスクリプトを書いてみた…のですが、流石に難産でした。
自分でも思い出せる気がしないので、コードを掲載すると共に解説します。
尚、2.7 2.8の両方で動作を確認していますが、2.8以降の collection の概念が入っていないので、後々不具合が出るかもしれません。

これらが
こうなる

コード

# half cut

import bpy
import bmesh

if bpy.context.mode == 'OBJECT':
    for obj_arm in bpy.context.selected_objects:
        if obj_arm.type != 'ARMATURE':
            continue
            
        print(obj_arm.name)
        
        # all object loop
        for obj in bpy.data.objects:
            if obj.type != 'MESH':
                continue
            
            # vg use check
            bEfected = False
            for modifier in obj.modifiers:
                if modifier.type != 'ARMATURE':
                    continue
                if not(modifier.use_vertex_groups):
                    continue
                if obj_arm.name != modifier.object.name:
                    continue
                bEfected = True
                break
            
            if not(bEfected):
                continue
            
            # remove right vg
            for vg in obj.vertex_groups:
                name = vg.name
                if name[len(name)-2:] == '.R':
                    obj.vertex_groups.remove(vg)

            # reset seams
            for vertex in obj.data.vertices:
                if vertex.co.x == 0:
                    for group in vertex.groups:
                        vg = obj.vertex_groups[group.group]
                        name = vg.name
                        if name[len(name)-2:] == '.L':
                            weight = vg.weight(vertex.index)
                            vg.add([vertex.index],weight*2,'REPLACE')

            # remove vertex
            me = obj.data
            bm = bmesh.new()
            bm.from_mesh(me)
            
            for v in bm.verts:
                if v.co.x < 0:
                    bm.verts.remove(v)
            bm.to_mesh(me)
            bm.free()
            
        # remove right side bone & rename left side bone
        bpy.ops.object.mode_set(mode='EDIT')

        arm = obj_arm.data
        for bone in arm.bones:
            name = bone.name
            if name[len(name)-2:] == '.R':
                remove_bone = arm.edit_bones[name]
                arm.edit_bones.remove(remove_bone)
            elif name[len(name)-2:] == '.L':
                bone.name = name[0:len(name)-2]

        bpy.ops.object.mode_set(mode='OBJECT')

使用方法は「リギングに使用したアーマチュアをオブジェクトモードで選択して」実行。

まずしょっぱなですが、アーマチュアの検索ループが入ります。
当初、リギングされたメッシュオブジェクト主体で作っていたのですが、1アーマチュアに複数オブジェクトという可能性がある訳ですよ。
これまで体だけ作ってきましたが、コレに服などを着せる場合、別オブジェクトで作る可能性があるわけですからね。

  • all object loop
と、いうわけで、アーマチュア選択ループの中に全オブジェクト検索ループが入ります。
ひとまずメッシュオブジェクト以外は省きます。

  • vg use check
リギングを行ったメッシュオブジェクトか?の判別です。
使用モディファイアの内、アーマチュア属性のものを拾います。
その後、頂点グループを使用しているか?影響を与えているアーマチュアの名前が一致するか?を判定していきます。

  • remove right vg
「右側」を意味する、語尾が .R の頂点グループをひたすら削除します。

  • reset seams
ミラーモディファイアで左右化したモデルは合わせ目で左右の頂点グループの影響度(~.R ~.L に左右化した頂点グループ)を半々で受けます。
右側の影響度に関しては、一つ前の処理で右側の頂点グループそのものを削除したので影響度は無いのですが、左側は倍にセットし直す必要があります。

…のですが、コレは大ハマりしました。
当初頂点グループの影響度はこんな感じのパラメータだと思っていたんですよね。
が、実際はこんな感じ。
…頂点グループの方が主体。
最初はなんでこんな面倒くさい事になってるんだと思いましたけど、冷静に考えてみると判りますね。
頂点は物体を示す最小単位な訳です、当然、立体に関わるパラメータは全て頂点にも紐づけられます。
私でも判る範囲で考えるなら、テクスチャUV、頂点法線、頂点カラー…
当初の私の予想通りだと、機能追加する度に頂点の処理を追加していく事になって頂点処理担当が忙殺される事は間違いないです。
なので、追加した機能の方で極力完結する後者のような設計になったんでしょうね。

…で、そっちの設計思想は理解するのですが、もう一つ
vg.add([vertex.index],weight*2,'REPLACE')
…コレは酷い
これは頂点に対する加重値を2倍にしているのですが…
値の変更の関数は普通 set だろうに、なぜ add 関数にパラメータで REPLACE にしておけばおkとか言ういい加減な作りにしたのか。
なんでこんな不可解なネーミングを許したのかまったく理解できませんでした。

  • remove vertex
オブジェクトから右側の頂点を全削除する処理なのですが…
一つ前の処理で
for vertex in obj.data.vertices:
と書いており、obj.data.vertices の中から右側の頂点だけ削除すれば良いじゃないかと当初は思っていました。
が、コレは動作しません。
obj.data の内容を一旦 BMesh に書き写してから BMesh 上で右側の頂点を削除して書き戻す必要があります。
正直、どうしてこんな処理になっているのかは解りませんが、作法だと思う事にしました。

  • remove right side bone & rename left side bone
最後は右サイドのボーンを削除し、左サイドのボーンをリネーム(~.L から .L の無い名前に変更)する処理。
特筆する部分もあまり無いのですが、ボーンの削除は編集モードでないとできない為、処理の最初に編集モードにセットし、最後にオブジェクトモードに戻しています。
尚、左側のボーンを変名した為、関連して頂点グループ名の ~.L だったものも変名されます。
この為、ボーン変名の前に影響を受ける全メッシュオブジェクトの処理を終えておく必要があります。


いや、難産でしたし、どうしてこんなに面倒くさい作りになってるんだろう?と何度も悩みましたが…
最初にスクリプトを書いたときはコードを書いていて吐き気がする程でしたが、今回は設計思想や開発陣の配置など内部的な事が薄っすら見えてそれなりに楽しめました。
次のステップへの弾みになると良いのですが。

2020年10月14日水曜日

脚部のリギング

 前回腕の捻りを作りまして、その際に「捻る」動作のリギング法が判ったので、ふとももに応用する事になったのですが…。
気付けば大腿部からつま先まで全てに修正を入れる事になりました…

  • 太腿
先ずはボーン数の比較
before

after


はい、別物ですね。むしろ以前のボーンは良くあんな少数で破綻しなかったなと思います。
で、これを動かしてみる。


開脚時に一定以上の角度を開脚すると腰の外側に周辺の肉を引き込むようにボーンを組んだのですが…少し不自然かも。
ただ、こうしないとシルエットが崩れるんですよねえ。


レッグカールをさせていますが、本来は踵がお尻に付かない想定です。上腕筋が付きすぎると自分の肩が掴めなくなるように、ハムストリングを鍛えすぎるとお尻に踵がつきません。
ただ、正座のようにむりやり膝の靭帯を伸ばせば付くので、この曲げ方は限界よりやや曲がっています。

このため、ややきついポリゴンの干渉がありますが、スルーしています。
ひかがみ(内膝)は膝が伸びた状態だと両端の腱より間の肉が浮きますが、コレは上下の骨の接合部が内側から肉を押し上げているかららしいです。
ただ、形状(というか動き方)が特殊なので曲げると見た目上へこむのですが…この動きは膝の曲げに対して係数をとる為のストレッチボーンを作り、これの伸縮をコピーする形で制御ボーンを作っていきました。
完全に膝が曲がるとふくらはぎが潰れますが、これは膝の曲げから係数をとってふくらはぎ等のボーンを稼働させているだけで、セルフコリジョンなどは一切していません。
やり方は以前記述しています。

  • 足首
以前は角度決め打ちでしたが、今回から目標ボーンを追従する形にしました。
何年か前に適当に作ったアキレス腱関係を改善していますが…手首や足首はリストバンド状の筋膜があって、複数の筋腱を束ねています。
これのお陰で動かしても形をある程度保っているのですが…リギングでバカ正直にボーンに割り付けると動かしたボーンにつられて変形し、型崩れを起こします。
このため、アキレス腱をふくらはぎ側と踵側に分け、接点を足首の曲角度に合わせてずらすようにボーンを組みました。

又、足首を内側に曲げると連動で親指が浮き、外側に曲げると小指が浮く(つまり捻られる)ようにしました。
足首の上下曲げは任意にできますけど、捻りはできないですからね。
本当は地面の平行面に合わせて、足の裏の角度を調整する処理を作ろうかと考えましたが辞めました。
地面が常に平行とは限らないですし、都度変えた方が良いかなと。

  • つま先

親指の付け根の関節だけ目標ボーンを追従する形をとっています。
そして、目標ボーンの拡縮を行うと親指の先端の拡縮ボーンが伸縮し、その伸縮に親指の指先が連動する形で曲がります。


又、親指以外のボーンはもっと顕著で、付け根、先端用の2本の指揮ボーンの拡縮に合わせて、指4本が連動するため、2本のボーンで4本の指の開閉を行っています。


拡縮ボーンを使って指を曲げるという処理は、かなり昔トニーマレン著の教書に載っていた気がするのですが…IKでやっていたのでしたっけ?
※追記 多分、トニーマレン著『3Dキャラクターアニメーション Blender 』です
既に今年の頭に教書を大量処分したので確認できませんが、足の指程度ならこういうざっくりとした処理でも良いかなと思いました。

今後は、一旦顔の作りを顎の骨だけ実装しつつ顔無し状態に戻し、微調整後に顔の作成に入りたいですね。
顔は、多ノードレンダリング前提で、なおかつトゥーン調を目指す為、全く別次元の技術が必要になっていきそうです。
やる事が変わる節目ですので、2.79から一気に2.9系に移行する予定です。
あと、多ノード処理はもう10年近くやっていないので、又勉強のやり直しです…先は長いなあ。

2020年10月5日月曜日

最新の Linux Mint で旧 Blender を標準に使う

 …お前は何を言っているんだ。と思われそうですが。
Linux Mint の標準ツールで Blender をダウンロードすると当然安定板の最新版が落ちてきますが…お察しの通り、私は未だに 2.79b を使っています。
慣れない操作でリギングしたくないからで、テクスチャ描きに移行したタイミングで 2.8 に変えたい、というかもう 2.9 がリリースされている。
流石に Mint20 もリリースされて半年経ちましたし、リリースされていない32ビット機ならともかく、64ビット機で使用しない訳にはいかない。なにより Mint20 にしていない関係で Geany 上の Python3 の挙動が怪しい。
…という訳で、先日メインのノートを Mint20 に移行しましてレガシーな Blender を使うのですが…

コレはひとまず Blender のダウンロードページから Previous Versions を選べば、凍結された旧バージョンの Blender 一式を落としてこれまして。

助かる事に展開してできたディレクトリの Bleder をクリックするだけで、ライブラリの依存関係を考えずに起動してくれます。


じゃあコレを .blend の標準起動アプリに設定すれば良いわけなんですけど、当然インストーラを経ていないので候補に挙がりません。

ひとまず、展開したディレクトリ名が長いので短くし、適当なディレクトリに移動し、ターミナルでコマンドで起動。
※例では、home のユーザーディレクトリに移動させ、展開したディレクトリ名を blender-2.79b に変名している

コマンドでの起動を確認したら、起動コマンドをコピーしまして。
これを~アプリケーションとして記憶するにチェックをし、「コマンドを直接指定する」に登録。

これにて無事 2.79b が標準で起動するようになります。
…いや、当然だろ?って言われそうなんですけど、当初リポジトリから旧バージョンを引っ張ってきてライブラリの互換性に問題が起きて…ってな事を繰り返していまして。
難しく考え過ぎたな、という反省を込めての忘備録です。

2020年9月29日火曜日

クリエイティブな 2in1 PCが欲しいという話

 モデリングがテクスチャ描きに移行できた時点でニューマシン(といっても中古可)に切り替えようと思って物色しているわけだが…先ず、前回書いた通り、Lenovo はダメそう。
7/7に発売を始めた Flex550 は intel 版はそこそこ在庫があるのに Ryzen 版は売り切れ。
これ、APUが手に入ってないんでは? intel とは Celeron 等でパイプがあって多少は融通できたのでは…

そんな訳で、他を見ると…明暗を分けたのは Dell と HP ですね。
HP は健闘してますが、Dell は生産拠点が中国にある事もあって新製品もままならないし、納期もあやしくなってきている。
で、私の「モデリング、レンダリングも出来て、テクスチャ描きができて、テストショット(短いムービー)が作れる、持ち運び可能な 2in1」というべらぼーに高い要求を満たす機械なんですが。
先ず、 Flex550 の Ryzen 5 バージョンに「価格は無理としても」追いついて欲しい…という観点で調べたのですが…

  • Asus ZenBook Flip 14 UX463FL
10世代 Core i5 に MX250 GPU を積んだ 2in1 の常識が吹っ飛んでいる機種です。
MX250 GPU について調べましたが、『ほぼ GT1030』でした。ゲーミング用にはやや弱いですが、GT1030 は何年か前に GGXrd2rev をプレーする為に組んだゲーミングマシンに積んだボードですし、決して遅くはありません。
コレに4コア4スレッドのCPUが付く訳で、FF15の FHD 標準画質がギリギリ動くレベルくらいではないかと。
が、こいつには最大の弱点が
日本未発売
ヲイヲイ。

  • 次は HP Spector x360 13
なぜかシークレットギフトキャンペーンで Core i5 版が10万円切りで売られてます。

同社に似た 2in1 に Envy 13 があるのですが…こっちはどうもアウト。
2世代 Ryzen 版だと、13インチ版の Ryzen7 が15インチ版の Ryzen5 に性能で劣るようです。
原因は排熱能力不足と判明しており、Envy の13インチ版はレンダリング等の高負荷処理はできません。…高負荷処理ができない Ryzen の存在意義とは…

じゃあ Spector なら大丈夫か?と言われればコレも不明。ただ、排熱の設計は Envy よりは良くできているようです。

  • では Spector のアウトレット品かと言うと、最後の候補。
前述の ZenBook Flip 14 UX463FL の前世代機 zenbook flip ux461un の中古品。
2018 年販売で8世代 Core i5、GPU は MX150。CPU、GPU ともに性能は1~2割ダウン程度。
コレは日本でも発売されていまして、ヨドバシの最終価格は10万円ちょっと。
なら中古で8万円台が適正なんですが…希少機種の為、プレミアがついて20万円以上で出している悪徳業者が居たりします。…まあ、オークションサイトでは5万円台なんてのもあるんですが、パソコンをオークションで買うのは博打が過ぎるでしょう。


…なんにしろ、モデリングの進捗次第です。
購入のタイミングで Flex550 が買えるのなら買いますしね。