Shade3D 公式

40 Round Corner( 2/2 ) [ Shade Labo ]


#1

関連記事:
  • 39 Round Corner ( 1/2 )

## 40 - 1  handle のない bezier line に対する精度
**sample - 4** [ script 40 - 1 ] に対し、**39 - 5 初期解の改善( その2 )** に記した test script - 3 を用いて **半径 2000** を指定して corner を丸めると次のようになります。

左側では端点から handle が出ますが、右側では端点の近傍から handle が出ています。


          [ fig 40 - 1 ]


このような現象は、片方が handle がある 直線 / 曲線 で、もう一方の handle のない 直線 / 曲線 の anchor point の近傍に解が存在する場合によく起こります。

handle の出ていない anchor point 近傍では、parameter で微分した bezier の勾配が限りなく 0 に近付いて行くため、もう一方の handle のある bezier の勾配の大きさとの間に大きな差が生じ、収束速度に差が生じることが原因です。

このように本来なら 一番構造が簡単な handle のない普通の直線 を bezier line として Newton 法で扱うと、精度が落ちたり、場合によっては 解が求まらない 問題が生じます。


**sample - 4**          [ script 40 - 1 ]
from vec3 import *

scene = xshade.scene()

size = 2000*scene.user_to_native_unit

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

dvx = vec3(size/2, 0, 0)
dvy = vec3(0, size/2/3**0.5, 0)

scene.create_part('sample - 4')
scene.begin_creating()
scene.begin_line(None, False)
scene.append_point(p0, None, p0 + (dvx + dvy)/3, None, None)
scene.append_point(p1, p1 - (dvx + dvy)/3, None, 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()

#2

40 - 2  Over Range Parameter


さらに bezier parameter が有効範囲を超えた **over range parameter** の場合でも注意が必要です。
bezier parameter は 0 ~ 1 の範囲をとりますが、Newton 法では計算の過程でこの range を越えることがあります。

[ script 40 - 2 ] は Shade 上で選択された 2つの point からなる bezier line が parameter -1 ~ 0 及び 1 ~ 2 の範囲でどのような形状をとるかを表示します。

この script で handle のある bezier line の over range parameter での形状を調べると次のようになり、曲線を延長したような形状を示します。

     

     [ fg 40 - 2 ]


しかし handle がないと次のような予期しない形状を示し、方程式の形によっては parameter があらぬ方向へ進んでしまい、解の不安定さの原因になることがあります。

          [ fig 40 - 3 ]


[ script 40 - 2 ]
import labo

scene = xshade.scene()

ob = scene.active_shape()

bz = labo.get_bezier(xshade, 0)


p = [bz[0]]
for i in range(11) :
	t = - float(i)/10
	p.append(labo.bezier_line_position(bz, t))
labo.make_simple_line(xshade, p)
ob1 = scene.active_shape()

p = [bz[3]]
for i in range(11) :
	t = 1 + float(i)/10
	p.append(labo.bezier_line_position(bz, t))
labo.make_simple_line(xshade, p)
ob2 = scene.active_shape()

scene.active_shapes = [ob1, ob2]

#3

40 - 3  距離 parameter


**40 - 1, 2** の問題に対処するため handle のあるなしに係わらず、**直線となる bezier line に対しては、bezier parameter ではなく 距離 parameter で handling する** ことにします。

これによって 精度 / 収束問題も over range parameter 問題も同時に解決 され、さらに 方程式の線形性が強くなって解が容易に求められる という利点もあります。

#  bezier parameter t における bezier line bz の座標

	p = labo.bezier_line_position(bz, t))

#  距離 parameter t における bezier line bz の座標

	p = bz[0] + t*(bz[3] - bz[0])

#  bezier parameter t における bezier line bz の接線ベクトル

	v = labo.bezier_line_tangent(bz, t))

#  距離 parameter t における bezier line bz の接線ベクトル

	v = bz[3] - bz[0]
	v.norm()

          [ fig 40 - 4 ]

距離 parameter を用いて Newton 法で解を得た後、得られた parameter を bezier parameter に変換しますが、この変換は次の要領で行います。

     

     [ fig 40 - 5 ]



[ script 40 - 3 ] は 3次方程式の解を求める sample script で、次の例題の解を得ます。

3次方程式の解法の詳細については 3次方程式の解法カルダノの式 としてググって下さい。


          [ fig 40 - 6 ]


[ script 40 - 3 ]          3次方程式の解を求める sample script
def signed_cubic_root(x) :
	if x >= 0 :
		return x**(1./3)
	else :
		return -(-x)**(1./3)
	
	
	
	
#  resolve_cubic_equation(d0 as float, d1 as float, d2 as float, d3 as float) as float list
#
#	係数 d0 ~ d3 で与えられる3次方程式の実根解を返す

def resolve_cubic_equation(d0, d1, d2, d3) :
	from math import acos, cos, pi
	
	t = []
	
	a = d1/d0
	b = d2/d0
	c = d3/d0
	p = -(a/3)**2 + b/3
	q = (a/3)**3 - (a/3)*(b/2) + c/2
	r = q**2 + p**3

	if r > 0 :
		t.append(signed_cubic_root(-q + r**0.5) + signed_cubic_root(-q - r**0.5) - a/3)
      
	elif r == 0 :
		t.append(2*signed_cubic_root(-q) - a/3)
		t.append(-signed_cubic_root(-q) - a/3)
      
	else :
		u = acos(min(1, max(-1, q/(p*(-p)**0.5))))
		t.append(2*(-p)**0.5*cos(u/3) - a/3)
		t.append(2*(-p)**0.5*cos(u/3 + pi*2./3) - a/3)
		t.append(2*(-p)**0.5*cos(u/3 + pi*4./3) - a/3)

	return t
	


d0 = 1.
d1 = 3.
d2 = -2.
d3 = -2.
		
xL = resolve_cubic_equation(d0, d1, d2, d3)

for x in xL :
	print 'x = ', x, '     f(x) = ', d0*x**3 + d1*x**2 + d2*x + d3


**test script - 4** [ script 40 - 4 ] は、test script - 3 に距離 parameter での handling を追加したものです。

これを用いて 先の sample - 4 に対し 半径 2000 を指定して corner を丸めると、両側の端点から handle が伸びるようになります。


          [ fig 40 - 7 ]


**test script - 4**          [ script 40 - 4 ]

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

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 4'):
		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]
				
					

					
#####  追加  #############################################################################

#     直線状の bezier line の 距離 parameter を bezier parameter に変換する操作に関する関数群
#		signed_cubic_root( )
#		resolve_cubic_equation( )
#		convert_param( )

	
#  関数 resolve_cubic_equation( ) 内で呼ばれる
	
def signed_cubic_root(x) :
	if x >= 0 :
		return x**(1./3)
	else :
		return -(-x)**(1./3)
	
	
	
	
#  resolve_cubic_equation(d0 as float, d1 as float, d2 as float, d3 as float) as float list
#
#	係数 d0 ~ d3 で与えられる3次方程式の実根解を返す

def resolve_cubic_equation(d0, d1, d2, d3) :
	from math import acos, cos, pi
	
	t = []
	
	a = d1/d0
	b = d2/d0
	c = d3/d0
	p = -(a/3)**2 + b/3
	q = (a/3)**3 - (a/3)*(b/2) + c/2
	r = q**2 + p**3

	if r > 0 :
		t.append(signed_cubic_root(-q + r**0.5) + signed_cubic_root(-q - r**0.5) - a/3)
      
	elif r == 0 :
		t.append(2*signed_cubic_root(-q) - a/3)
		t.append(-signed_cubic_root(-q) - a/3)
      
	else :
		u = acos(min(1, max(-1, q/(p*(-p)**0.5))))
		t.append(2*(-p)**0.5*cos(u/3) - a/3)
		t.append(2*(-p)**0.5*cos(u/3 + pi*2./3) - a/3)
		t.append(2*(-p)**0.5*cos(u/3 + pi*4./3) - a/3)

	return t


	

#  convert_param(bz as matrix, param as float) as float
#
#	handle のない直線状の bezier line bz において、param で与えられる位置に相当する bz の bezier parameter を返す

	
def convert_param(bze, param) :
	import copy
	
	if (param == 0) or (param == 1) :	#  本関数が呼び出される前に、0 or 1 に近い param 値は既に 0 or 1 に丸められている
		return param					#  本関数内の計算での丸め誤差によって 0 ~ 1 の間に解が見つけ出されないような事態は回避される
		
	else :
		bz = copy.deepcopy(bze)
		
		#  bz  を X 軸上に座標変換
		v = bz[3] - bz[0]
		p = bz[0] + param*v
	
		Mt = matrix().translate(-p[0], -p[1], -p[2])
		Q = quaternion().slerp(v, vec3(1, 0, 0), True, False)
		if Q != None :
			Mq = Q.matrix()
			M = Mt*Mq
		else :							#  180度 quaternion 回転
			M = Mt
		M.transform(bz)

		#  e0, e1, e2, e3 :	bz の parameter t における位置を与える3次式の X 成分の 4つの係数 
		a = bz[0][0]
		b = bz[1][0]
		c = bz[2][0]
		d = bz[3][0]
		e0 = -a + 3*b - 3*c + d
		e1 = 3*(a - 2*b + c)
		e2 = 3*(-a + b)
		e3 = a
	
		#  e0, e1, e2, e3 を係数とする3次方程式の解を求める
		tL = resolve_cubic_equation(e0, e1, e2, e3)		
		for t in tL :
			if t >= 0 and t <= 1 :		#  0 <= t  <= 1 なる解は必ず存在する
				return t				

