Blenderで提供されているPython API機能(bpy)を使ってテキストベースで、3Dモデリングをしてみようと思い立ち、実際に一般的に行われるモデリングの手順と比較しながらやってみました。

実際にモデルを作りながら比較したことで、結果として私はこちらのテキストベースのモデリング手法に落ち着きそうです。作業を行ってみて月並みですが次のような利点に気が付きました。
POINT!
  • 手順の再現性
  • 複数パターンのモデル生成が容易
  • ショートカットを覚える手間が少ない
  • Blender特有の操作感に慣れる手間が少ない
  • 成果物をGitなどVMS管理可能
デメリットとして挙げるならモデリング速度です。一からモデルを作成する場合、熟練のBlenderユーザーには作業スピードで最終的には追いつけないことがあると思います。

このほかのメリット、デメリットについては実際に作業の手順を確認しながら自分自身で判断してみてください。向き不向きもあると思います。

本記事の作成手順についてはこちら↓の記事と手順を対応させているので、比較しながら見てください。

▼比較用一般的なモデリング手順



コピペで実行できる最終的なコード全文は本記事末尾に置いています。


「閉じている本」モデル完成品


下のような「閉じている本」のモデリングを行います。テキストベースの手法でも同じものをつくることで比較していきます。
360°すべての視点から眺めて見てみてください。

Blender Python APIを使ったモデルの生成

Pythonを使用する利点を活かして、このようにワンクリックで複数のモデルパターンを容易に生成することができるものを作成してみます。


Blender Python APIによるモデリング環境


使用したBlenderの版数は以下です。
BlenderVersion 4.2.0 stable
LanguageEnglish
Commit Time「16 Jun 24, 07:50」

PYTHON INTERACTIVE CONSOLE 3.11.7 (main, Feb  5 2024, 18:45:06) [MSC v.1928 64 bit (AMD64)]
Builtin Modules:       bpy, bpy.data, bpy.ops, bpy.props, bpy.types, bpy.context, bpy.utils, bgl, gpu, blf, mathutils
Convenience Imports:   from mathutils import *; from math import *
Convenience Variables: C = bpy.context, D = bpy.data

Blender Launcherを使うと、バージョン管理など比較的に楽に行うことができます。

Blenderのバージョンが比較記事と多少違いますが、手順自体に違いはありません

Editor: Visual Studio Code
エディタは好みのものをご使用ください。


Blender Python API(bpy)基本操作


Blender Python APIで用いる機能について簡単に以下にまとめます。

作成したPythonの実行方法

インタラクティブにコンソールに入力していく方法もありますが、今回はテキストファイルにまとめてスクリプトを記載して、読み込み、実行する方法を取ります。
  1. Blenderを開く
  2. 上のツールバー「Scripting」(見えていない場合はツールバーをスクロール)
  3. 「Open」→作成したPythonコードを選択
  4. 「▶」実行ボタンでスクリプト実行

各操作と対応するbpyコマンド確認方法

BlenderのPython APIで、一般的なBlender操作と対応するコマンドを知りたい場合、上記の「Info(情報)エディタ」にBlender上で行った操作に対応するコマンドが表示されます。

bpyの公式ページを参考にしても良いですが、基本的なbpyの文法についてはこちらを参照することでほとんどすべて知ることができます。

軸の説明

  • Z軸:縦軸
  • X軸:平面軸
  • Y軸:平面軸

モデル確認時の視点変更

  • 「マウスホイール」+ドラッグ:視点中心を支点として360°視点変更
  • 「Shift」+「マウスホイール」+ドラッグ:視点中心を変更
  • マウスホイール:拡大縮小
  • 「テンキー0」:カメラ視点
  • 「テンキー1」:正面(y軸)
  • 「テンキー3」:正面(x軸)
  • 「テンキー7」:正面(z軸:見下ろし視点)
  • 「テンキー5」:投影方法切替え(透視投影/平行投影)
  • 「Numpad」(テンキー真中上"/"):視点の中心に選択オブジェクト
  • 「Shift」+「C」:視点とビューポート原点(赤白のターゲット)初期化

各モードの説明

<Object interaction Mode>
  • Object Mode:オブジェクトの追加や変形
  • Edit Mode:各オブジェクトの編集
<編集モード>
  • Vertex Select:頂点選択モード
  • Edge Select:辺選択モード
  • Face Select:面選択モード
<編集モードの説明>
  • Propotional Editing Mode:選択した辺、点、面を起点に減衰しながら周囲を追従移動


Blender Python APIを用いた本作成手順


以下で制作までの過程を示します。

1. Blenderを開いた初期状態

まず状況に合わせた初期状況の統一が面倒なので一度すべてのオブジェクトを消して、Blenderを開いた初期状態と同じような状況をbpyで再現します。

こうすることで、「どんな状況からスクリプトを実行しても同じ結果が得られる」という点でも便利になります。
  • モード情報(左上)「Object Mode」

Python:環境初期化
# 未使用アイテムを削除
def rm_nonused_all_items():
    for mesh in bpy.data.meshes:        # 未使用のメッシュデータブロックを削除
        if not mesh.users:
            bpy.data.meshes.remove(mesh)
    for texture in bpy.data.textures:   # 未使用のテクスチャデータブロックを削除
        if not texture.users:
            bpy.data.textures.remove(texture)
    for texture in bpy.data.lights:     # 未使用のテクスチャデータブロックを削除
        if not texture.users:
            bpy.data.lights.remove(texture)
    for texture in bpy.data.cameras:    # 未使用のテクスチャデータブロックを削除
        if not texture.users:
            bpy.data.cameras.remove(texture)
    for material in bpy.data.materials: # 未使用のマテリアルを削除
        if not material.users:
            bpy.data.materials.remove(material)
            
# Object Modeへ切り替え
bpy.ops.object.mode_set(mode='OBJECT')
# ------------------------------------------------------------------
# - Delete all objects in the scene
# ------------------------------------------------------------------
# 全てのオブジェクトを選択
bpy.ops.object.select_all(action='SELECT')
# 全てのオブジェクトを削除
bpy.ops.object.delete(use_global=False)
# 未使用になったアイテムを削除
rm_nonused_all_items()
# ------------------------------------------------------------------
# - 1. Blenderを開いた初期状態 (Create Cude)
# ------------------------------------------------------------------
# キューブを生成
bpy.ops.mesh.primitive_cube_add(
    size=2              # 1辺の長さ
,   location=(0, 0, 0)  # 配置場所
,   scale=(1,1,1)       # x, y, y サイズ x軸方向に1倍, y軸方向に1倍, z軸方向に1倍
)
# キューブに名前を設定
bpy.context.object.name = "MyCube_001"
# カメラを追加
bpy.ops.object.camera_add(
    location=(7, -7, 5)
,   rotation=(math.radians(63), math.radians(0), math.radians(47))
,   scale=(1, 1, 1)
)
# カメラに名前を設定
bpy.context.object.name = "MyCamera_001"
# Lightを追加
bpy.ops.object.light_add(
    type='POINT'            # 'POINT':点光源 , 'SUN':平行光源, 'SPOT':スポットライト, 'AREA':面光源
,   location=(4, 1, 6)
,   scale=(1, 1, 1)
)
# ライトに名前を設定
bpy.context.object.name = "MyPointLight_001"
# - すべてのオブジェクトの選択を解除
bpy.ops.object.select_all(action='DESELECT')
ここまでで、Blenderを開いて何も触っていない状況と同じようなベースとなる環境ができたはずです。

