Shade3D 公式

25 Custom 掃引 [ Shade Labo ]


#1

**16 / 05 / 17** script を二カ所修正

16 / 05 / 18 script を一カ所修正

          修正箇所については 末尾 25 - 10 Script 修正 の項を参照

16 / 06 / 14, 17     script に v2_prev に係わる記述を追加 / 修正

          追加内容は 29 掃引の断面配置への追加事項 参照


関連記事 :
  • 22 掃引 ( 1 / 3 )
  • 23 掃引 ( 2 / 3 )
  • 24 掃引 ( 3 / 3 )

25 - 1  custum_sweep( )


**22~24 掃引** で掃引体の仕様について、いくつかの留意点に触れました。
**断面配置**
  • handle 方向の違いによる形状変化に、大きなギャップを持った不連続が生じる

**handle 長さ**
  • 円形などの基本形状をなす中心線に対しても 掃引体の太さの不均一が顕著
  • bezier 区間の両端 handle 先端が一致する場合の 掃引体形状がいびつ

**コーナー**
  • 曲線コーナー部分の 掃引体形状がいびつ
  • scale limitter の 仕様に問題あり

これらに対する改良提案を行ったのが **custom_sweep( )** で、[ script 25 - 1 ] はこれと Shade 掃引 を比較する script です。
[ script 25 - 1 ]

16 / 05 / 17 script を一部修正

16 / 05 / 18 script を一部修正

     修正箇所については 末尾 25 - 10 Script 修正 の項を参照

16 / 06 / 14, 17 script に v2_prev に係わる記述を追加 / 修正

          追加内容は 29 掃引の断面配置への追加事項 参照

import labo
from matrix import *
from quaternion import *

		

#  sweep_section_quaternion(bz as vec3 matrix ,atrix) as quaternion
#
#	bezier data bz から掃引時の断面形状の姿勢を与える quaternion を返す

def sweep_section_quaternion(bz) :
	v1 = labo.bezier_line_tangent(bz, 0)			#  outhandle 側接線ベクトル
	v2 = labo.bezier_line_tangent(bz, 1)			#  inhandle 側接線ベクトル

	if (v1.abs2() == 0) or (v2.abs2() == 0) :		#   重点
		return quaternion()
		
	elif abs(v1.dot(v2)) >= 0.99999994 :			#  区間 bz の outhandle と inhandle が 0度平行		#  16 / 05 / 17 修正
		return quaternion()	
		
	else :
		t1 = 0
		Q = quaternion()
		for i in range(16) :						#  bezier parameter を16等分して細かく回転を16回繰り返す
			t2 = float(i + 1)/16
			v2 = labo.bezier_line_tangent(bz, t2)
			Q = quaternion().slerp(v1, v2)*Q
			t1 = t2
			v1 = v2
	
		return Q
			
		
			
				
#  sweep_plane_normal(p0 as vec3, p1 as vec3, p2 as vec3, p3 as vec3) as vec3
#
#	掃引中心線の bezier data bz (p0, p1, p2) から、掃引体交差方向の handle 長さを求めるための面法線を返す
#	基準となる anchor point に handle  が出ていることが前提
#
#		p0, p1, p2, p3 :	outhandle 側を求める場合は		bz[0], bz[1], bz[2], bz[3]
#							inhandle 側を求める場合は		bz[3], bz[2], bz[1], bz[0]

def sweep_plane_normal(p0, p1, p2, p3) :
	from math import pi, acos, sin, atan
	
	v1 = p1 - p0					#  v1.abs() != 0 が前提
	v2 = p2 - p1
	v1.norm()
	v2.norm()
	
	if v2.abs2() == 0 :
		v3 = p3 - p1
		v3.norm()
		if v3.abs2() == 0 :
			return v1
		else :
			vn = v1 + v3
			vn.norm()
			vp = v1 + vn
			vp.norm()
			return vp
		
	else :
		a = v1.dot(v2)
		if abs(a) >= 0.99999994 :
			return v1
		
		elif a >= 0 :
			vn = v1 + v2
			vn.norm()
			
			sinA = a
			cosA = (1 - sinA**2)**0.5
			beta = (pi - acos(a))/2
			r = (1 - sinA)/3
			
			axis = v1*v2
			axis.norm()
			v = v1 - v2
			v.norm()
			u = v/sin(beta) + v1*r/cosA
			u.norm()
			phi = acos(u.dot(v))

		else :
			axis = v1*v2
			phi = atan(4./3) - pi/4
			Q = quaternion().rotation(axis, pi/4)
			vn = Q*v1

		v3 = p3 - p2
		v3.norm()
		if v3.abs2() == 0 :
			return vn
		else :
			d = v1.dot(v2)
			w = -(v1-2*d*v2)
			w.norm()
			e = v3.dot(w)
			Q = quaternion().rotation(axis, e*phi)
			vp = Q*vn
			vp.norm()
			return vp

	
	
	
#  sweep_handle(w as vec3, u as vec3,  Q as vec3, r as float, p as vec3, vp as vec3, corner as bool) as vec3
#
#	handle 座標を返す

def sweep_handle(w, u, Q, r, p, vp, corner) :
	#     L :  点 w を通り、u 方向に進む直線
	#	PL0 : 点 Q を通り面法線 u なる面
	#	PL1 : 点 p を通り面法線 vp なる面
	#	精度確保のため r = 1 とはせず、r = bounding box size / 16 としてある

	a = u.dot(w - Q)			#  点 w と面 PL0 との符号付き距離		
	d1 = vp.dot(w - p)			#  点 w と 面 PL1 との符号付き距離
	d2 = vp.dot(w + r*u - p)	#  点 w + r*u と 面 PL1 との符号付き距離
	d = r*d1/(d1 - d2)			#  点 w と、直線 L と 面 PL1 との交点までの 符号付き距離
	if not corner :
		d = d + a				#  handle の符号つき長さ
	if d < 0 :					#  食い込みが発生する
		d = -d/12
	return w + d*u
		