#############################################################################################



		
#  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,  straight as bool 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, straight, x, b) :												##########  変更  ##########
	
	##########  追加, 変更  ##################################################
	u = []												#  接線ベクトル
	for i in range(2) :
		if not straight[i] :							#  bz[i] が直線でない
			u.append(labo.bezier_line_tangent(bz[i], x[i]))
		else :											#  bz[i] が直線
			vv = bz[i][3] - bz[i][0]
			vv.norm()
			u.append(vv)
			
	p = []												#  corner 曲線の端点
	for i in range(2) :
		if not straight[i] :							#  bz[i] が直線でない
			p.append(labo.bezier_line_position(bz[i], x[i]))
		else :											#  bz[i] が直線
			p.append(bz[i][0] + x[i]*(bz[i][3] - bz[i][0]))
	######################################################################
			
	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, straight as bool list, x as float list, size as float) as float, float / float
#
#	収束計算の対象となる関数
#	Jacobi  行列を求める際にも呼ばれる
	
def func(idx, bz, straight, x, size) :											##########  変更  ##########
	r = base_func(bz, straight, 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, straight as bool list, x as float list) as vec3 list
#
#	 収束計算で求められた corner 曲線の端点を与える bezier parameter x から、corner 曲線の outhandle, inhandle 座標を返す
		
def get_handle(bz, straight, x) :											##########  変更  ##########
	#	p[] :	corner 曲線の端点座標
	#	u[] :	接線ベクトル
	#	v[] :	corner 半径ベクトル
	#	r :		半径
	#	vn :		corner 法線 
	
	[p, u, v, r] = base_func(bz, straight, 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, straight as bool list) as double, list, double list
#
#	初期差分 dx[], 初期解 x[ ] を返す

def set_dx_x(bz, straight) :								##########  変更  ##########
	#  dx[ ] : 初期差分
	
	##########  変更  ##################################################
	if not straight[0] :				#  in 側が曲線
		u1 = bz[0][3] - bz[0][2]
	else :								#  in 側が直線
		u1 = (bz[0][3] - bz[0][0])/3
	
	if not straight[1] :				#  out 側が曲線
		u2 = bz[1][1] - bz[1][0]
	else :								#  out 側が直線
		u2 = (bz[1][3] - bz[1][0])/3
	##################################################################
	
	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[ ] を再設定
	if (not straight[0]) or (not straight[1]) :								##########  追加  ##########
		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							#  打ち切り計算回数

		
	##########  追加  ##################################################
	#  straight[ ] :	bz[ ]  の直線性を表す flag
	straight = []
	for i in range(2) :
		v0 = bz[i][3] - bz[i][0]
		v0.norm()
		v1 = labo.bezier_line_tangent(bz[i], 0)
		v2 = labo.bezier_line_tangent(bz[i], 1)
		b = (abs(v0.dot(v1)) > 0.9999) and (abs(v0.dot(v2)) > 0.9999)		#  bz が直線
		straight.append(b)
	##################################################################
	
	
	#  dx[ ] : 初期差分,   x[ ] : 初期解
	dx, x = set_dx_x(bz, straight)													##########  変更  ##########
	
		
	#  反復計算
	f1, f2 = func(0, bz, straight, 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, straight, [x[0] - dx[0], x[1]], size))/dx[0]		##########  変更  ##########
		J_01 = (f1 - func(1, bz, straight, [x[0], x[1] - dx[1]], size))/dx[1]		##########  変更  ##########
		J_10 = (f2 - func(2, bz, straight, [x[0] - dx[0], x[1]], size))/dx[0]		##########  変更  ##########
		J_11 = (f2 - func(2, bz, straight, [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, straight, 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, straight, x)				#  handle 座標 list を求める	##########  変更  ##########

			##########  追加  ############################################################
			for i in range(2) :
				if straight[i] :
					x[i] = convert_param(bz[i], x[i])		#  x[i] を距離ベースから bezier parameter ベースに変換
			###########################################################################
			
			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()

#4

40 - 4  直線 corner


**sample - 5a, 5b, 5c** [ script 40 - 5 ] に対して **test script - 4** を用いて 半径 1000 を指定して実行すると、**5b** のケースでは解が求まりません。

          [ fig 40 - 8 ]


**直線 corner が水平垂直の直角** になっている場合、連立方程式の構成から **Jacobi 行列の対角要素が常に 0 になる** という特殊な状況になり、x[0], x[1] のいずれか一方が先に正しい解に到達すると dx[ ] が 0 となって解けなくなってしまうのが原因です。      [ 40 - 9 ]

     

     [ fig 40 - 10 ]


x[0], x[1] のいずれか一方が先に正しい解に到達するのは、**初期解** と差分 dx に課せられた **limitter** = 0.2 との兼ね合いから発生します。

そもそも直線で挟まれた corner の場合、距離パラメータの採用により連立方程式は線形性が強くなるため 一回の trial で収束する はずです。

しかし差分 dx に与えた limitter によって強制的に複数回の trial になってしまうために、上記のような現象が生じてしまいます。


そこで直線 corner の場合、corner 角が直角であるか否かに係わらず、差分に limitter を課すことを止めます

これにより距離パラメータの採用による連立方程式の効率的な求解が有効に活用されることになり、一回の trial で簡単に解が得られます



**test script - 5** [ script 40 - 6 ] は、test script - 4 に上記の仕様を追加したものです。

これを用いて 先の sample - 5a, 5b, 5c半径 1000 を指定して corner を丸めると一回の trial で解が得られます。


          [ fig 40 - 11 ]



**sampl - 5a, 5b, 5c**          [ script 40 - 5 ]
from vec3 import *

scene = xshade.scene()

size = 1000*scene.user_to_native_unit

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


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


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

scene.create_part('sample - 5b')
scene.begin_creating()
scene.begin_line(None, False)
scene.append_point(p0, None, None, None, None)
scene.append_point(p1, None, None, None, None)
scene.append_point(p2, None, None, None, None)
scene.end_line()
scene.end_creating()
ob2 = scene.active_shape()
ob2.dad.activate()


p0 = vec3(-0.2*size, size, 0)
p1 = vec3(size, size, 0)
p2 = vec3(1.1*size,  -size, 0)

scene.create_part('sample - 5c')
scene.begin_creating()
scene.begin_line(None, False)
scene.append_point(p0, None, None, None, None)
scene.append_point(p1, None, None, None, None)
scene.append_point(p2, None, 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 - 5**     [ script 40 - 6 ]

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

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 5'):
		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]
				
					

					
#     直線状の bezier line の 距離 parameter を bezier parameter に変換する操作に関する関数群    ####################
#		signed_cubic_root( )
#		resolve_cubic_equation( )
#		convert_param( )

	
#  関数 resolve_cubic_equation( ) 内で呼ばれる
	
def signed_cubic_root(x) :
	if x >= 0 :
		return x**(1./3)
	else :
		return -(-x)**(1./3)
	
	
	
	
#  resolve_cubic_equation(d0 as float, d1 as float, d2 as float, d3 as float) as float list
#
#	係数 d0 ~ d3 で与えられる3次方程式の実根解を返す

def resolve_cubic_equation(d0, d1, d2, d3) :
	from math import acos, cos, pi
	
	t = []
	
	a = d1/d0
	b = d2/d0
	c = d3/d0
	p = -(a/3)**2 + b/3
	q = (a/3)**3 - (a/3)*(b/2) + c/2
	r = q**2 + p**3

	if r > 0 :
		t.append(signed_cubic_root(-q + r**0.5) + signed_cubic_root(-q - r**0.5) - a/3)
      
	elif r == 0 :
		t.append(2*signed_cubic_root(-q) - a/3)
		t.append(-signed_cubic_root(-q) - a/3)
      
	else :
		u = acos(min(1, max(-1, q/(p*(-p)**0.5))))
		t.append(2*(-p)**0.5*cos(u/3) - a/3)
		t.append(2*(-p)**0.5*cos(u/3 + pi*2./3) - a/3)
		t.append(2*(-p)**0.5*cos(u/3 + pi*4./3) - a/3)

	return t


	

#  convert_param(bz as matrix, param as float) as float
#
#	handle のない直線状の bezier line bz において、param で与えられる位置に相当する bz の bezier parameter を返す

	
def convert_param(bze, param) :
	import copy
	
	if (param == 0) or (param == 1) :	#  本関数が呼び出される前に、0 or 1 に近い param 値は既に 0 or 1 に丸められている
		return param					#  本関数内の計算での丸め誤差によって 0 ~ 1 の間に解が見つけ出されないような事態は回避される
		
	else :
		bz = copy.deepcopy(bze)
		
		#  bz  を X 軸上に座標変換
		v = bz[3] - bz[0]
		p = bz[0] + param*v
	
		Mt = matrix().translate(-p[0], -p[1], -p[2])
		Q = quaternion().slerp(v, vec3(1, 0, 0), True, False)
		if Q != None :
			Mq = Q.matrix()
			M = Mt*Mq
		else :					#  180度 quaternion 回転
			M = Mt
		M.transform(bz)

		#  e0, e1, e2, e3 :	bz の parameter t における位置を与える3次式の X 成分の 4つの係数 
		a = bz[0][0]
		b = bz[1][0]
		c = bz[2][0]
		d = bz[3][0]
		e0 = -a + 3*b - 3*c + d
		e1 = 3*(a - 2*b + c)
		e2 = 3*(-a + b)
		e3 = a
	
		#  e0, e1, e2, e3 を係数とする3次方程式の解を求める
		tL = resolve_cubic_equation(e0, e1, e2, e3)		
		for t in tL :
			if t >= 0 and t <= 1 :				#  0 <= t  <= 1 なる解は必ず存在する
				return t				

#############################################################################################



		
#  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,  straight as bool 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, straight, x, b) :
	u = []												#  接線ベクトル
	for i in range(2) :
		if not straight[i] :							#  bz[i] が直線でない
			u.append(labo.bezier_line_tangent(bz[i], x[i]))
		else :											#  bz[i] が直線
			vv = bz[i][3] - bz[i][0]
			vv.norm()
			u.append(vv)
			
	p = []												#  corner 曲線の端点
	for i in range(2) :
		if not straight[i] :							#  bz[i] が直線でない
			p.append(labo.bezier_line_position(bz[i], x[i]))
		else :											#  bz[i] が直線
			p.append(bz[i][0] + x[i]*(bz[i][3] - bz[i][0]))

			
	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, straight as bool list, x as float list, size as float) as float, float / float
#
#	収束計算の対象となる関数
#	Jacobi  行列を求める際にも呼ばれる
	
def func(idx, bz, straight, x, size) :
	r = base_func(bz, straight, 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, straight as bool list, x as float list) as vec3 list
#
#	 収束計算で求められた corner 曲線の端点を与える bezier parameter x から、corner 曲線の outhandle, inhandle 座標を返す
		
def get_handle(bz, straight, x) :
	#	p[] :	corner 曲線の端点座標
	#	u[] :	接線ベクトル
	#	v[] :	corner 半径ベクトル
	#	r :		半径
	#	vn :		corner 法線 
	
	[p, u, v, r] = base_func(bz, straight, 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, straight as bool list) as double, list, double list
#
#	初期差分 dx[], 初期解 x[ ] を返す

def set_dx_x(bz, straight) :
	#  dx[ ] : 初期差分
	if not straight[0] :				#  in 側が曲線
		u1 = bz[0][3] - bz[0][2]
	else :								#  in 側が直線
		u1 = (bz[0][3] - bz[0][0])/3
	
	if not straight[1] :				#  out 側が曲線
		u2 = bz[1][1] - bz[1][0]
	else :								#  out 側が直線
		u2 = (bz[1][3] - bz[1][0])/3

	
	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[ ] を再設定
	if (not straight[0]) or (not straight[1]) :
		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							#  打ち切り計算回数

		
	#  straight[ ] :	bz[ ]  の直線性を表す flag
	straight = []
	for i in range(2) :
		v0 = bz[i][3] - bz[i][0]
		v0.norm()
		v1 = labo.bezier_line_tangent(bz[i], 0)
		v2 = labo.bezier_line_tangent(bz[i], 1)
		b = (abs(v0.dot(v1)) > 0.9999) and (abs(v0.dot(v2)) > 0.9999)		#  bz が直線
		straight.append(b)

	
	#  dx[ ] : 初期差分,   x[ ] : 初期解
	dx, x = set_dx_x(bz, straight)
	
		
	#  反復計算
	f1, f2 = func(0, bz, straight, 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, straight, [x[0] - dx[0], x[1]], size))/dx[0]
		J_01 = (f1 - func(1, bz, straight, [x[0], x[1] - dx[1]], size))/dx[1]	
		J_10 = (f2 - func(2, bz, straight, [x[0] - dx[0], x[1]], size))/dx[0]
		J_11 = (f2 - func(2, bz, straight, [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) :
			if not straight[0] or not straight[1] :					#  直線 corner には lilitter を課さない		##########  追加  ##########
				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, straight, 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, straight, x)				#  handle 座標 list を求める

			for i in range(2) :
				if straight[i] :
					x[i] = convert_param(bz[i], x[i])		#  x[i] を距離ベースから bezier parameter ベースに変換
			
			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

40 - 5  解の検出範囲


**sample - 6a, 6b** [ script 40 - 7 ] に **test script - 5** で Round Corner 処理を行うと次のようになります。
**sample 6a** では Newton 法による解の求め方のロジックから、**半径が 568 を越える円弧は求められません**。

     

     [ fig 40 - 12 ]



一方 **sample 6b** では半径 617 ~ 723 の間でも円弧が求まりますが、これらは **180度以上の丸み角を持ち**、かつ、 617 ~ 723 の間の任意の半径長さに対して求まるものではなく、**所々で求まらないサイズがあります**。

corner point 寄りの初期解から始めた場合、基本的には 613 を越えては求まらない のですが、corner point を挟んで左右対称という特殊性から、偶然求まってしまったというのが実情です。
     

     [ fig 40 - 13 ]



半径 613 の円弧端点と 617 の円弧端点の間で円弧が求まっていないのは、この間に描ける円弧半径が 613 よりも小さいためです。( もう一桁詳細に言えば、613.6 ~ 616.5 )

この区間に相当する bezier parameter 位置で指定される円弧端点で描ける円弧半径は次のようになります。

          [ fig 40 - 14 ]



**sample - 6a, b**  [ script 40 - 7 ]
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(1.5*size, 0, 0)
dvy = vec3(0, 1.5*size, 0)

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



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

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


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

#6

40 - 6  もう一つの初期解


**test script - 6** [ script 40 - 8 ] は、解の検出範囲を広げるために次の処理を test script - 5 に追加したものです。
  • corner point 寄りの初期解で解が求まらなかったなら、反対側の corner 端点寄りの初期解 0 / 1 から再度求める

  • 180度以上 の丸み角を持つ corner は作らない


これを用いて先の **sample - 6a, 6b** に Round Corner 処理を施すと次のようになり、連続的な数値の半径で round corner が幅広く得られます。

     

     [ fig 40 - 15 ]



**test script - 6**  [ script 40 - 8 ]

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

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 6'):
		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]
				
					


