Shade3D 公式

39 Round Corner( 1/2 ) [ Shade Labo ]


#1

関連記事:
  • 37 Newton 法 ( 1/2 )
  • 38 Newton 法 ( 2/2 )

## 39 - 1  Bezier 曲線と Newton 法
Newton 法を用いれば Bezier 曲線の交点や曲線 corner の丸め操作などが、**少ない trial 回数で高い精度の解が得られます**。

ただし、一般的な Bezier 曲線に対して適切な初期解を与えれば という条件が付きます。


Bezier parameter を変数とした Newtorn 法には大きな問題があり、これらの相性は合いません。

  • 適切な初期解を推定する平易なロジックが困難

  • 収束計算を阻害する Bezier 特有の構造的問題


初期解問題は Newton 法で解く方程式の内容によってその難しさが左右されますが、想定されるあらゆるケースを網羅できるようなロジックの組み立ては困難を極めます。

適切な初期解を求めるよりも、初期解が簡単に得られるように曲線の細分化などの前処理を工夫する方が楽かもしれません。


また構造欠陥とは、最も単純な構造を持つ handle のない直線が、収束を阻害するというものです。

冒頭で述べた 一般的な Bezier 曲線 とは 適度な長さを持つ handle のある bezier 曲線 を意味します。


これに対し Bezier 曲線 corner の丸め操作では初期解に係わる問題は比較的簡単な処理で解決され、Newton 法の適用が容易な Bezer 関連の処理の一つになっています。


#2

39 - 2  基本ロジック


関連記事:
  • 34 Cap ( 1/3 )

曲線コーナーの point 座標と handle 座標の求め方は次のようになります

     

     [ fig 39 - 1 ]



**< 円弧定義面 Vn >**
点 **P0**, **P1** を端点とする円弧は **P0**, **P1** での接線ベクトル **U0**, **U1** を用いて求められる面 **Vn** 内で定義されます。

     

     [ fig 39 - 2 ]



**< Newton 法で用いる 変数 と 方程式 >**
円弧半径 **r0**, **r1** は定義面内で接線ベクトル **U0**, **U1** に直交する二直線 **V0**, **V1**の交点 **C** を中心として定義されますが、接線ベクトルが平行に近づくと丸め誤差の影響で交点 **C** の位置が不安定になります。

そこで接線ベクトルが平行に近づいた場合は r0 = r1 とみなして別の方法で求めます。

     

     [ fig 39 - 3 ]



**< 定義面内で円弧 handle 長さ h >**
円弧 handle 長さ h を求める式は **34 Cap ( 1/3 )** の [ fig 34 - 7 ] を参照

     

     [ fig 3 - 4 ]



**< corner 円弧 handle 座標 outH, inH >**
corner 前後の線形状が平面内に存在しなければ、corner 円弧 handle は円弧定義面内には存在しません。

     

     [ fig 39 - 5 ]


#3

39 - 3  基本 Script


[ script 39 - 1 ] は Round Corner 処理の基本的骨格を与える **test script - 1** で、次の機能を持ちます。
  • 選択された 3 点からなる開いた線形状 を対象とし、

  • 中央のポイント が corner 形状になっていれば、指定した半径で丸める

  • 収束計算の 途中経過を report

  • undo が効かないので、original を残す オプションを追加



**test script - 1** では初期値 x[ ] を次のように **[ 0.9999, 0.0001] なる固定値** として handling しています。
	#  初期解, 初期差分
	dx = [-0.0001, 0.0001]				#  導関数を求めるための初期差分
	x = [1 - dx[0], dx[1]]				#  初期解		( bezier parameter )

収束計算の中で parameter 値 x[ ] は連立方程式の解として求められた dx[ ] によって都度更新されます。
	#  x[ ] の更新
	for i in range(2) :
		x[i] += dx[i]

このままでは多くののケースで x[ ] は暴走してしまうので、実際には値更新の際に dx[ ] の絶対値に **limitter 値 0.2** を与えています。

この 0.2 なる値は経験的に定めています。

	#  x[ ] の更新
	for i in range(2) :
		dx[i] = copysign(min(limitter, abs(dx[i])), dx[i])		#  limitter = 0.2
		x[i] += dx[i]


ここで limitter の必要性を確認するために次のテストを行います。

[ script 39 - 2 ] で出力される形状 sample - 1 に対して、test script - 1半径 800 を指定して corner を丸めると、5回の trial で解が求まります。

          [ fig 39 - 6 ]


次に limitter を働かせる行を comment out して実行すると、解は求まりません。
		#  x[ ] の更新
		for i in range(2) :
#			dx[i] = copysign(min(limitter, abs(dx[i])), dx[i])		#  limitter = 0.2
			x[i] += dx[i]

この場合、report 結果が示すように初回の trial で parameter 値 x[ ] が 4 桁にも暴走し、関数値も14桁という途方もない数字になっているのが解ります。

          [ fig 39 - 7 ]


ただし、この limitter はある特別な条件下では逆に **収束を阻害する** 要因になってしまいます。

そのことについては 40 Round Corner ( 2/2 ) で詳しく述べます。



**test script - 1**          [ script 39 - 1 ]
import labo
from matrix import *
from quaternion import *



#  open_dialog() as float
#
#	 入力 dialog を開く

def open_dialog() :	
	#  dialog uuid は Online GUID Generator により生成		https://www.guidgenerator.com
	dialog = xshade.create_dialog_with_uuid('4df32b3d-dd35-492f-8c50-ee5f01fa0be4')

	unit_id = scene.unit
	if unit_id == 0 :
		unit = 'mm'
	elif unit_id == 1 :
		unit = 'cm'
	elif unit_id == 2 :
		unit = 'm'
	elif unit_id == 3 :
		unit = 'km'
	elif unit_id == 4 :
		unit = 'inch'
	elif unit_id == 5 :
		unit = 'foot'
	elif unit_id == 6 :
		unit = 'yard'
	elif unit_id == 7 :
		unit = 'mile'
		
	dialog.begin_group('')
	idx_1 = dialog.append_float('半径   ', '   ' + unit)	
	idx_2 = dialog.append_bool('original を残す')
	dialog.end_group()

	if not dialog.ask('Round Corner test 1'):
		return None	
	else :
		size =  dialog.get_value(idx_1)*scene.user_to_native_unit
		if size <= 0 :
			print '半径は正の値にして下さい'
			return None
			
		copy_shape = dialog.get_value(idx_2)		#	original を残す指定 flag
		
		return[size, copy_shape]
				
					
		
