Shade3D 公式

28 水平掃引 [ Shade Labo ]


#1

**16 / 06 / 05**    script を 一カ所 修正

     修正 / 追加箇所については 末尾 28 - 6 Script 修正 の項を参照


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

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


16 / 07 / 01     一部のロジックを修正

     改善ロジックについては 28 - 3 [ fig 28 - 11 ] を参照
     script の修正箇所については 末尾 28 - 6 Script 修正 の項を参照


関連記事:
  • 26 滑らかな掃引(1/2)
  • 27 滑らかな掃引(2/2)

28 - 1  概要


( ここで図示する形状の掃引中心線と断面形状を出力する script は、本項の最後にまとめて記述してあります )

螺旋状の中心線に沿って掃引を行うと、下図中央のように断面は螺旋に沿って進むにつれて傾いていきます。

これを下図右のように断面の水平を維持したままでの掃引体を作るのが 水平掃引 です。

水平掃引は 滑らかな掃引 を与える script c_sweep( ) の一部を改編して作成します。


          [ fig 28 - 1 ]


三次元的に広がる曲線には、必ず **ねじれ** が生じます。

従って中心線のねじれに沿って掃引体を得る Custom 掃引でも断面の向きは変化します。

Custom 掃引による掃引体が滑らかなのであって、水平掃引ではこれを強制的に逆向きにねじるものであり、水平掃引体の方が歪んでる と言えます。

とはいえ、見た目では水平掃引の方が綺麗に見えてしまいますから、その意味での有用性はあるでしょう。


          [ fig 28 - 2 ]

     

     [ fig 28 - 3 ]


水平掃引のもう一つの有用性は、三次元的に広がる閉じた中心線に対する掃引です。

既に見てきたように掃引断面は始端から始まって、順次中心線に沿って一定のルールの下に配置されていき、最後のポイントに対する断面が配置されれば、そこから始端の断面にわたる交差方向線形状は なりゆき で作られます。

この時、場合によってはこの最後の区間で交差線形状が激しくねじれてしまいます。

水平掃引はこのような場合でも綺麗に閉じる形状を与えます。


          [ fig 28 - 4 ]


とはいえ、決して万能ではなく、水平を維持するように強制的にねじりを与えるので、**垂直に近い立ち上がり部分で大きなねじれが生じることがあります**。

さらに、理由は後述しますが、掃引断面は中心線に対して垂直に位置することを前提 とし、断面配置間の曲がり角は90度以内に納めることが望ましい という条件があります。

傾いていても掃引は実行可能ですが、滑らかな形状にはなりません。


          [ fig 28 - 5 ]



[ script 28 - 1 ]

     

import labo
from vec3 import *
from matrix import *
from math import sin, cos, pi


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


#  中心線
h = 0.75
n = 8
scale = 10000
dt = 2*pi/n
p = []
for i in range(4*n) :
	t = i*dt
	x = cos(t)
	y = i*h/n
	z = -sin(t)
	p.append(vec3(x, y, z))
	
Ms = matrix().scale(scale, scale, scale)
Ms.transform(p)

scene.begin_creating()
scene.create_line('中心線', p, False)
scene.end_creating()

scene.smooth()
ob1 = scene.active_shape()
p0 = vec3(ob1.control_point(0).position)
outh = vec3(ob1.control_point(0).out_handle)
u1 = outh - p0
v1 = u1*vec3(-1, 0, 0)
Mt = matrix().translate(p0[0], p0[1], p0[2])


#  断面
p = [[0, 0.2, 0], [-0.125, -0.1, 0], [0.125, -0.1, 0]]
p = vec3list(p)
Ms.transform(p)

scene.begin_creating()
scene.create_line('断面', p, True)
scene.end_creating()
ob2 = scene.active_shape()
u2 = vec3(0, 0, 1)
v2 = vec3(0, 1, 0)
Q = labo.attitude_control_quaternion(u2, v2, u1, v1)
Mq = Q.matrix()
ob2.transform(Mq*Mt)


scene.active_shapes = [ob1, ob2]


[ script 28 - 2 ]

     

import labo
from vec3 import *
from matrix import *
from math import sin, cos, pi


scene = xshade.scene()
scene.exit_modify_mode()
scene.lathe_rotation = [1, 0, 0, 0]


