Shade3D 公式

31 太さの変わる掃引(1/3 ) [ Shade Labo ]


#1

31 - 1  概要


**太さの変わる掃引** とは、掃引中心線に沿って配置される掃引断面にグラフ入力によって指定されるスケール値を乗じた掃引体を作るものです。

掃引断面のサイズを変化させながら配置し交差方向に smooth をかけるという単純な処理では、交差方向線形状の handle 方向は綺麗には揃いません。

太さの変わる掃引では、この関係を綺麗に整えます。

     

     [ fig 31 - 1 ]


#2

31 - 2  グラフ入力


下図は拙作の Mac 用ツール PowerPack に実装している **グラフ入力パレット** と **太さの変わる掃引** によって作成された掃引体です。

     [ fig 31 - 2 ]


Shade の script や plugin 機能でこのような入力ユーザーインターフェースを作ることはできません。

そこでここでは Shade 上に作成した線形状を利用した簡易的なグラフ入力機能を用意し、script で 太さの変わる掃引 を実現してみます。




< グラフ入力の仕組み >


グラフ入力の仕組みの概要は次の通りです。

  1. Shade 上でグラフの形状を表す線形状と、グラフのフレームを表す線形状の2つを作成

  2. labo に登録された class simple_graph( ) の constructor 引数として2つの線形状の shape object 渡し、instance を作成

  3. constructorでは渡された線形状からグラフ値読み取りのためのデータ群を作成し、property として保持

  4. simple_graph の instance を太さの変わる掃引を作成する関数 c_sweep_x( ) に引数の一つとして渡す

  5. 関数 c_sweep_x( ) の中で simple_graph のグラフ値読み取り関数から、指定する位置におけるスケール値を取得する


#3

31 - 3  グラフ入力を与える線形状


グラフ入力には予め2つの線形状を作成しますが、必要最小限のルール以外はかなり自由に作成して構わない仕様にしてあります。

**< graph data 線形状 >**
グラフの形を表す **開いた線形状** で、直線でも曲線でも可ですが、overhang が生じないように作成します。

overhang が生じていてもエラーは発生しませんが、データの読み取り時にはその部分は無視されます。

サイズは自由。

     

     [ fig 31 - 3 ]




**< graph frame 線形状 >**
グラフのフレームを表す **閉じた線形状**

ただし、フレームといっても X 方向の領域は graph data で規定され、graph frame は Y = 0 と Y = 1 の位置を規定する のに用いられます。

graph frame による Y 値は graph frame の #0, #1 point 座標 で決定されますから、四角形ツール で作成することを推奨します。

サイズも縦横比も自由。

     

     [ fig 31 - 4 ]




以下、上段に graph data と graph frame の作成例を示し、下段にはそれらがどのように解釈されるかを示します。
          [ fig 31 - 5 ]




**< graph data / frame を作成する場所 >**
graph data / frame 線形状を作成する作業面は 正面 / 平面 / 側面 図のいずれでも構いませんが、作業面に対して奥行き方向の距離は無視されます。

     

     [ fig 31 - 6 ]


#4

31 - 4  Class simple_graph( )


labo に登録されている Class simple_graph( ) の仕様は次のようになっています。
**概要**
  • graph data として渡された線形状を parameter 基準で128分割して X-Y 平面上で定義される折れ線として保持

  • この折れ線を X 方向を 0~1 に、Y 方向を graph frame で与えられる Y = 0, 1 を基準にしてサイズを規格化

  • 関数 y(x) によって X( 0~1) に対する Y 値を折れ線データから線形補間で返す



**Constructor**
  • graph = simple_graph(xshade as xshade, line_1 as line object, line_2 as line object)

  • constructor 引数には graph data と graph frame の2つの line object を渡すが、渡す順序は自由

  • constructor 内で 開いた線形状を graph data閉じた線形状を graph frame と判定



**関数 y(x as float) as float**
  • graph data の X( 0~1) に対する Y 値を返す