2. 本全体の縦横比、厚さを決める


ベースとなる環境を整えたので、ここからオブジェクトを変形していきます。

2.1. キューブを選択
2.2. Z軸方向にキューブを拡大縮小する
2.3. Y軸方向にキューブを拡大縮小する
ここで本の厚さ、縦横比などの大まかな形が完成します。上記手順をスクリプト化すると次のようになります。

Python:
# アクティブオブジェクト選択
def active_element_select(
        object_name         # アクティブ化するオブジェクトの名前
):
    # アクティブオブジェクトを選択
    myobject = bpy.data.objects.get(object_name)
    if myobject:
        myobject.select_set(True)  # 選択状態にする
        bpy.context.view_layer.objects.active = myobject  # アクティブオブジェクトに設定
    return myobject

# ------------------------------------------------------------------
# - 2. 本全体の縦横比、厚さを決める
# ------------------------------------------------------------------
# 2.1 オブジェクト(Cube)を選択する
my_active_element = active_element_select(object_name='MyCube_001')
# 2.2 Z軸方向にキューブを拡大縮小する
# 2.3 Y軸方向にキューブを拡大縮小する
my_active_element.scale = (1.0, 1.4, 0.25)    # (参考値:X:1.0, Y:1.4, Z:0.25)

3. 本の背表紙を作る


3.1. 編集モード(Edit Mode)に切り替え

本棚に並べた時の正面となる本の背表紙が、平面だと味気ないので丸みを持たせていきます。背表紙を中心から膨らませるように変形していきます。

これには、ループカットという機能を使用します。

名前の通り、オブジェクト一周をぐるりと囲うように辺が作られます。この操作によって、オブジェクトに辺と面を増やすことができます。

3.2. ループカットを行う箇所場所を選択する
3.3. ループカットする辺の数を決める
今回は、辺が5つになるようにしています。

次に辺の移動を行い、背表紙を形作っていきます。
3.4. 辺選択モード(Edge Select)へ切り替え
3.5. ループカット機能で増やした側面の辺を選択
3.6. Proportional Editingモードへ切り替える
3.7. 辺を移動させる
  • Propotional Editing Mode:選択した辺、点、面を起点に減衰しながら周囲を追従移動
他の辺も一緒に選択している辺に追従しながら丸みを持たせて変形させることができる。
POINT!プロポーショナルとは、比例関係という意味で、「均整のとれた」などの意味があります。
ここまでで遠目から見たら本と言える形の大枠が整います。上記手順をスクリプト化するとこのようになります。(※上ですでに定義した関数については省略しています)

Python:
# エッジ、面、頂点などの選択用関数
def element_select(
        element_list        # 選択するエレメントリスト
,       select_mode         # モード(VERT, EDGE, FACE)
,       object_name="NaN"   # オブジェクト名
):
    if (object_name != "NaN"):
        # アクティブオブジェクトを選択
        obj = bpy.data.objects.get(object_name)
        if obj:
            obj.select_set(True)  # 選択状態にする
            bpy.context.view_layer.objects.active = obj  # アクティブオブジェクトに設定
    # アクティブなオブジェクトを取得
    obj = bpy.context.object
    # メッシュオブジェクトかどうかを確認
    if obj and obj.type == 'MESH':
        # メッシュデータを取得
        mesh = obj.data
        # オブジェクトモードからエディットモードに切り替え
        bpy.ops.object.mode_set(mode='EDIT')
        # モード切り替え
        bpy.ops.mesh.select_mode(type=select_mode)
        # すべての要素の選択を解除
        bpy.ops.mesh.select_all(action='DESELECT')
        # オブジェクトモードに切り替え
        bpy.ops.object.mode_set(mode='OBJECT')
        # 特定の要素を選択(3D Viewから要素インデックスを確認)
        for i in range(len(element_list)):
            target_ele_index = element_list[i]
            if (select_mode == "FACE"):
                mesh.polygons[target_ele_index].select = True
            elif (select_mode == "EDGE"):
                mesh.edges[target_ele_index].select = True
            elif (select_mode == "VERT"):
                mesh.vertices[target_ele_index].select = True
        # 再度エディットモードに切り替え
        bpy.ops.object.mode_set(mode='EDIT')
    else:
        print("No mesh object selected.")

# ------------------------------------------------------------------
# - 3. 本の背表紙を作る
# ------------------------------------------------------------------
# 3.1 Edit Modeへ切り替え
bpy.ops.object.mode_set(mode='EDIT')
# 3.2 ループカットを行う箇所を選択
# 3.3 ループカットする辺の数を決める
bpy.ops.mesh.loopcut_slide(
    MESH_OT_loopcut={
        "number_cuts":5             # 追加するループ数
    ,   "smoothness":0              # 0~1:スムージングの強さ
    ,   "falloff":'INVERSE_SQUARE'  # どのようにカットが減衰するか "INVERSE_SQUARE":逆2乗フォールオフ(例:SHARP)
    ,   "object_index":0            # 編集するオブジェクトのインデックス(通常0:最初のオブジェクト)
    ,   "edge_index":1              # ループカットを適用するエッジのインデックス
    }
,   TRANSFORM_OT_edge_slide={
        "value":0                   # カットのスライド位置(0:スライドしない)
    ,   "single_side":False         # 片側にだけスライドを適用するか(False:両側にスライド適用)
    ,   "use_even":False            # スライドを均等にするか(False:均等にしない)
    }
)
# 3.4 エッジセレクトモードへ切り替え
# モードを切り替え(これを行うとメッシュデータの更新が正しく行われる)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='EDGE')
# 3.5 ループカット機能で増やした側面の辺を選択
# オブジェクト(Cube)を選択する
my_active_element = active_element_select(object_name='MyCube_001')
my_active_element.name = "MyBook_001"   # Mybook_001へ名前を変更
# エッジの選択
element_select(
    element_list= [49]
,   select_mode="EDGE"
)
# 3.6 Proportional Editingモードへ切り替える
# 3.7 辺を移動させる
bpy.ops.transform.translate(
    value=(-0.31, -0, -0)                               # 移動する距離(x, y, z)
,   use_proportional_edit=True                          # プロポーショナル編集(周囲の頂点に影響するか)
,   proportional_edit_falloff='SMOOTH'                  # プロポーショナル編集の影響の強さ('SMOOTH':影響が滑らかに減少することを意味する
,   proportional_size=0.7                               # プロポーショナル編集の範囲指定
,   use_proportional_connected=False                    # 接続された頂点にのみ影響を与えるか(False:全体に影響)
,   use_proportional_projected=False                    # 投影された範囲に提要されるか(False:通常の3D空間に適用)
)

4. 本の紙の束部分(小口/天)を作る

