見出し画像

【Blender Python】数式で表現される曲線を描く

今回は数式で表現される曲線を描いてみようと思います。

描きたい曲線を微小な区間に区切って、それぞれの区間にオブジェクトを配置していけば思い通りの曲線が描けるだろうと考えました。

2つの (x, y, z) 座標を与えたときにそれらを始点と終点とするようなオブジェクトを配置するやり方が分からなかったので関数 draw_line を作りました。

Blenderのバージョンは2.93 LTSです。

1. コード

import math
import numpy as np
import bpy

# 既存のメッシュオブジェクトを削除
for msh in bpy.data.meshes:
   bpy.data.meshes.remove(msh)


# 2つの(x, y, z)座標の間にメッシュオブジェクトで線を引く関数
def draw_line(pos_from, pos_to, stroke_weight=0.02, obj_type='cylinder'):
   if pos_from == pos_to:
       return

   line_center = (np.mean([pos_from[0], pos_to[0]]),
                  np.mean([pos_from[1], pos_to[1]]),
                  np.mean([pos_from[2], pos_to[2]]))

   len_xyz = np.sqrt((pos_from[0] - pos_to[0]) ** 2 +
                     (pos_from[1] - pos_to[1]) ** 2 +
                     (pos_from[2] - pos_to[2]) ** 2)

   len_zx = np.sqrt((pos_from[0] - pos_to[0]) ** 2 +
                    (pos_from[2] - pos_to[2]) ** 2)
   len_z = np.abs(pos_to[2] - pos_from[2])

   if (pos_from[0], pos_from[2]) == (pos_to[0], pos_to[2]):
       azimuth = math.radians(90)
   else:
       azimuth = np.arccos(len_z / len_zx)
   if (pos_to[2] - pos_from[2]) * (pos_to[0] - pos_from[0]) >= 0:
       azimuth_sign = 1
   else:
       azimuth_sign = -1
   altura = np.arccos(len_zx / len_xyz)
   if (pos_to[1] - pos_from[1]) * (pos_to[2] - pos_from[2]) >= 0:
       altura_sign = 1
   else:
       altura_sign = -1
   if pos_from[2] == pos_to[2]:
       if (pos_to[0] - pos_from[0]) * (pos_to[1] - pos_from[1]) > 0:
           altura_sign = 1
       else:
           altura_sign = -1
   line_rotation = (-altura_sign * altura, azimuth_sign * azimuth, 0)

   if obj_type == 'cylinder':
       # 円柱で線を描く
       bpy.ops.mesh.primitive_cylinder_add(location=line_center,
           radius=stroke_weight, depth=len_xyz, rotation=line_rotation)
       bpy.context.object.name = 'line'
   elif obj_type == 'cube':
       # 直方体で線を描く
       bpy.ops.mesh.primitive_cube_add(location=line_center, size=1, rotation=line_rotation)
       line_obj = bpy.context.object
       line_obj.name = 'line_cube'
       line_obj.scale.x = stroke_weight
       line_obj.scale.y = stroke_weight
       line_obj.scale.z = len_xyz


# パラメータtに応じて(x, y, z)座標を返す関数
def myfunc(t):
   x = np.cos(t * 2)
   y = np.sin(t * 3)
   z = np.cos(t * 5)
   return((x, y, z))

# tパラメータの範囲と分割数を設定
# 分割数を増やすと滑らかになるが処理は重くなる
ts = np.linspace(0, 7, 500)

for i in range(len(ts) - 1):
   pos_from = myfunc(ts[i])
   pos_to   = myfunc(ts[i + 1])
   draw_line(pos_from, pos_to)

どのような曲線を描くかは myfunc 関数で定義しています。
ここでは3Dリサジュー曲線を描こうとしています。

Blender内蔵のPythonにはデフォルトでNumpyがインストールされているので数学的な処理ができて便利ですね。

2. 実行結果

画像1

平行投影でx軸方向から見た図

画像2

平行投影でy軸方向から見た図

画像3

平行投影でz軸方向から見た図

画像4

3. draw_line 関数の使い方

必須のパラメータは2つの (x, y, z) 座標です。

stroke_weight オプションで線の太さを設定できます。
デフォルト値は 0.02 です。

obj_type オプションで線を描画するオブジェクトの形を設定できます。
'cylinder' を指定すると円柱で、'cube' を指定すると直方体で描画します。
デフォルト値は 'cylinder' です。

注意
tan のように値が跳躍する箇所のある関数を描こうとすると draw_line 関数ではその部分も無理やり繋げて描画するので変な形になることがあります。

他にもバグなどあれば教えて下さい。

まとめ

BlenderとPythonを使って数式で表現される曲線を描いてみました。

みなさんも是非いろんな関数を描いてみて下さい。

この記事が気に入ったらサポートをしてみませんか?