#  get_radius(p as vec3 list, v as vec3 list) as float list
#
#	p[0] / p[1] を通り、 v[0] / v[1] に進む二直線の最接近点を c としたとき、
#	[   | p[0] - c |  ,  | p[1] - c |   ] を返す

def get_radius(p, v) :
	w2 = p[1] - p[0]
	s1 = v[0].dot(w2)
	s2 = -v[1].dot(w2)
	s = v[0].dot(v[1])
	
	if s**2 <= 0.9999 :				#  v[0], v[1] が平行に近くなると、最接近点 c の位置が不安定になる
		t1 = (s2*s + s1)/(1 - s**2)
		t2 = (s1*s + s2)/(1 - s**2)
	else :							#  v[0], v[1] が平行とみなす
		r = s1/(1 - v[0].dot(v[1]))
		t1 = r
		t2 = r

	return [t1, t2]
	
	

	
#  base_func(bz as matrix list, x as float list, b as bool) as float list / [ vec3 list, vec3 list, vec3 list]
#
#	corner を構成する2つの bzier line bz の parameter x で与えられるポイント座標を基準とし、
#
#	b = False の場合
#		corner 半径を返す
#		返す先は func( ) で、Jacobi 行列を求める際や、収束計算の中で呼ばれる 
#
#	b = True の場合
#		corner 曲線の端点, 接線ベクトル, 半径ベクトル, 半径 を返す
#		返す先は get_handle( ) で、収束計算終了後に handle 長さを求めるために呼ばれる
	
def base_func(bz, x, b) :
	u = []												#  接線ベクトル
	for i in range(2) :
		u.append(labo.bezier_line_tangent(bz[i], x[i]))
			
	p = []												#  corner 曲線の端点
	for i in range(2) :
		p.append(labo.bezier_line_position(bz[i], x[i]))
			
	w1 = u[0] - u[1]
	w2 = p[1] - p[0]
	vn = w1*w2	
	vn.norm()											#  corner 法線
														#  corner point が flat なケースは弾かれているので vn != 0 vector
			
	v = []
	for i in range(2) :
		vv = vn*u[i]
		vv.norm()
		v.append(vv)									#  corner 半径ベクトル
	
	r = get_radius(p, v)								#  corner 半径	
	if not b :	
		return r							
	else :
		return [p, u, v, (r[0] + r[1])/2]				#  corner 曲線の端点, 接線ベクトル, 半径ベクトル, 半径


	

#  func(idx as int, bz as matrix list, x as float list, size as float) as float, float / float
#
#	収束計算の対象となる関数
#	Jacobi  行列を求める際にも呼ばれる
	
def func(idx, bz, x, size) :
	r = base_func(bz, x, False)						#  corner 半径を求める
	
	if idx == 0 :	
		return r[0] -  size, r[1] - size			#  計算された corner 半径と target 半径 size との差が返される関数値
	elif idx == 1 :
		return r[0] - size
	elif idx == 2 :
		return r[1] - size
		
		
		

#  get_handle(bz as matrix list, x as float list) as vec3 list
#
#	 収束計算で求められた corner 曲線の端点を与える bezier parameter x から、corner 曲線の outhandle, inhandle 座標を返す
		
def get_handle(bz, x) :	
	#	p[] :	corner 曲線の端点座標
	#	u[] :	接線ベクトル
	#	v[] :	corner 半径ベクトル
	#	r :		半径
	#	vn :		corner 法線 
	
	[p, u, v, r] = base_func(bz, x, True)		#  p :  corner 曲線の端点	u :  接線ベクトル	v :  半径ベクトル	r : 半径
	w1 = u[0] - u[1]
	w1.norm()
	cosB = w1.dot(- v[0])
	sinB = (1 - cosB**2)**0.5
	h = 4./3*r*(1 - cosB)/sinB					#  handle 長さ ( corner 面法線基準 )
	
	w2 = p[1] - p[0]
	vn = w1*w2									#  corner  法線
	vn.norm()	
	cosT = vn.dot(u[0])
	sinT = (1 - cosT**2)**0.5	

	outH = p[0] + h/sinT*u[0]					#  corner 部分の outhandle 座標
	inH = p[1] - h/sinT*u[1]					#  corner 部分の inhandle 座標
	
	return [outH, inH]

	


#  set_dx_x() as double, list, double list
#
#	初期差分 dx[], 初期解 x[ ] を返す

def set_dx_x() :
	dx = [-0.0001, 0.0001]				#  導関数を求めるための初期差分
	x = [1 + dx[0],dx[1]]				#  初期解		( bezier parameter )
	
	return dx, x



	
#  round_corner(bz as matrix list, size as float) as datalist
#
#	半径 size なる round corner を与える bezier parameter list と handle 座標 list を返す
#
#	解を求めることができない、あるいは、解が存在しなければ、None を返す