#     直線状の bezier line の 距離 parameter を bezier parameter に変換する操作に関する関数群    ####################
#		signed_cubic_root( )
#		resolve_cubic_equation( )
#		convert_param( )

	
#  関数 resolve_cubic_equation( ) 内で呼ばれる
	
def signed_cubic_root(x) :
	if x >= 0 :
		return x**(1./3)
	else :
		return -(-x)**(1./3)
	
	
	
	
#  resolve_cubic_equation(d0 as float, d1 as float, d2 as float, d3 as float) as float list
#
#	係数 d0 ~ d3 で与えられる3次方程式の実根解を返す

def resolve_cubic_equation(d0, d1, d2, d3) :
	from math import acos, cos, pi
	
	t = []
	
	a = d1/d0
	b = d2/d0
	c = d3/d0
	p = -(a/3)**2 + b/3
	q = (a/3)**3 - (a/3)*(b/2) + c/2
	r = q**2 + p**3

	if r > 0 :
		t.append(signed_cubic_root(-q + r**0.5) + signed_cubic_root(-q - r**0.5) - a/3)
      
	elif r == 0 :
		t.append(2*signed_cubic_root(-q) - a/3)
		t.append(-signed_cubic_root(-q) - a/3)
      
	else :
		u = acos(min(1, max(-1, q/(p*(-p)**0.5))))
		t.append(2*(-p)**0.5*cos(u/3) - a/3)
		t.append(2*(-p)**0.5*cos(u/3 + pi*2./3) - a/3)
		t.append(2*(-p)**0.5*cos(u/3 + pi*4./3) - a/3)

	return t


	

#  convert_param(bz as matrix, param as float) as float
#
#	handle のない直線状の bezier line bz において、param で与えられる位置に相当する bz の bezier parameter を返す

	
def convert_param(bze, param) :
	import copy
	
	if (param == 0) or (param == 1) :	#  本関数が呼び出される前に、0 or 1 に近い param 値は既に 0 or 1 に丸められている
		return param					#  本関数内の計算での丸め誤差によって 0 ~ 1 の間に解が見つけ出されないような事態は回避される
		
	else :
		bz = copy.deepcopy(bze)
		
		#  bz  を X 軸上に座標変換
		v = bz[3] - bz[0]
		p = bz[0] + param*v
	
		Mt = matrix().translate(-p[0], -p[1], -p[2])
		Q = quaternion().slerp(v, vec3(1, 0, 0), True, False)
		if Q != None :
			Mq = Q.matrix()
			M = Mt*Mq
		else :					#  180度 quaternion 回転
			M = Mt
		M.transform(bz)

		#  e0, e1, e2, e3 :	bz の parameter t における位置を与える3次式の X 成分の 4つの係数 
		a = bz[0][0]
		b = bz[1][0]
		c = bz[2][0]
		d = bz[3][0]
		e0 = -a + 3*b - 3*c + d
		e1 = 3*(a - 2*b + c)
		e2 = 3*(-a + b)
		e3 = a
	
		#  e0, e1, e2, e3 を係数とする3次方程式の解を求める
		tL = resolve_cubic_equation(e0, e1, e2, e3)		
		for t in tL :
			if t >= 0 and t <= 1 :				#  0 <= t  <= 1 なる解は必ず存在する
				return t				