[ script 31 - 1 ] は labo に登録されている Class simple_graph です。
[ script 31 - 1 ]
class simple_graph() :

	#	 init
	#		graph = simple_graph(xshade, line object, line object)	
	#
	#		引数として渡される二つの線形状は、グラフ形状 と グラフフレーム ( 四角形線形状 )
	#		グラフ形状とグラフフレームの識別は内部で判定されるので、引数の順序は指定しない
	#		識別は線形状の開閉情報で判定 ( 開 - グラフ形状  閉 - グラフフレーム )
	#
	#		 グラフ形状は2点以上のポイント数を含み、グラフフレームは四角線形状であること
	#
	#		 二つの線形状は X-Y, Y-Z, Z-X 平面のいずれかに平行に作成されていることを前提とする
	#		どの面に作成されているかはグラフフレームの座標情報から判定される
	
	def __init__(self, *value) :
		from math import pi
		
		#  check
		if len(value) == 3 :
			xshade = value[0]
			scene = xshade.scene()
		
			if isinstance(value[1], xshade.line) and isinstance(value[2], xshade.line) :	
				#  選択された二つの線形状の内、開いている方をグラフ形状、閉じている方をグラフフレーム形状とみなす
				if value[1].closed and not value[2].closed :
					ob1 = value[2]
					ob2 = value[1]
				elif not value[1].closed and value[2].closed :
					ob1 = value[1]
					ob2 = value[2]
				else :
					raise SimpleGraphError(2)
			else :
				raise SimpleGraphError(1)
		else :
			raise SimpleGraphError(0)
			
		if (ob1.number_of_control_points < 2) or (ob2.number_of_control_points != 4 ) :
			raise SimpleGraphError(3)
			
		
		#  ob2(グラフフレーム形状)から、グラフが描かれている作業面を判定し、座標値読み取り時の変換 matrix Mst を求める
		m2 = matrix(ob2.local_to_world_matrix)
		p0 = m2*vec3(ob2.control_point(0).position)
		p2 = m2*vec3(ob2.control_point(2).position)
		v = p0 - (p0 + p2)/2

		if abs(v[2]) <= abs(v[0]) and abs(v[2]) <= abs(v[1]) :		#  X - Y 平面
			M1 = matrix().scale(1, 1, 0)	
			M2 = matrix(4)		
				
		elif abs(v[1]) <= abs(v[2]) and abs(v[1]) <= abs(v[0]) :	#  Z - X 平面
			M1 = matrix().scale(1, 0, 1)	
			M2 = matrix().rotate_x(pi/2)

		else :														#  Y - Z 平面
			M1 = matrix().scale(0, 1, 1)	
			M2 = matrix().rotate_y(-pi/2)
			
		Mst = M1*M2
		
		
		#  y0, y1 : 	scale = 0, 100 % の位置
		p0 = vec3(ob2.control_point(0).position)*m2*Mst
		p1 = vec3(ob2.control_point(1).position)*m2*Mst
	
		y0 = min(p0[1], p1[1])
		y1 = max(p0[1], p1[1])
		if y0 == y1 :
			raise SimpleGraphError(4)
		

		#  bZs :   graph
		obL = scene.active_shapes
		ob1.activate()
		bZs = get_bezier(xshade, -1)
		Mst.transform(bZs)
		scene.active_shapes = obL
		
		n = len(bZs)
		x0 = min(bZs[0][0][0], bZs[n - 1][3][0])
		x1 = max(bZs[0][0][0], bZs[n - 1][3][0])
		if x0 == x1 :
			raise SimpleGraphError(5)
		Mt = matrix().translate(-x0, -y0, 0)
		Ms = matrix().scale(1/(x1 - x0), 1/(y1 - y0), 1)
		M = Mt*Ms
		M.transform(bZs)								#  bZs を規格化
		

		#  x[ ], y[] :	規格化された graph data
		x = []
		y = []
		for bz in bZs :
			p1 = bezier_line_position(bz, 0)
			p2 = bezier_line_position(bz, 1)
			if p1[0] != p2[0] :
				n = int(128*(p2[0] - p1[0])) + 1		#  bz の分割数
				for i in range(n):
					t = float(i)/n
					p = bezier_line_position(bz, t)
					x.append(p[0])
					y.append(p[1])
		x.append(1.)
		y.append(bz[3][1])
		

		# ―data
		self.__data = [x, y]

		

	
	#  y(x as float) as float
	#
	# 		graph data の x ( 0 ~ 1 ) に対する y 値を返す
	
	def y(self, x) :
		xx = self.__data[0]
		yy = self.__data[1]
		k = 1	
		while x > xx[k] :
			k += 1

		y = yy[k] - (yy[k] - yy[k - 1])*(xx[k] - x)/(xx[k] - xx[k - 1])
		
		return y
		
		
		
class SimpleGraphError(ValueError) :
	def __init__(self, index) :
		if index == 0 :
			t = '      引数が3つでない     simple_graph      irregular number of arguments'
		elif index == 1 :
			t = '      引数が線形状でない     simple_graph      not xshade.line'
		elif index == 2 :
			t = '      線形状の開閉条件が不正     simple_graph      irregular line closed conditions'
		elif index == 3 :
			t = '      線形状のポイント数が不正     simple_graph      irregular line control points number'
		elif index == 4 :
			t = '       グラフの scale 値が不正     simple_graph      irregular graph scale value'
		elif index == 5 :
			t = '      グラフの X 値が不正     simple_graph      irregular graph x value'
			
		super(ValueError, self).__init__(t)


Shade 上に作成した **graph data, graph frame の2つの線形状を選択して** [ script 31 - 2 ] を実行すると、simple_graph object を作成し X = 0 ~ 1 を100等分して Y 値を求め、折れ線として出力します。

         [ fig 31 - 7 ]


[ script 31 - 2 ]
import labo
from matrix import *
	
	
scene = xshade.scene()

scale = 10000				#  規格化されたグラフ出力時のスケール値

[ob1, ob2] = scene.active_shapes

ob2.activate()
m1 = matrix(ob2.world_to_local_matrix)
Ms = matrix().scale(scale, scale, scale)
M = Ms*m1


#  simple_graph のインスタンス作成
graph = labo.simple_graph(xshade, ob1, ob2)

#  f : graph frame
f = []
f.append(vec3(1, 1, 0))
f.append(vec3(1, 0, 0))
f.append (vec3(0, 0, 0))
f.append(vec3(0, 1, 0))
M.transform(f)

#  p : graph data
p = []
for i in range(101) :
	x = i/100.
	p.append(vec3(x, graph.y(x), 0))
#	print x, graph.y(x)
M.transform(p)

#  出力
scene.create_part('graph')
ob3 = scene.active_shape()

scene.begin_creating()
scene.create_line('graph frame', f, True)
scene.create_line('graph data', p, False)
scene.end_creating()

ob3.activate()