def round_corner(bz, size) :
	from math import copysign
	
	#  変数設定
	limitter = 0.2						#  parameter の暴走を防ぐための dx[0], dx[1] の絶対値の最大制限値
	epsilon = 5e-4						#  端点判定しきい値
	threshold = size*1e-5				#  収束判定しきい値
	maxN = 30							#  打ち切り計算回数

		
	#  dx[ ] : 初期差分,   x[ ] : 初期解
	dx, x = set_dx_x()
	
		
	#  反復計算
	f1, f2 = func(0, bz, x, size)			#  初期解における関数値 f1, f2 を求める
	result = False
	
	for kk in range(maxN) :	
	
		for i in range(2) :
			if abs(dx[i]) > 0.0001 :		
				dx[i] = copysign(0.0001, dx[i])			#  この方が収束が早い
					
		#  F[]
		F = [-f1, -f2]
											
		#  J[[,], [,]]		jacobi 行列
		J_00 = (f1 - func(1, bz, [x[0] - dx[0], x[1]], size))/dx[0]
		J_01 = (f1 - func(1, bz, [x[0], x[1] - dx[1]], size))/dx[1]
		J_10 = (f2 - func(2, bz, [x[0] - dx[0], x[1]], size))/dx[0]
		J_11 = (f2 - func(2, bz, [x[0], x[1] - dx[1]], size))/dx[1]
		J = [[J_00, J_01], [J_10, J_11]]
			
				
		#  [J][dx] = [F] なる二元連立方程式を解き、差分 dx[ ] を求める
		det_J = J[0][0]*J[1][1] - J[0][1]*J[1][0]
		
		if det_J == 0 :
			print '収束計算打ち切り ( 連立方程式が解けなくなった )'
			if (abs(f1) < threshold*10) and (abs(f2) < threshold*10) :
				result = True										#  収束したと見なす ( 精度はやや落ちる )
				kk += -1											#  report のために
			break													#  計算打ち切り

		dx = [(J[1][1]*F[0] -  J[0][1]*F[1])/det_J, (- J[1][0]*F[0] +  J[0][0]*F[1])/det_J]

	
		#  x[ ] の更新
		for i in range(2) :
			dx[i] = copysign(min(limitter, abs(dx[i])), dx[i])		#  limitter = 0.2
			x[i] += dx[i]
		
		if kk >= 10 :										
			if (abs(dx[0]) == limitter) or (abs(dx[1]) == limitter) :	#  計算回数が10回を過ぎても差分が大きい = 振動している
				break												#  収束しないと見なす
		
	
		#  関数値の更新
		f1, f2 = func(0, bz, x, size)	
		print 'f1 = ', f1, '     x[0] = ', x[0], '     dx[0] = ', dx[0]
		print 'f2 = ', f2, '     x[1] = ', x[1], '     dx[1] = ', dx[1]

				
		#  収束判定
		if (abs(f1) < threshold) and (abs(f2) < threshold) :
			result = True
			break													#  計算打ち切り

		if (dx[0] == 0) or (dx[1] == 0) :
			print '収束計算打ち切り ( 連立方程式が解けなくなった )'	
			if (abs(f1) < threshold*10) and (abs(f2) < threshold*10) :
				result = True										#  収束したと見なす ( 精度はやや落ちる )
			break													#  計算打ち切り

			
				
	if not result :
		print '収束せず'
		return None
		
	else :
		if abs(x[0]) <= epsilon :
			x[0] = 0
		if abs(x[1] - 1) <= epsilon :
			x[1] = 1
				
		if (x[0] < 0) or (x[0] > 1) or (x[1] < 0) or (x[1] > 1) :
			print '解が存在せず'
			return None
			
		else :		
			hL = get_handle(bz, x)						#  handle 座標 list を求める

			print '繰り返し計算回数 : ' + str(kk + 1), '     x = ', x
					
			return [x, hL]								#  x[] : 	挿入ポイントの位置を示す bezier parameter list
														#  hL[] :		handle 座標 list
		
			

scene = xshade.scene()

scene = xshade.scene()

dialog_data = open_dialog()
if dialog_data != None :
	[size, copy_shape] = dialog_data
	
	ob = scene.active_shape()
	if isinstance(ob, xshade.line) :
		if ob.dad.part_type != 1 :								#  自由曲面内の線形状は不可
			if not ob.closed :									#  開いた線形状
				if ob.number_of_control_points >= 3 :			#  point 数が3点以上
					#  bz1 :	corner 前後の bezier line
					bz1 = labo.get_bezier(xshade, 0)
					bz2 = labo.get_bezier(xshade, 1)
						
					#  corner 形状 check
					v1 = labo.bezier_line_tangent(bz1, 1)
					v2 = labo.bezier_line_tangent(bz2, 0)
					b = (v1.abs2() != 0 and v2.abs2() != 0) and (v1.dot(v2) <= 0.9999)
					
					if b :
						#  bz1, bz2 から丸め処理に関するデータを取得
						data = round_corner([bz1, bz2], size)	
						if data != None :
							#	out_param, in_param :	挿入ポイントの位置を示す bezier parameter list
							#	outH, inH :				handle 座標 list
							[[out_param, in_param], [outH, inH]] = data	
							
							#  丸め処理
							if copy_shape :						#  dialog で original を残す が選択されている
								ob.copy_object(None)
								ob = ob.bro
								ob.name = str(size/scene.user_to_native_unit)
								ob.activate()
							mx1 = matrix(ob.world_to_local_matrix)	
							ob.insert_control_point(1 + in_param)	
							ob.insert_control_point(out_param)
							ob.remove_control_point(2)
							ob.control_point(1).out_handle = outH*mx1
							ob.control_point(1).linked = True
							ob.control_point(2).in_handle = inH*mx1
							ob.control_point(2).linked = True
								
							if out_param == 0 :
								ob.control_point(0).out_handle = outH*mx1
								ob.control_point(0).linked = True
								ob.remove_control_point(0)
					
							scene.enter_modify_mode()

**sample - 1**          [ script 39 - 2 ]
from vec3 import *

scene = xshade.scene()

size = 1000*scene.user_to_native_unit

p0 = vec3(-size, -size, 0)
p1 = vec3(0, size, 0)
p2 = vec3(size, -size, 0)

dvx = vec3(size, 0, 0)
dvy = vec3(0, size, 0)

scene.create_part('sample - 1')
scene.begin_creating()
scene.begin_line(None, False)
scene.append_point(p0, None, p0 + dvy, None, None)
scene.append_point(p1, None, None, None, None)
scene.append_point(p2, p2 + dvy, None, None, None)
scene.end_line()
scene.end_creating()
ob1 = scene.active_shape()
ob1.activate()

scene.enter_modify_mode()
scene.select_all()

#4

39 - 4  初期解の改善( その1 )


**test script - 1** のままでも多くのケースをカバーできますが、**sample - 2** [ script 39 - 3 ] を丸める場合、半径 200 では解が求まりますが、**半径 100 では求まりません**。
          [ fig 39 - 8 ]
ここで半径 200, 100 それぞれに対する script 実行時の report の最終部分を比較します。

bezier parameter は 0 ~ 1 の範囲をとりますが、半径 100 の場合、解として得られた値はこの 領域外の値 を示しています。

これは初期解の与え方が [ 0.9999, 0.0001] 固定というように大雑把すぎて、半径 100 では解の探査が期待する方向とは反対方向へ向かってしまったのが原因です。

corner point 両側の handle 長さに極端な差があるケースで、このような現象が生じることがあります


          [ fig 39 - 9 ]


**sample - 2**          [ script 39 - 3 ]
from vec3 import *

scene = xshade.scene()

size = 1000*scene.user_to_native_unit

p0 = vec3(- size, 0, 0)
p1 = vec3(0, size, 0)
p2 = vec3(size, - size, 0)

dvx = vec3(size, 0, 0)
dvy = vec3(0, size, 0)

scene.create_part('sample - 2')
scene.begin_creating()
scene.begin_line(None, False)
scene.append_point(p0, None, p0 + 2.1*dvx, None, None)
scene.append_point(p1, None, p1 - dvx - 3*dvy, None, None)
scene.append_point(p2, None, None, None, None)
scene.end_line()
scene.end_creating()
ob1 = scene.active_shape()
ob1.activate()