#############################################################################################



		
#  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,  straight as bool 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, straight, x, b) :
	u = []												#  接線ベクトル
	for i in range(2) :
		if not straight[i] :							#  bz[i] が直線でない
			u.append(labo.bezier_line_tangent(bz[i], x[i]))
		else :											#  bz[i] が直線
			vv = bz[i][3] - bz[i][0]
			vv.norm()
			u.append(vv)
			
	p = []												#  corner 曲線の端点
	for i in range(2) :
		if not straight[i] :							#  bz[i] が直線でない
			p.append(labo.bezier_line_position(bz[i], x[i]))
		else :											#  bz[i] が直線
			p.append(bz[i][0] + x[i]*(bz[i][3] - bz[i][0]))
			
	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, straight as bool list, x as float list, size as float) as float, float / float
#
#	収束計算の対象となる関数
#	Jacobi  行列を求める際にも呼ばれる
	
def func(idx, bz, straight, x, size) :	
	r = base_func(bz, straight, 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, straight as bool list, x as float list) as vec3 list
#
#	 収束計算で求められた corner 曲線の端点を与える bezier parameter x から、corner 曲線の outhandle, inhandle 座標を返す
		
def get_handle(bz, straight, x) :
	#	p[] :	corner 曲線の端点座標
	#	u[] :	接線ベクトル
	#	v[] :	corner 半径ベクトル
	#	r :		半径
	#	vn :		corner 法線 
	
	[p, u, v, r] = base_func(bz, straight, x, True)	#  p :  corner 曲線の端点	u :  接線ベクトル	v :  半径ベクトル	r : 半径
	w1 = u[0] - u[1]
	w1.norm()
	cosB = w1.dot(- v[0])
	if cosB < 0 :								#  丸み角が 180度以上				##########  追加  ##########
		return None								#  Round corner 処理は行わない	##########  追加  ##########
		
	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, straight as bool list) as double, list, double list
#
#	初期差分 dx[], 初期解 x[ ] を返す	( alternate = False ケース )

def set_dx_x(bz, straight) :
	#  dx[ ] : 初期差分
	if not straight[0] :				#  in 側が曲線
		u1 = bz[0][3] - bz[0][2]
	else :								#  in 側が直線
		u1 = (bz[0][3] - bz[0][0])/3
	
	if not straight[1] :				#  out 側が曲線
		u2 = bz[1][1] - bz[1][0]
	else :								#  out 側が直線
		u2 = (bz[1][3] - bz[1][0])/3
	
	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[ ] を再設定
	if (not straight[0]) or (not straight[1]) :
		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, 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




##########  追加  #################################################################
#  set_dx_x_2(bz as matrix list, straight as bool list) as double, list, double list
#
#	初期差分 dx[], 初期解 x[ ] を返す	( alternate = True ケース )

def set_dx_x_2(bz, straight) :
	dx = [-0.0001, 0.0001]				#  初期差分
	x = [0, 1]							#  初期解

	#  初期解での接線ベクトルが平行な場合、初期差分 dx[ ] , 初期解 x[ ] を再設定
	v1 = labo.bezier_line_tangent(bz[0], x[0])												
	v2 = labo.bezier_line_tangent(bz[1], x[1])	
	xx = dx[1]
	 	
	while abs(v1.dot(v2)) > 0.999 :
		if xx < 0.1 - 1e-8 :																	
			xx *= 10																		
		elif xx < 0.9 - 1e-8 :																	
			xx += 0.1																		
		elif xx < 0.99 - 1e-8 :																
			xx += 0.01											
		else :																			
			return None, None			#  これ以上は、計算不能として扱う ( ここに入るケースはないはず )

		dx = [xx, - xx]
		x = [xx, 1 - xx]																		
		v1 = labo.bezier_line_tangent(bz[0], x[0])											
		v2 = labo.bezier_line_tangent(bz[1], x[1])

	return dx, x
################################################################################



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

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

		
	#  straight[ ] :	bz[ ]  の直線性を表す flag
	straight = []
	for i in range(2) :
		v0 = bz[i][3] - bz[i][0]
		v0.norm()
		v1 = labo.bezier_line_tangent(bz[i], 0)
		v2 = labo.bezier_line_tangent(bz[i], 1)
		b = (abs(v0.dot(v1)) > 0.9999) and (abs(v0.dot(v2)) > 0.9999)		#  bz が直線
		straight.append(b)

		
	##########  追加, 変更  ############################################################
	
	#  dx[ ] : 初期差分    x[ ] : 初期解
	if not alternate :
		dx, x = set_dx_x(bz, straight)
		
	else :
		if straight[0] and straight[1] :						#  bz[0], bz[1] がともに直線ならば何も実行せずに帰る
			return None
																
		f1, f2 = func(0, bz, straight, [0, 1], size)

		if (abs(f1) >= threshold) or (abs(f2) >= threshold) :	#  初期値 [0, 1] が解ではない
			dx, x = set_dx_x_2(bz, straight)
			
		else :													#  初期値 [0, 1] が解
			hL = get_handle(bz, straight, x)					#  handle 座標 list を求める
			
			if hL == None :										#  丸み角が 180度以上
				print '丸み角が 180度以上'
				return None

			for i in range(2) :
				if straight[i] :
					x[i] = convert_param(bz[i], x[i])			#  x[i] を距離ベースから bezier parameter ベースに変換
		
			print 'f1 = ', f1, '     x[0] = ', x[0]
			print 'f2 = ', f2, '     x[1] = ', x[1]
			print '繰り返し計算回数 : 0     x = ', x
					
			return [x, hL]										#  x[] : 	挿入ポイントの位置を示す bezier parameter list
																#  hL[] :	handle 座標 list	
	
	if dx == None :
		return None
	################################################################################
	
		
	#  反復計算
	f1, f2 = func(0, bz, straight, 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, straight, [x[0] - dx[0], x[1]], size))/dx[0]
		J_01 = (f1 - func(1, bz, straight, [x[0], x[1] - dx[1]], size))/dx[1]
		J_10 = (f2 - func(2, bz, straight, [x[0] - dx[0], x[1]], size))/dx[0]
		J_11 = (f2 - func(2, bz, straight, [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) :
			if not straight[0] or not straight[1] :					#  直線 corner には lilitter を課さない
				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, straight, 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, straight, x)				#  handle 座標 list を求める
			
			if hL == None :									#  丸み角が 180度以上			##########  追加  ##########
				print '丸み角が 180度以上'												##########  追加  ##########
				return None																##########  追加  ##########

			for i in range(2) :
				if straight[i] :
					x[i] = convert_param(bz[i], x[i])		#  x[i] を距離ベースから bezier parameter ベースに変換
			
			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, False)						##########  変更  ##########
						if data == None :													##########  追加  ##########
							data = round_corner([bz1, bz2], size, True)						##########  追加  ##########
								
						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()

#7

40 - 7  解の精度限界


**39 - 2 基本ロジック** [ fig 39 - 3 ] に記したように、円弧端点での接線ベクトルがほぼ平行と呼べるくらい平行に近づくと、丸め誤差により正確な半径を得るのが難しくなります。

このため、丸み角度が180度になる次のようなケースではどうしても目に見える誤差が生じます。

下図は sample - 7 [ script 40 - 9 ] に、半径 1000 を指定して test script - 6 で Round Corner 処理を施したものです。


          [ fig 40 - 16 ]


円弧 handle は両端の点から出るのではなく、端点の近傍に挿入されたポイントから円弧が始まっています。

ところが形状の上位 matrix に変換が掛かっていると、丸め誤差の影響が増えるため最初の corner point 寄りの初期解では求められず、次のステップで両端寄りの初期解 0 / 1 により両端のポイントから handle の出る正確な円弧が得られます。

このように特別な条件下では丸め誤差の大きくなるケースでかえって正確な円弧形状が得られる逆転現象が生じてしまいます。

しかし この平行ケースを除けば、test script - 6 は精度の高い円弧を与えます


ただし通常のモデリングでは考えられないような極端なケースで解が求められないことはあります。

例えば下図に示すように、sample 8 [ script 40 - 10 ] に対して、半径 1 での丸めは可能ですが、半径 0.5 での丸めでは解が求まりません

          [ fig 40 - 17 ]



**sample - 7**  [ script 40 - 9 ]
from vec3 import *

scene = xshade.scene()

size = 2000*scene.user_to_native_unit

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

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

scene.create_part('sample - 7')
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()

**sample - 8**  [ script 40 - 10 ]
from vec3 import *

scene = xshade.scene()

size = 2000*scene.user_to_native_unit

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

dvy = vec3(0, size, 0)

scene.create_part('sample - 8')
scene.begin_creating()
scene.begin_line(None, False)
scene.append_point(p0, None, None, None, None)
scene.append_point(p1, p1 - dvy, p1 - 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()

#8

40 - 8  処理内容のまとめ


ここまでに解説してきた Newton 法を用いた Round Corner 処理をまとめます。
**< 初期解に係わる処理 >**
1)増分値に limitter を与える( **39 - 3** )

          parameter 暴走を回避