実際にページに当たる部分と本の表面部分の境目を作っていきます。具体的にはページ部分と表紙、裏表紙、背表紙との間に存在する段差部分です。

4.1. 面選択モード(Face Select)へ切り替える
4.2. 側面の部分を全て選択する
選択した状態では、面はデフォルトでオレンジ色になっていると思います。
4.3. 選択している面を内側に差し込む
現在選択している面をへこませることで、表紙部分と、紙束部分の段差を作りますが、表紙や背表紙など、本を覆う表紙部分の厚さの分は、段差を付ける必要がないので、選択している面を内側に縮小させていきます。
選択面を移動することで、新たな辺が追加され、それにより面が追加されました。

ここまでで、本を覆っている表紙、背表紙、裏表紙部分と、本のページの紙束部分に面がおおよそ分けられたと思います。

4.4. 微修正
背表紙や表紙部分など、まとめて辺を差し込んだことで厚みがイメージと異なるものになっているかもしれません。

今回は、表/裏表紙の厚さが適切になるところまで、面を差し込んだため、背表紙の厚みに違和感が出てしまいました。その点を修正していきます。

4.5. 辺選択(Edge Select)モードへ切り替え
4.6. 背表紙の厚みに当たる内側の辺を選択
4.7. 辺の移動モードを切り替え
4.8. 選択した辺の移動
背表紙の厚さが適切な位置になるところまで、辺を移動させます。上下両方の辺が移動できていることを確認します。

次にページとなる部分と、表紙部分に段差を付けていきます。
4.9. 面選択モード(Face Select)に切り替えてページ部分を選択
4.10. 面の押し出し、引き込み
Extrude Face Along Normals」(領域を法線方向に押し出し)を用いて行います。

選択した面を表紙部分に比べ、内側に引き込みます。

ここまでで、かなり目標の本の形になります。上記手順をスクリプト化したものが以下です。

Python:
# ------------------------------------------------------------------
# - 4. 本の紙の束部分(小口/天)を作る
# ------------------------------------------------------------------
# 4.1 面選択モードへ切り替え
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')
# 4.2 側面の部分を全て選択する
element_select(
    element_list=[  3, 10, 9, 8, 7, 6, 
                    2, 15, 14, 13, 12, 11, 
                    1, 20, 19, 18, 17, 16]
,   select_mode="FACE"
,   object_name="MyBook_001"
)
# 4.3 選択している面を内側に差し込む
bpy.ops.mesh.inset(
    thickness=0.15          # インセットの厚さ(内側に押し込まれる)
,   depth=0                 # インセットの深さ(押し出し)
)
# 4.4 微修正
# 4.5 Edge Modeへ切り替え
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='EDGE')
# 4.6 背表紙の厚みに当たる内側の辺を選択
element_select(
    element_list=[52, 64, 63, 62, 61, 60,
                  65, 66, 67, 68, 69, 53]
,   select_mode="EDGE"
,   object_name="MyBook_001"
)
# 4.8 選択した辺の移動
bpy.ops.transform.translate(
    value=(-0.11, -0, -0)
)
# 4.9 面選択モード(Face Select)に切り替えてページ部分を選択
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')
# 面の選択
element_select(
    element_list=[1, 20, 19, 18, 17, 16,
                  2, 15, 14, 13, 12, 11,
                  3, 10, 9, 8, 7, 6]
,   select_mode="FACE"
,   object_name="MyBook_001"
)
# 4.10 面の押し出し、引き込み
# 法線方向への面押し出し、引き込み
bpy.ops.mesh.extrude_region_shrink_fatten(  # Extrude region together along local normals
    MESH_OT_extrude_region={}
,   TRANSFORM_OT_shrink_fatten={
        "value":-0.035                      # 収縮/膨張の量
    }
)

5. 角を滑らかにする


全ての角が鋭利なので、角が滑らかになるように微修正していきます。

5.1. 辺モード(Edge Select)へ切り替え
5.2. 本を覆う表紙の内側/外側の辺を全て選択
5.3. 選択した辺にベベル(面取り)をかけていく
ベベル(面取り)とは、ポリゴンのエッジ部分に面を追加して滑らかにする工程です。
「マウスのホイール」で追加する面/辺の数を調整することができます。辺を増やせば増やすほど、滑らかにはなりますが、データとしては重くなるので見極めが大切です。

現状以下のようにベベルが掛かるはずです。
言葉だけでは分かりにくいですが、ここではすべての角に対して均等にベベルが掛かっていません。そこで、次の手順を取ります。

5.3.1. 「適用」を行い、ベベルを均等にかける

オブジェクトのスケール(拡大、縮小)をおこなうと、X/Y/Z軸の比率のずれが起こります。そのため、オブジェクトのベベル幅は一定にはなりません。そこで、次のようにオブジェクトに適用(初期化処理のようなもの)を行う必要があります。
これで、角に対して均等に適切にベベルが掛かりました。先ほどの画像と比較してみてください。
POINT!ベベル操作はエッジや面の向きなどを基準として、ベベルサイズや、形状が決定されます。また、オブジェクトがスケール(サイズ変更)された場合、ベベルサイズもスケールされます。そのため、今回の場合も、立方体からサイズや形状を変更したため、ベベルが期待したようにかからない場合があります。ベベル操作を行う前にオブジェクトの変換情報をリセットして、正規化することで、一貫性のあるベベル操作を実現します。
より詳しい、適用についての説明は以下などが参考になりました。
https://cgbox.jp/2021/12/21/blender-apply/


5.4. 本の背表紙を滑らかにする
  • View Window上で右クリック
  • 「Object Context Munu」(オブジェクトコンテクストメニュー)が表示される
  • 「Shade Smooth」(スムーズシェード)をクリック
「補完した頂点の法線を使用し、面をスムーズに表示・レンダリングします」と記載のある通り、角ばっていた本の背表紙などが、滑らかに変換されました。

5.5. 角のなめらかさの調整
本の背表紙が滑らかになりましたが、本の角など、全体的に角ばってほしくないところまで滑らかになってしまいメリハリがなくなったので、調整します。
POINT!「Auto Smooth」を設定していない状態では、オブジェクト全体にスムーズシェード(Shade Smooth=滑らかに反射)が掛かっている状態ですが、「Auto Smooth」にチェックを入れることで、一方の面を基準として平面とみたとき、もう一方の面がその面より一定角度(初期は30°)以上傾いていれば、フラットシェード(鮮明なエッジ)、一定角度より差が無ければスムーズシェード(滑らかな局面)が掛かった状態となります。
この角度あたりの話についてはどこを調べても分かりにくいのですが、以下がかなり参考になったので見てみてください。
https://note.com/suzuumi/n/ndfe3306be8cb

ここまでで、本の背表紙部分は、なめらかに、角ばってほしい箇所はそのまま表現されました。
上記手順をスクリプト化したものが以下です。