scene.enter_modify_mode()
scene.select_all()


以上のことから初期解設定の改善が必要です。

そこで最適化とまでは行きませんが、次のようなもう少しマシな初期解が得られる次のような設定方法を採用します。


< 初期差分 dx[ ] , 初期解 x[ ] の求め方 >
     

     [ fig 39 - 10 ]



**test script - 2** [ script 39 - 4 ] はこの初期解設定を組み込んだもので、これを用いれば **sample 2** を半径 100 で丸められます

          [ fig 39 - 11 ]


**test script - 2**          [ script 39 - 4 ]

test script - 1 からの追加変更箇所は行末に ########## を追加

import labo
from matrix import *
from quaternion import *



#  open_dialog() as float
#
#	 入力 dialog を開く

def open_dialog() :	
	#  dialog uuid は Online GUID Generator により生成		https://www.guidgenerator.com
	dialog = xshade.create_dialog_with_uuid('4df32b3d-dd35-492f-8c50-ee5f01fa0be4')

	unit_id = scene.unit
	if unit_id == 0 :
		unit = 'mm'
	elif unit_id == 1 :
		unit = 'cm'
	elif unit_id == 2 :
		unit = 'm'
	elif unit_id == 3 :
		unit = 'km'
	elif unit_id == 4 :
		unit = 'inch'
	elif unit_id == 5 :
		unit = 'foot'
	elif unit_id == 6 :
		unit = 'yard'
	elif unit_id == 7 :
		unit = 'mile'
		
	dialog.begin_group('')
	idx_1 = dialog.append_float('半径   ', '   ' + unit)	
	idx_2 = dialog.append_bool('original を残す')
	dialog.end_group()

	if not dialog.ask('Round Corner test 2'):
		return None	
	else :
		size =  dialog.get_value(idx_1)*scene.user_to_native_unit
		if size <= 0 :
			print '半径は正の値にして下さい'
			return None
			
		copy_shape = dialog.get_value(idx_2)		#	original を残す指定 flag
		
		return[size, copy_shape]
				
					
		
#  get_radius(p as vec3 list, v as vec3 list) as float list
#
#	p[0] / p[1] を通り、 v[0] / v[1] に進む二直線の最接近点を c としたとき、
#	[   | p[0] - c |  ,  | p[1] - c |   ] を返す

def get_radius(p, v) :
	w2 = p[1] - p[0]
	s1 = v[0].dot(w2)
	s2 = -v[1].dot(w2)
	s = v[0].dot(v[1])
	
	if s**2 <= 0.9999 :				#  v[0], v[1] が平行に近くなると、最接近点 c の位置が不安定になる
		t1 = (s2*s + s1)/(1 - s**2)
		t2 = (s1*s + s2)/(1 - s**2)
	else :							#  v[0], v[1] が平行とみなす
		r = s1/(1 - v[0].dot(v[1]))
		t1 = r
		t2 = r

	return [t1, t2]
	
	

	
#  base_func(bz as matrix list, x as float list, b as bool) as float list / [ vec3 list, vec3 list, vec3 list]
#
#	corner を構成する2つの bzier line bz の parameter x で与えられるポイント座標を基準とし、
#
#	b = False の場合
#		corner 半径を返す
#		返す先は func( ) で、Jacobi 行列を求める際や、収束計算の中で呼ばれる 
#
#	b = True の場合
#		corner 曲線の端点, 接線ベクトル, 半径ベクトル, 半径 を返す
#		返す先は get_handle( ) で、収束計算終了後に handle 長さを求めるために呼ばれる
	
def base_func(bz, x, b) :
	u = []												#  接線ベクトル
	for i in range(2) :
		u.append(labo.bezier_line_tangent(bz[i], x[i]))
			
	p = []												#  corner 曲線の端点
	for i in range(2) :
		p.append(labo.bezier_line_position(bz[i], x[i]))
			
	w1 = u[0] - u[1]
	w2 = p[1] - p[0]
	vn = w1*w2	
	vn.norm()											#  corner 法線
														#  corner point が flat なケースは弾かれているので vn != 0 vector
			
	v = []
	for i in range(2) :
		vv = vn*u[i]
		vv.norm()
		v.append(vv)									#  corner 半径ベクトル
	
	r = get_radius(p, v)								#  corner 半径	
	if not b :	
		return r							
	else :
		return [p, u, v, (r[0] + r[1])/2]				#  corner 曲線の端点, 接線ベクトル, 半径ベクトル, 半径


	

#  func(idx as int, bz as matrix list, x as float list, size as float) as float, float / float
#
#	収束計算の対象となる関数
#	Jacobi  行列を求める際にも呼ばれる
	
def func(idx, bz, x, size) :
	r = base_func(bz, x, False)						#  corner 半径を求める
	
	if idx == 0 :	
		return r[0] -  size, r[1] - size			#  計算された corner 半径と target 半径 size との差が返される関数値
	elif idx == 1 :
		return r[0] - size
	elif idx == 2 :
		return r[1] - size
		
		
		

#  get_handle(bz as matrix list, x as float list) as vec3 list
#
#	 収束計算で求められた corner 曲線の端点を与える bezier parameter x から、corner 曲線の outhandle, inhandle 座標を返す
		
def get_handle(bz, x) :	
	#	p[] :	corner 曲線の端点座標
	#	u[] :	接線ベクトル
	#	v[] :	corner 半径ベクトル
	#	r :		半径
	#	vn :		corner 法線 
	
	[p, u, v, r] = base_func(bz, x, True)		#  p :  corner 曲線の端点	u :  接線ベクトル	v :  半径ベクトル	r : 半径
	w1 = u[0] - u[1]
	w1.norm()
	cosB = w1.dot(- v[0])
	sinB = (1 - cosB**2)**0.5
	h = 4./3*r*(1 - cosB)/sinB					#  handle 長さ ( corner 面法線基準 )
	
	w2 = p[1] - p[0]
	vn = w1*w2									#  corner  法線
	vn.norm()	
	cosT = vn.dot(u[0])
	sinT = (1 - cosT**2)**0.5	

	outH = p[0] + h/sinT*u[0]					#  corner 部分の outhandle 座標
	inH = p[1] - h/sinT*u[1]					#  corner 部分の inhandle 座標
	
	return [outH, inH]

	

	
######  変更  #############################################################

#  set_dx_x(bz as matrix list) as double, list, double list
#
#	初期差分 dx[], 初期解 x[ ] を返す