2)corner から伸びる in / out handle 長さ比に応じて初期解を設定 [ 注 - 1 ]( 39 - 4

          極端な handle 長さ比への対応


3)超鋭角 corner での初期解設定の改善 [ 注 - 1 ]( 39 - 5


**< handle のない直線への対応 >**
4)距離 parameter で handling( **40 - 3** )

         anchor point 近傍での収束速度が遅くなることへの対応
         over range parameter での不安定化を回避


**< 初期解に係わる処理 >**
5)直線 corner での limitter 解除( **40 - 4** ) ( この処理は 直線に対する距離 parameter の適用 を前提とする )

        直角 corner での特殊ケースへの対応
        線形性の強い連立方程式の効率的な求解


**< 幅広い解の取得 >**
6)corner 端点寄りの初期解による求解を追加( **40 - 6** )

7)180度以上の丸み角を除外( 40 - 6


**[ 注 - 1 ]**

通常のモデリングで考えられるような形状をサポートするが、極端な形状に対しては解が求まらないことがある( 40 - 7 )


#9

40 - 9  Option


**test script - 7** [ script 40 - 11 ] は、test script - 6 に次の2つのオプション機能を追加したものです。
**< 丸み >**
handle 長さを調整

          [ fig 40 - 18 ]



**< サイズ指定選択 >**
指定するサイズを **丸みの半径** と **ベベルサイズ** から選択

          [ fig 40 - 19 ]



ベベルサイズ指定の場合、Newton 法で求める連立方程式 f1, f2 の構成には注意が必要です。

単純に考えれば次のようにすればよさそうです。

          [ fig 40 - 20 ]



しかし Jacobi 行列の求め方を考えると、下図のように parameter の進む方向が逆向きになってしまい、解が求められなくなる可能性が生じます。

実際に直線で挟まれる corner で θ が’60度以下になると、そのような状況に陥ります。

     

     [ fig 40 - 21 ]



そこで連立方程式 f1, f2 は次のように定めます。

     

     [ fig 40 - 22 ]



**test script - 7**  [ script 40 - 11 ]

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

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('b6fcba8b-8200-4e7c-876b-e8b03344d727')

	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_selection('基準   /半径/ベベルサイズ')
	idx_2 = dialog.append_float('サイズ', '   ' + unit)	
	idx_3 = dialog.append_float('丸み   ','   標準値 = 1  ( 0 以上 )')
	dialog.set_value(idx_3 , 1.)
	idx_4 = dialog.append_bool('original を残す')
	dialog.end_group()

	if not dialog.ask('Round Corner test 7'):
		return None	
	else :
		##########  変更  #################################################################
		base = dialog.get_value(idx_1)
		size =  dialog.get_value(idx_2)*scene.user_to_native_unit
		if size <= 0 :
			print 'サイズは正の値にして下さい'
			return None
			
		round = max(0., dialog.get_value(idx_3))	#  丸み
		copy_shape = dialog.get_value(idx_4)		#	original を残す指定 flag
		
		return [base, size, round, copy_shape]
		################################################################################	
					


#     直線状の bezier line の 距離 parameter を bezier parameter に変換する操作に関する関数群    ####################
#		signed_cubic_root( )
#		resolve_cubic_equation( )
#		convert_param( )

	
#  関数 resolve_cubic_equation( ) 内で呼ばれる
	
def signed_cubic_root(x) :
	if x >= 0 :
		return x**(1./3)
	else :
		return -(-x)**(1./3)
	
	
	
	
#  resolve_cubic_equation(d0 as float, d1 as float, d2 as float, d3 as float) as float list
#
#	係数 d0 〜 d3 で与えられる3次方程式の実根解を返す

def resolve_cubic_equation(d0, d1, d2, d3) :
	from math import acos, cos, pi
	
	t = []
	
	a = d1/d0
	b = d2/d0
	c = d3/d0
	p = -(a/3)**2 + b/3
	q = (a/3)**3 - (a/3)*(b/2) + c/2
	r = q**2 + p**3

	if r > 0 :
		t.append(signed_cubic_root(-q + r**0.5) + signed_cubic_root(-q - r**0.5) - a/3)
      
	elif r == 0 :
		t.append(2*signed_cubic_root(-q) - a/3)
		t.append(-signed_cubic_root(-q) - a/3)
      
	else :
		u = acos(min(1, max(-1, q/(p*(-p)**0.5))))
		t.append(2*(-p)**0.5*cos(u/3) - a/3)
		t.append(2*(-p)**0.5*cos(u/3 + pi*2./3) - a/3)
		t.append(2*(-p)**0.5*cos(u/3 + pi*4./3) - a/3)

	return t


	

#  convert_param(bz as matrix, param as float) as float
#
#	handle のない直線状の bezier line bz において、param で与えられる位置に相当する bz の bezier parameter を返す

	
def convert_param(bze, param) :
	import copy
	
	if (param == 0) or (param == 1) :	#  本関数が呼び出される前に、0 or 1 に近い param 値は既に 0 or 1 に丸められている
		return param					#  本関数内の計算での丸め誤差によって 0 〜 1 の間に解が見つけ出されないような事態は回避される
		
	else :
		bz = copy.deepcopy(bze)
		
		#  bz  を X 軸上に座標変換
		v = bz[3] - bz[0]
		p = bz[0] + param*v
	
		Mt = matrix().translate(-p[0], -p[1], -p[2])
		Q = quaternion().slerp(v, vec3(1, 0, 0), True, False)
		if Q != None :
			Mq = Q.matrix()
			M = Mt*Mq
		else :					#  180度 quaternion 回転
			M = Mt
		M.transform(bz)

		#  e0, e1, e2, e3 :	bz の parameter t における位置を与える3次式の X 成分の 4つの係数 
		a = bz[0][0]
		b = bz[1][0]
		c = bz[2][0]
		d = bz[3][0]
		e0 = -a + 3*b - 3*c + d
		e1 = 3*(a - 2*b + c)
		e2 = 3*(-a + b)
		e3 = a
	
		#  e0, e1, e2, e3 を係数とする3次方程式の解を求める
		tL = resolve_cubic_equation(e0, e1, e2, e3)		
		for t in tL :
			if t >= 0 and t <= 1 :				#  0 <= t  <= 1 なる解は必ず存在する
				return t				

#############################################################################################



		
#  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,  straight as bool list, x as float list, flag as int) as float list / [ vec3 list, vec3 list, vec3 list]
#
#	corner を構成する2つの bzier line bz の parameter x で与えられるポイント座標を基準とし、
#
#	flag == 0 		( base = 0 半径指定 )
#		corner 半径を返す
#		返す先は func( ) で、Jacobi 行列を求める際や、収束計算の中で呼ばれる 
#
#	flag == 1 		( base = 1 ベベルサイズ指定 )
#		corner 半径, ベベルサイズ を返す
#		返す先は func( ) で、Jacobi 行列を求める際や、収束計算の中で呼ばれる 
#
#	flag == 2 
#		corner 曲線の端点座標, 接線ベクトル, 半径ベクトル, 半径 を返す
#		返す先は get_handle( ) で、収束計算終了後に handle 長さを求めるために呼ばれる
	
def base_func(bz, straight, x, flag) :											##########  変更  ##########
	u = []												#  接線ベクトル
	for i in range(2) :
		if not straight[i] :							#  bz[i] が直線でない
			u.append(labo.bezier_line_tangent(bz[i], x[i]))
		else :											#  bz[i] が直線
			vv = bz[i][3] - bz[i][0]
			vv.norm()
			u.append(vv)
			
	p = []												#  corner 曲線の端点
	for i in range(2) :
		if not straight[i] :							#  bz[i] が直線でない
			p.append(labo.bezier_line_position(bz[i], x[i]))
		else :											#  bz[i] が直線
			p.append(bz[i][0] + x[i]*(bz[i][3] - bz[i][0]))
			
	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 flag == 0 :	
		return r										#  corner 半径
	elif flag == 1 :
		vv = v[0] - v[1]
		vv.norm()
		return [r, w2.dot(vv)]							#  corner 半径,  ベベルサイズ
	elif flag == 2 :
		return [p, u, v, (r[0] + r[1])/2]				#  corner 曲線の端点座標, 接線ベクトル, 半径ベクトル, 半径
	################################################################################


	

#  func(idx as int, bz as matrix list, straight as bool list, x as float list, base as int, size as float) as float, float / float
#
#	収束計算の対象となる関数
#	Jacobi  行列を求める際にも呼ばれる
	
##########  変更  #################################################################
def func(idx, bz, straight, x, base, size) :	
	if base == 0 :									#  size = 半径
		r = base_func(bz, straight, x, 0)			#  r : 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
			
	elif base == 1 :								#  size = ベベルサイズ
		r, w = base_func(bz, straight, x, 1)		#  r : corner 半径		w : ベベルサイズ
		if idx == 0 :	
			return r[0] - r[1], w - size			#  2つの corner 半径の差 及び 計算されたベベルサイズと指定値のそれとの差 が返される関数値
		elif idx == 1 :
			return r[0] - r[1]
		elif idx == 2 :
			return w - size
################################################################################
		
		
		

#  get_handle(bz as matrix list, straight as bool list, x as float list, round as float) as vec3 list
#
#	 収束計算で求められた corner 曲線の端点を与える bezier parameter x から、corner 曲線の outhandle, inhandle 座標を返す
		
def get_handle(bz, straight, x, round) :											##########  変更  ##########
	#	p[] :	corner 曲線の端点座標
	#	u[] :	接線ベクトル
	#	v[] :	corner 半径ベクトル
	#	r :		半径
	#	vn :		corner 法線 
	
	[p, u, v, r] = base_func(bz, straight, x, 2)	#  p :  corner 曲線の端点	u :  接線ベクトル	v :  半径ベクトル	r : 半径	##########  変更  ##########
	w1 = u[0] - u[1]
	w1.norm()
	cosB = w1.dot(- v[0])
	if cosB < 0 :								#  丸み角が 180度以上
		return None								#  Round corner 処理は行わない
		
	sinB = (1 - cosB**2)**0.5
	h = 4./3*r*(1 - cosB)/sinB					#  handle 長さ ( corner 面法線基準 )
	
	##########  変更  #################################################################
	if cosB == 0 :
		h_max = 10								#  最大 handle 長さ
	else :
		h_max = min(10*r, r*sinB/cosB)			#  最大 handle 長さ
	h = min(h_max, h*round)						#  丸み round を考慮した ハンドル 長さ
	################################################################################
	
	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, straight as bool list) as double, list, double list