Python:
# ------------------------------------------------------------------
# - 5. 角を滑らかにする
# ------------------------------------------------------------------
# 5.1 辺モード(Edge Select)へ切り替え
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='EDGE')
# 5.2 本を覆う表紙の内側/外側の辺を全て選択
element_select(
    element_list=[1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 
                  43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]
,   select_mode="EDGE"
,   object_name="MyBook_001"
)
# 5.3 選択した辺にベベル(面取り)をかけていく
# 5.3.1 「適用」を行い、ベベルを均等にかける
# Object Modeへ切り替え
bpy.ops.object.mode_set(mode='OBJECT')
# All Transforms(全トランスフォーム)を選択
bpy.ops.object.transform_apply(
    location=True               # オブジェクトの位置を適用、現在の位置が新しい基準点(原点)になる
,   rotation=True               # オブジェクトの回転を適用、現在の回転が新しい基準点(0度)になる
,   scale=True                  # オブジェクトのスケールを適用、現在のスケールが新しい基準点(1.0)になる
)
# EDIT Modeに切り替え
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='EDGE')
# ベベルをかける
bpy.ops.mesh.bevel(
    offset=0.01             # ベベルのオフセット処理、エッジからエッジまでの距離(値が大きいほど面取り幅が大きい)
,   offset_pct=0            # オフセット距離を%で指定
,   segments=3              # ベベルに追加するセグメント数(値が大きいほど滑らかになる)
,   affect='EDGES'          # エッジに適用されるか頂点に適用されるか指定
)
# 5.4 本の背表紙を滑らかにする
# Object Modeへ切り替え
bpy.ops.object.mode_set(mode='OBJECT')
# 5.5 角のなめらかさの調整
bpy.ops.object.shade_auto_smooth(angle=math.radians(30))    # 30度

6. 本に凹凸による装飾をする


本の形はできたので、装飾を行っていきます。上記までの編集方法を用いて、お好みで装飾を行ってください。以下は操作の一例です。

6.1. 編集モードへの切り替え
6.2 面選択に切り替え
6.3. 装飾する
表紙などに凹凸で装飾を施して、本のモデリングは完成です。

上記行程をスクリプト化したものが以下です。

Python:
# ------------------------------------------------------------------
# - 6. 本に凹凸による装飾をする
# ------------------------------------------------------------------
# 6.1 編集モードへの切り替え
# 6.2 面選択に切り替え
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')
# 6.3 装飾する
# 面の選択
element_select(
    element_list=[24]
,   select_mode="FACE"
,   object_name="MyBook_001"
)
# 面の差し込み
bpy.ops.mesh.inset(
    thickness=0.30
,   depth=0
)
# 面サイズの変更
bpy.ops.transform.resize(
    value=(1, 0.18, 1)
,   orient_type='NORMAL'
)
# 面の移動
bpy.ops.transform.translate(
    value=(0, 0.7, 0)
,   orient_type='GLOBAL'
)
# 面の押し込み
bpy.ops.mesh.extrude_region_move(
    MESH_OT_extrude_region={}, 
    TRANSFORM_OT_translate={
        "value":(0, 0, -0.02)
    ,   "orient_type":'NORMAL'
    }
)

7. 色を付ける

作成したモデルに簡単に色を付けていきます。

7.1. ベースカラーを付ける

7.2. ページ部分の紙の色を変える
今のままでは、すべて上で選択した色になっているので、紙の部分の色を変えていきます。

7.3. Materialの追加
7.4. 色の割り当て
上記行程をスクリプト化したものが以下です。

Python:
# 3Dビューのエリアを見つけてMaterial Previewへ切り替え
def set_material_preview():
    for area in bpy.context.screen.areas:
        if area.type == 'VIEW_3D':
            for space in area.spaces:
                if space.type == 'VIEW_3D':
                    space.shading.type = 'MATERIAL'
                    return
# マテリアルの追加 + BaseColor設定
def set_material_baseColor(
        material_name="Base_name"
    ,   surface="Principled BSDF"
    ,   red=0.1
    ,   green=0.1
    ,   blue=0.1
    ,   alpha=1
):
    # 新しいマテリアルを作成
    new_material = bpy.data.materials.new(name="Material.001")
    # ノードを使用する設定にする
    new_material.use_nodes = True
    # マテリアルの名前を変更
    new_material.name = material_name
    # オブジェクトに新しいマテリアルを割り当てる
    obj = bpy.context.object
    # 新しいマテリアルを追加
    obj.data.materials.append(new_material)
    # 名前でマテリアルスロットをアクティブにする
    for i, mat in enumerate(obj.material_slots):
        if mat.material.name == material_name:
            obj.active_material_index = i
            break
    # 「Base Color」カラーパレットから色を選択
    principled_bsdf = new_material.node_tree.nodes.get(surface)
    if principled_bsdf:
        principled_bsdf.inputs['Base Color'].default_value = (
            red,    # 赤
            green,  # 緑
            blue,   # 青
            alpha   # 透明度
        )
    else:
        print("Principled BSDF シェーダーノードが見つかりませんでした。")
    # Assign
    bpy.ops.object.material_slot_assign()
# ------------------------------------------------------------------
# - 7. 色を付ける
# ------------------------------------------------------------------
# 7.1 ベースカラーを付ける
# Objectモードへの切り替え
bpy.ops.object.mode_set(mode='OBJECT')
# 「Material Preview」(マテリアルプレビュー)へ切り替え(定義関数)
set_material_preview()
# 作成した本のモデルを選択
active_element_select(object_name="MyBook_001")
# マテリアル追加
# 「Base Color」カラーパレットから色を選択
set_material_baseColor(
        material_name="book_base_color"
    ,   surface="Principled BSDF"
    ,   red=0.166538
    ,   green=0.175551
    ,   blue=0.800119
    ,   alpha=1
)
# 7.2. ページ部分の紙の色を変える
# Edit Modeへ切り替え
# Face Modeへ切り替え
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')
# ページ部分の面を全て選択
element_select(
    element_list=[45, 50, 49, 48, 47, 46,
                  44, 55, 54, 53, 52, 51,
                  43, 60, 59, 58, 57, 56,
                  174]
,   select_mode="FACE"
,   object_name="MyBook_001"
)
# 7.3. Materialの追加
# 7.4. 色の割り当て
set_material_baseColor(
        material_name="book_page_color"
    ,   surface="Principled BSDF"
    ,   red=0.937475
    ,   green=1
    ,   blue=0.892473
    ,   alpha=1
)

9. しおりを作る


必要に応じて装飾を追加していきます。完成した画像は本記事末尾にあります。

9.1. しおりの造形を整える