def set_dx_x(bz) :
	#  dx[ ] : 初期差分
	u1 = bz[0][3] - bz[0][2]
	u2 = bz[1][1] - bz[1][0]
	r1, r2 = u1.abs(), u2.abs()			#  handle 長さ
	
	if r1 == 0 and r2 == 0 :
		dx = [-0.1, 0.1]
	elif r1 == 0 :
		dx = [-0.1, 0.0001]
	elif r2 == 0 :
		dx = [-0.0001, 0.1]
	elif r1 >= r2 :
		dx = [-0.0001, min(0.1, 0.0001*(r1/r2)**0.5)]	
	else :
		dx = [max(-0.1, -0.0001*(r2/r1)**0.5), 0.0001]
		
		
	#  x[ ] : 初期解
	x = [1 + dx[0], dx[1]]
	
	return dx, x
########################################################################


	
#  round_corner(bz as matrix list, size as float) as datalist
#
#	半径 size なる round corner を与える bezier parameter list と handle 座標 list を返す
#
#	解を求めることができない、あるいは、解が存在しなければ、None を返す

def round_corner(bz, size) :
	from math import copysign
	
	#  変数設定
	limitter = 0.2						#  parameter の暴走を防ぐための dx[0], dx[1] の絶対値の最大制限値
	epsilon = 5e-4						#  端点判定しきい値
	threshold = size*1e-5				#  収束判定しきい値
	maxN = 30							#  打ち切り計算回数

		
	#  dx[ ] : 初期差分,   x[ ] : 初期解
	dx, x = set_dx_x(bz)									##########  変更  ##########
	
		
	#  反復計算
	f1, f2 = func(0, bz, x, size)			#  初期解における関数値 f1, f2 を求める
	result = False
	
	for kk in range(maxN) :	
	
		for i in range(2) :
			if abs(dx[i]) > 0.0001 :		
				dx[i] = copysign(0.0001, dx[i])			#  この方が収束が早い
					
		#  F[]
		F = [-f1, -f2]
											
		#  J[[,], [,]]		jacobi 行列
		J_00 = (f1 - func(1, bz, [x[0] - dx[0], x[1]], size))/dx[0]
		J_01 = (f1 - func(1, bz, [x[0], x[1] - dx[1]], size))/dx[1]
		J_10 = (f2 - func(2, bz, [x[0] - dx[0], x[1]], size))/dx[0]
		J_11 = (f2 - func(2, bz, [x[0], x[1] - dx[1]], size))/dx[1]
		J = [[J_00, J_01], [J_10, J_11]]
			
				
		#  [J][dx] = [F] なる二元連立方程式を解き、差分 dx[ ] を求める
		det_J = J[0][0]*J[1][1] - J[0][1]*J[1][0]
		
		if det_J == 0 :
			print '収束計算打ち切り ( 連立方程式が解けなくなった )'
			if (abs(f1) < threshold*10) and (abs(f2) < threshold*10) :
				result = True										#  収束したと見なす ( 精度はやや落ちる )
				kk += -1											#  report のために
			break													#  計算打ち切り

		dx = [(J[1][1]*F[0] -  J[0][1]*F[1])/det_J, (- J[1][0]*F[0] +  J[0][0]*F[1])/det_J]

	
		#  x[ ] の更新
		for i in range(2) :
			dx[i] = copysign(min(limitter, abs(dx[i])), dx[i])		#  limitter = 0.2
			x[i] += dx[i]
		
		if kk >= 10 :										
			if (abs(dx[0]) == limitter) or (abs(dx[1]) == limitter) :	#  計算回数が10回を過ぎても差分が大きい = 振動している
				break												#  収束しないと見なす
		
	
		#  関数値の更新
		f1, f2 = func(0, bz, x, size)	
		print 'f1 = ', f1, '     x[0] = ', x[0], '     dx[0] = ', dx[0]
		print 'f2 = ', f2, '     x[1] = ', x[1], '     dx[1] = ', dx[1]

				
		#  収束判定
		if (abs(f1) < threshold) and (abs(f2) < threshold) :
			result = True
			break													#  計算打ち切り

		if (dx[0] == 0) or (dx[1] == 0) :
			print '収束計算打ち切り ( 連立方程式が解けなくなった )'	
			if (abs(f1) < threshold*10) and (abs(f2) < threshold*10) :
				result = True										#  収束したと見なす ( 精度はやや落ちる )
			break													#  計算打ち切り

			
				
	if not result :
		print '収束せず'
		return None
		
	else :
		if abs(x[0]) <= epsilon :
			x[0] = 0
		if abs(x[1] - 1) <= epsilon :
			x[1] = 1
				
		if (x[0] < 0) or (x[0] > 1) or (x[1] < 0) or (x[1] > 1) :
			print '解が存在せず'
			return None
			
		else :		
			hL = get_handle(bz, x)						#  handle 座標 list を求める

			print '繰り返し計算回数 : ' + str(kk + 1), '     x = ', x
					
			return [x, hL]								#  x[] : 	挿入ポイントの位置を示す bezier parameter list
														#  hL[] :		handle 座標 list
		
			

scene = xshade.scene()

scene = xshade.scene()

dialog_data = open_dialog()
if dialog_data != None :
	[size, copy_shape] = dialog_data
	
	ob = scene.active_shape()
	if isinstance(ob, xshade.line) :
		if ob.dad.part_type != 1 :								#  自由曲面内の線形状は不可
			if not ob.closed :									#  開いた線形状
				if ob.number_of_control_points >= 3 :			#  point 数が3点以上
					#  bz1 :	corner 前後の bezier line
					bz1 = labo.get_bezier(xshade, 0)
					bz2 = labo.get_bezier(xshade, 1)
						
					#  corner 形状 check
					v1 = labo.bezier_line_tangent(bz1, 1)
					v2 = labo.bezier_line_tangent(bz2, 0)
					b = (v1.abs2() != 0 and v2.abs2() != 0) and (v1.dot(v2) <= 0.9999)
					
					if b :
						#  bz1, bz2 から丸め処理に関するデータを取得
						data = round_corner([bz1, bz2], size)	
						if data != None :
							#	out_param, in_param :	挿入ポイントの位置を示す bezier parameter list
							#	outH, inH :				handle 座標 list
							[[out_param, in_param], [outH, inH]] = data	
							
							#  丸め処理
							if copy_shape :						#  dialog で original を残す が選択されている
								ob.copy_object(None)
								ob = ob.bro
								ob.name = str(size/scene.user_to_native_unit)
								ob.activate()
							mx1 = matrix(ob.world_to_local_matrix)	
							ob.insert_control_point(1 + in_param)	
							ob.insert_control_point(out_param)
							ob.remove_control_point(2)
							ob.control_point(1).out_handle = outH*mx1
							ob.control_point(1).linked = True
							ob.control_point(2).in_handle = inH*mx1
							ob.control_point(2).linked = True
								
							if out_param == 0 :
								ob.control_point(0).out_handle = outH*mx1
								ob.control_point(0).linked = True
								ob.remove_control_point(0)
					
							scene.enter_modify_mode()