#
#	初期差分 dx[], 初期解 x[ ] を返す	( alternate = False ケース )

def set_dx_x(bz, straight) :
	#  dx[ ] : 初期差分
	if not straight[0] :				#  in 側が曲線
		u1 = bz[0][3] - bz[0][2]
	else :								#  in 側が直線
		u1 = (bz[0][3] - bz[0][0])/3
	
	if not straight[1] :				#  out 側が曲線
		u2 = bz[1][1] - bz[1][0]
	else :								#  out 側が直線
		u2 = (bz[1][3] - bz[1][0])/3
	
	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[ ] を再設定
	if (not straight[0]) or (not straight[1]) :
		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, 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




#  set_dx_x_2(bz as matrix list, straight as bool list) as double, list, double list
#
#	初期差分 dx[], 初期解 x[ ] を返す	( alternate = True ケース )

def set_dx_x_2(bz, straight) :
	dx = [-0.0001, 0.0001]				#  初期差分
	x = [0, 1]							#  初期解

	#  初期解での接線ベクトルが平行な場合、初期差分 dx[ ] , 初期解 x[ ] を再設定
	v1 = labo.bezier_line_tangent(bz[0], x[0])												
	v2 = labo.bezier_line_tangent(bz[1], x[1])	
	xx = dx[1]
	 	
	while abs(v1.dot(v2)) > 0.999 :
		if xx < 0.1 - 1e-8 :																	
			xx *= 10																		
		elif xx < 0.9 - 1e-8 :																	
			xx += 0.1																		
		elif xx < 0.99 - 1e-8 :																
			xx += 0.01											
		else :																			
			return None, None				#  これ以上は、計算不能として扱う ( ここに入るケースはないはず )

		dx = [xx, - xx]
		x = [xx, 1 - xx]																		
		v1 = labo.bezier_line_tangent(bz[0], x[0])											
		v2 = labo.bezier_line_tangent(bz[1], x[1])

	return dx, x



	
#  round_corner(bz as matrix list, base as int, size as float, round as float, alternate as bool) as datalist
#
#	半径 size なる round corner を与える bezier parameter list と handle 座標 list を返す
#
#	解を求めることができない、あるいは、解が存在しなければ、None を返す
#
#		base :		0 : size = 半径	1 : size = ベベルサイズ
#		size :		半径 or ベベルサイズ
#		round :		丸み
#		alternate :	初期解の選択 flag	

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

		
	#  straight[ ] :	bz[ ]  の直線性を表す flag
	straight = []
	for i in range(2) :
		v0 = bz[i][3] - bz[i][0]
		v0.norm()
		v1 = labo.bezier_line_tangent(bz[i], 0)
		v2 = labo.bezier_line_tangent(bz[i], 1)
		b = (abs(v0.dot(v1)) > 0.9999) and (abs(v0.dot(v2)) > 0.9999)	#  bz が直線
		straight.append(b)

	
	#  dx[ ] : 初期差分    x[ ] : 初期解
	if not alternate :
		dx, x = set_dx_x(bz, straight)
		
	else :
		if straight[0] and straight[1] :						#  bz[0], bz[1] がともに直線ならば何も実行せずに帰る
			return None
																
		f1, f2 = func(0, bz, straight, [0, 1], base, size)										##########  変更  ##########

		if (abs(f1) >= threshold) or (abs(f2) >= threshold) :	#  初期値 [0, 1] が解ではない
			dx, x = set_dx_x_2(bz, straight)
			
		else :													#  初期値 [0, 1] が解
			hL = get_handle(bz, straight, x, round)				#  handle 座標 list を求める		##########  変更  ##########
			
			if hL == None :										#  丸み角が 180度以上
				print '丸み角が 180度以上'
				return None

			for i in range(2) :
				if straight[i] :
					x[i] = convert_param(bz[i], x[i])			#  x[i] を距離ベースから bezier parameter ベースに変換
		
			print 'f1 = ', f1, '     x[0] = ', x[0]
			print 'f2 = ', f2, '     x[1] = ', x[1]
			print '繰り返し計算回数 : 0     x = ', x
					
			return [x, hL]										#  x[] : 	挿入ポイントの位置を示す bezier parameter list
																#  hL[] :	handle 座標 list	
	if dx == None :
		return None
	
		
	#  反復計算
	f1, f2 = func(0, bz, straight, x, base, 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, straight, [x[0] - dx[0], x[1]], base, size))/dx[0]	##########  変更  ##########
		J_01 = (f1 - func(1, bz, straight, [x[0], x[1] - dx[1]], base, size))/dx[1]	##########  変更  ##########
		J_10 = (f2 - func(2, bz, straight, [x[0] - dx[0], x[1]], base, size))/dx[0]	##########  変更  ##########
		J_11 = (f2 - func(2, bz, straight, [x[0], x[1] - dx[1]], base, 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) :
			if not straight[0] or not straight[1] :					#  直線 corner には lilitter を課さない
				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, straight, x, base, 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, straight, x, round)		#  handle 座標 list を求める	##########  変更  ##########
			
			if hL == None :								#  丸み角が 180度以上
				print '丸み角が 180度以上'
				return None

			for i in range(2) :
				if straight[i] :
					x[i] = convert_param(bz[i], x[i])	#  x[i] を距離ベースから bezier parameter ベースに変換
			
			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 :
	[base, size, round, 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], base, size, round, False)		##########  変更  ##########
						if data == None :	
							data = round_corner([bz1, bz2], base, size, round, True)	##########  変更  ##########
								
						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
								if base == 0 :													##########  変更  ##########
									ob.name = '半径 = ' + str(size/scene.user_to_native_unit)	##########  変更  ##########
								elif base == 1 :												##########  変更  ##########
									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()

#10

40 - 10  Script


**test script - 7** をベースにして、一般化された round corner 処理を行う script を [ script 40 - 12 ] に記します 。
**< 仕様 >**
  1. 選択された線形状( 自由曲面内の線形状は除く )が対象で、複数選択可

  2. 編集モードでは 選択ポイント が対象、それ以外では 全てのポイント が対象

  3. ローカル座標系をサポート

  4. 隣接する corner point に対する丸め処理が干渉する場合、上流側のポイントに対する処理が優先

          [ fig 40 - 23 ]



Shade による 角の丸め と ベベルサイズ指定 / 半径指定 のよる Round Corner 処理で、**同一サイズ指定による違い** は次のようになります。           [ fig 40 - 24 ]

**完成 script**          [ script 40 - 12 ]
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('b6fcba8b-8200-4e7c-876b-e8b03344d727')

	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_selection('基準   /半径/ベベルサイズ')
	idx_2 = dialog.append_float('サイズ', '   ' + unit)	
	idx_3 = dialog.append_float('丸み   ','   標準値 = 1  ( 0 以上 )')
	dialog.set_value(idx_3 , 1.)
	idx_4 = dialog.append_bool('original を残す')
	dialog.end_group()

	if not dialog.ask('Round Corner test 6'):
		return None	
	else :
		base = dialog.get_value(idx_1)
		size =  dialog.get_value(idx_2)*scene.user_to_native_unit
		if size <= 0 :
			print 'サイズは正の値にして下さい'
			return None
			
		round = max(0., dialog.get_value(idx_3))	#  丸み
		copy_shape = dialog.get_value(idx_4)		#	original を残す指定 flag
		
		return [base, size, round, copy_shape]
		
					


#     直線状の bezier line の 距離 parameter を bezier parameter に変換する操作に関する関数群
#		signed_cubic_root( )
#		resolve_cubic_equation( )
#		convert_param( )

	
#  関数 resolve_cubic_equation( ) 内で呼ばれる
	
def signed_cubic_root(x) :
	if x >= 0 :
		return x**(1./3)
	else :
		return -(-x)**(1./3)
	
	
	
	
#  resolve_cubic_equation(d0 as float, d1 as float, d2 as float, d3 as float) as float list
#
#	係数 d0 〜 d3 で与えられる3次方程式の実根解を返す