Python:
# ------------------------------------------------------------------
# - 9. しおりを作る
# ------------------------------------------------------------------
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')
# ページ部分の面を1つ選択
element_select(
    element_list=[58]
,   select_mode="FACE"
,   object_name="MyBook_001"
)
# 「Ⅰ」:面を内側に差し込む
bpy.ops.mesh.inset(
    thickness=0.0001         # インセットの厚さ(内側に押し込まれる)
,   depth=0                 # インセットの深さ(押し出し)
)
# 「S」:面のサイズを変更
bpy.ops.transform.resize(
    value=(0.022, 0.1, 1)
,   orient_type='NORMAL'      # 面の法線方向を維持してリサイズ
)
# 「G」:面を移動
bpy.ops.transform.translate(
    value=(0, -0.9, 0)
,   orient_type='NORMAL'
)
# 移動ギズモ(座標)をLOCALに変更
bpy.context.scene.transform_orientation_slots[1].type = 'LOCAL'
# 「E」:しおりとなる面を外側に押し出す
bpy.ops.mesh.extrude_region_move(
    MESH_OT_extrude_region={}, 
    TRANSFORM_OT_translate={
        "value":(0, 0.12, 0)
    }
)
# 9.1. しおりの造形を整える
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='VERT')
# しおりの先端、4つの頂点を選択した状態で、右クリック
element_select(
    element_list=[184, 185, 187, 186]
,   select_mode="VERT"
,   object_name="MyBook_001"
)
# 「Vertex Context Menu」→「Subdivide」(細分化)を選択
bpy.ops.mesh.subdivide()
# 新しくできた中心の3つの頂点を縦に選択
element_select(
    element_list=[190, 192, 189]
,   select_mode="VERT"
,   object_name="MyBook_001"
)
# 移動ギズモ(座標)をNORMALに変更
bpy.context.scene.transform_orientation_slots[1].type = 'LOCAL'
# 「G」+「Y」:Y軸方向に頂点を移動
bpy.ops.transform.translate(
    value=(0, -0.05, 0)
,   orient_type='LOCAL'
)
# マテリアルの割り当て
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')
# 面の選択
element_select(
    element_list=[58, 183, 184, 182, 186, 188,
                  187, 185]
,   select_mode="FACE"
,   object_name="MyBook_001"
)
# Materialの追加
set_material_baseColor(
        material_name="bookmark_color"
    ,   surface="Principled BSDF"
    ,   red=1
    ,   green=0.846541
    ,   blue=0.0296142
    ,   alpha=1
)

8. 本の複製と配置

配置やマテリアルなど複数のオブジェクトに対して編集するような処理はPython APIを使うことで、for文などを用いて簡潔に処理させることができます。

Python:
# マテリアルの上書き
def override_material_rm(
        new_material_name="new_name",
        old_material_name="old_name",
        surface="Principled BSDF",
        red=0.1,
        green=0.1,
        blue=0.1,
        alpha=1
):
    # アクティブなオブジェクトを取得
    obj = bpy.context.object
    # 各オブジェクトのマテリアルスロットを確認
    for i, mat_slot in enumerate(obj.material_slots):
        if mat_slot.material and mat_slot.material.name == old_material_name:
            # 既存のマテリアルが共有されている場合、マテリアルを複製して新しいマテリアルを作成
            if mat_slot.material.users >= 1:
                # マテリアルを複製する
                new_material = mat_slot.material.copy()
                # オブジェクトに新しいマテリアルを割り当てる
                obj.data.materials[i] = new_material
            break
    # マテリアルの名前を変更
    new_material.name = new_material_name
    bsdf_node = new_material.node_tree.nodes.get(surface)
    if bsdf_node:
        bsdf_node.inputs['Base Color'].default_value = (red, green, blue, alpha)
# ------------------------------------------------------------------
# - 8. 本の複製と配置
# ------------------------------------------------------------------
bpy.ops.object.mode_set(mode='OBJECT')
# 3冊本を追加
for i in range(3):
    # オブジェクトを選択
    my_active_element = active_element_select(object_name='MyBook_00'+str(i+1))
    # 「Shift」+「D」:オブジェクトを複製
    bpy.ops.object.duplicate_move(
        OBJECT_OT_duplicate={
            "linked":False              # オリジナルのオブジェクトにリンクされない(編集してもオリジナルに影響しない)
        ,   "mode":'TRANSLATION'        # 複製後にオブジェクトが移動
        }, 
        TRANSFORM_OT_translate={
            "value":(0, 0, 0.5)
        ,   "orient_type":'GLOBAL'
        })
    # 複製されたオブジェクトをアクティブオブジェクトとして取得
    duplicated_element = bpy.context.active_object
    duplicated_element.name = "MyBook_00"+str(i+2)   # 名前を変更
# オブジェクトの回転
for i in range(4):
    # すべてのオブジェクトの選択を解除
    bpy.ops.object.select_all(action='DESELECT')
    # オブジェクトを選択
    my_active_element = active_element_select(object_name='MyBook_00'+str(i+1))
    rotation_value = random.uniform(-180, 180)  # 乱数生成
    bpy.ops.transform.rotate(
        value=math.radians(rotation_value)
    ,   orient_axis='Z'
    ,   orient_type='GLOBAL'
    )
# ベースカラー変更
for i in range(4):
    # すべてのオブジェクトの選択を解除
    bpy.ops.object.select_all(action='DESELECT')
    # オブジェクトを選択
    my_active_element = active_element_select(object_name='MyBook_00'+str(i+1))
    # 色の割り当て
    override_material_rm(
        new_material_name="book_base_color"+str(i+1)
    ,   old_material_name="book_base_color"
    ,   surface="Principled BSDF"
    ,   red=random.uniform(0, 0.5)      # 乱数
    ,   green=random.uniform(0, 0.5)    # 乱数
    ,   blue=random.uniform(0, 0.5)     # 乱数
    ,   alpha=1
    )
    bpy.ops.object.select_all(action='DESELECT')


WebGL(Three.js)を用いた本の描画


Three.jsを用いて、上で作成したモデルをブラウザ上で描画します。
実際に描画した例は以下です。

▼Exportしたモデルの描画例

▼作成したモデルの表示例

参考:


Pythonコード全文


以下をコピペして、Blenderで実行すれば最初に示した本のモデルが生成されます。

Blender Python API:
import bpy
import math
import random
# ==================================================================
# = 関数定義
# ==================================================================

# 未使用アイテムを削除
def rm_nonused_all_items():
    for mesh in bpy.data.meshes:        # 未使用のメッシュデータブロックを削除
        if not mesh.users:
            bpy.data.meshes.remove(mesh)
    for texture in bpy.data.textures:   # 未使用のテクスチャデータブロックを削除
        if not texture.users:
            bpy.data.textures.remove(texture)
    for texture in bpy.data.lights:     # 未使用のテクスチャデータブロックを削除
        if not texture.users:
            bpy.data.lights.remove(texture)
    for texture in bpy.data.cameras:    # 未使用のテクスチャデータブロックを削除
        if not texture.users:
            bpy.data.cameras.remove(texture)
    for material in bpy.data.materials: # 未使用のマテリアルを削除
        if not material.users:
            bpy.data.materials.remove(material)

# アクティブオブジェクト選択
def active_element_select(
        object_name         # アクティブ化するオブジェクトの名前
):
    # アクティブオブジェクトを選択
    myobject = bpy.data.objects.get(object_name)
    if myobject:
        myobject.select_set(True)  # 選択状態にする
        bpy.context.view_layer.objects.active = myobject  # アクティブオブジェクトに設定
    return myobject