#  custom_sweep(ob1 as shape, ob2 as shape, limitter1 as float = 10., limitter2 as float = 3.) as shape
#
#	掃引体を Shade 上に作成し、その shape を返す
#		ob1 :		掃引中心線形状
#		ob2 :		掃引断面形状	
#		limitter1 :	直線コーナーの scale limitter	
#		limitter2 :	曲線コーナーの scale limitter	

def custom_sweep(ob1, ob2, limitter1 = 10., limitter2 = 3.) :
	if (ob1 == None) or (ob2 == None) :
		return
	if (not isinstance(ob1, xshade.line)) or (not isinstance(ob2, xshade.line)) :
		return

	#  掃引中心線データ取得 nop, closed, bZ
	ob1.activate()
	nop = ob1.number_of_control_points		#  ポイント数
	if nop < 2 :
		return
	closed = ob1.closed						#  開閉情報
	bZ = labo.get_bezier(xshade, -1)		#  全区間の bezier data [matrix, matrix, … ]
	
	
	# mx1, mx2, mx22
	mx1 = matrix(ob2.world_to_local_matrix)
	mx2 = matrix(ob2.local_to_world_matrix)
	if not closed :
		mx22 = matrix(ob1.local_to_world_matrix)
		
		
	#  掃引中心線 の handle 情報取得
	has_in_handle = []			#  掃引中心線の inhandle の有無のリスト
	has_out_handle = []			#  掃引中心線の outhandle の有無のリスト
	linked = []					#  掃引中心線の hanndle link リスト
	for i in range(nop) :
		has_in_handle.append(ob1.control_point(i).has_in_handle)
		has_out_handle.append(ob1.control_point(i).has_out_handle)
		linked.append(ob1.control_point(i).linked)

	
	#  ob0 : 掃引体を格納する自由曲面
	ob2.activate()
	scene.create_surface_part(None)
	ob0 = scene.active_shape()				#   掃引体を格納する自由曲面
	ob0.surface_closed = closed
	ob2.place_child(1)

	
	#  掃引断面を配置
	p1 = bZ[0][0]
	Mt1 = matrix().translate(-p1[0], -p1[1], -p1[2])
	Q1 = quaternion()
	v2_prev = None
	n = nop - 1
	cornerL = [False]
		
	for i in range(n) :
		p2 = bZ[i][3]
		v2 = labo.bezier_line_tangent(bZ[i], 1)
		Mt2 = matrix().translate(p2[0], p2[1], p2[2])
		Q2 = sweep_section_quaternion(bZ[i])
		Q2 = Q2*Q1
		Mr = Q2.matrix()
		corner = False
		
		if i == n - 1 and not closed :
			M = mx2*Mt1*Mr*Mt2*mx1
			
		else :
			v3 = labo.bezier_line_tangent(bZ[i + 1], 0)
			if v3.abs2() == 0 :							#  重点
				M = mx2*Mt1*Mr*Mt2*mx1
				if v2_prev == None :					#  最初の重点
					v2_prev = v2
				
			elif v2.abs2() == 0 :					#  最後の重点
				if v2_prev == None or v2_prev.abs2() == 0 :		#  始点が重点から始まっている	#  16 / 06 / 17 修正
					M = mx2*Mt1*Mr*Mt2*mx1											#  16 / 06 / 14 追加
				else :																#  16 / 06 / 14 追加
					Qc = quaternion().slerp(v2_prev, v3, 1, False)	#  16 / 05 / 17 修正
					if Qc != None :									#  16 / 05 / 17 修正
						Q2 = Qc*Q2									#  16 / 05 / 17 修正
						Mc = Qc.matrix()							#  16 / 05 / 17 修正
						M = mx2*Mt1*Mr*Mc*Mt2*mx1					#  16 / 05 / 17 修正
					else :											#  16 / 05 / 17 修正
						M = mx2*Mt1*Mr*Mt2*mx1						#  16 / 05 / 17 修正
					v2_prev = None
				
			else :									#  16 / 05 / 18 修正	
				if abs(v2.dot(v3)) >= 0.99999994 :	#  掃引中心線の point が corner point  ではない		#  16 / 05 / 18 修正
					M = mx2*Mt1*Mr*Mt2*mx1
				
				else :								#  掃引中心線の point が corner point 
					w = v2 + v3
					w.norm()
					corner = True
					v1 = labo.bezier_line_tangent(bZ[i], 0)
					v4 = labo.bezier_line_tangent(bZ[i + 1], 1)
					if v1.dot(v2) + v3.dot(v4) > 1.98 :	#  直線コーナー
						limitter = limitter1
					else :								#  曲線コーナー
						limitter = limitter2
					Q3 = quaternion().slerp(v2, w)
					Mq = Q3.matrix()
					
					v = v2 - v3
					cosT = w.dot(v2)
					Q4 = quaternion().slerp(v, vec3(0, 1, 0))
					Mqs1 = Q4.matrix()
					Q5 = Q4.inverse()
					Mqs2 = Q5.matrix()
					Ms = matrix().scale(1, min(limitter, 1/cosT), 1)#	
					M = mx2*Mt1*Mr*Mq*Mqs1*Ms*Mqs2*Mt2*mx1

					Q6 = quaternion().slerp(v2, v3)
					Q2 = Q6*Q2							#   次の point での掃引断面配置に必要

	
		ob2.copy_object(M)
		ob = ob2.bro
		ob.place_brother(i)
	
		Q1 = Q2
		cornerL.append(corner)

	
	#  掃引体の交差方向 handle をセット
	bb = labo.vec3_bounding_box_size(bZ)
	r = bb[3]/16												#  sweep_handle( ) の引数に使用
	
	n = ob2.number_of_control_points
	ob0.switch()
	ob = ob0.son
	for i in range(n) :
		ob = ob.bro												#  掃引体内部の交差方向線形状
		
		for j in range(nop) :
			w = vec3(ob.control_point(j).position)*mx2			#  交差方向線形状の point 座標
			
			if not has_in_handle[j] :
				inH = w											#  inhandle 座標
			else :
				if (j == 0) and not closed :
					p = vec3(ob1.control_point(0).in_handle)*mx22
					inH = w + p - bZ[0][0]						#  inhandle 座標
				else :
					if j == 0 :
						k = nop - 1
					else :
						k = j - 1
					vp = sweep_plane_normal(bZ[k][3], bZ[k][2], bZ[k][1], bZ[k][0])
					u = bZ[k][2] - bZ[k][3] 
					u.norm()
					p = bZ[k][2]
					Q = bZ[k][3]
					inH = sweep_handle(w, u, Q, r, p, vp, cornerL[j])		#  inhandle 座標#
					
	
			if not has_out_handle[j] :
				outH = w										#  outhandle 座標
			else :
				if (j == nop - 1) and not closed :
					p = vec3(ob1.control_point(nop - 1).out_handle)*mx22
					outH = w + p - bZ[nop - 2][3]				#  outhandle 座標
				else :
					vp = sweep_plane_normal(bZ[j][0], bZ[j][1], bZ[j][2], bZ[j][3])
					u = bZ[j][1] - bZ[j][0] 
					u.norm()
					p = bZ[j][1]
					Q = bZ[j][0]
					outH = sweep_handle(w, u, Q, r, p, vp, cornerL[j])		#  outhandle 座標
						

			ob.control_point(j).in_handle = inH*mx1
			ob.control_point(j).out_handle = outH*mx1					
			ob.control_point(j).linked = linked[j]
	
		
	ob0.switch()
	ob0.activate()
	ob0.disclosed = False
	
	return ob0
	

	
	