def resolve_cubic_equation(d0, d1, d2, d3) :
	from math import acos, cos, pi
	
	t = []
	
	a = d1/d0
	b = d2/d0
	c = d3/d0
	p = -(a/3)**2 + b/3
	q = (a/3)**3 - (a/3)*(b/2) + c/2
	r = q**2 + p**3

	if r > 0 :
		t.append(signed_cubic_root(-q + r**0.5) + signed_cubic_root(-q - r**0.5) - a/3)
      
	elif r == 0 :
		t.append(2*signed_cubic_root(-q) - a/3)
		t.append(-signed_cubic_root(-q) - a/3)
      
	else :
		u = acos(min(1, max(-1, q/(p*(-p)**0.5))))
		t.append(2*(-p)**0.5*cos(u/3) - a/3)
		t.append(2*(-p)**0.5*cos(u/3 + pi*2./3) - a/3)
		t.append(2*(-p)**0.5*cos(u/3 + pi*4./3) - a/3)

	return t


	

#  convert_param(bz as matrix, param as float) as float
#
#	handle のない直線状の bezier line bz において、param で与えられる位置に相当する bz の bezier parameter を返す

	
def convert_param(bze, param) :
	import copy
	
	if (param == 0) or (param == 1) :	#  本関数が呼び出される前に、0 or 1 に近い param 値は既に 0 or 1 に丸められている
		return param					#  本関数内の計算での丸め誤差によって 0 〜 1 の間に解が見つけ出されないような事態は回避される
		
	else :
		bz = copy.deepcopy(bze)
		
		#  bz  を X 軸上に座標変換
		v = bz[3] - bz[0]
		p = bz[0] + param*v
	
		Mt = matrix().translate(-p[0], -p[1], -p[2])
		Q = quaternion().slerp(v, vec3(1, 0, 0), True, False)
		if Q != None :
			Mq = Q.matrix()
			M = Mt*Mq
		else :					#  180度 quaternion 回転
			M = Mt
		M.transform(bz)

		#  e0, e1, e2, e3 :	bz の parameter t における位置を与える3次式の X 成分の 4つの係数 
		a = bz[0][0]
		b = bz[1][0]
		c = bz[2][0]
		d = bz[3][0]
		e0 = -a + 3*b - 3*c + d
		e1 = 3*(a - 2*b + c)
		e2 = 3*(-a + b)
		e3 = a
	
		#  e0, e1, e2, e3 を係数とする3次方程式の解を求める
		tL = resolve_cubic_equation(e0, e1, e2, e3)		
		for t in tL :
			if t >= 0 and t <= 1 :				#  0 <= t  <= 1 なる解は必ず存在する
				return t				

#######################################################################



		
#  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,  straight as bool list, x as float list, flag as int) as float list / [ vec3 list, vec3 list, vec3 list]
#
#	corner を構成する2つの bzier line bz の parameter x で与えられるポイント座標を基準とし、
#
#	flag == 0 		( base = 0 半径指定 )
#		corner 半径を返す
#		返す先は func( ) で、Jacobi 行列を求める際や、収束計算の中で呼ばれる 
#
#	flag == 1 		( base = 1 ベベルサイズ指定 )
#		corner 半径, ベベルサイズ を返す
#		返す先は func( ) で、Jacobi 行列を求める際や、収束計算の中で呼ばれる 
#
#	flag == 2 
#		corner 曲線の端点座標, 接線ベクトル, 半径ベクトル, 半径 を返す
#		返す先は get_handle( ) で、収束計算終了後に handle 長さを求めるために呼ばれる
	
def base_func(bz, straight, x, flag) :
	u = []												#  接線ベクトル
	for i in range(2) :
		if not straight[i] :							#  bz[i] が直線でない
			u.append(labo.bezier_line_tangent(bz[i], x[i]))
		else :											#  bz[i] が直線
			vv = bz[i][3] - bz[i][0]
			vv.norm()
			u.append(vv)
			
	p = []												#  corner 曲線の端点
	for i in range(2) :
		if not straight[i] :							#  bz[i] が直線でない
			p.append(labo.bezier_line_position(bz[i], x[i]))
		else :											#  bz[i] が直線
			p.append(bz[i][0] + x[i]*(bz[i][3] - bz[i][0]))
			
	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 flag == 0 :	
		return r										#  corner 半径
	elif flag == 1 :
		vv = v[0] - v[1]
		vv.norm()
		return [r, w2.dot(vv)]							#  corner 半径,  ベベルサイズ
	elif flag == 2 :
		return [p, u, v, (r[0] + r[1])/2]				#  corner 曲線の端点座標, 接線ベクトル, 半径ベクトル, 半径



	

#  func(idx as int, bz as matrix list, straight as bool list, x as float list, base as int, size as float) as float, float / float
#
#	収束計算の対象となる関数
#	Jacobi  行列を求める際にも呼ばれる

def func(idx, bz, straight, x, base, size) :	
	if base == 0 :									#  size = 半径
		r = base_func(bz, straight, x, 0)			#  r : 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
			
	elif base == 1 :								#  size = ベベルサイズ
		r, w = base_func(bz, straight, x, 1)		#  r : corner 半径		w : ベベルサイズ
		if idx == 0 :	
			return r[0] - r[1], w - size			#  2つの corner 半径の差 及び 計算されたベベルサイズと指定値のそれとの差 が返される関数値
		elif idx == 1 :
			return r[0] - r[1]
		elif idx == 2 :
			return w - size

		
		
		

#  get_handle(bz as matrix list, straight as bool list, x as float list, round as float) as vec3 list
#
#	 収束計算で求められた corner 曲線の端点を与える bezier parameter x から、corner 曲線の outhandle, inhandle 座標を返す
		
def get_handle(bz, straight, x, round) :
	#	p[] :	corner 曲線の端点座標
	#	u[] :	接線ベクトル
	#	v[] :	corner 半径ベクトル
	#	r :		半径
	#	vn :		corner 法線 
	
	[p, u, v, r] = base_func(bz, straight, x, 2)	#  p :  corner 曲線の端点		u :  接線ベクトトル	v :  半径ベクトル		r : 半径
	w1 = u[0] - u[1]
	w1.norm()
	cosB = w1.dot(- v[0])
	if cosB < 0 :								#  丸み角が 180度以上
		return None								#  Round corner 処理は行わない
		
	sinB = (1 - cosB**2)**0.5
	h = 4./3*r*(1 - cosB)/sinB					#  handle 長さ ( corner 面法線基準 )

	if cosB == 0 :
		h_max = 10								#  最大 handle 長さ
	else :
		h_max = min(10*r, r*sinB/cosB)			#  最大 handle 長さ
	h = min(h_max, h*round)						#  丸み round を考慮した ハンドル 長さ
	
	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, straight as bool list) as double, list, double list
#
#	初期差分 dx[], 初期解 x[ ] を返す	( alternate = False ケース )

def set_dx_x(bz, straight) :
	#  dx[ ] : 初期差分
	if not straight[0] :				#  in 側が曲線
		u1 = bz[0][3] - bz[0][2]
	else :								#  in 側が直線
		u1 = (bz[0][3] - bz[0][0])/3
	
	if not straight[1] :				#  out 側が曲線
		u2 = bz[1][1] - bz[1][0]
	else :								#  out 側が直線
		u2 = (bz[1][3] - bz[1][0])/3
	
	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[ ] を再設定
	if (not straight[0]) or (not straight[1]) :
		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 :	
					return None, 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




#  set_dx_x_2(bz as matrix list, straight as bool list) as double, list, double list
#
#	初期差分 dx[], 初期解 x[ ] を返す	( alternate = True ケース )

def set_dx_x_2(bz, straight) :
	dx = [-0.0001, 0.0001]				#  初期差分
	x = [0, 1]							#  初期解

	#  初期解での接線ベクトルが平行な場合、初期差分 dx[ ] , 初期解 x[ ] を再設定
	v1 = labo.bezier_line_tangent(bz[0], x[0])												
	v2 = labo.bezier_line_tangent(bz[1], x[1])	
	xx = dx[1]
	 	
	while abs(v1.dot(v2)) > 0.999 :
		if xx < 0.1 - 1e-8 :																	
			xx *= 10																		
		elif xx < 0.9 - 1e-8 :																	
			xx += 0.1																		
		elif xx < 0.99 - 1e-8 :																
			xx += 0.01											
		else :																			
			return None, None				#  これ以上は、計算不能として扱う ( ここに入るケースはないはず )

		dx = [xx, - xx]
		x = [xx, 1 - xx]																		
		v1 = labo.bezier_line_tangent(bz[0], x[0])											
		v2 = labo.bezier_line_tangent(bz[1], x[1])

	return dx, x



	