# エッジ、面、頂点などの選択用関数
def element_select(
        element_list        # 選択するエレメントリスト
,       select_mode         # モード(VERT, EDGE, FACE)
,       object_name="NaN"   # オブジェクト名
):
    if (object_name != "NaN"):
        # アクティブオブジェクトを選択
        obj = bpy.data.objects.get(object_name)
        if obj:
            obj.select_set(True)  # 選択状態にする
            bpy.context.view_layer.objects.active = obj  # アクティブオブジェクトに設定
    # アクティブなオブジェクトを取得
    obj = bpy.context.object
    # メッシュオブジェクトかどうかを確認
    if obj and obj.type == 'MESH':
        # メッシュデータを取得
        mesh = obj.data
        # オブジェクトモードからエディットモードに切り替え
        bpy.ops.object.mode_set(mode='EDIT')
        # モード切り替え
        bpy.ops.mesh.select_mode(type=select_mode)
        # すべての要素の選択を解除
        bpy.ops.mesh.select_all(action='DESELECT')
        # オブジェクトモードに切り替え
        bpy.ops.object.mode_set(mode='OBJECT')
        # 特定の要素を選択(3D Viewから要素インデックスを確認)
        for i in range(len(element_list)):
            target_ele_index = element_list[i]
            if (select_mode == "FACE"):
                mesh.polygons[target_ele_index].select = True
            elif (select_mode == "EDGE"):
                mesh.edges[target_ele_index].select = True
            elif (select_mode == "VERT"):
                mesh.vertices[target_ele_index].select = True
        # 再度エディットモードに切り替え
        bpy.ops.object.mode_set(mode='EDIT')
    else:
        print("No mesh object selected.")

# 3Dビューのエリアを見つけてMaterial Previewへ切り替え
def set_material_preview():
    for area in bpy.context.screen.areas:
        if area.type == 'VIEW_3D':
            for space in area.spaces:
                if space.type == 'VIEW_3D':
                    space.shading.type = 'MATERIAL'
                    return
    print("3Dビューが見つかりませんでした")

# マテリアルの追加 + BaseColor設定
def set_material_baseColor(
        material_name="Base_name"
    ,   surface="Principled BSDF"
    ,   red=0.1
    ,   green=0.1
    ,   blue=0.1
    ,   alpha=1
):
    # 新しいマテリアルを作成
    new_material = bpy.data.materials.new(name="Material.001")
    # ノードを使用する設定にする
    new_material.use_nodes = True
    # マテリアルの名前を変更
    new_material.name = material_name
    # オブジェクトに新しいマテリアルを割り当てる
    obj = bpy.context.object
    # 新しいマテリアルを追加
    obj.data.materials.append(new_material)
    # 名前でマテリアルスロットをアクティブにする
    for i, mat in enumerate(obj.material_slots):
        if mat.material.name == material_name:
            obj.active_material_index = i
            break
    # 「Base Color」カラーパレットから色を選択
    principled_bsdf = new_material.node_tree.nodes.get(surface)
    if principled_bsdf:
        principled_bsdf.inputs['Base Color'].default_value = (
            red,    # 赤
            green,  # 緑
            blue,   # 青
            alpha   # 透明度
        )
    else:
        print("Principled BSDF シェーダーノードが見つかりませんでした。")
    # Assign
    bpy.ops.object.material_slot_assign()

# マテリアルの上書き
def override_material_rm(
        new_material_name="new_name",
        old_material_name="old_name",
        surface="Principled BSDF",
        red=0.1,
        green=0.1,
        blue=0.1,
        alpha=1
):
    # アクティブなオブジェクトを取得
    obj = bpy.context.object
    # 各オブジェクトのマテリアルスロットを確認
    for i, mat_slot in enumerate(obj.material_slots):
        if mat_slot.material and mat_slot.material.name == old_material_name:
            # 既存のマテリアルが共有されている場合、マテリアルを複製して新しいマテリアルを作成
            if mat_slot.material.users >= 1:
                # マテリアルを複製する
                new_material = mat_slot.material.copy()
                # オブジェクトに新しいマテリアルを割り当てる
                obj.data.materials[i] = new_material
            break
    # マテリアルの名前を変更
    new_material.name = new_material_name
    bsdf_node = new_material.node_tree.nodes.get(surface)
    if bsdf_node:
        bsdf_node.inputs['Base Color'].default_value = (red, green, blue, alpha)

# ==================================================================
# = Blender Python APIを使うための準備
# ==================================================================

def update_3d_view_overlay():
    for area in bpy.context.screen.areas:
        if area.type == 'VIEW_3D':
            space = area.spaces.active
            area.spaces.active.show_gizmo_object_translate = True   # 移動ギズモを表示
            if space.type == 'VIEW_3D':
                overlay = space.overlay             # オーバーレイの設定を取得
                overlay.show_edge_crease = True     # エッジのクリース(強調されたエッジ)の表示有効化
                overlay.show_face_center = False    # 面の中心を表示
                overlay.show_face_normals = False   # 面の法線を表示
                overlay.show_vertex_normals = False # 頂点の法線を表示
                overlay.show_edge_seams = True      # エッジのシーム(接合部を表示)
                overlay.show_extra_indices = True   # インデックス(麵屋エッジなど)を表示
                print("Overlay settings updated.")
                return
    print("No 3D View area found.")

def preferences_setting():
    # Orbit Around Selection(選択の周りをオービットする)
    bpy.context.preferences.inputs.use_rotate_around_active = True
    # Depthオプションを有効にする
    bpy.context.preferences.inputs.use_zoom_to_mouse = True
    # Smooth View(スムースビュー)時間(ミリ秒単位)
    bpy.context.preferences.view.smooth_view = 200
    
# ギズモの種類を設定
# LOCAL / GLOBAL etc.
bpy.context.scene.transform_orientation_slots[1].type = 'NORMAL'

update_3d_view_overlay()
preferences_setting()

# ==================================================================
# = Main Code
# ==================================================================

# Object Modeへ切り替え
bpy.ops.object.mode_set(mode='OBJECT')

# ------------------------------------------------------------------
# - Delete all objects in the scene
# ------------------------------------------------------------------

# 全てのオブジェクトを選択
bpy.ops.object.select_all(action='SELECT')
# 全てのオブジェクトを削除
bpy.ops.object.delete(use_global=False)
# 未使用になったアイテムを削除
rm_nonused_all_items()

# ------------------------------------------------------------------
# - 1. Blenderを開いた初期状態 (Create Cude)
# ------------------------------------------------------------------

# キューブを生成
bpy.ops.mesh.primitive_cube_add(
    size=2              # 1辺の長さ
,   location=(0, 0, 0)  # 配置場所
,   scale=(1,1,1)       # x, y, y サイズ x軸方向に1倍, y軸方向に1倍, z軸方向に1倍
)

# キューブに名前を設定
bpy.context.object.name = "MyCube_001"

# カメラを追加
bpy.ops.object.camera_add(
    location=(7, -7, 5)
,   rotation=(math.radians(63), math.radians(0), math.radians(47))
,   scale=(1, 1, 1)
)

# カメラに名前を設定
bpy.context.object.name = "MyCamera_001"

# Lightを追加
bpy.ops.object.light_add(
    type='POINT'            # 'POINT':点光源 , 'SUN':平行光源, 'SPOT':スポットライト, 'AREA':面光源
,   location=(4, 1, 6)
,   scale=(1, 1, 1)
)