#  Shade  上で 掃引中心線 , 掃引断面 の順で選択して実行

scene = xshade.scene()

scene.exit_modify_mode()
[ob1, ob2] = scene.active_shapes

#  Shade original sweep
ob1.activate()
scene.memory()
ob2.activate()
ob2.copy_object(None)
ob2.bro.activate()
scene.sweep()
ob3 = scene.active_shape().dad
ob3.activate()
ob3.name = 'Shade sweep'
ob3.disclosed = False


#  custom sweep
ob2.copy_object(None)
ob4 = ob2.bro
ob5 = custom_sweep(ob1, ob4)
ob5.name = 'custom'

scene.active_shapes = [ob3, ob5]

#2

25 - 2  断面配置


Shade 掃引では中心線の形状により断面配置に3つの方法があり、中心線の形状変化と断面配置の変化に不一致とずれが生じています。

custom_sweep( ) では断面配置の方法として Case 2 即ち できるだけ中心線のねじれに忠実に沿った断面配置をおこなう方法を全面的に適用します。

これにより、どのような掃引体でも緩やかにカーブする形状が得られ、変化の不連続などの問題も解決します。

何故 Shade 掃引が bezier 区間が平坦と見なされる場合に他の 2 つの Case を適用しているか、その理由は不明です。


**断面配置方法**

bezier 区間を parameter で16分割し、分割区間それぞれでの両端ポイントでの接線ベクトルに直交する軸を回転軸として、quaternion 回転操作を16回繰り返す。


          [ fig 22 - 12 ]



**< コードの変更点 >**

ここでの変更に伴い、custom_sweep( ) では 関数 sweep_section_quaternion( ) が全面的に変更され、簡潔なものになります。


[ script 25 - 2 ]

#  sweep_section_quaternion(bz as vec3 matrix ,atrix) as quaternion
#
#	bezier data bz から掃引時の断面形状の姿勢を与える quaternion を返す

def sweep_section_quaternion(bz) :
	v1 = labo.bezier_line_tangent(bz, 0)			#  outhandle 側接線ベクトル
	v2 = labo.bezier_line_tangent(bz, 1)			#  inhandle 側接線ベクトル

	if (v1.abs2() == 0) or (v2.abs2() == 0) :		#   重点
		return quaternion()
		
	elif v1.dot(v2) >= 0.99999994 :					#  区間 bz の outhandle と inhandle が 0度平行
		return quaternion()
		
	else :
		t1 = 0
		Q = quaternion()
		for i in range(16) :						#  bezier parameter を16等分して細かく回転を16回繰り返す
			t2 = float(i + 1)/16
			v2 = labo.bezier_line_tangent(bz, t2)
			Q = quaternion().slerp(v1, v2)*Q
			t1 = t2
			v1 = v2
	
		return Q


下図は [ script 22 - 8 ], [ script 22 - 9 ], [ script 22 - 10 ] のサンプル形状に対して [ script 25 - 1 ] で比較したものです。

特に左側の上下段を比較すると、**custom_sweep( )**での緩やかにカーブする形状が注目されます。

     上段 : Shade 掃引
     下段 : custom_sweep( )


     

     [ fig 25 - 1 ]


#3

25 - 3  handle 長さ


Shade の handle 長さの決定方法の解明はできませんでしたが、**custom_sweep( )** で採用する handle 長さの設定方法の基本的な考えは次のとおりです。
  1. 中心線の bezier 区間両端の handle が対称的にのびる基本形状において、中点で両端と同じ太さを持つ掃引体を得る

  2. 中心線が平坦に近い場合、掃引体の太さの均一化の向上を図る

  3. 中心線が三次元的に複雑にねじれている場合、工夫の余地がほとんど無く、Shade 掃引と同じ あるいは それに近いものとする



これに基づき、補正角 φ とそれに掛かる係数 e を次のように定めます。
  1. 対称的な handle 構成に基づいて補正角 φ を定める

  2. handle の対称性からのずれを表す係数 e を定め、φ に乗じる