#5

39 - 5  初期解の改善( その2 )


**sample - 3a, 3b, 3c** [ script 39 - 5 ] に対して、**tst script - 2** で **半径 500** を指定して corner を丸めると、**3a, 3b** では解が求まりますが、**3c** では収束計算の途中で連立方程式が解けなくなり解は求まりません。

3a, 3b, 3c は共に corner point での 両側の接線ベクトルの交差角が 180度 であり、inhandle, outhandle の有無が異なります。

     

     [ fig 39 - 12 ]



このように corner 角度が180度近くになると、**test script - 2** の初期解では解が得られなくなるケースが現れます。

そこで超鋭角 corner では初期差分の設定に次のロジックを追加し、それに伴って初期解を再設定することにします。


          [ fig 39 - 13 ]


#  corner point での交差角が180度近くの場合に 初期差分 dx[ ] , 初期解 x[ ] を再設定
v1 = labo.bezier_line_tangent(bz[0], x[0])												
v2 = labo.bezier_line_tangent(bz[1], x[1])

if v1.dot(v2) < -0.999 :							#  corner point での交差角が180度に近い
	u1 = bz[0][3] - labo.bezier_line_position(bz[0], x[0])
	u2 = bz[1][0] - labo.bezier_line_position(bz[1], x[1])
	
	dx[0] *= -1
	while v1.dot(v2) < -0.999 :	
		if u1.abs2() >= u2.abs2() :
			k = 1
		else :
			k = 0
		
		if dx[k]< 0.1 - 1e-8 :																	
			dx[k] *= 10																		
		elif dx[k] < 0.9 - 1e-8 :																	
			dx[k] += 0.1																		
		elif dx[k] < 0.99 - 1e-8 :																
			dx[k] += 0.01	
		else :	
			print 'これ以上の鋭角 corner は非実用的と判断し、計算不能として扱う'
			return None
			
		x = [1 - dx[0], dx[1]]
		if k == 0 :
			v1 = labo.bezier_line_tangent(bz[0], x[0])
			u1 = bz[0][3] - labo.bezier_line_position(bz[0], x[0])	
		else :										
			v2 = labo.bezier_line_tangent(bz[1], x[1])
			u2 = bz[1][0] - labo.bezier_line_position(bz[1], x[1])
		
	dx[0] *= -1
	x = [1 + dx[0], dx[1]]


**test script - 3** [ script 39 - 6 ] は **test script - 2** に上記を追加したものです。

これにより超鋭角 corner にも丸みを付けられるようになります。

このように Round Corner 処理では 39 - 3, 4, 5 項の三つの処理で初期解問題の主要な部分が解決されます

     

     [ fig 39 - 14 ]


**sample - 3a, 3b, 3c**          [ script 39 - 5 ]
from vec3 import *

scene = xshade.scene()

size = 1500*scene.user_to_native_unit


p0 = vec3(- size, 0, 0)
p1 = vec3(size, - size/2, 0)
p2 = vec3(0, - size, 0)

dvx = vec3(size, 0, 0)
dvy = vec3(0, size, 0)

scene.create_part('sample - 3a')
scene.begin_creating()
scene.begin_line(None, False)
scene.append_point(p0, None, p0 + dvx, None, None)
scene.append_point(p1, None, None, None, None)
scene.append_point(p2, p2 + dvy, None, None, None)
scene.end_line()
scene.end_creating()
ob1 = scene.active_shape()
ob1.dad.activate()


scene.create_part('sample - 3b')
scene.begin_creating()
scene.begin_line(None, False)
scene.append_point(p0, None, None, None, None)
scene.append_point(p1, p1-2*dvx + dvy, None, None, None)
scene.append_point(p2, p2 + dvy, None, None, None)
scene.end_line()
scene.end_creating()
ob2 = scene.active_shape()
ob2.dad.activate()


scene.create_part('sample - 3c')
scene.begin_creating()
scene.begin_line(None, False)
scene.append_point(p0, None, None, None, None)
scene.append_point(p1, p1-2*dvx + dvy, p1- dvx + dvy/2, None, None)
scene.append_point(p2, p2 + dvy, None, None, None)
scene.end_line()
scene.end_creating()
ob3 = scene.active_shape()


scene.active_shapes = [ob1, ob2, ob3]
scene.enter_modify_mode()
scene.select_all()


**test script - 3**          [ script 39 - 6 ]

test script - 2 からの追加変更箇所は行末に ########## を追加

import labo
from matrix import *
from quaternion import *



#  open_dialog() as float
#
#	 入力 dialog を開く

def open_dialog() :	
	#  dialog uuid は Online GUID Generator により生成		https://www.guidgenerator.com
	dialog = xshade.create_dialog_with_uuid('4df32b3d-dd35-492f-8c50-ee5f01fa0be4')

	unit_id = scene.unit
	if unit_id == 0 :
		unit = 'mm'
	elif unit_id == 1 :
		unit = 'cm'
	elif unit_id == 2 :
		unit = 'm'
	elif unit_id == 3 :
		unit = 'km'
	elif unit_id == 4 :
		unit = 'inch'
	elif unit_id == 5 :
		unit = 'foot'
	elif unit_id == 6 :
		unit = 'yard'
	elif unit_id == 7 :
		unit = 'mile'
		
	dialog.begin_group('')
	idx_1 = dialog.append_float('半径   ', '   ' + unit)	
	idx_2 = dialog.append_bool('original を残す')
	dialog.end_group()

	if not dialog.ask('Round Corner test 3'):
		return None	
	else :
		size =  dialog.get_value(idx_1)*scene.user_to_native_unit
		if size <= 0 :
			print '半径は正の値にして下さい'
			return None
			
		copy_shape = dialog.get_value(idx_2)		#	original を残す指定 flag
		
		return[size, copy_shape]
				
					
		