# ライトに名前を設定
bpy.context.object.name = "MyPointLight_001"

# - すべてのオブジェクトの選択を解除
bpy.ops.object.select_all(action='DESELECT')

# ------------------------------------------------------------------
# - 2. 本全体の縦横比、厚さを決める
# ------------------------------------------------------------------

# 2.1 オブジェクト(Cube)を選択する
my_active_element = active_element_select(object_name='MyCube_001')
# 2.2 Z軸方向にキューブを拡大縮小する
# 2.3 Y軸方向にキューブを拡大縮小する
my_active_element.scale = (1.0, 1.4, 0.25)    # (参考値:X:1.0, Y:1.4, Z:0.25)

# ------------------------------------------------------------------
# - 3. 本の背表紙を作る
# ------------------------------------------------------------------

# 3.1 Edit Modeへ切り替え
bpy.ops.object.mode_set(mode='EDIT')

# 3.2 ループカットを行う箇所を選択
# 3.3 ループカットする辺の数を決める
bpy.ops.mesh.loopcut_slide(
    MESH_OT_loopcut={
        "number_cuts":5             # 追加するループ数
    ,   "smoothness":0              # 0~1:スムージングの強さ
    ,   "falloff":'INVERSE_SQUARE'  # どのようにカットが減衰するか "INVERSE_SQUARE":逆2乗フォールオフ(例:SHARP)
    ,   "object_index":0            # 編集するオブジェクトのインデックス(通常0:最初のオブジェクト)
    ,   "edge_index":1              # ループカットを適用するエッジのインデックス
    }
,   TRANSFORM_OT_edge_slide={
        "value":0                   # カットのスライド位置(0:スライドしない)
    ,   "single_side":False         # 片側にだけスライドを適用するか(False:両側にスライド適用)
    ,   "use_even":False            # スライドを均等にするか(False:均等にしない)
    }
)

# 3.4 エッジセレクトモードへ切り替え

# モードを切り替え(これを行うとメッシュデータの更新が正しく行われる)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='EDGE')

# 3.5 ループカット機能で増やした側面の辺を選択

# 2.1 オブジェクト(Cube)を選択する
my_active_element = active_element_select(object_name='MyCube_001')
my_active_element.name = "MyBook_001"   # Mybook_001へ名前を変更

# エッジの選択
element_select(
    element_list= [49]
,   select_mode="EDGE"
)

# 辺を移動(プロポーショナル編集)
bpy.ops.transform.translate(
    value=(-0.31, -0, -0)                               # 移動する距離(x, y, z)
,   use_proportional_edit=True                          # プロポーショナル編集(周囲の頂点に影響するか)
,   proportional_edit_falloff='SMOOTH'                  # プロポーショナル編集の影響の強さ('SMOOTH':影響が滑らかに減少することを意味する
,   proportional_size=0.7                               # プロポーショナル編集の範囲指定
,   use_proportional_connected=False                    # 接続された頂点にのみ影響を与えるか(False:全体に影響)
,   use_proportional_projected=False                    # 投影された範囲に提要されるか(False:通常の3D空間に適用)
)

# ------------------------------------------------------------------
# - 4. 本の紙の束部分(小口/天)を作る
# ------------------------------------------------------------------

# 4.1 面選択モードへ切り替え
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')

# 4.2 側面の部分を全て選択する

# 面の選択
element_select(
    element_list=[  3, 10, 9, 8, 7, 6, 
                    2, 15, 14, 13, 12, 11, 
                    1, 20, 19, 18, 17, 16]
,   select_mode="FACE"
,   object_name="MyBook_001"
)

# 4.3 選択している面を内側に差し込む

bpy.ops.mesh.inset(
    thickness=0.15          # インセットの厚さ(内側に押し込まれる)
,   depth=0                 # インセットの深さ(押し出し)
)

# 4.4 微修正
# 4.5 Edge Modeへ切り替え
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='EDGE')

# 4.6 背表紙の厚みに当たる内側の辺を選択

# 辺の選択
element_select(
    element_list=[52, 64, 63, 62, 61, 60,
                  65, 66, 67, 68, 69, 53]
,   select_mode="EDGE"
,   object_name="MyBook_001"
)

# 4.8 選択した辺の移動
bpy.ops.transform.translate(
    value=(-0.11, -0, -0)
)

# 4.9 面選択モード(Face Select)に切り替えてページ部分を選択
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')

# 面の選択
element_select(
    element_list=[1, 20, 19, 18, 17, 16,
                  2, 15, 14, 13, 12, 11,
                  3, 10, 9, 8, 7, 6]
,   select_mode="FACE"
,   object_name="MyBook_001"
)

# 4.10 面の押し出し、引き込み

# 法線方向への面押し出し、引き込み
bpy.ops.mesh.extrude_region_shrink_fatten(  # Extrude region together along local normals
    MESH_OT_extrude_region={}
,   TRANSFORM_OT_shrink_fatten={
        "value":-0.035                      # 収縮/膨張の量
    }
)

# ------------------------------------------------------------------
# - 5. 角を滑らかにする
# ------------------------------------------------------------------

# 5.1 辺モード(Edge Select)へ切り替え
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='EDGE')

# 5.2 本を覆う表紙の内側/外側の辺を全て選択
element_select(
    element_list=[1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 
                  43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]
,   select_mode="EDGE"
,   object_name="MyBook_001"
)

# 5.3 選択した辺にベベル(面取り)をかけていく
# 5.3.1 「適用」を行い、ベベルを均等にかける

# Object Modeへ切り替え
bpy.ops.object.mode_set(mode='OBJECT')

# All Transforms(全トランスフォーム)を選択
bpy.ops.object.transform_apply(
    location=True               # オブジェクトの位置を適用、現在の位置が新しい基準点(原点)になる
,   rotation=True               # オブジェクトの回転を適用、現在の回転が新しい基準点(0度)になる
,   scale=True                  # オブジェクトのスケールを適用、現在のスケールが新しい基準点(1.0)になる
)

# EDIT Modeに切り替え
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='EDGE')

# ベベルをかける
bpy.ops.mesh.bevel(
    offset=0.01            # ベベルのオフセット処理、エッジからエッジまでの距離(値が大きいほど面取り幅が大きい)
,   offset_pct=0            # オフセット距離を%で指定
,   segments=3              # ベベルに追加するセグメント数(値が大きいほど滑らかになる)
,   affect='EDGES'          # エッジに適用されるか頂点に適用されるか指定
)

# 5.4 本の背表紙を滑らかにする

# Object Modeへ切り替え
bpy.ops.object.mode_set(mode='OBJECT')

# 5.5 角のなめらかさの調整
bpy.ops.object.shade_auto_smooth(angle=math.radians(30))    # 30度

# ------------------------------------------------------------------
# - 6. 本に凹凸による装飾をする
# ------------------------------------------------------------------

# 6.1 編集モードへの切り替え
# 6.2 面選択に切り替え
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')

# 6.3 装飾する

# 面の選択
element_select(
    element_list=[24]
,   select_mode="FACE"
,   object_name="MyBook_001"
)