#  round_corner(bz as matrix list, base as int, size as float, round as float, alternate as bool) as datalist
#
#	半径 size なる round corner を与える bezier parameter list と handle 座標 list を返す
#
#	解を求めることができない、あるいは、解が存在しなければ、None を返す
#
#		base :		0 : size = 半径	1 : size = ベベルサイズ
#		size :		半径 or ベベルサイズ
#		round :		丸み
#		alternate :	初期解の選択 flag	

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

		
	#  straight[ ] :	bz[ ]  の直線性を表す flag
	straight = []
	for i in range(2) :
		v0 = bz[i][3] - bz[i][0]
		v0.norm()
		v1 = labo.bezier_line_tangent(bz[i], 0)
		v2 = labo.bezier_line_tangent(bz[i], 1)
		b = (abs(v0.dot(v1)) > 0.9999) and (abs(v0.dot(v2)) > 0.9999)		#  bz が直線
		straight.append(b)

	
	#  dx[ ] : 初期差分    x[ ] : 初期解
	if not alternate :
		dx, x = set_dx_x(bz, straight)
		
	else :
		if straight[0] and straight[1] :						#  bz[0], bz[1] がともに直線ならば何も実行せずに帰る
			return None
																
		f1, f2 = func(0, bz, straight, [0, 1], base, size)

		if (abs(f1) >= threshold) or (abs(f2) >= threshold) :	#  初期値 [0, 1] が解ではない
			dx, x = set_dx_x_2(bz, straight)
			
		else :													#  初期値 [0, 1] が解
			hL = get_handle(bz, straight, x, round)				#  handle 座標 list を求める
			
			if hL == None :										#  丸み角が 180度以上
				return None

			for i in range(2) :
				if straight[i] :
					x[i] = convert_param(bz[i], x[i])			#  x[i] を距離ベースから bezier parameter ベースに変換
		
			return [x, hL]										#  x[] : 	挿入ポイントの位置を示す bezier parameter list
																#  hL[] :	handle 座標 list	
	if dx == None :
		return None
	
		
	#  反復計算
	f1, f2 = func(0, bz, straight, x, base, 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, straight, [x[0] - dx[0], x[1]], base, size))/dx[0]
		J_01 = (f1 - func(1, bz, straight, [x[0], x[1] - dx[1]], base, size))/dx[1]
		J_10 = (f2 - func(2, bz, straight, [x[0] - dx[0], x[1]], base, size))/dx[0]
		J_11 = (f2 - func(2, bz, straight, [x[0], x[1] - dx[1]], base, 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 :
			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) :
			if not straight[0] or not straight[1] :					#  直線 corner には lilitter を課さない
				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, straight, x, base, size)
		
				
		#  収束判定
		if (abs(f1) < threshold) and (abs(f2) < threshold) :
			result = True
			break													#  計算打ち切り

		if (dx[0] == 0) or (dx[1] == 0) :
			if (abs(f1) < threshold*10) and (abs(f2) < threshold*10) :
				result = True										#  収束したと見なす ( 精度はやや落ちる )
			break													#  計算打ち切り

			
				
	if not result :
		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) :
			return None
			
		else :		
			hL = get_handle(bz, straight, x, round)		#  handle 座標 list を求める
			
			if hL == None :								#  丸み角が 180度以上
				return None

			for i in range(2) :
				if straight[i] :
					x[i] = convert_param(bz[i], x[i])	#  x[i] を距離ベースから bezier parameter ベースに変換
		
			return [x, hL]								#  x[] : 	挿入ポイントの位置を示す bezier parameter list
														#  hL[] :	handle 座標 list
		
			

scene = xshade.scene()

scene = xshade.scene()

dialog_data = open_dialog()
if dialog_data != None :
	[base, size, round, copy_shape] = dialog_data

	#  active control points, 線形状データ取得
	modify_mode = scene.is_modify_mode
	is_local = (scene.local != None)					#  local mode flag
	obL2 = []
	obL = scene.active_shapes
	
	for ob in obL :
		if isinstance(ob, xshade.line) :
			if ob.dad.part_type != 1 :					#  自由曲面内の線形状は不可
				if not modify_mode :					#  全てのポイントが対象
					nop = ob.number_of_control_points
					acp = [ k for k in range(nop)]
					if copy_shape :						#  dialog で original を残す が選択されている
						ob.copy_object(None)
						ob = ob.bro
					ob.active_vertex_indices = acp		#  全てのポイントを選択状態にする
				else :
					acp = ob.active_vertex_indices
					if len(acp) >= 1 :					#  対象ポイントがある 
						if copy_shape :					#  dialog で original を残す が選択されている
							ob.copy_object(None)
							ob = ob.bro
				
				if len(acp) >= 1 :						#  選択ポイントがある 
					if not is_local :
						mx1 = matrix(ob.world_to_local_matrix)
					else :
						Mt = matrix(scene.local.transformation_matrix)
						mx0 = Mt.inverse()
						mx1 = Mt*matrix(ob.world_to_local_matrix)
						
					closed = ob.closed
					ob.activate()
					result = False
					idx = 0
					
					while len(acp) >= idx + 1 :
						nop = ob.number_of_control_points
						ac = acp[idx]					#  idx 番目の選択ポイント
						
						#  bz1, bz2 :	選択ポイント前後の bezier line
						if ac == 0 :
							if not closed :
								idx += 1
								continue
							else :
								idx1 = nop - 1
								idx2 = 0

						elif ac == nop - 1 :
							if not closed :
								idx += 1
								continue
							else :
								idx1 = ac - 1
								idx2 = ac

						else :
							idx1 = ac - 1
							idx2 = ac

						bz1 = labo.get_bezier(xshade, idx1)
						bz2 = labo.get_bezier(xshade, idx2)
						if is_local :
							mx0.transform(bz1)
							mx0.transform(bz2)
			
			
						#  指定するポイントの corner 形状 check ( 角の有無を確認 )
						v1 = labo.bezier_line_tangent(bz1, 1)
						v2 = labo.bezier_line_tangent(bz2, 0)
						
						if not ((v1.abs2() != 0 and v2.abs2() != 0) and (v1.dot(v2) <= 0.9999)) :	#  corner  の丸め不可
							idx += 1
							continue
							
						else :
							#  bz1, bz2 から丸め処理に関するデータを取得
							data = round_corner([bz1, bz2], base, size, round, False)
							if data == None :	
								data = round_corner([bz1, bz2], base, size, round, True)

							if data == None :								#  解が求まらなかった
								idx += 1
								continue
							
							result = True	
							[[out_param, in_param], [outH, inH]] = data		#  out_param, in_param :	挿入ポイントの位置を示す bezier parameter list
																			#  outH, inH :				handle 座標 list
								
							#  丸め処理
							#  control poin 追加
							if (out_param != 0) and (in_param != 1) :
								k = 1
								nop += 2
								if idx1 > idx2 :
									ob.insert_control_point(idx1 + out_param)
									ob.insert_control_point(idx2 + in_param)
								else :
									ob.insert_control_point(idx2 + in_param)
									ob.insert_control_point(idx1 + out_param)
								
							elif (out_param == 0) and (in_param != 1) :
								k = 0
								nop += 1
								ob.insert_control_point(idx2 + in_param)
								
							elif (out_param != 0) and (in_param == 1) :
								k = 1
								nop += 1
								ob.insert_control_point(idx1 + out_param)
							
							else :
								k = 0
							
							
							#  handle 設定,  link 設定
							if idx1 > idx2 :
								out_p = nop - 1
								in_p = 1

							else :
								out_p = idx1 + k
								in_p = idx2 + 1 + k
								if in_p == nop :
									in_p = 0
									
							if out_param == 0 :
								out_linked = ob.control_point(out_p).linked
							if in_param == 1 :
								in_linked = ob.control_point(in_p).linked
									
							ob.control_point(out_p).out_handle = outH*mx1
							ob.control_point(in_p).in_handle = inH*mx1	
							
							if out_param != 0 :
								ob.control_point(out_p).linked = True
							else :
								ob.control_point(out_p).linked = out_linked
								
							if in_param != 1 :
								ob.control_point(in_p).linked = True
							else :
								ob.control_point(in_p).linked = in_linked
							
							
							#  corner point 削除
							acp = ob.active_vertex_indices			#  選択ポイントを再取得
							ob.remove_control_point(acp[idx])		#  corner point 削除
							acp = ob.active_vertex_indices			#  選択ポイントを再取得
						
					if result :
						obL2.append(ob)
					else :
						if copy_shape :						#  dialog で copy を残す が選択されている
							ob.remove()
						
						
	if len(obL2) >= 1 :
		scene.active_shapes = obL2
	else :
		scene.active_shapes = obL

#11

はじめまして。

Shade歴15年の者です。現在はWin版の17proを使用しています(Winodows10です)。
メーカーで業務使用しております。

「角の丸め」機能は頻繁に使ってきましたが、 隣り合う辺の角度が90°でない場合、 指定した半径Rと違うRになることを、最近になって初めて知りました。困っています。

本記事を拝見し、
・02 vec3
・03 matrix
・04 quaternion module
・05 labo module
・40 script40-12

をインストールしました。
02と40は各々をメモ帳にコードをコピペして、unicodeとして拡張子.pyで保存。
その他はDropBOXからDLしてきました。
それら全ての.pyファイルは、
C:\Program Files\Shade3D\Shade3D ver.17\bin\scripts
フォルダに入れて、Shadeを再起動。
簡単なテスト形状を選択し、スクリプトから「script40-12」を実行したのですが、メッセージウィンドウには以下のものが出てしまいました。

File “”, line 1
i
^
SyntaxError: invalid syntax

使い方が間違えているのでしょうか?
「ShallWeCustomize」などはDLして一部をインストールして使えているのですが・・。


#12

masayuki さん、こんにちわ

あいにくと Win 環境については全くの無知でして、masayuki さんの行った手続きが正しいのかどうかも判断がつきません。

Win ユーザーの方で、どなたか詳しい方がいらっしゃいましたなら、代わってアドバイスをいただけないでしょうか ?