#  get_radius(p as vec3 list, v as vec3 list) as float list
#
#	p[0] / p[1] を通り、 v[0] / v[1] に進む二直線の最接近点を c としたとき、
#	[   | p[0] - c |  ,  | p[1] - c |   ] を返す

def get_radius(p, v) :
	w2 = p[1] - p[0]
	s1 = v[0].dot(w2)
	s2 = -v[1].dot(w2)
	s = v[0].dot(v[1])
	
	if s**2 <= 0.9999 :				#  v[0], v[1] が平行に近くなると、最接近点 c の位置が不安定になる
		t1 = (s2*s + s1)/(1 - s**2)
		t2 = (s1*s + s2)/(1 - s**2)
	else :							#  v[0], v[1] が平行とみなす
		r = s1/(1 - v[0].dot(v[1]))
		t1 = r
		t2 = r

	return [t1, t2]
	
	

	
#  base_func(bz as matrix list, x as float list, b as bool) as float list / [ vec3 list, vec3 list, vec3 list]
#
#	corner を構成する2つの bzier line bz の parameter x で与えられるポイント座標を基準とし、
#
#	b = False の場合
#		corner 半径を返す
#		返す先は func( ) で、Jacobi 行列を求める際や、収束計算の中で呼ばれる 
#
#	b = True の場合
#		corner 曲線の端点, 接線ベクトル, 半径ベクトル, 半径 を返す
#		返す先は get_handle( ) で、収束計算終了後に handle 長さを求めるために呼ばれる
	
def base_func(bz, x, b) :
	u = []												#  接線ベクトル
	for i in range(2) :
		u.append(labo.bezier_line_tangent(bz[i], x[i]))
			
	p = []												#  corner 曲線の端点
	for i in range(2) :
		p.append(labo.bezier_line_position(bz[i], x[i]))
			
	w1 = u[0] - u[1]
	w2 = p[1] - p[0]
	vn = w1*w2	
	vn.norm()											#  corner 法線
														#  corner point が flat なケースは弾かれているので vn != 0 vector
			
	v = []
	for i in range(2) :
		vv = vn*u[i]
		vv.norm()
		v.append(vv)									#  corner 半径ベクトル
	
	r = get_radius(p, v)								#  corner 半径	
	if not b :	
		return r							
	else :
		return [p, u, v, (r[0] + r[1])/2]				#  corner 曲線の端点, 接線ベクトル, 半径ベクトル, 半径


	

#  func(idx as int, bz as matrix list, x as float list, size as float) as float, float / float
#
#	収束計算の対象となる関数
#	Jacobi  行列を求める際にも呼ばれる
	
def func(idx, bz, x, size) :
	r = base_func(bz, x, False)						#  corner 半径を求める
	
	if idx == 0 :	
		return r[0] -  size, r[1] - size			#  計算された corner 半径と target 半径 size との差が返される関数値
	elif idx == 1 :
		return r[0] - size
	elif idx == 2 :
		return r[1] - size
		
		
		

#  get_handle(bz as matrix list, x as float list) as vec3 list
#
#	 収束計算で求められた corner 曲線の端点を与える bezier parameter x から、corner 曲線の outhandle, inhandle 座標を返す
		
def get_handle(bz, x) :	
	#	p[] :	corner 曲線の端点座標
	#	u[] :	接線ベクトル
	#	v[] :	corner 半径ベクトル
	#	r :		半径
	#	vn :		corner 法線 
	
	[p, u, v, r] = base_func(bz, x, True)		#  p :  corner 曲線の端点	u :  接線ベクトル	v :  半径ベクトル	r : 半径
	w1 = u[0] - u[1]
	w1.norm()
	cosB = w1.dot(- v[0])
	sinB = (1 - cosB**2)**0.5
	h = 4./3*r*(1 - cosB)/sinB					#  handle 長さ ( corner 面法線基準 )
	
	w2 = p[1] - p[0]
	vn = w1*w2									#  corner  法線
	vn.norm()	
	cosT = vn.dot(u[0])
	sinT = (1 - cosT**2)**0.5	

	outH = p[0] + h/sinT*u[0]					#  corner 部分の outhandle 座標
	inH = p[1] - h/sinT*u[1]					#  corner 部分の inhandle 座標
	
	return [outH, inH]

	

	
#  set_dx_x(bz as matrix list) as double, list, double list
#
#	初期差分 dx[], 初期解 x[ ] を返す

def set_dx_x(bz) :
	#  dx[ ] : 初期差分
	u1 = bz[0][3] - bz[0][2]
	u2 = bz[1][1] - bz[1][0]
	r1, r2 = u1.abs(), u2.abs()			#  handle 長さ
	
	if r1 == 0 and r2 == 0 :
		dx = [-0.1, 0.1]
	elif r1 == 0 :
		dx = [-0.1, 0.0001]
	elif r2 == 0 :
		dx = [-0.0001, 0.1]
	elif r1 >= r2 :
		dx = [-0.0001, min(0.1, 0.0001*(r1/r2)**0.5)]	
	else :
		dx = [max(-0.1, -0.0001*(r2/r1)**0.5), 0.0001]
		
		
	#  x[ ] : 初期解
	x = [1 + dx[0], dx[1]]
	
	
	######  追加  #############################################################
	#  corner point での交差角が180度近くの場合に 初期差分 dx[ ] , 初期解 x[ ] を再設定
	v1 = labo.bezier_line_tangent(bz[0], x[0])												
	v2 = labo.bezier_line_tangent(bz[1], x[1])

	if v1.dot(v2) < -0.999 :							#  corner point での交差角が180度に近い
		u1 = bz[0][3] - labo.bezier_line_position(bz[0], x[0])
		u2 = bz[1][0] - labo.bezier_line_position(bz[1], x[1])
		
		dx[0] *= -1
		while v1.dot(v2) < -0.999 :	
			if u1.abs2() >= u2.abs2() :
				k = 1
			else :
				k = 0
			
			if dx[k]< 0.1 - 1e-8 :																	
				dx[k] *= 10																		
			elif dx[k] < 0.9 - 1e-8 :																	
				dx[k] += 0.1																		
			elif dx[k] < 0.99 - 1e-8 :																
				dx[k] += 0.01	
			else :	
				print 'これ以上の鋭角 corner は非実用的と判断し、計算不能として扱う'
				return None
				
			x = [1 - dx[0], dx[1]]
			if k == 0 :
				v1 = labo.bezier_line_tangent(bz[0], x[0])
				u1 = bz[0][3] - labo.bezier_line_position(bz[0], x[0])	
			else :										
				v2 = labo.bezier_line_tangent(bz[1], x[1])
				u2 = bz[1][0] - labo.bezier_line_position(bz[1], x[1])
			
		dx[0] *= -1
		x = [1 + dx[0], dx[1]]
	########################################################################
	
	return dx, x



	