# 面の差し込み
bpy.ops.mesh.inset(
    thickness=0.30
,   depth=0
)

# 面サイズの変更
bpy.ops.transform.resize(
    value=(1, 0.18, 1)
,   orient_type='NORMAL'
)


# 面の移動
bpy.ops.transform.translate(
    value=(0, 0.7, 0)
,   orient_type='GLOBAL'
)

# 面の押し込み
bpy.ops.mesh.extrude_region_move(
    MESH_OT_extrude_region={}, 
    TRANSFORM_OT_translate={
        "value":(0, 0, -0.02)
    ,   "orient_type":'NORMAL'
    }
)

# ------------------------------------------------------------------
# - 7. 色を付ける
# ------------------------------------------------------------------

# 7.1 ベースカラーを付ける

# Objectモードへの切り替え
bpy.ops.object.mode_set(mode='OBJECT')

# 「Material Preview」(マテリアルプレビュー)へ切り替え(定義関数)
# bpy.context.space_data.shading.type = 'MATERIAL'
set_material_preview()

# 作成した本のモデルを選択
active_element_select(object_name="MyBook_001")

# マテリアル追加
# 「Base Color」カラーパレットから色を選択
set_material_baseColor(
        material_name="book_base_color"
    ,   surface="Principled BSDF"
    ,   red=0.166538
    ,   green=0.175551
    ,   blue=0.800119
    ,   alpha=1
)

# 7.2. ページ部分の紙の色を変える

# Edit Modeへ切り替え
# Face Modeへ切り替え
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')

# ページ部分の面を全て選択
element_select(
    element_list=[45, 50, 49, 48, 47, 46,
                  44, 55, 54, 53, 52, 51,
                  43, 60, 59, 58, 57, 56,
                  174]
,   select_mode="FACE"
,   object_name="MyBook_001"
)

# 7.3. Materialの追加
# 7.4. 色の割り当て
set_material_baseColor(
        material_name="book_page_color"
    ,   surface="Principled BSDF"
    ,   red=0.937475
    ,   green=1
    ,   blue=0.892473
    ,   alpha=1
)

# ------------------------------------------------------------------
# - 9. しおりを作る
# ------------------------------------------------------------------

# Mode 切り替え
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')

# ページ部分の面を1つ選択
element_select(
    element_list=[58]
,   select_mode="FACE"
,   object_name="MyBook_001"
)

# 「Ⅰ」:面を内側に差し込む
bpy.ops.mesh.inset(
    thickness=0.0001         # インセットの厚さ(内側に押し込まれる)
,   depth=0                 # インセットの深さ(押し出し)
)

# 「S」:面のサイズを変更
bpy.ops.transform.resize(
    value=(0.022, 0.1, 1)
,   orient_type='NORMAL'      # 面の法線方向を維持してリサイズ
)

# 「G」:面を移動
bpy.ops.transform.translate(
    value=(0, -0.9, 0)
,   orient_type='NORMAL'
)

# 移動ギズモ(座標)をLOCALに変更
bpy.context.scene.transform_orientation_slots[1].type = 'LOCAL'

# 「E」:しおりとなる面を外側に押し出す
bpy.ops.mesh.extrude_region_move(
    MESH_OT_extrude_region={}, 
    TRANSFORM_OT_translate={
        "value":(0, 0.12, 0)
    }
)

# 9.1. しおりの造形を整える

# 「1」:頂点選択モード(Vertex Select)に切り替え
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='VERT')

# しおりの先端、4つの頂点を選択した状態で、右クリック
element_select(
    element_list=[184, 185, 187, 186]
,   select_mode="VERT"
,   object_name="MyBook_001"
)

# 「Vertex Context Menu」→「Subdivide」(細分化)を選択
bpy.ops.mesh.subdivide()

# 新しくできた中心の3つの頂点を縦に選択
element_select(
    element_list=[190, 192, 189]
,   select_mode="VERT"
,   object_name="MyBook_001"
)

# 移動ギズモ(座標)をNORMALに変更
bpy.context.scene.transform_orientation_slots[1].type = 'LOCAL'

# 「G」+「Y」:Y軸方向に頂点を移動
bpy.ops.transform.translate(
    value=(0, -0.05, 0)
,   orient_type='LOCAL'
)

# マテリアルの割り当て

# Face Modeへ切り替え
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')

# 面の選択
element_select(
    element_list=[58, 183, 184, 182, 186, 188,
                  187, 185]
,   select_mode="FACE"
,   object_name="MyBook_001"
)

# Materialの追加
# 色の割り当て
set_material_baseColor(
        material_name="bookmark_color"
    ,   surface="Principled BSDF"
    ,   red=1
    ,   green=0.846541
    ,   blue=0.0296142
    ,   alpha=1
)

# ------------------------------------------------------------------
# - 8. 本の複製と配置
# ------------------------------------------------------------------

# 「Tab」:オブジェクトモード(Object Mode)に切り替え
bpy.ops.object.mode_set(mode='OBJECT')

# 3冊本を追加
for i in range(3):
    # オブジェクトを選択
    my_active_element = active_element_select(object_name='MyBook_00'+str(i+1))
    # 「Shift」+「D」:オブジェクトを複製
    bpy.ops.object.duplicate_move(
        OBJECT_OT_duplicate={
            "linked":False              # オリジナルのオブジェクトにリンクされない(編集してもオリジナルに影響しない)
        ,   "mode":'TRANSLATION'        # 複製後にオブジェクトが移動
        }, 
        TRANSFORM_OT_translate={
            "value":(0, 0, 0.5)
        ,   "orient_type":'GLOBAL'
        })
    # 複製されたオブジェクトをアクティブオブジェクトとして取得
    duplicated_element = bpy.context.active_object
    duplicated_element.name = "MyBook_00"+str(i+2)   # 名前を変更

# オブジェクトの回転
for i in range(4):
    # すべてのオブジェクトの選択を解除
    bpy.ops.object.select_all(action='DESELECT')
    # オブジェクトを選択
    my_active_element = active_element_select(object_name='MyBook_00'+str(i+1))
    rotation_value = random.uniform(-180, 180)  # 乱数生成
    bpy.ops.transform.rotate(
        value=math.radians(rotation_value)
    ,   orient_axis='Z'
    ,   orient_type='GLOBAL'
    )
    
# ベースカラー変更
for i in range(4):
    # すべてのオブジェクトの選択を解除
    bpy.ops.object.select_all(action='DESELECT')
    # オブジェクトを選択
    my_active_element = active_element_select(object_name='MyBook_00'+str(i+1))
    # 色の割り当て
    override_material_rm(
        new_material_name="book_base_color"+str(i+1)
    ,   old_material_name="book_base_color"
    ,   surface="Principled BSDF"
    ,   red=random.uniform(0, 0.5)      # 乱数
    ,   green=random.uniform(0, 0.5)    # 乱数
    ,   blue=random.uniform(0, 0.5)     # 乱数
    ,   alpha=1
    )
    bpy.ops.object.select_all(action='DESELECT')

以上
このエントリーをはてなブックマークに追加
コメントを閉じる

コメント

コメントフォーム
記事の評価
  • リセット
  • リセット