#  中心線
scene.create_disk('中心線', [-5000, 0, 0], 5000, 1)
scene.convert_to_line_object()
ob1 = scene.active_shape()
ob1.closed = False
ob1.remove_control_point(3)
ob1.copy_object([[-1, 0, 0, 0],[0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
ob2 = ob1.bro
ob2.reverse()
ob2.activate()
scene.memory()
ob1.activate()
scene.append()
ob2.remove()
ob1.closed = True

v = vec3(0, 3000, 0)
for i in [1, 2, 3] :
	p =  vec3(ob1.control_point(i).position)
	ob1.control_point(i).position = p + v
	p =  vec3(ob1.control_point(i).in_handle)
	ob1.control_point(i).in_handle = p + v
	p =  vec3(ob1.control_point(i).out_handle)
	ob1.control_point(i).out_handle = p + v

	
#  断面
scene.create_disk('断面', [-5000, 0, 5000], 2000, 0)
scene.convert_to_line_object()
ob3 = scene.active_shape()

scene.copy_object([-5000, 0, 5000], None, [45, 0, 0], None)
scene.unsmooth()
ob4 = scene.active_shape()

scene.active_shapes = [ob1, ob3, ob4]


[ script 28 - 3 ]

     

import labo
from vec3 import *
from matrix import *
from math import sin, cos, pi


scene = xshade.scene()
scene.exit_modify_mode()
scene.lathe_rotation = [1, 0, 0, 0]


#  中心線
a = 0.5
h = 0.5
scale = 10000
dt = 4*pi/30
p = []
for i in range(30) :
	t = i*dt
	x = cos(t) + a*cos(t/2)
	z = -sin(t) + a*sin(t/2)
	y = h*sin(t*3./2)
	p.append(vec3(x, y, z))
	
M = matrix().scale(scale, scale, scale)
M.transform(p)

scene.begin_creating()
scene.create_line('中心線', p, True)
scene.end_creating()

scene.smooth()
ob1 = scene.active_shape()
p0 = vec3(ob1.control_point(0).position)
outh = vec3(ob1.control_point(0).out_handle)
u1 = outh - p0
v1 = u1*vec3(1, 0, 0)
Mt = matrix().translate(p0[0], p0[1], p0[2])


#  断面
r = 0.3
scene.create_disk('断面', [0, 0, 0], r*scale, 0)
scene.move_object(None, None, [45, 0, 0], None)
scene.convert_to_line_object()
ob2 = scene.active_shape()
u2 = vec3(-1, 0, 0)
v2 = vec3(0, 1, 0)
Q = labo.attitude_control_quaternion(u2, v2, u1, v1)
Mq = Q.matrix()
ob2.transform(Mq*Mt)


scene.active_shapes = [ob1, ob2]


[ script 28 - 4 ]

     

scene = xshade.scene()
scene.exit_modify_mode()
scene.lathe_rotation = [1, 0, 0, 0]


#  中心線
scale = 10000
scene.create_disk('中心線', [0, 0, 0], 1*scale, 2)
scene.move_object(None, None, [0, 0, 45], None)
scene.convert_to_line_object()
ob1 = scene.active_shape()


#  断面
r = 0.3
scene.create_disk('断面', [0, 0, 0], r*scale, 0)
scene.move_object(None, None, None, [0, -1*scale, 0])
scene.move_object(None, None, [0, 0, 45], None)
scene.convert_to_line_object()
ob2 = scene.active_shape()


scene.active_shapes = [ob1, ob2]

#2

28 - 2  断面配置


関連記事:
  • 21 Quaternion

断面配置は **21 Quaternion** で紹介した関数 attitude_control_quaternion( ) を用いれば簡単です。
def **attitude_control_quaternion**( u1 as vec3, v1 as vec3, u2 as vec3, v2 as vec3, fit as integer = 0 ) as quaternion
  • 一組のベクトル u1, v1 が、もう一組のベクトル u2, v2 に一致するように回転する quaternion を返す

  • u1, v1 は単位ベクトルでなくてもよく、直交条件も必要ないが、平行であってはならない

  • u2, v2 も単位ベクトルでなくてもよく、直交条件も必要ないが、平行であってはならない

  • u1, v1 のなす角と u2, v2 のなす角が異なる場合、

          fit = 0 ならば、u1 と u2 を一致させる
          fit = 1 ならば、u1,v1 の中間線と u2, v2 の中間線を一致させる


          [ fig 28 - 6 ]


[ script 28 - 5 ] は **c_sweep( )** の関数 **sweep_section_quaternion( )** を改編した関数 **h_sweep_section_quaternion( )** を記したものです。
[ script 28 - 5 ]
#  h_sweep_section_quaternion(bz as vec3 matrix) as quaternion
#
#	bezier data bz から水平掃引時の断面形状の姿勢を与える quaternion を返す

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

	if v1.abs2() == 0 :							#   重点
		return quaternion()	
	else :	
		Q = labo.attitude_control_quaternion(v1, vec3(0, 1, 0), v2, vec3(0, 1, 0), 0)
		if Q != None :
			return Q
		else :									#  v1 or v2 が vec3(0, 1, 0) と平行
			return quaternion().slerp(v1, v2)
		
#  注記
#	quaternion().slerp( ) から attitude_control_quaternion( ) に変更されたので、abs(v1.dot(v2)) >= 0.99999994 の判定は不要に

#3

28 - 3  交差方向線形状の handle 方向( その1 )


**16 / 06 / 07**     一部のロジックを改善

     改善ロジックについては [ fig 28 - 11 ] を参照
     それに合わせて [ script 28 - 6 ] を改編


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

水平掃引では断面を強制的にねじることになるので、その断面交差線の handle 方向 **Vt** は中心線の接線ベクトル **U** に対して傾きます。

下図では VtU と平行ではなく、図に対して垂直方向に傾いています。

     

     [ fig 28 - 7 ]


この断面交差線の handle 方向 **Vt** は以下のように求められます。
          [ fig 28 - 8 ]
          [ fig 28 - 9 ]
          [ fig 28 - 10 ]

**16 / 06 / 07 [ fig 28 - 11 ] ロジック改善に伴い、差し替え**      [ fig 28 - 11 ]

断面ポイント **W1** は中心線上のポイント **Q** を通り、中心線に対して垂直な面内に位置する必要があります。

そうでなければ、Vt で与えられる断面交差線の handle 方向は正しい向きになりません。

最初に述べた 掃引断面は中心線に対して垂直に位置すること という条件はこのことによります。

また 断面配置間の曲がり角は90度以内に納めることが望ましい という条件も、隣接する断面ポイント座標を利用することから生じます。


[ script 28 - 6 ] は 断面交差線の handle 方向 Vt を求める関数 get_vt( ) を記したものです。


[ script 28 - 6 ]

16 / 07 / 01 ロジック修正に伴い、差し替え

#  get_vt(w as vec3 list, q as vec3, r as float) as vec3
#
#		交差線形状の handle の方向を与える vector を返す
#		w[0] :	交差線形状の当該 point の一つ手前の point 座標
#		w[1] :	交差線形状の当該 point の座標
#		w[2] :	交差線形状の当該 point の一つ先の point 座標
#		q :		 中心線 point 座標
#		r :		中心線 boinding box size / 16

def get_vt(w, q, r) :
	#  w[0] = w[1] = w[2] となる三重点のケースではここに入らない
	v1 = w[0] - w[1]
	v2 = w[2] - w[1]
	v1.norm()
	v2.norm()                    
	v0 = v2 - v1
	v0.norm()
	
	v = w[1] - q
	a = v.norm()
	if a < r*1e-5 :		# w[1]  と q が一致しているとみなす
		vt = v0
	else :
		d = v.dot(v0)
		vt = v0 - d*v
		vt.norm()
		
	return vt

#4

28 - 4  交差方向線形状の handle 方向( その2 )


前項で述べたように、断面交差線の handle の向きは、そのポイントの交差方向に隣接する2つのポイントの座標に依存します。

全く同じではありませんが線形状に smooth を施したときの処理に似ています。


中心線に直線部分や平坦な部分がある場合に 28 - 3 で述べたルールを全面的に適用すると、交差線 handle の方向が下図左のように歪んでしまいます。

これに対し、下図右のように直線 / 平坦部分に接続するポイントでの交差方向 handle の向きを掃引中心線の handle 方向に合わせておけば、綺麗な形状が得られます。

つまり、この部分では 28 - 3 のルールではなく、通常の掃引のルールを適用する必要があります。


     [ fig 28 - 12 ]


          [ fig 28 - 13 ]


水平掃引での平坦性判定は **水平平坦性判定** になり、bounding box size を用いた判定になります。

          [ fig 28 - 14 ]



[ script 28 - 7 ] は 掃引中心線の 直線 / 平坦性 判定を含む関数 **h_flatness_list( )** を記したものです。

[ script 28 - 7 ]

#  h_flatness_list(bZs as vec3 list, closed as bool) as bool list
#
#		bZs :		掃引中心線の control point 座標 list
#		closed :	中心線の開閉情報
#
#		掃引中心線の各 bezier line 区間の平坦性 list fL を返す
#		list の始端と終端には次なる項目を追加
#			中心線が閉じている場合、その隣接項目
#			中心線が開いている場合、False

def h_flatness_list(bZs, closed):
	flatL = []
	for bz in bZs :
		v1 = labo.bezier_line_tangent(bz, 0)		#  outhandle 側接線ベクトル
		v2 = labo.bezier_line_tangent(bz, 1)		#  inhandle 側接線ベクトル
		v = bz[3] - bz[0]
			
		if v1.abs2() == 0 :							#  重点
			flatL.append(False)
		elif (v1.dot(v2) >= 0.9999) :				#  bz が直線 ( 事前に屈曲点で分割されているので v1, v2 の平行判定のみで OK )
			flatL.append(True)
		elif v.abs2() == 0 :						#  bezier 区間の始端と終端が同一座標で、且つ handle が出ている
			flatL.append(True)
		else :
			bb = labo.vec3_bounding_box_size(bz)
			flatL.append(bb[1]/bb[3] <= 0.0001)		#  bz の bounding box が水平平坦
		
	#   関数 h_sweep_handle( ) にて、平坦性 list fL の隣接二値を比較するため、 list の先頭と終端に値を追加
	if not closed :
		flatL.insert(0, True)
		flatL.append(True)
	else :
		flatL.insert(0, flatL[len(flatL) - 1])
		flatL.append(flatL[1])
		
	return flatL

#5

28 - 5  Script


以上から関数 **c_sweep( )** を水平掃引を作成する機能に改編した関数 **h_sweep( )** を作成、しきい角度などを指定するユーザーインターフェースを追加して [ script 28 - 8 ] として下に記します。

この script では分割しきい値の限度を 90度 にしてあり、分割なし の選択項目はありません。


[ script 28 - 8 ]

16 / 06 / 05   script を一部修正

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


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

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


16 / 07 / 01   ロジック修正に伴い 関数 get_vt( ) を変更

     変更箇所については [ script 28 - 6 ] を参照


**c_sweep( ) からの変更や追加部分には行末に ############ を付しています**
import labo
from matrix import *
from quaternion import *

		
##  全面変更	  ########################################################################

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

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

	if v1.abs2() == 0 :							#   重点
		return quaternion()	
	else :	
		Q = labo.attitude_control_quaternion(v1, vec3(0, 1, 0), v2, vec3(0, 1, 0), 0)
		if Q != None :
			return Q
		else :									#  v1 or v2 が vec3(0, 1, 0) と平行
			return quaternion().slerp(v1, v2)
		
#  注記
#	quaternion().slerp( ) から attitude_control_quaternion( ) に変更されたので、abs(v1.dot(v2)) >= 0.99999994 の判定は不要に
####################################################################################		
		
			
				
#  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

	
	
	
#  h_sweep_handle(w as vec3, vr as vec3,  Q as vec3, r as float, p as vec3, vp as vec3) as vec3
#
#	handle 座標を返す

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

	d1 = vp.dot(w - p)			#  点 w と 面 PL1 との符号付き距離
	d2 = vp.dot(w + r*vr - p)	#  点 w + r*vr と 面 PL1 との符号付き距離
	d = r*d1/(d1 - d2)			#  点 w を通り vr 方向に進む直線と、面 PL1 との交点までの 符号付き距離
	if d < 0 :					#  食い込みが発生する
		d = -d/12
	return w + d*vr
		

	
#  bezier_inflection_point( bz as vec3 list) as float list
#
#		bz : bezier control point 座標を格納した vec3 list
#		bezier line bz の屈曲点位置の bezier parameter を返す。
#		屈曲点がない場合は、空 list が返される

def bezier_inflection_point(bz) :
	w1 = (bz[1] - bz[0])*(bz[2] - bz[3])
	w2 = (bz[2] - bz[1])*(bz[3] - bz[0])
	w3 = (bz[2] - bz[0])*(bz[1] - bz[3])
	r1 = w1.norm()
	r2 = w2.norm()
	r3 = w3.norm()
	L = [[r1, w1], [r2, w2], [r3, w3]]
	L.sort()
	vn = L[2][1]
	if vn.norm() == 0 :				#  bz は直線であり、変曲点は存在しない
		return []

	#  bz2 : 原点を通り面法線 vn なる面に bz を投影	
	bz2 = []
	for i in range(4) :
		d = vn.dot(bz[i])
		bz2.append(bz[i] - d*vn)
	
	#  Q : bz2 の階差表現
	bz2 = matrix(False, bz2)
	Md = labo.bezier_d()
	Q = bz2*Md
	
	#  こちらの方法でも可
#	bz2 = matrix(True, bz2)
#	Md = labo.bezier_d()
#	Md.transpose()
#	Q = Md*bz2			

	A = Q[2]*Q[3]
	B = Q[1]*Q[3]
	C = Q[1]*Q[2]
	
	bb = labo.vec3_bounding_box_size(Q)
	bb.pop()
	bb2 = sorted(bb)
	for i in range(3) :
		if bb2[0] == bb[i] :
			a = A[i]
			b = B[i]
			c = C[i]
			t = []
			if a != 0 :
				d = b**2 - 4*a*c
				if d > 0 :
					tt = (-b - d**0.5)/(2*a)
					if tt > 0 and tt < 1 :
						t.append(tt)
					tt = (-b + d**0.5)/(2*a)
					if tt >= 0 and tt <= 1 :
						t.append(tt)
				elif d == 0 :
					tt = -b/(2*a)
					if tt > 0 and tt < 1 :
						t.append(tt)
			elif b != 0 :
				tt = -c/b
				if tt > 0 and tt < 1 :
					t.append(tt)
			
			if len(t) == 1 :
				v1 = labo.bezier_line_tangent(bz, 0)
				v2 = labo.bezier_line_tangent(bz, 1)
				v0 = labo.bezier_line_tangent(bz, t[0])
				if v0.dot(v1) >= 0.9999 or  v0.dot(v2) >= 0.9999 :	#  屈曲点での接線ベクトルが、始端 / 終端での接線ベクトルと
					t.pop()											#  ほとんど同じであれば、その屈曲点では分割しない
					
			elif len(t) == 2 :
				t.sort()	
				v1 = labo.bezier_line_tangent(bz, 0)
				v2 = labo.bezier_line_tangent(bz, 1)
				v0 = labo.bezier_line_tangent(bz, t[1])
				if v0.dot(v2) >= 0.9999 :							#  屈曲点での接線ベクトルが、終端での接線ベクトルと
					del t[1]										#  ほとんど同じであれば、その屈曲点では分割しない
				v0 = labo.bezier_line_tangent(bz, t[0])
				if v0.dot(v1) >= 0.9999 :							#  屈曲点での接線ベクトルが、始端での接線ベクトルと
					del t[0]										#  ほとんど同じであれば、その屈曲点では分割しない
			return t
				
	

#  bezier_divide_by_total_curvature( bz as vec3 list, threshold as float) as float list
#
#		bz : 		bezier control point 座標を格納した vec3 list
#		threshold :	分割しきい値
#
#		bezier line bz を分割する bezier parameter を返す。
#		分割がない場合は、空 list が返される	

def bezier_divide_by_total_curvature(bz, threshold) :
	paramL = []								#  分割点の bezier parameter を格納する list
	
	v1 = labo.bezier_line_tangent(bz, 0)
	if v1.abs2() == 0 :
		return paramL						#  重点による長さのない区間
	
	#  bz に最適分割を行って cL[], pL[] にデータを格納する	
	pL = [0.]								#  各区間を分割する bezer parameter を格納する list
	cL = [0.]								#  各区間までの全曲率を格納する list
	v2 = labo.bezier_line_tangent(bz, 1)
	
	a = v1.dot(v2)
	if abs(a) > 0.99999994 :
		w = bz[3] - bz[0]
		if w.abs2() == 0 :
			return paramL					#  重点とみなす
		elif a < - 0.99999994 :
			w.norm()
			if abs(w.dot(v1)) > 0.99999994 :
				return paramL				#  重点と同じ扱いにする
	
	make_total_curvature_list(bz, 0., v1, 1., v2, pL, cL, 8)	#  最適分割を行って cL[], pL[] にデータを格納する 再帰関数
	theta = cL[len(cL) - 1]										#  bz の全曲率
	
	if theta > threshold :								#  bz の全曲率がしきい値を越えている
		n = int((theta - 0.00001)/threshold) + 1		#  分割数 ( 丸め誤差による分割数の不均一防止のため 0.00001 を減ずる )
		dc = theta/n				
		paramL = [0.]									#  分割点の bezier parameter を格納 (最初は 0. )
		j = 1
		for i in range(1, n) :
			c = i*dc
			while c > cL[j] :
				j += 1
			paramL.append(pL[j] - (pL[j] - pL[j - 1])*(cL[j] - c)/(cL[j ] - cL[j - 1]))	#  分割点の bezier parameter
		paramL.append(1.)																#  分割点の bezier parameter list の最後に 1 を追加
		
	return paramL
	

		
		
#  make_total_curvature_list(bz as vec3 list, t1 as float, v1 as vec3, t2 as float, v2 as vec3, pL as float list, cL as float list, n as int)
#
#		bz の parameter 区間 t1〜t2 を最適サイズに分割して、cL[], pL[] にデータを格納する再帰関数
#
#		bz : 		bezier control point 座標を格納した vec3 list
#		t1, v1 :	計算対象区間の 始端 bezier parameter と その点での接線ベクトル	
#		t2, v2 :	計算対象区間の 終端 bezier parameter と その点での接線ベクトル
#		pL :		各区間を分割する bezer parameter を格納
#		cL :		分割された各区間までの全曲率を格納
#		n :			分割数 ( bezier parameter t1〜t2 を n 分割する )

def make_total_curvature_list(bz, t1, v1, t2, v2, pL, cL, n) :
	from math import acos
	
	u = v1
	s1 = t1
	ds = (t2 - t1)/n								#  parameter  増分
	
	for i in range(1, n + 1) :						#  分割数 n で分割
		s2 = s1 + ds

		if i < n :
			v = labo.bezier_line_tangent(bz, s2)	# parameter t での接線ベクトル
		else :
			v = v2
			
		cosT = max(-1, min(1, u.dot(v)))
		
		if cosT >= 0.99966 :						#  0.99966 = cos(1.5度)
			pL.append(s2)
			cL.append(cL[len(cL) - 1] + acos(cosT))	
		else :
			theta =  acos(cosT)
			m = max(2, 1 + int(theta*180/pi/1.5))					#  分割数
			make_total_curvature_list(bz, s1, u, s2, v, pL, cL, m)	#  再分割
			
		u = v
		s1 = s2
		

		
#  get_smooth_handle(p1 as vec3, p2 as vec3, p3 as vec3) as bool
#
#	in handle, anchor point, out handle が一直線に並んでいるかを返す
#	p1 :		in handle 座標
#	p2 :		anchor point 座標
#	p3 :		out handle 座標	
	
def get_smooth_handle(p1, p2, p3) :
	v1 = p2 - p1				#  ここに入る前に v1, v2 に長さがあることは確認済み
	v2 = p3 - p2
	v1.norm()
	v2.norm()
	return v1.dot(v2) >= 0.99999994
	
	
	
#  get_poisition_list(ob as shape, nop as int, closed as bool, mx2 as matrix) as vec3 list
#
#		ob :		掃引体交差方向線形状
#		nop :	ob の ポイント数
#		closed :	ob の開閉情報
#		mx2 :	ob の xshade.scene().local_to_world_matrix
#
#		ob の 隣接する3つの anchor point 座標の lisit の list [[vec3, vec3, vec3], [vec3, vec3, vec3}, ‥]  を返す

def get_poisition_list(ob, nop, closed, mx2) :
	pL = []

	p2 = vec3(ob.control_point(1).position)*mx2
	p1 =  vec3(ob.control_point(0).position)*mx2
	if not closed :	
		p0 = p1	
	else :														
		p0 = vec3(ob.control_point(nop - 1).position)*mx2
	pL.append([p0, p1, p2])
	
	for i in range(2, nop) :
		p0 = p1
		p1 = p2
		p2 = vec3(ob.control_point(i).position)*mx2
		pL.append([p0, p1, p2])
		
	p0 = p1
	p1 = p2
	if not closed :
		p2 = p1
	else :
		p2 = pL[0][1]
	pL.append([p0, p1, p2])
	
	return pL
	
	
	
#  h_flatness_list(bZs as vec3 list, closed as bool) as bool list
#
#		bZs :		掃引中心線の control point 座標 list
#		closed :	中心線の開閉情報
#
#		掃引中心線の各 bezier line 区間の平坦性 list fL を返す
#		list の始端と終端には次なる項目を追加
#			中心線が閉じている場合、その隣接項目
#			中心線が開いている場合、False

def h_flatness_list(bZs, closed):
	flatL = []
	for bz in bZs :
		v1 = labo.bezier_line_tangent(bz, 0)		#  outhandle 側接線ベクトル
		v2 = labo.bezier_line_tangent(bz, 1)		#  inhandle 側接線ベクトル
		v = bz[3] - bz[0]
			
		if v1.abs2() == 0 :							#  重点
			flatL.append(False)
		elif (v1.dot(v2) >= 0.9999) :				#  bz が直線 ( 事前に屈曲点で分割されているので v1, v2 の平行判定のみで OK )
			flatL.append(True)
		elif v.abs2() == 0 :						#  bezier 区間の始端と終端が同一座標で、且つ handle が出ている
			flatL.append(True)
		else :
			bb = labo.vec3_bounding_box_size(bz)
			flatL.append(bb[1]/bb[3] <= 0.0001)		#  bz の bounding box が水平平坦
		
	#   関数 h_sweep_handle( ) に与える引数の選択において、平坦性 list fL の隣接二値を比較するため、 list の先頭と終端に値を追加
	if not closed :
		flatL.insert(0, True)
		flatL.append(True)
	else :
		flatL.insert(0, flatL[len(flatL) - 1])
		flatL.append(flatL[1])
		
	return flatL
	
	
	

#  get_vt(w as vec3 list, q as vec3, r as float) as vec3
#
#		交差線形状の handle の方向を与える vector を返す
#		w[0] :	交差線形状の当該 point の一つ手前の point 座標
#		w[1] :	交差線形状の当該 point の座標
#		w[2] :	交差線形状の当該 point の一つ先の point 座標
#		q :		 中心線 point 座標
#		r :		中心線 boinding box size / 16

def get_vt(w, q, r) :
	#  w[0] = w[1] = w[2] となる三重点のケースではここに入らない
	v1 = w[0] - w[1]
	v2 = w[2] - w[1]
	v1.norm()
	v2.norm()                    
	v0 = v2 - v1
	v0.norm()
	
	v = w[1] - q
	a = v.norm()
	if a < r*1e-5 :		# w[1]  と q が一致しているとみなす
		vt = v0
	else :
		d = v.dot(v0)
		vt = v0 - d*v
		vt.norm()
		
	return vt



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

def h_sweep(ob1, ob2, threshold, limitter1 = 10., limitter2 = 3.) :
	if (ob1 == None) or (ob2 == None) :
		return
	if (not isinstance(ob1, xshade.line)) or (not isinstance(ob2, xshade.line)) :
		return
		
	#  check
	if threshold < 0 :	
		return
	if limitter1 < 1 or limitter2 < 1 :
		return
	

	#  掃引中心線データ取得 nop, closed, bZ
	ob1.activate()
	nop = ob1.number_of_control_points		#  ポイント数
	if nop < 2 :
		return
	closed = ob1.closed						#  開閉情報
	bZs = labo.get_bezier(xshade, -1)		#  全区間の bezier data [matrix, matrix, … ]
	
	
	# mx1, mx2, mx22, ps, pe
	mx1 = matrix(ob2.world_to_local_matrix)
	mx2 = matrix(ob2.local_to_world_matrix)
	if not closed :
		mx22 = matrix(ob1.local_to_world_matrix)
		ps = vec3(ob1.control_point(0).in_handle)*mx22			#  掃引体の交差方向 handle 作成時に使用
		pe = vec3(ob1.control_point(nop - 1).out_handle)*mx22	#  掃引体の交差方向 handle 作成時に使用
		
	#  r							
	bb = labo.vec3_bounding_box_size(bZs)	
	r = bb[3]/16												#  sweep_handle( ), get_vt( ) の引数に使用	
	
		
	#  掃引中心線 の handle 情報取得
	has_in_handle = []			#  掃引中心線の inhandle の有無のリスト
	has_out_handle = []			#  掃引中心線の outhandle の有無のリスト
	linked = []					#  掃引中心線の hanndle link リスト
	smooth_handle = []			#  中心線の smooth handle lリスト			##  smooth_handle 追加  #####################
	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)
		
		##  smooth_handle 追加  #########################################################################
		if has_in_handle[i] and has_out_handle[i] :
			if i == 0 :
				if closed :
					smooth_handle.append(get_smooth_handle(bZs[nop - 1][2], bZs[0][0], bZs[0][1]))
				else :
					smooth_handle.append(False)	
			elif i == nop - 1 :
				if closed :
					smooth_handle.append(get_smooth_handle(bZs[nop - 2][2], bZs[nop - 1][0], bZs[nop - 1][1]))
				else :
					smooth_handle.append(False)		
			else :
				smooth_handle.append(get_smooth_handle(bZs[i - 1][2], bZs[i][0], bZs[i][1]))		
		else :
			smooth_handle.append(False)
			
		#	c_sweep( ) での cornerL[ ] の代わりに smooth_handle[ ] を用いる
		#	両者は共に handle 長さを求める際に参照されるが、その参照用途の違いから定義が異なる
		#
		#	c_sweep( ) での cornerL[ ]
		#		関数 c_sweep_handle( ) 内で コーナー handle であるか否かの識別に参照
		#		接線ベクトル ベースで定義
		#
		#	h_sweep( ) での smooth_handle[ ]
		#		関数 h_sweep_handle( ) に与える引数選択のために in / out handle の直線性の識別に参照
		#		実 handle ベースで定義
		############################################################################################

	#  bZs を分割	
#	if threshold > 0 :								##   屈曲点での分割は必ず行う  #####################

	#  屈曲点で分割
	bZs1 = []
	insert_list = []
	k = 0
	for bz in bZs :
		k += 1
		t = bezier_inflection_point(bz)
		n = len(t)
		if n == 0 :				#  屈曲点なし
			bZs1.append(bz)
		elif n == 1 :			#  屈曲点が一カ所あり
			bZs1.append(labo.bezier_line_subdivision(bz, 0, t[0]))
			bZs1.append(labo.bezier_line_subdivision(bz, t[0], 1))
			insert_list.append(k)
			nop += 1
		else :					#  屈曲点が二カ所あり
			bZs1.append(labo.bezier_line_subdivision(bz, 0, t[0]))
			bZs1.append(labo.bezier_line_subdivision(bz, t[0], t[1]))
			bZs1.append(labo.bezier_line_subdivision(bz, t[1], 1))
			insert_list.append(k)
			insert_list.append(k)
			nop += 2

	if len(insert_list) > 0 :
		insert_list.reverse()
		for k in insert_list :
			has_in_handle.insert(k, True)
			has_out_handle.insert(k, True)
			linked.insert(k, True)
			smooth_handle.insert(k, True)		##  smooth_handle 追加  #####################
	
	#  threshold に従って分割	
	if threshold > 0 :		
		bZs2 = []
		insert_list = []
		k = 0
		for bz in bZs1 :
			k += 1
			t = bezier_divide_by_total_curvature(bz, threshold)
			n = len(t)
			if n == 0 : 
				bZs2.append(bz)
			else :
				for i in range(n - 1) :
					bZs2.append(labo.bezier_line_subdivision(bz, t[i], t[i + 1]))
					insert_list.append(k)
					nop += 1
				insert_list.pop()
				nop += -1
					
		if len(insert_list) > 0 :
			insert_list.reverse()
			for k in insert_list :
				has_in_handle.insert(k, True)
				has_out_handle.insert(k, True)
				linked.insert(k, True)
				smooth_handle.insert(k, True)		##  smooth_handle 追加  #####################
		bZs = bZs2
													
											
	
	#  ob0 : 掃引体を格納する自由曲面
	ob2.activate()
	scene.create_surface_part(None)
	ob0 = scene.active_shape()				#   掃引体を格納する自由曲面
	ob0.surface_closed = closed
	ob2.place_child(1)

	
	#  掃引断面を配置
	p1 = bZs[0][0]
	Mt1 = matrix().translate(-p1[0], -p1[1], -p1[2])
	Q1 = quaternion()
	n = nop - 1
	v2_prev = None
#	cornerL = [False]						##  cornerL  削除  #####################						
		
	for i in range(n) :
		p2 = bZs[i][3]
		v2 = labo.bezier_line_tangent(bZs[i], 1)
		Mt2 = matrix().translate(p2[0], p2[1], p2[2])
		
		##  h_sweep_section_quaternion( ) に変更  ######################################################
		Q2 = h_sweep_section_quaternion(bZs[i])	
		Q2 = Q2*Q1
		Mr = Q2.matrix()
#		corner = False						##  cornerL  削除  #####################

		if i == n - 1 and not closed :
			M = mx2*Mt1*Mr*Mt2*mx1
			
		else :
			v3 = labo.bezier_line_tangent(bZs[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 :
					##  quaternion().slerp( ) から attitude_control_quaternion( ) に変更  #########################################
					Qc = labo.attitude_control_quaternion(v2_prev, vec3(0, 1, 0), v3, vec3(0, 1, 0), 0)
					Q2 = Qc*Q2
					Mc = Qc.matrix()
					M = mx2*Mt1*Mr*Mc*Mt2*mx1
				v2_prev = None
				
			else :
				##    abs(v2.dot(v3)) >= 0.99999994 を 正負二つの処理に分割  #################################################
				if v2.dot(v3) >= 0.99999994 :			#  掃引中心線の point が corner point  ではない
					M = mx2*Mt1*Mr*Mt2*mx1
				
				elif v2.dot(v3) <= -0.99999994 :		#  掃引中心線の point が 180 度 corner point
#					corner = True																		##  corner  削除  ###################
					M = mx2*Mt1*Mr*Mt2*mx1
					Qc = labo.attitude_control_quaternion(v2, vec3(0, 1, 0), v3, vec3(0, 1, 0), 0)	
					Q2 = Qc*Q2							#   次の point での掃引断面配置に必要
				#################################################################################################
				
				else :									#  掃引中心線の point が corner point 
					w = v2 + v3
					w.norm()
#					corner = True						##  corner  削除  ###################
					v1 = labo.bezier_line_tangent(bZs[i], 0)
					v4 = labo.bezier_line_tangent(bZs[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
					
					##  quaternion().slerp( ) から attitude_control_quaternion( ) に変更  #########################################
					Q6 = labo.attitude_control_quaternion(v2, vec3(0, 1, 0), v3, vec3(0, 1, 0), 0)	
					Q2 = Q6*Q2						#   次の point での掃引断面配置に必要
					
	
		ob2.copy_object(M)
		ob = ob2.bro
		ob.place_brother(i)
	
		Q1 = Q2
#		cornerL.append(corner)					##  cornerL  削除  #####################

	
	#  掃引体の交差方向 handle をセット
	n = ob2.number_of_control_points
	ob0.switch()
	ob = ob0.son
	
	flatL = h_flatness_list(bZs, closed)						#  中心線の各 bezier 区間の平坦性 list							##  追加  ##################
	
	for i in range(n) :
		ob = ob.bro												#  掃引体内部の交差方向線形状
		pL = get_poisition_list(ob, nop, closed, mx2)			# ob の 隣接する3つの anchor point 座標の lisit					##  追加  ##################
		vt = None												#  交差方向 handle の方向 ( inhandle と outhandle が平行な場合 )	##  追加  ##################
		
		for j in range(nop) :
			w = pL[j][1]										#  交差方向線形状の point 座標								##  pL[ ] を利用  ##################
			if not has_in_handle[j] :
				inH = w											#  inhandle 座標
			else :
				if (j == 0) and not closed :
					inH = w + ps - bZs[0][0]					#  inhandle 座標
				else :
					if j == 0 :
						k = nop - 1
					else :
						k = j - 1
					vp = sweep_plane_normal(bZs[k][3], bZs[k][2], bZs[k][1], bZs[k][0])
					u = bZs[k][2] - bZs[k][3] 
					u.norm()
					p = bZs[k][2]

					##  handle 長さの求め方を 2ケースに分ける  ########################################################################
					if flatL[j] or flatL[j + 1] or not smooth_handle[j] :	#  掃引中心線の当該 point  前後の bezier 区間のいずれかが平坦、あるいは not smooth handle
						inH = h_sweep_handle(w, u, r, p, vp)				#  inhandle 座標
					else :
						vt = get_vt(pL[j], bZs[k][3], r)
						inH = h_sweep_handle(w, -vt, r, p, vp)				#  inhandle 座標
					#######################################################################################################
	
			if not has_out_handle[j] :
				outH = w										#  outhandle 座標
			else :
				if (j == nop - 1) and not closed :
					outH = w + pe - bZs[nop - 2][3]				#  outhandle 座標
				else :
					vp = sweep_plane_normal(bZs[j][0], bZs[j][1], bZs[j][2], bZs[j][3])
					u = bZs[j][1] - bZs[j][0] 
					u.norm()
					p = bZs[j][1]
						
					##  handle 長さの求め方を 2ケースに分ける  ########################################################################
					if flatL[j] or flatL[j + 1] or not smooth_handle[j] :	#  掃引中心線の当該 point  前後の bezier 区間のいずれかが平坦、あるいは not smooth handle
						outH = h_sweep_handle(w, u, r, p, vp)				#  outhandle 座標
					else :		
						if vt == None :
							vt = get_vt(pL[j], bZs[j][0], r)	
						outH = h_sweep_handle(w, vt, r, p, vp)				#  outhandle 座標
						vt = None
					#######################################################################################################

			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()

#  dialog uuid は Online GUID Generator により生成		https://www.guidgenerator.com
dialog = xshade.create_dialog_with_uuid('9fb5cae0-4e8e-4c0e-b3af-fe7948b9938b')

dialog.begin_tab_group('分割')
level_ = dialog.append_selection('level/90度/60度/45度/30度/22.5度/15度')
dialog.end_tab_group()

dialog.begin_tab_group('limitter')
limitter1_ = dialog.append_float('直線コーナー', '( defalt 10 )')
limitter2_ = dialog.append_float('曲線コーナー', '( defalt 3 )')
dialog.end_tab_group()

dialog.set_value(4, 10)
dialog.set_value(5, 3)

if dialog.ask('水平掃引'):
	level = dialog.get_value(level_)
	from math import pi
	if level == 0 :	
		threshold = pi/2	
	elif level == 1 :		
		threshold = pi/3
	elif level == 2 :	
		threshold = pi/4
	elif level == 3 :	
		threshold = pi/6
	elif level == 4 :	
		threshold = pi/8
	elif level == 5 :	
		threshold = pi/12
		
		
	limitter1 = dialog.get_value(limitter1_)
	limitter2 = dialog.get_value(limitter2_)

	scene.exit_modify_mode()
	[ob1, ob2] = scene.active_shapes
	ob2.copy_object(None)
	ob3 = ob2.bro
	ob4 = h_sweep(ob1, ob3, threshold, limitter1, limitter2)
	if ob4 != None :
		ob4.activate()    
	else :
		scene.active_shapes = [ob1, ob2]

#6

28 - 6  Script 修正 / 改善( 16 / 06 / 05,   07 / 01 )


[ script 28 - 8 ] に対して、下記二カ所 追加 / 変更 を行いました
**16 / 06 / 05**
**def bezier_divide_by_total_curvature(bz, threshold) :**
**旧**
#  bz に最適分割を行って cL[], pL[] にデータを格納する	
pL = [0.]								#  各区間を分割する bezer parameter を格納する list
cL = [0.]								#  各区間までの全曲率を格納する list
v2 = labo.bezier_line_tangent(bz, 1)
	
w = bz[3] - bz[0]										
if (w.abs2() == 0) and(abs(v1.dot(v2)) > 0.99999994) :		
	return paramL						#  重点とみなす
	
make_total_curvature_list(bz, 0., v1, 1., v2, pL, cL, 8)	#  最適分割を行って cL[], pL[] にデータを格納する 再帰関数
theta = cL[len(cL) - 1]										#  bz の全曲率

**追加 / 変更**
#  bz に最適分割を行って cL[], pL[] にデータを格納する	
	pL = [0.]								#  各区間を分割する bezer parameter を格納する list
	cL = [0.]								#  各区間までの全曲率を格納する list
	v2 = labo.bezier_line_tangent(bz, 1)
	
	a = v1.dot(v2)
	if abs(a) > 0.99999994 :
		w = bz[3] - bz[0]
		if w.abs2() == 0 :
			return paramL					#  重点とみなす
		elif a < - 0.99999994 :
			w.norm()
			if abs(w.dot(v1)) > 0.99999994 :
				return paramL				#  重点と同じ扱いにする
	
	make_total_curvature_list(bz, 0., v1, 1., v2, pL, cL, 8)		#  最適分割を行って cL[], pL[] にデータを格納する 再帰関数
	theta = cL[len(cL) - 1]										#  bz の全曲率


**16 / 07 /01**
**def get_vt(w, q, r) :**
**旧**
def get_vt(w) :
	#  w[0] = w[1] = w[2] となる三重点のケースではここに入らない
	
	v1 = w[0] - w[1]
	v2 = w[2] - w[1]
	h1 = v1.norm()
	h2 = v2.norm()
	vt = h1**0.5*v2 - h2**0.5*v1
	vt = v2 - v1
	vt.norm()
		
	return vt

**変更**
def get_vt(w, q, r) :
	#  w[0] = w[1] = w[2] となる三重点のケースではここに入らない
	v1 = w[0] - w[1]
	v2 = w[2] - w[1]
	v1.norm()
	v2.norm()                    
	v0 = v2 - v1
	v0.norm()
	
	v = w[1] - q
	a = v.norm()
	if a < r*1e-5 :		# w[1]  と q が一致しているとみなす
		vt = v0
	else :
		d = v.dot(v0)
		vt = v0 - d*v
		vt.norm()
		
	return vt