以下に、φ, e の求め方を記します。
**< 中点で両端と同じ太さを持つ掃引体を得るのに必要な高さ r >**
          [ fig 25 - 2 ]
          [ fig 25 - 3 ]


**< handle 長さの基準となる面法線 Vp を求めるための補正角 φ >**
                [ fig 25 - 4 ]


**< [ fig 23 - 4 ] の Case 4 での補正角 φ’ >**

     

     [ fig 25 - 5 ]






< handle の対称性に基づいた係数 e ( -1 ~ 1 ) >


          [ fig 25 - 6 ]






< 係数 e の意味 >


補正角 φ は中心線が対称な handle を持つ基本形状から定められており、λ→ 0e → 1 となる

          [ fig 25 - 7 ]




handle の対称性が大きく損なわれる場合、λ→ π / 2e → 0 となり、Shade 掃引に近いものになる
( handle が三次元的に伸びる場合も同様 )

          [ fig 25 - 8 ]




相対する handle が0度平行に近い場合、λ→ πe → -1 となり、曲がりの内側と外側の handle 長さの差が小さくなる

          [ fig 25 - 9 ]






< handle 先端が一致する特殊ケース >


さらに、bezier 区間の両端 handle 先端が一致する場合の掃引体形状の改善策として、[ fig 23 - 4 ] の Case 1 での Vp を次のよう設定します。


          [ fig 25 - 10 ]


#4

25 - 4  handle 長さの変更に伴うコード変更点


handle 長さの変更に伴い、**custom_sweep( )** では 関数 **get_phi( )** が不要となり、関数 **sweep_plane_normal( )** の記述が変更され、与える引数も増やされます。
**sweep_plane_normal( ) に与える引数**
  • シミュレーション script では 4つの bezier control point の内の 3つ
  • custom_sweep( ) では 4つの bezier control point 全て

[ script 25 - 2 ]     変更部分を抜粋
#  sweep_plane_normal(p0 as vec3, p1 as vec3, p2 as vec3, p3 as vec3) as vec3
#
#	掃引中心線の bezier data bz (p0, p1, p2) から、掃引体交差方向の handle 長さを求めるための面法線を返す
#	 基準となる anchor point に handle  が出ていることが前提
#
#		p0, p1, p2, p3 :	outhandle 側を求める場合は		bz[0], bz[1], bz[2], bz[3]
#							inhandle 側を求める場合は		bz[3], bz[2], bz[1], bz[0]

def sweep_plane_normal(p0, p1, p2, p3) :		#  [fig 25 - 6 ]
	from math import pi, acos, sin, atan		#  [fig 25 - 5 ]
	
	v1 = p1 - p0								#  v1.abs() != 0 が前提
	v2 = p2 - p1
	v1.norm()
	v2.norm()
	
	if v2.abs2() == 0 :
		v3 = p3 - p1			#  [fig 25 - 10 ]
		v3.norm()				#  [fig 25 - 10 ]
		if v3.abs2() == 0 :		#  [fig 25 - 10 ]
			return v1			#  [fig 25 - 10 ]
		else :					#  [fig 25 - 10 ]
			vn = v1 + v3		#  [fig 25 - 10 ]
			vn.norm()			#  [fig 25 - 10 ]
			vp = v1 + vn		#  [fig 25 - 10 ]
			vp.norm()			#  [fig 25 - 10 ]
			return vp			#  [fig 25 - 10 ]
		
	else :
		a = v1.dot(v2)
		if abs(a) >= 0.99999994 :
			return v1
		
		elif a >= 0 :
			vn = v1 + v2							#  [fig 25 - 4 ]
			vn.norm()								#  [fig 25 - 4 ]
			
			sinA = a								#  [fig 25 - 4 ]
			cosA = (1 - sinA**2)**0.5				#  [fig 25 - 4 ]
			beta = (pi - acos(a))/2					#  [fig 25 - 4 ]
			r = (1 - sinA)/3						#  [fig 25 - 4 ]
			
			axis = v1*v2							#  [fig 25 - 4 ]
			axis.norm()								#  [fig 25 - 4 ]
			v = v1 - v2								#  [fig 25 - 4 ]
			v.norm()								#  [fig 25 - 4 ]
			u = v/sin(beta) + v1*r/cosA				#  [fig 25 - 4 ]
			u.norm()								#  [fig 25 - 4 ]
			phi = acos(u.dot(v))					#  [fig 25 - 4 ]
			
		else :
			axis = v1*v2							#  [fig 25 - 5 ]
			phi = atan(4./3) - pi/4					#  [fig 25 - 5 ]
			Q = quaternion().rotation(axis, pi/4)	#  [fig 25 - 5 ]
			vn = Q*v1								#  [fig 25 - 5 ]

		v3 = p3 - p2								#  [fig 25 - 6 ]
		v3.norm()									#  [fig 25 - 6 ]
		if v3.abs2() == 0 :							#  [fig 25 - 6 ]
			return vn								#  [fig 25 - 6 ]
		else :										#  [fig 25 - 6 ]
			d = v1.dot(v2)							#  [fig 25 - 6 ]
			w = -(v1-2*d*v2)						#  [fig 25 - 6 ]
			w.norm()								#  [fig 25 - 6 ]
			e = v3.dot(w)							#  [fig 25 - 6 ]
			Q = quaternion().rotation(axis, e*phi)	#  [fig 25 - 6 ]
			vp = Q*vn								#  [fig 25 - 6 ]
			vp.norm()								#  [fig 25 - 6 ]
			return vp								#  [fig 25 - 6 ]

#5

25 - 5  出力例( その1 )


**< 太さの均一性 >**
[ script 25 - 3 ] でサンプル形状を出力、 [ script 25 - 1 ] を実行して Shade 掃引と **custom_sweep( )** の結果を出力し、 [ script 25 - 4 ] で中央部の幅を測定してレポートします。

          [ fig 25 - 11 ]


[ script 25 - 3 ]

from math import pi, sin, cos

