Blenderで読み込んだ分子モデルにマテリアルを自動設定したかった
23.9.14追記
修正版作りました。
molファイルを編集することなく、そのまま読み込めるようにしてあります。https://sanasnote.blogspot.com/2023/09/blendermol.html
以前こんな投稿をしました。
→ http://sanasnote.blogspot.com/2021/07/3d.html
Avogadroでmolファイルを3Dモデルに変換してBlenderに読み込んで、なんやかんや着色したりパワポに貼ったりするという内容です。
マテリアルをひとつひとつ選択して設定しなきゃいけないので、分子量が大きかったり構造が混み入ってたりすると苦行になります。
というわけで、なんかいい感じに自動設定してくれるスクリプト作れないかな?と思ってやってみたのがこの記事です。
結論から言うと、最低限動くことは動く、という感じです。
具体的な方針としては、
1. molファイルを原子部分と結合部分の情報にそれぞれ分割して保存する
2. pythonでtxtファイルを読み込み、原子と結合それぞれの原子種リストを作る
※Blenderに読み込んだ時のオブジェクトの並びは、元のmolファイル中の原子の順番に対応しています
3. リストに従ってマテリアルをひとつずつ割り当てていく
以下スクリプトです。
(Stickモデル用です。CPKモデル用はページ末尾に貼っておきます。)
#for stick-model import bpy import csv import re #-------------------------------------------- # molファイルから作成した原子の情報と結合の情報が入ったtxtファイルを指定する atom_file="C:/******************.txt" bond_file="C:/******************.txt" #---------------------------------------------------------- # delete all materials and material_slots # あらかじめマテリアルとマテリアルスロットを全部消す # これを入れないと実行するたびにマテリアルが増殖する for mats in bpy.data.materials: bpy.data.materials.remove(mats) for o in range(len(bpy.data.objects)): bpy.context.view_layer.objects.active = bpy.data.objects[o] for ms in range(len(bpy.data.objects[o].material_slots)): bpy.ops.object.material_slot_remove(ms) bpy.context.view_layer.objects.active = None #---------------------------------------------------------- # 貼り付けるマテリアルを設定しておく # どれにも該当しない場合はmaのマテリアルが割り当てられる # makematerial("名前", 色(RGBA), 発光(省略可)) # 作ったらmlistとmdictの末尾に付け加える def make_material(name, color, emit = 1): ma = bpy.data.materials.new(name) ma.use_nodes = True bsdf = ma.node_tree.nodes["Principled BSDF"] bsdf.inputs[0].default_value = color bsdf.inputs[4].default_value = 0.2 bsdf.inputs[7].default_value = 0.3 bsdf.inputs[18].default_value = emit ma.diffuse_color = color return ma ma = make_material("Others",(0.9,0.9,0.9,1)) mc = make_material("Carbon",(0,0,0,1)) mh = make_material("Hydrogen",(0.2,0.5,0.9,1)) mn = make_material("Nitrogen",(0,0.05,0.95,1)) mo = make_material("Oxygen",(1,0.05,0.05,1)) mb = make_material("Boron",(0.05,0.75,0.05,1)) mf = make_material("Fluorine",(0.5,0.95,0.5,1)) mcl = make_material("Chlorine",(0.3,1,0.1,1)) mbr = make_material("Bromine",(0.5,0.15,0.1,1)) mi = make_material("Iodine",(0.4,0.1,0.7,1)) mp = make_material("Phosphorus",(1,0.4,0.7,1)) ms = make_material("Sulfur",(1,0.9,0,1)) mlist = [ma, mc, mh, mn, mo, mb, mf, mcl, mbr, mi, mp, ms] mdict = {'C' : 1, 'H': 2, 'N': 3, 'O': 4, 'B': 5, 'F': 6, 'Cl': 7, 'Br': 8, 'I': 9, 'P': 10, 'S': 11} #---------------------------------------------------------- # 原子と結合のリストを作る # 正規表現でむりやり検索している # 原子のほうは「アルファベットの文字列」、結合のほうは「1つめと2つめの数字」を取得している # make atom-list ------------------- with open(atom_file, 'r') as fa: rla = fa.readlines() atomlist = [] for l in rla: at = re.search('[a-zA-Z]+',l) atomlist.append(at.group()) print("Atoms: ", atomlist) # make bond-list -------------------- with open(bond_file, 'r') as fb: rlb = fb.readlines() bondlist = [] for l in rlb: bntmp = re.finditer('\d+',l) tmp1=int(next(bntmp).group())-1 tmp2=int(next(bntmp).group())-1 bondlist.append(atomlist[tmp1]) bondlist.append(atomlist[tmp2]) print("Bonds: ", bondlist) # オブジェクト番号と対応した並びの原子(マテリアル)リストを作る # これをもとにマテリアルを割り当てていく alllist=[] alllist = bondlist + atomlist print("Set Materials in this order:", alllist) # set materials ---------------------- # マテリアルを割り当てる cnt=0 for o in range(len(bpy.data.objects)): if bpy.data.objects[o].type == "MESH" : if mdict.get(alllist[cnt]) == None: matmp = ma else: matmp = mlist[mdict.get(alllist[cnt])] bpy.data.objects[o].data.materials.append(matmp) for m in bpy.data.objects[o].material_slots: m.material = matmp cnt += 1マテリアル設定などは「BlenderユーザーのためのPython入門」(C&R研究所)のコードを参考に作成しています。 https://www.c-r.com/book/detail/1411
-----------
トルエンのstickモデルを例にして実際に動かすとこんな感じになります。
まずmolファイルをテキストエディタで開いて、
上側と下側をそれぞれコピペしてテキストファイルとして保存します。
※空行を作るとエラーが出ます。行数とオブジェクト数が対応しなくなってしまうためです
Blenderを開いて、最初に立方体を消してから分子のwrlファイルを読み込みます。
ここの詳細な操作は前の記事を参照してください。
※余計なオブジェクトがあると動作しません。分子模型のみが読み込まれている状態にしてください。
テキストエディタを開いて、先ほどのスクリプトを貼り付けます。
先ほどのテキストファイルのpathをスクリプト冒頭のatom_file, bond_fileに指定します。
いろいろ調べながら手探りで作った感じなので、改善策などあれば教えていただけると嬉しいです。
上のスクリプトから結合部分を抜いただけで、操作手順も全く同じです。
#for CPK-model import bpy import csv import re #-------------------------------------------- atom_file="C:/******************.txt" #---------------------------------------------------------- #delete all materials and material_slots for mats in bpy.data.materials: bpy.data.materials.remove(mats) for o in range(len(bpy.data.objects)): bpy.context.view_layer.objects.active = bpy.data.objects[o] for ms in range(len(bpy.data.objects[o].material_slots)): bpy.ops.object.material_slot_remove(ms) bpy.context.view_layer.objects.active = None #---------------------------------------------------------- def make_material(name, color, emit = 1): ma = bpy.data.materials.new(name) ma.use_nodes = True bsdf = ma.node_tree.nodes["Principled BSDF"] bsdf.inputs[0].default_value = color bsdf.inputs[4].default_value = 0.2 bsdf.inputs[7].default_value = 0.3 bsdf.inputs[18].default_value = emit ma.diffuse_color = color return ma ma = make_material("Others",(0.9,0.9,0.9,1)) mc = make_material("Carbon",(0,0,0,1)) mh = make_material("Hydrogen",(0.2,0.5,0.9,1)) mn = make_material("Nitrogen",(0,0.05,0.95,1)) mo = make_material("Oxygen",(1,0.05,0.05,1)) mb = make_material("Boron",(0.05,0.75,0.05,1)) mf = make_material("Fluorine",(0.5,0.95,0.5,1)) mcl = make_material("Chlorine",(0.3,1,0.1,1)) mbr = make_material("Bromine",(0.5,0.15,0.1,1)) mi = make_material("Iodine",(0.4,0.1,0.7,1)) mp = make_material("Phosphorus",(1,0.4,0.7,1)) ms = make_material("Sulfur",(1,0.9,0,1)) mlist = [ma, mc, mh, mn, mo, mb, mf, mcl, mbr, mi, mp, ms] mdict = {'C' : 1, 'H': 2, 'N': 3, 'O': 4, 'B': 5, 'F': 6, 'Cl': 7, 'Br': 8, 'I': 9, 'P': 10, 'S': 11} #---------------------------------------------------------- # make atom-list ------------------- with open(atom_file, 'r') as fa: rla = fa.readlines() atomlist = [] for l in rla: at = re.search('[a-zA-Z]+',l) atomlist.append(at.group()) print("Atoms: ", atomlist) # set materials ---------------------- cnt=0 for o in range(len(bpy.data.objects)): if bpy.data.objects[o].type == "MESH" : if mdict.get(atomlist[cnt]) == None: matmp = ma else: matmp = mlist[mdict.get(atomlist[cnt])] bpy.data.objects[o].data.materials.append(matmp) for m in bpy.data.objects[o].material_slots: m.material = matmp cnt += 1
関連記事
パワポに直接挿入可能な3D分子モデルを作るBlenderで芳香環に面を張るやつ
コメント
コメントを投稿