#  round_corner(bz as matrix list, size as float) as datalist
#
#	半径 size なる round corner を与える bezier parameter list と handle 座標 list を返す
#
#	解を求めることができない、あるいは、解が存在しなければ、None を返す

def round_corner(bz, size) :
	from math import copysign
	
	#  変数設定
	limitter = 0.2						#  parameter の暴走を防ぐための dx[0], dx[1] の絶対値の最大制限値
	epsilon = 5e-4						#  端点判定しきい値
	threshold = size*1e-5				#  収束判定しきい値
	maxN = 30							#  打ち切り計算回数

		
	#  dx[ ] : 初期差分,   x[ ] : 初期解
	dx, x = set_dx_x(bz)
	
		
	#  反復計算
	f1, f2 = func(0, bz, x, size)			#  初期解における関数値 f1, f2 を求める
	result = False
	
	for kk in range(maxN) :	
	
		for i in range(2) :
			if abs(dx[i]) > 0.0001 :		
				dx[i] = copysign(0.0001, dx[i])			#  この方が収束が早い
					
		#  F[]
		F = [-f1, -f2]
											
		#  J[[,], [,]]		jacobi 行列
		J_00 = (f1 - func(1, bz, [x[0] - dx[0], x[1]], size))/dx[0]
		J_01 = (f1 - func(1, bz, [x[0], x[1] - dx[1]], size))/dx[1]
		J_10 = (f2 - func(2, bz, [x[0] - dx[0], x[1]], size))/dx[0]
		J_11 = (f2 - func(2, bz, [x[0], x[1] - dx[1]], size))/dx[1]
		J = [[J_00, J_01], [J_10, J_11]]
			
				
		#  [J][dx] = [F] なる二元連立方程式を解き、差分 dx[ ] を求める
		det_J = J[0][0]*J[1][1] - J[0][1]*J[1][0]
		
		if det_J == 0 :
			print '収束計算打ち切り ( 連立方程式が解けなくなった )'
			if (abs(f1) < threshold*10) and (abs(f2) < threshold*10) :
				result = True										#  収束したと見なす ( 精度はやや落ちる )
				kk += -1											#  report のために
			break													#  計算打ち切り

		dx = [(J[1][1]*F[0] -  J[0][1]*F[1])/det_J, (- J[1][0]*F[0] +  J[0][0]*F[1])/det_J]

	
		#  x[ ] の更新
		for i in range(2) :
			dx[i] = copysign(min(limitter, abs(dx[i])), dx[i])		#  limitter = 0.2
			x[i] += dx[i]
		
		if kk >= 10 :										
			if (abs(dx[0]) == limitter) or (abs(dx[1]) == limitter) :	#  計算回数が10回を過ぎても差分が大きい = 振動している
				break												#  収束しないと見なす
		
	
		#  関数値の更新
		f1, f2 = func(0, bz, x, size)	
		print 'f1 = ', f1, '     x[0] = ', x[0], '     dx[0] = ', dx[0]
		print 'f2 = ', f2, '     x[1] = ', x[1], '     dx[1] = ', dx[1]

				
		#  収束判定
		if (abs(f1) < threshold) and (abs(f2) < threshold) :
			result = True
			break													#  計算打ち切り

		if (dx[0] == 0) or (dx[1] == 0) :
			print '収束計算打ち切り ( 連立方程式が解けなくなった )'	
			if (abs(f1) < threshold*10) and (abs(f2) < threshold*10) :
				result = True										#  収束したと見なす ( 精度はやや落ちる )
			break													#  計算打ち切り

			
				
	if not result :
		print '収束せず'
		return None
		
	else :
		if abs(x[0]) <= epsilon :
			x[0] = 0
		if abs(x[1] - 1) <= epsilon :
			x[1] = 1
				
		if (x[0] < 0) or (x[0] > 1) or (x[1] < 0) or (x[1] > 1) :
			print '解が存在せず'
			return None
			
		else :		
			hL = get_handle(bz, x)						#  handle 座標 list を求める

			print '繰り返し計算回数 : ' + str(kk + 1), '     x = ', x
					
			return [x, hL]								#  x[] : 	挿入ポイントの位置を示す bezier parameter list
														#  hL[] :		handle 座標 list
		
			

scene = xshade.scene()

scene = xshade.scene()

dialog_data = open_dialog()
if dialog_data != None :
	[size, copy_shape] = dialog_data
	
	ob = scene.active_shape()
	if isinstance(ob, xshade.line) :
		if ob.dad.part_type != 1 :								#  自由曲面内の線形状は不可
			if not ob.closed :									#  開いた線形状
				if ob.number_of_control_points >= 3 :			#  point 数が3点以上
					#  bz1 :	corner 前後の bezier line
					bz1 = labo.get_bezier(xshade, 0)
					bz2 = labo.get_bezier(xshade, 1)
						
					#  corner 形状 check
					v1 = labo.bezier_line_tangent(bz1, 1)
					v2 = labo.bezier_line_tangent(bz2, 0)
					b = (v1.abs2() != 0 and v2.abs2() != 0) and (v1.dot(v2) <= 0.9999)
					
					if b :
						#  bz1, bz2 から丸め処理に関するデータを取得
						data = round_corner([bz1, bz2], size)	
						if data != None :
							#	out_param, in_param :	挿入ポイントの位置を示す bezier parameter list
							#	outH, inH :				handle 座標 list
							[[out_param, in_param], [outH, inH]] = data	
							
							#  丸め処理
							if copy_shape :						#  dialog で original を残す が選択されている
								ob.copy_object(None)
								ob = ob.bro
								ob.name = str(size/scene.user_to_native_unit)
								ob.activate()
							mx1 = matrix(ob.world_to_local_matrix)	
							ob.insert_control_point(1 + in_param)	
							ob.insert_control_point(out_param)
							ob.remove_control_point(2)
							ob.control_point(1).out_handle = outH*mx1
							ob.control_point(1).linked = True
							ob.control_point(2).in_handle = inH*mx1
							ob.control_point(2).linked = True
								
							if out_param == 0 :
								ob.control_point(0).out_handle = outH*mx1
								ob.control_point(0).linked = True
								ob.remove_control_point(0)
					
							scene.enter_modify_mode()