scene = xshade.scene()
scene.exit_modify_mode()


L = 10000
tL = [30, 60, 90]
hL = [7500, 10000, 15000]

obL = []

for i in range(3) :
	t = tL[i]
	h = hL[i]
	x = cos(t*pi/180)
	y = sin(t*pi/180)

	scene.create_part()
	ob = scene.active_shape()
	obL.append(ob)
	
	scene.begin_creating()
	scene.begin_line('中心線', False)
	scene.append_point([-10000, 0, 0], None, [-10000 + x*h, y*h, 0], None, None)
	scene.append_point([10000, 0, 0], [10000 - x*h, y*h, 0], None, None, None)
	scene.end_line()
	scene.end_creating()
	ob1 = scene.active_shape()

	scene.begin_creating()
	scene.create_line('断面', [[-10000 - y*5000, x*5000, 0], [-10000, 0, 0], [-10000 + y*5000, -x*5000, 0]], False)
	scene.end_creating()

	ob.activate()
	
scene.active_shapes = obL

[ script 25 - 4 ]

from labo import *

scene = xshade.scene()

ob = scene.active_shape()
ob.switch()
ob1 = ob.son.bro
ob2 = ob1.bro

ob1.activate()
bz1 = labo.get_bezier(xshade, 0)
ob2.activate()
bz2 = labo.get_bezier(xshade, 0)

ob.switch()
ob.activate()

print ''

p1 = labo.bezier_line_position(bz1, 0.5)
p2 = labo.bezier_line_position(bz2, 0.5)
v = p1 - p2
print v.abs()

v = bz1[0] - bz2[0]
print v.abs()


[ script 25 - 5 ] でサンプル形状を出力、 [ script 25 - 1 ] を実行して Shade 掃引と **custom_sweep( )** の結果を比較します。

中心線がの曲がりが比較的おとなしければ、多くの場合 custom_sweep( ) はより均一な太さを与えますが、下図右端のように窮屈な屈曲がある極端なケースではかえって均一性が損なわれる傾向が見られます。


( 赤線が Shade 掃引、黒線が custom_sweep( ) によるもの )


          [ fig 25 - 12 ]

[ script 25 - 5 ]

scene = xshade.scene()

scene.create_part()
ob1 = scene.active_shape()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-8000, -10000, 0], None, [-8000, -2000, 0], None, None)
scene.append_point([8000, 10000, 0], [8000, 2000, 0], None, None, None)
scene.end_line()
scene.end_creating()

scene.begin_creating()
scene.create_line('断面', [[-13000, -10000, 0], [-8000, -10000, 0], [-3000, -10000, 0]], False)
scene.end_creating()

ob1.activate()


scene.create_part()
ob2 = scene.active_shape()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-8000, -10000, 0], None, [-8000, 5000, 0], None, None)
scene.append_point([8000, 5000, 0], [3000, -2000, 0], None, None, None)
scene.end_line()
scene.end_creating()

scene.begin_creating()
scene.create_line('断面', [[-13000, -10000, 0], [-8000, -10000, 0], [-3000, -10000, 0]], False)
scene.end_creating()

ob2.activate()


scene.create_part()
ob3  = scene.active_shape()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-8000, -10000, 0], None, [-8000, -1000, 0], None, None)
scene.append_point([10000, 0, 0], [-10000, 0, 0], None, None, None)
scene.end_line()
scene.end_creating()

scene.begin_creating()
scene.create_line('断面', [[-13000, -10000, 0], [-8000, -10000, 0], [-3000, -10000, 0]], False)
scene.end_creating()

ob3.activate()


scene.create_part()
ob4  = scene.active_shape()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-8000, -10000, 0], None, [-8000, -1000, 0], None, None)
scene.append_point([10000, -5000, 0], [0, -10000, 0], None, None, None)
scene.end_line()
scene.end_creating()

scene.begin_creating()
scene.create_line('断面', [[-13000, -10000, 0], [-8000, -10000, 0], [-3000, -10000, 0]], False)
scene.end_creating()


scene.active_shapes = [ob1, ob2, ob3, ob4]



**< handle 先端が一致する特殊ケース >**
bezier 区間の両端 handle 先端が一致する場合でも **custom_sweep( )** はより均一な太さを与えます。

[ script 25 - 6 ] でサンプル形状を出力、 [ script 25 - 1 ] を実行します。


( 赤線が Shade 掃引、黒線が custom_sweep( ) によるもの )


          [ fig 25 - 13 ]


[ script 25 - 6 ]

scene = xshade.scene()
scene.exit_modify_mode()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-10000, -10000, 0], None, [-10000, 4000, 0], None, None)
scene.append_point([10000, 10000, 0], [-10000, 4000, 0], None, None, None)
scene.end_line()
scene.end_creating()
ob1 = scene.active_shape()


scene.begin_creating()
scene.create_line('断面', [[-15000, -10000, 0], [-5000, -10000, 0]], False)
scene.end_creating()
ob2 = scene.active_shape()


scene.active_shapes = [ob1, ob2]

#6

25 - 6  コーナー


**< handle 長さ >**
handle 長さの求め方については **23 掃引(2/3)**の [ fig 23 - 6 ] で示しました。

     

     [ fig 23 - 6 ]




コーナー部分の形状を滑らかにするために、コーナー部分では上図 a の長さを考慮しない handle 長さを用います。

     

     [ fig 25 - 14 ]






< limitter >


また、limitter の仕様を次のように変更し、ユーザー指定できるようにします。

     limitter : 下図 scale 値の上限


default の limitter 値をいくらに設定するかは難しい問題です。

直線コーナーであればある程度の大きな値が必要ですが、曲線コーナーの場合では、曲がり具合と断面サイズとの関係に大きく左右されるため、一律の値を適用するのが困難です。


