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で芳香環に面を張るやつ

コメント

このブログの人気の投稿

ブルアカで学ぶ韓国語#1_生徒名のハングル表記

パワポに直接挿入可能な3D分子モデルを作る

VSCodeにLaTeXを入れました