そこで、直線コーナーの値と曲線コーナーの値を分け、デフォルト値を次のようにしました。

  • 直線コーナー : 10
  • 曲線コーナー : 3

          [ fig 25 - 15 ]


#7

25 - 7  コーナーの変更に伴うコード変更点


ここでの変更に伴い、**custom_sweep( )** では 関数 **sweep_handle( )** の記述が一部変更され、与える引数も増やされます。

また、断面配置において、鋭角コーナー / 非鋭角コーナー の区別が無くなり、limitter に関する記述が追加されます。


sweep_handle( ) に追加される引数

     corner as bool : 当該 anchor point の corner flag


[ script 25 - 5 ]     変更部分を抜粋
#  sweep_handle(w as vec3, u as vec3,  Q as vec3, r as float, p as vec3, vp as vec3, corner as bool) as vec3
#
#	handle 座標を返す

def sweep_handle(w, u, Q, r, p, vp, corner) :			##############  [ fig 25 - 14 ]
	#     L :  点 w を通り、u 方向に進む直線
	#	PL0 : 点 Q を通り面法線 u なる面
	#	PL1 : 点 p を通り面法線 vp なる面
	#	精度確保のため r = 1 とはせず、r = bounding box size / 16 としてある

	a = u.dot(w - Q)			#  点 w と面 PL0 との符号付き距離		
	d1 = vp.dot(w - p)			#  点 w と 面 PL1 との符号付き距離
	d2 = vp.dot(w + r*u - p)	#  点 w + r*u と 面 PL1 との符号付き距離
	d = r*d1/(d1 - d2)			#  点 w と、直線 L と 面 PL1 との交点までの 符号付き距離
	if not corner :										##############  [ fig 25 - 14 ]
		d = d + a				#  handle の符号つき長さ
	if d < 0 :					#  食い込みが発生する
		d = -d/12
	return w + d*u
		


#  custom_sweep(ob1 as shape, ob2 as shape, limitter1 as float = 10., limitter2 as float = 3.) as shape
#
#	掃引体を Shade 上に作成し、その shape を返す
#		ob1 :		掃引中心線形状
#		ob2 :		掃引断面形状
#		limitter1 :	直線コーナーの scale liitter	
#		limitter2 :	曲線コーナーの scale liitter	

def custom_sweep(ob1, ob2, limitter1 = 10., limitter2 = 3.) :		##############  [ fig 25 - 15 ]
	if (ob1 == None) or (ob2 == None) :
		return
	if (not isinstance(ob1, xshade.line)) or (not isinstance(ob2, xshade.line)) :
		return

	#  掃引中心線データ取得 nop, closed, bZ
	ob1.activate()
	nop = ob1.number_of_control_points		#  ポイント数
	if nop < 2 :
		return
	closed = ob1.closed						#  開閉情報
	bZ = labo.get_bezier(xshade, -1)		#  全区間の bezier data [matrix, matrix, … ]

	
	# mx1, mx2, mx22
	mx1 = matrix(ob2.world_to_local_matrix)
	mx2 = matrix(ob2.local_to_world_matrix)
	if not closed :
		mx22 = matrix(ob1.local_to_world_matrix)
		
		
	#  掃引中心線 の handle 情報取得
	has_in_handle = []			#  掃引中心線の inhandle の有無のリスト
	has_out_handle = []			#  掃引中心線の outhandle の有無のリスト
	linked = []					#  掃引中心線の hanndle link リスト
	for i in range(nop) :
		has_in_handle.append(ob1.control_point(i).has_in_handle)
		has_out_handle.append(ob1.control_point(i).has_out_handle)
		linked.append(ob1.control_point(i).linked)

	
	#  ob0 : 掃引体を格納する自由曲面
	ob2.activate()
	scene.create_surface_part(None)
	ob0 = scene.active_shape()				#   掃引体を格納する自由曲面
	ob0.surface_closed = closed
	ob2.place_child(1)

	
	#  掃引断面を配置
	p1 = bZ[0][0]
	Mt1 = matrix().translate(-p1[0], -p1[1], -p1[2])
	Q1 = quaternion()
	v2_prev = None
	n = nop - 1
	cornerL = [False]									##############  [ fig 25 - 14 ]
		
	for i in range(n) :
		p2 = bZ[i][3]
		v2 = labo.bezier_line_tangent(bZ[i], 1)
		Mt2 = matrix().translate(p2[0], p2[1], p2[2])
		Q2 = sweep_section_quaternion(bZ[i])
		Q2 = Q2*Q1
		Mr = Q2.matrix()
		corner = False									##############  [ fig 25 - 14 ]
		
		if i == n - 1 and not closed :
			M = mx2*Mt1*Mr*Mt2*mx1
			
		else :
			v3 = labo.bezier_line_tangent(bZ[i + 1], 0)
			if v3.abs2() == 0 :						#  重点
				M = mx2*Mt1*Mr*Mt2*mx1
				if v2_prev == None :				#  最初の重点
					v2_prev = v2
				
			elif v2.abs2() == 0 :					#  最後の重点
				if v2_prev == None or v2_prev.abs2() == 0 :	#  始点が重点から始まっている
					M = mx2*Mt1*Mr*Mt2*mx1
				else :
					Qc = quaternion().slerp(v2_prev, v3, 1, False)	
					if Qc != None :	
						Q2 = Qc*Q2
						Mc = Qc.matrix()
						M = mx2*Mt1*Mr*Mc*Mt2*mx1
					else :
						M = mx2*Mt1*Mr*Mt2*mx1
					v2_prev = None

			else :
				if w.dot(v2) >= 0.99999994 :			#  掃引中心線の point が corner point  ではない
					M = mx2*Mt1*Mr*Mt2*mx1
				
				else :									#  掃引中心線の point が corner point 
					w = v2 + v3
					w.norm()
					corner = True										##############  [ fig 25 - 14 ]
					v1 = labo.bezier_line_tangent(bZ[i], 0)				##############  [ fig 25 - 15 ]	
					v4 = labo.bezier_line_tangent(bZ[i + 1], 1)			##############  [ fig 25 - 15 ]
					if v1.dot(v2) + v3.dot(v4) > 1.98 :	#  直線コーナー	##############  [ fig 25 - 15 ]
						limitter = limitter1							##############  [ fig 25 - 15 ]
					else :								#  曲線コーナー	##############  [ fig 25 - 15 ]
						limitter = limitter2							##############  [ fig 25 - 15 ]
					Q3 = quaternion().slerp(v2, w)
					Mq = Q3.matrix()
					
					v = v2 - v3
					cosT = w.dot(v2)
					Q4 = quaternion().slerp(v, vec3(0, 1, 0))
					Mqs1 = Q4.matrix()
					Q5 = Q4.inverse()
					Mqs2 = Q5.matrix()
					Ms = matrix().scale(1, min(limitter, 1/cosT), 1)	##############  [ fig 25 - 15 ]	
					M = mx2*Mt1*Mr*Mq*Mqs1*Ms*Mqs2*Mt2*mx1
						
					Q6 = quaternion().slerp(v2, v3)
					Q2 = Q6*Q2							#   次の point での掃引断面配置に必要

	
		ob2.copy_object(M)
		ob = ob2.bro
		ob.place_brother(i)
	
		Q1 = Q2
		cornerL.append(corner)											##############  [ fig 25 - 14 ]

	
	#  掃引体の交差方向 handle をセット
	bb = labo.vec3_bounding_box_size(bZ)
	r = bb[3]/16												#  sweep_handle( ) の引数に使用
	
	n = ob2.number_of_control_points
	ob0.switch()
	ob = ob0.son
	for i in range(n) :
		ob = ob.bro												#  掃引体内部の交差方向線形状
		
		for j in range(nop) :
			w = vec3(ob.control_point(j).position)*mx2			#  交差方向線形状の point 座標
			
			if not has_in_handle[j] :
				inH = w											#  inhandle 座標
			else :
				if (j == 0) and not closed :
					p = vec3(ob1.control_point(0).in_handle)*mx22
					inH = w + p - bZ[0][0]						#  inhandle 座標
				else :
					if j == 0 :
						k = nop - 1
					else :
						k = j - 1
					vp = sweep_plane_normal(bZ[k][3], bZ[k][2], bZ[k][1], bZ[k][0])
					u = bZ[k][2] - bZ[k][3] 
					u.norm()
					p = bZ[k][2]
					Q = bZ[k][3]
					inH = sweep_handle(w, u, Q, r, p, vp, cornerL[j])	#  inhandle 座標		##############  [ fig 25 - 14 ]
					
	
			if not has_out_handle[j] :
				outH = w										#  outhandle 座標
			else :
				if (j == nop - 1) and not closed :
					p = vec3(ob1.control_point(nop - 1).out_handle)*mx22
					outH = w + p - bZ[nop - 2][3]				#  outhandle 座標
				else :
					vp = sweep_plane_normal(bZ[j][0], bZ[j][1], bZ[j][2], bZ[j][3])
					u = bZ[j][1] - bZ[j][0] 
					u.norm()
					p = bZ[j][1]
					Q = bZ[j][0]
					outH = sweep_handle(w, u, Q, r, p, vp, cornerL[j])	#  outhandle 座標	##############  [ fig 25 - 14 ]
						

			ob.control_point(j).in_handle = inH*mx1
			ob.control_point(j).out_handle = outH*mx1					
			ob.control_point(j).linked = linked[j]

#8

25 - 8  出力例( その2 )


[ script 25 - 7 ] でサンプル出力を行い、[ script 25 - 1 ] を実行します。

( 赤線が Shade 掃引、黒線が custom_sweep( ) によるもの )

          { fig 25 - 16 ]


[ script 25 - 8 ] でサンプル出力を行い、[ script 25 - 1 ] を実行します。

( 赤線が Shade 掃引、黒線が custom_sweep( ) によるもの )

          [ fig 25 - 17 ]


[ script 25 - 7 ]
scene = xshade.scene()

scene.create_part()
ob1 = scene.active_shape()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-8000, 0, 0], None, [-8000, 5000, 0], None, None)
scene.append_point([0, 15000, 0], [-5000, 12000, 0], [5000, 12000, 0], None, None)
scene.append_point([8000, 0, 0], [8000, 5000, 0], None, None, None)
scene.end_line()
scene.end_creating()

scene.begin_creating()
scene.create_line('断面', [[-13000, 0, 0], [-8000, 0, 0], [-3000, 0, 0]], False)
scene.end_creating()

ob1.activate()


scene.create_part()
ob2 = scene.active_shape()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-8000, 0, 0], None, [-8000, 5000, 0], None, None)
scene.append_point([0, 15000, 0], [-5000, 12000, 0], [5000, 12000, 0], None, None)
scene.append_point([8000, 0, 0], [8000, 5000, 0], None, None, None)
scene.end_line()
scene.end_creating()

scene.begin_creating()
scene.create_line('断面', [[-15000, 0, 0], [-8000, 0, 0], [-1000, 0, 0]], False)
scene.end_creating()


scene.active_shapes = [ob1, ob2]

[ script 25 - 8 ]
scene = xshade.scene()

scene.create_part()
ob1 = scene.active_shape()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-10000, 0, 0], None, [-5000, 0, 0], None, None)
scene.append_point([0, 5000, 0], [-1500, 2500, 0], [1500, 2500, 0], None, None)
scene.append_point([10000, 0, 0], [5000, 0, 0], None, None, None)
scene.end_line()
scene.end_creating()

scene.begin_creating()
scene.create_line('断面', [[-10000, 1250, 0], [-10000, 0, 0], [-10000, -1250, 0]], False)
scene.end_creating()

scene.begin_creating()
scene.create_line('断面', [[-10000, 2500, 0], [-10000, 0, 0], [-10000, -2500, 0]], False)
scene.end_creating()

ob1.activate()


scene.create_part()
ob2 = scene.active_shape()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-10000, 0, 0], None, [-5000, 0, 0], None, None)
scene.append_point([0, 5000, 0], [-500, 2500, 0], [500, 2500, 0], None, None)
scene.append_point([10000, 0, 0], [5000, 0, 0], None, None, None)
scene.end_line()
scene.end_creating()

scene.begin_creating()
scene.create_line('断面', [[-10000, 1250, 0], [-10000, 0, 0], [-10000, -1250, 0]], False)
scene.end_creating()

scene.begin_creating()
scene.create_line('断面', [[-10000, 2500, 0], [-10000, 0, 0], [-10000, -2500, 0]], False)
scene.end_creating()

ob2.activate()


scene.create_part()
ob3 = scene.active_shape()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-10000, 0, 0], None, [-5000, 0, 0], None, None)
scene.append_point([0, 5000, 0], [-200, 2500, 0], [200, 2500, 0], None, None)
scene.append_point([10000, 0, 0], [5000, 0, 0], None, None, None)
scene.end_line()
scene.end_creating()

scene.begin_creating()
scene.create_line('断面', [[-10000, 1250, 0], [-10000, 0, 0], [-10000, -1250, 0]], False)
scene.end_creating()

scene.begin_creating()
scene.create_line('断面', [[-10000, 2500, 0], [-10000, 0, 0], [-10000, -2500, 0]], False)
scene.end_creating()


scene.active_shapes = [ob1, ob2, ob3]

#9

25 - 9  handle 長さ - 補遺


**23 - 9 < 食い込みが発生するケース >** で触れたように、食い込み = handle が逆を向く 場合には、handle 長さを短くしてその方向を反転する 処理が施されます。

この仕様は上記 25 - 8 で記した曲線コーナーで重要な働きをする場合があります。


[ script 25 - 9 ] でサンプル出力を行い、[ script 25 - 1 ] を実行したものと、[ script 25 - 1 ] 内の関数 sweep_handle( ) の食い込み発生の処理部分を comment out してから実行したものを比較します。

def sweep_handle(w, u, Q, r, p, vp, corner) :
	#     L :  点 w を通り、u 方向に進む直線
	#	PL0 : 点 Q を通り面法線 u なる面
	#	PL1 : 点 p を通り面法線 vp なる面
	#	精度確保のため r = 1 とはせず、r = bounding box size / 16 としてある

	a = u.dot(w - Q)			#  点 w と面 PL0 との符号付き距離		
	d1 = vp.dot(w - p)		#  点 w と 面 PL1 との符号付き距離
	d2 = vp.dot(w + r*u - p)	#  点 w + r*u と 面 PL1 との符号付き距離
	d = r*d1/(d1 - d2)		#  点 w と、直線 L と 面 PL1 との交点までの 符号付き距離
	if not corner :
		d = d + a			#  handle の符号つき長さ
#	if d < 0 :					#  食い込みが発生する		######  comment out
#		d = -d/12										######  comment out
	return w + d*u

( 黒線が original **custom_sweep( )** の結果、赤線が処理を除外した場合の結果 )

          [ fig 25 - 18 ]


[ script 25 - 9 ]

scene = xshade.scene()

scene.create_part()
ob1 = scene.active_shape()

scene.begin_creating()
scene.begin_line('中心線', False)
scene.append_point([-13000, 0, 0], None, [-7000, 0, 0], None, None)
scene.append_point([0, 12000, 0], [-500, 5000, 0], [500, 5000, 0], None, None)
scene.append_point([13000, 0, 0], [7000, 0, 0], None, None, None)
scene.end_line()
scene.end_creating()

scene.begin_creating()
scene.create_line('断面', [[-13000, 4000, 0], [-13000, 0, 0], [-13000, -4000, 0]], False)
scene.end_creating()

ob1.activate()

#10

25 - 10  Script 修正( 16 / 05 / 17, 18 )


[ script 25 - 1 ] の handle の出ている重点での扱いに対して、下記二カ所修正を行いました
**def sweep_section_quaternion(bz) :**

elif v1.dot(v2) >= 0.99999994 :				#  区間 bz の outhandle と inhandle が 0度平行
	return quaternion()	

**修正     abs( ) を追加**
elif abs(v1.dot(v2)) >= 0.99999994 :		#  区間 bz の outhandle と inhandle が平行
	return quaternion()


**def custom_sweep(ob1, ob2, limitter1 = 10., limitter2 = 3.) :**

#  掃引断面を配置

elif v2.abs2() == 0 :						#  最後の重点
	Qc = quaternion().slerp(v2_prev, v3)
	Q2 = Qc*Q2	
	Mc = Qc.matrix()
	M = mx2*Mt1*Mr*Mc*Mt2*mx1
	v2_prev = None
				
else :
	w = v2 + v3
	w.norm()
	if w.dot(v2) >= 0.99999994 :			#  掃引中心線の point が corner point  ではない
    		M = mx2*Mt1*Mr*Mt2*mx1
    				
	else :									#  掃引中心線の point が corner point 
    		corner = True

**修正**
#  掃引断面を配置

elif v2.abs2() == 0 :						#  最後の重点
	Qc = quaternion().slerp(v2_prev, v3, 1, False)
	if Qc != None :
		Q2 = Qc*Q2	
		Mc = Qc.matrix()
		M = mx2*Mt1*Mr*Mc*Mt2*mx1
	else :
		M = mx2*Mt1*Mr*Mt2*mx1
	v2_prev = None
				
else :
	if abs(v2.dot(v3)) >= 0.99999994 :	#  掃引中心線の point が corner point  ではない
		M = mx2*Mt1*Mr*Mt2*mx1
				
	else :								#  掃引中心線の point が corner point 
		w = v2 + v3
		w.norm()
		corner = True