Shade3D 公式

12 Bezier Patch [ Shade Labo ]


#1

12 - 1 Bezier Patch と Shade Bezier 曲面


bezier line が 4 つの制御点で定義されたように、bezier surface は 4 x 4 = 16 個の制御点で定義され、これを bezier patch と呼びます。

     [ fig 12 - 1 ]


     [ fig 12 - 2 ]


一方 Shade の bezier 曲面は U, V 各方向に 2 つずつ、計4本の bezier line で構成され、その制御点総数は12個です。

bezier patch 16個中、中央の4つについては、定義されていません。

16個もの変数を楽に制御できるようなユーザーインターフェースはほとんど無理であり、Shade では [ fig 12 - 1 ] の A, B, C, D 4つの制御点を他の制御点によって決定されるようにしています。

つまり、16個の独立変数の内、4つが従属変数として表されるよう別途定義し、12個の独立変数で曲面を定義するように変更している ということです。

また、こうすることで 2つの bezier 曲面が滑らかに連続するようにセットするのが簡単になります。


#2

12 - 2   4つの従属変数


Shade では [ fig 12 - 1 ] の制御点 A の座標は 3 つの座標値 P, H, h を用いて次のように定義されます。( B~D も同様 )

          [ 式 12 - 1 ]


          [ fig 12 - 3 ]


[ script 12 - 1 ] は以上の検証のための script です。

Shade 上で選択された自由曲面の指定するブロックの bezier patch を線形状群として出力するもので、[ 式 12 - 1 ] で求めたものと、DXF ファイル経由で得たものの2種類を出力し、比較します。


検証結果
               [ fig 12 - 4 ]


< script 実行の前に >

bezier patch の16個の制御点の内、従属変数とした A, B, C, D 4つの制御点座標を得る機能は script には用意されていませんが、DXF エクスポートで「 自由曲面としてエクスポート 」すると、16個の制御点座標を入手できます。

この script を実行する前に予め DXF エクスポートのプロパティ設定を次の要領でマニュアルで行っておく必要があります。

  • メニュー > ファイル > エクスポート >DXF を選択

  • DXFエクスポート設定欄の「 ベジェ曲面 」にチェックを入れる

  • OK ボタンを押す

  • 保存のためのダイアログが現れるので「 キャンセル 」ボタンを押す

一度このプロパティを設定しておけば、Shade を再起動してもしばらくはその設定が継続されたままになります。

     

     [ fig 12 - 4 ]


[ script 12 - 1 ]

import labo

#  bet_bezier_patch(bP as vec3 matrix)
#  bz 内の4つの未定義 bezier patch 座標をセット
#
def set_bezier_patch(bP) :
	for [[i, j], [ii, jj]] in [[[1, 1],[-1, -1]],[[1, 2], [-1, 1]], [[2, 1], [1, -1]], [[2, 2], [1, 1]]] :
		p0 = bP[i + ii][j + jj]
		p1 = bP[i + ii][j]
		p2 = bP[i ][j + jj]
		bP[i][j] = p1 + p2 - p0
			


scene = xshade.scene()

#  bezier patch を求める自由曲面のブロック指定
m = 0
n = 0

#  選択自由曲面の m, n ブロックの12個の bezier patch 制御点座標を取得
bP = labo.get_bezier_patch_base(xshade, m, n) 	

#  4つの未定義 bezier patch 座標をセット
set_bezier_patch(bP)

#  選択自由曲面の m, n ブロックの bezier patch を DXF 経由で取得
bP2 = labo.get_bezier_patch_by_DXF(xshade, m, n)

scene.create_part()
labo.make_bezier_surface(xshade, bP)		#  当該ブロックを自由曲面として出力
	
#  bezier patch  bP を出力
labo.make_bezier_patch(xshade, bP, 'bezier patch')

#  bezier patch  bP2 を出力
labo.make_bezier_patch(xshade, bP2, 'bezier patch by DXF')

scene.select_parent(1)

16 Bezier Patch 分割 [ Shade Labo ]
#3

12 - 3 Patch 補正


[ script 12 - 1 ] を球体状の自由曲面に適用すると、極の部分で異なった結果になります。

               [ fig 12 - 4 ]


極部分のコントロールポイントには交差方向のハンドルがありませんから [ 式 12 - 1 ] に従えば、上図中央のようになりますし、DXF で与えられる bezier patch の方が滑らかな球面を構成しそうだということも感覚的に予想できます。

Shade ではハンドルが出ていない場合に補正を加えて赤い patch のように修正します。




この補正は次のようになっています。

  • bezier patch のアンカーポイント P において、そこから伸びるハンドル h0 - P に長さがなく( *1 )、かつ、その交差方向のハンドル h1 - P に長さがある場合( *2 )、奥にあるハンドル H - Q の 1/2 長さのベクトルを h1 に加える。

          [ 式 12 - 2 ]

  • ハンドル長さの 0 判定は次による

     *1 の判定では bounding box size x 1/10000 以下
     *2 の判定では bounding box size x 1/1000 以下


なお、bounding box size( X, Y, Z 方向の box size の和 ) は 12個の制御点座標から求められるもので、xshade.scene().active_shape().bounding_box_size で得られるものとは異なります。

          [ fig 12 - 5 ]


よって bezier patch を求める関数は次のようになり、labo module に登録してあります。


[ scripr 12 - 2 ]

#  get_bezier_patch(xshade as xshade, m as int, n as int, uuid as string = None ) as vec3 matrix
#	自由曲面の指定する区間 m, n ( 0基数 ) の bezier patch 座標を vec3 matrix として返す
#	デフォルトでは選択自由曲面、uuid に Shade で取得した自由曲面の uuid を渡せば、そのデータを取得
#
def get_bezier_patch(xshade, m, n, uuid = None) :
	bP = get_bezier_patch_base(xshade, m, n, uuid) 		#  12 個の bezier pach 座標を取得
	if bP == None :
		return None
		
	bb = vec3_bounding_box_size(bP)						#  bP の bounding box size
	epsilon1 = bb[3]/10000								#  0 handle 判定しきい値
	epsilon2 = bb[3]/1000								#  0 handle 判定しきい値
	
	#  4 つの bezier patch 座標をセット
	for [[i, j], [ii, jj]] in [[[1, 1],[-1, -1]],[[1, 2], [-1, 1]], [[2, 1], [1, -1]], [[2, 2], [1, 1]]] :
		p0 = bP[i + ii][j + jj]
		p1 = bP[i + ii][j]
		p2 = bP[i ][j + jj]

		v1 = p1 - p0
		v2 = p2 - p0
		if (v1.abs() <= epsilon1) and (v2.abs() > epsilon2) :
			p1 = p0 + (bP[i - 2*ii][j] - bP[i - 2*ii][j + jj])/2
		if (v2.abs() <= epsilon1) and (v1.abs() > epsilon2) :
			p2 = p0 + (bP[i][j - 2*jj] - bP[i + ii][j - 2*jj])/2
	
		bP[i][j] = p1 + p2 - p0
			
	return bP

[ script 12 - 3 ] ~ [ script 12 - 5 ] でこれを検証します。
[ script 12 - 3 ]
import labo
from vec3 import *


scene = xshade.scene()
scene.create_part

for h in [4, 4.0001] :		#  h1, h2 : handle  長さ
	#  sample 自由曲面		
	bz = []
	bz.append([[0, 10000, 0], [0, 10000, 5000], [0, 5000, 10000], [0, 0, 10000]])
	bz.append([[h, 10000, 0], None, None, [5000, 0, 10000]])
	bz.append([[5000, 10000, -5000], None, None, [15000, 0, 0]])
	bz.append([[5000, 10000, -5000], [13000, 10000, -5000], [15000, 5000, -5000], [15000, 0, -5000]])
	bz = vec3list(bz)

	#  bz の bounding box size と handle size の比
	bb = labo.vec3_bounding_box_size(bz)
	ratio = h/bb[3]					

	scene.create_part(str(ratio))

	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)

	#  選択自由曲面の 0 0 ブロックの bezier patch 制御点座標を取得
	bP = labo.get_bezier_patch(xshade, 0, 0) 	

	#  選択自由曲面の 0, 0 ブロックの bezier patch を DXF 経由で取得
	bP2 = labo.get_bezier_patch_by_DXF(xshade, 0, 0)

	#  bezier patch  bP を出力
	labo.make_bezier_patch(xshade, bP, 'bezier patch')

	#  bezier patch  bP2 を出力
	labo.make_bezier_patch(xshade, bP2, 'bezier patch by DXF')

	scene.select_parent(1)
	
scene.select_parent(1)

[ script 12 - 4 ]
import labo
from vec3 import *


scene = xshade.scene()
scene.create_part

for [h1, h2] in [[4, 40], [4, 40.001], [40, 4], [40.001, 4]] :		#  h1, h2 : handle  長さ
	#  sample 自由曲面		
	bz = []
	bz.append([[0, 10000, 0], [0, 10000, h2], [0, 5000, 10000], [0, 0, 10000]])
	bz.append([[h1, 10000, 0], None, None, [5000, 0, 10000]])
	bz.append([[5000, 10000, -5000], None, None, [15000, 0, 0]])
	bz.append([[5000, 10000, -5000], [13000, 10000, -5000], [15000, 5000, -5000], [15000, 0, -5000]])
	bz = vec3list(bz)

	#  bz の bounding box size と handle size の比
	bb = labo.vec3_bounding_box_size(bz)
	ratio1 = float(h1)/bb[3]					
	ratio2 = float(h2)/bb[3]
	
	if ratio1 >=1e-3 :
		t1 = str(ratio1*1000) + 'e-3'
	else :
		t1 = str(ratio1*10000) + 'e-4'	
	if ratio2 >=1e-3 :
		t2 = str(ratio2*1000) + 'e-3'
	else :
		t2 = str(ratio2*10000) + 'e-4'	

	scene.create_part(t1 + '_' +  t2)

	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)

	#  選択自由曲面の 0 0 ブロックの bezier patch 制御点座標を取得
	bP = labo.get_bezier_patch(xshade, 0, 0) 	

	#  選択自由曲面の 0, 0 ブロックの bezier patch を DXF 経由で取得
	bP2 = labo.get_bezier_patch_by_DXF(xshade, 0, 0)

	#  bezier patch  bP を出力
	labo.make_bezier_patch(xshade, bP, 'bezier patch')

	#  bezier patch  bP2 を出力
	labo.make_bezier_patch(xshade, bP2, 'bezier patch by DXF')

	scene.select_parent(1)
	
scene.select_parent(1)

[ script 12 - 5 ]
import labo
from vec3 import *


scene = xshade.scene()
scene.create_part

for h in [5.321, 5.322] :		#  h1, h2 : handle  長さ
	#  sample 自由曲面		
	bz = []
	bz.append([[0, 10000, 0], [0, 10000, 5000], [0, 5000, 10000], [0, 0, 10000]])
	bz.append([[h, 10000, 0], None, None, [5000, 0, 10000]])
	bz.append([[5000, 10000, -5000], None, None, [15000, 0, 0]])
	bz.append([[5000, 10000, -5000], [13000, 10000, -5000], [15000, 5000, -5000], [15000, 0, -5000]])
	bz = vec3list(bz)

	scene.create_part()

	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)
	
	#  自由曲面を回転
	scene.move_object([0, 0, 0], None, [0, -115, 0], None)
	scene.move_object([0, 0, 0], None, [40, 0, 0], None)
	scene.move_object([0, 0, 0], None, [0, 0, 48], None)

	#  選択自由曲面の 0 0 ブロックの bezier patch 制御点座標を取得
	bP = labo.get_bezier_patch(xshade, 0, 0) 	

	#  選択自由曲面の 0, 0 ブロックの bezier patch を DXF 経由で取得
	bP2 = labo.get_bezier_patch_by_DXF(xshade, 0, 0)

	#  bezier patch  bP を出力
	labo.make_bezier_patch(xshade, bP, 'bezier patch')

	#  bezier patch  bP2 を出力
	labo.make_bezier_patch(xshade, bP2, 'bezier patch by DXF')

	#  bz の bounding box size と handle size の比
	bb = labo.vec3_bounding_box_size(bP)
	ratio = h/bb[3]
	scene.select_parent(1)
	scene.active_shape().name = str(ratio)
	
scene.select_parent(1)

#4

EN:
There seems to be more going on with curved surface generation than what is shown in this post, especially since the release of Shade3D 17.1.

In short, I detect noticeable deviations from freeform surfaces obtained by Equation 12-1 and the polygon output from Shade. None of the vertices of the polygon output lie on the surfaces obtained from Equation 12-1, save for the boundary curves. Given a surface with an average bounding box of 950 mm, the surface obtained by Equation 12-1 deviates from the vertices of Shade’s polygon output by as much as 5mm.

JA:
特にShade3D 17.1がリリースされて以来、この記事で示されているものよりも湾曲したサーフェスの生成が多く発生しているようです。

つまり、式12-1で得られた自由曲面からの目立つ偏差とシェードのポリゴン出力を検出します。 ポリゴン出力の頂点のいずれも、式12-1で得られたサーフェス上にはなく、境界曲線のために保存されていません。 平均外接矩形が950mmの表面が与えられると、式12-1によって得られた表面は、シェードのポリゴン出力の頂点から5mmだけずれている。


#5

Hi, thank you for your advice.

I confirmed the problem, and found the below.



Shade pythn script problem ( Shade 16, 17, 18 )

Shade script “xshade.scene().save_DXF()” doesn’t support file DXF export setting properties.

The check of “Bezier curved surface” doesn’t work.

I will revise related scripts.

pic%201



Shade 17 / 18 changed Bezier patch base

< Shade 15 / 16 >
Bezier patches by DXF file and rendering base are different.

< Shade 17 / 18 >
Bezier patches by DXF file and rendering base are the same, but the patches are different between Shade 15/16 and Shade 17/18.


I guess that Shade 17/18 changed DXF file library and rendering patch library due to NURBS support.

It takes time, but I will examine the Bezier Patch and report it.


#6

I also noticed that curved surfaces render different than if they were converted to polygon mesh objects. Converting to a polygon mesh at higher subdivision levels reveals
Both are rendered at subdivision level 4 in Shade 12.

I guess results like these, when converting to a polygon mesh, is what prompted the change to the surface generation algorithm in Shade 17.1?


#7

Ja, I’ve recognized the problem of surface subdivision at rendering.

We have two major problems to be solved.

One is bezier patch interpolate alogorithm of Sade 17 and the other is surface subdivision algorithm at rendering.

The former is already found out.

The algorithm is quite difficult and need illustration to explain.

pic%2016

The latter isn’t just for Shade 17, but for any Shade versions.

I have no idea yet.

Anyway I’m already writing a draft of new Shade Labo topic, please give me some time.


#8

Some topics will be posted. ( sorry, in Japanese )



< 12 - 4 Specifications change of Shade 16 and later >

It shows summary of specifications change and modified labo module function.

  • Shade 16 threshold value for zero handle
  • Shade 17 threshold value for zero handle
  • Shade 17 bezier patch interpolate algorithm

Modified function ( labo module )

get_bezier_patch ( xshade as xshade, m as int, n as int, uuid as string = None, legacy as bool = None )

     legacy : specify bezier patch interpolate algorithm

     legacy = True : Shade 15 base
     legacy = False : Shade 17 base
     legacy = None : as per current Shade version ( if Shade 16, Shade 15 base is applied )



< 12 - 5 New script for Shade 16 and later >

Shade 16 and later don’t support DXF file export setting properties.

get_bezier_patch_by_DXF_2( ) is shown as a substitute of get_bezier_patch_by_DXF( ).

The script require some manual operations.

  • At first, export DXF file by manual
  • Run the script
  • Script requests input of DXF file path



< 12 - 6 Verification of patch interpolate algorithm ( Shade 16 ) >

< 12 - 7 Patch interpolate algorithm ( Shade17 and later ) >

< 12 - 8 Verification of patch interpolate algorithm ( Shade17 and later ) >


= = = = = = = = = = = = = = = = = = = = = = = = = = =

Shade Bezier surface subdivision at rendering is different from normal method.

It takes much time to clarify the Shade method and I will report a new topic 16.5 Bezier Surface Subdivision at Rendering


Shade 15 ( flat shading )      rendering of brazier surface / bezier patch subdivision / both

Shade_15%201 Shade_15%202 Shade_15%203


Shade 17 ( flat shading )      rendering of brazier surface / bezier patch subdivision / both

Shade_17%201 Shade_17%202 Shade_17%203


bezier surface

1


polygon mesh

black : subdivision at rendering
red : normal patch subdivision

left : Shade 15 / right : Shade 17

2 3


#9

12 - 4  Shade 仕様の変更に関して ( Shade 16 以降 )



< Shade 16>


12 - 3 項で述べた patch 補正の際の 長さのない handle の判定基準が次のように変更されています。

      *1  当該ハンドルの長さの 0 判定
      *2  交差ハンドルの長さの 0 判定


     Shade 15

     レンダリング時のポリゴン分割

          判定時の基準座標:  world 座標
          *1 :  bounding box size x 1e-4 未満
          *2:   bounding box size x 1e-4 未満

     DXF file 出力時

          判定時の基準座標: local 座標
           *1 :   bounding box size x 1e-4 以下
           *2 :   bounding box size x 1e-3 以下


     Shade 16

     レンダリング時のポリゴン分割

          判定時の基準座標: world 座標
          *1 :   bounding box size x 1e-6 未満
          *2 :   bounding box size x 1e-6 未満

     DXF file 出力時

          判定時の基準座標: local 座標
           *1 :   bounding box size x 1e-6 以下
           *2 :   bounding box size x 1e-5 未満


Shade 16 用の Patch 補正検証 script は 12 - 6 項を参照。



< Shade 17 以降>


bezier patch 中央4点の 内挿法 及び 長さのない handle 判定基準 が全面的に変更されており、詳細は 12 - 7 項を参照。



< labo module 改訂 ver. 1.7 >


labo に登録している自由曲面から bezier ptch を取得する関数 get_bezier_patch( ) を改訂し、Shade 17 ベースの patch も得られるようにしました。


labo.get_bezier_patch ( xshade as xshade, m as int, n as int, uuid as string = None, regacy as bool = None )

      legacy :   内挿基準を指定

     regacy = True :       Shade 15 ベース
     regacy = False :       Shade 17 ベース
     regacy = None :       使用する Shade version に合わせる( Shade 16 の場合は Shade 15 ベース )

     長さのない handle 判定基準を レンダリング時のポリゴン分割 のそれに合わせる




/ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /




12 - 5   Script 不具合 / 変更への対策 ( Shade 16 以降 )



Shade 16 以降 で 12 - 2 項 < script 実行の前に > で述べた DXF export property のマニュアルによる 事前の設定 が効かなくなっており、DXF 経由の bezier patch 取得が不可 となっています。( Mac で確認 )

#  選択自由曲面の 0, 0 ブロックの bezier patch を DXF 経由で取得
bP2 = labo.get_bezier_patch_by_DXF(xshade, 0, 0)			#  Shade 16 以降で不具合

さらに Shade 18 から DXF file の header 構成が変更されています。

そこで新たな関数 get_bezier_patch_by_DXF_2( ) を用意しました。( labo module には未登録 )

この関数は次の手順で使用し、vec3 matrix で与えられる bezier patch を取得します。



1) 事前にマニュアル操作により DXF file を出力

        出力 property を次のように設定

     

      [ fig 12 - 6 ]



2) script 実行

3) script が開く DXF file path 入力欄 に path を入力

     pic%2015      [ fig 12 - 7 ]




[ script 12 - 6 ]      Shade 16 用 検証用 script

まず 自由曲面をマニュアルで DXF file に出力し、その自由曲面を選択したまま script を実行して path を入力、DXF file 経由の bezie patch と 自由曲面をポリゴン分割したものが出力される。

#  get_bezier_patch_by_DXF_2(m as int, n as int) as vec3 matrix	
#
# 	自由曲面の指定する区間 m, n ( 0基数 )の bezier patch 座標を 予め保存しておいた DXF file 経由で vec3 matrix として返す
	
def get_bezier_patch_by_DXF_2(m, n) :	
	import os

	scene = xshade.scene()
	
	bP = labo.get_bezier_patch_base(xshade, m, n, None)	 #  選択自由曲面の区間 m, n の bezier patch 座標を matrix として取得
	if bP == None :
		return None
		
		
	#  dialog uuid は Online GUID Generator により生成		https://www.guidgenerator.com
	dialog = xshade.create_dialog_with_uuid('ab2a1365-0b51-4cf2-8577-c42d5881d3da')

	file_path = dialog.append_string('DXF file path')

	if dialog.ask('DXF file path') :
		path = dialog.get_value(file_path)				#  予めマニュアルで保存しておいた DXF file の path を取得
	else :
		return None
	

	if not os.path.exists :
		print ' 指定した file が見つからない - get_bezier_patch_by_DXF_2'
		return None
	else :
		f = open(path)									#  DXF file 読み込み
		t = f.read()
		f.close()

	
	if shade.version >= 510000 :						#  Shade 18 以降
		idx1 = 51
		idx2 = 59
		k = 63
	else :												#  Shade 17 まで
		idx1 = 11
		idx2 = 19
		k = 23

	s = t.split()
	if (s[idx1] !='16') or (s[idx2] != 'VERTEX') :
		print 'DXF エクスポートの設定として、「ベジェ曲面」にチェックを入れておいて下さい。'
		return None
	else :
		bP = matrix()
		for i in range(4) :
			v = []
			for j in range(4) :
				v.append(vec3(float(s[k]), float(s[k + 4]), -float(s[k + 2])))	#  DXF file の座標は傾いている
				k = k + 12
			bP.append(v)

		return bP



import labo
from vec3 import *
from matrix import *


scene = xshade.scene()
obL = []

#  選択自由曲面の copy を作成、ポリゴン分割を実施
ob = scene.active_shape()
scene.copy_object(None, None, None, None)
ob2 = scene.active_shape()
ob2.convert_to_polygon_mesh_with_subdivision_level(2)
ob3 = scene.active_shape()


#  選択自由曲面の 0, 0 ブロックの bezier patch を DXF 経由で取得
ob.activate()
bP = get_bezier_patch_by_DXF_2(0, 0)

#  bezier patch  bP を出力
if bP != None :
	labo.make_bezier_patch(xshade, bP, 'bezier patch by DXF')
	obL.append(scene.active_shape())

	
obL.append(ob3)
scene.active_shapes = obL



/ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /




12 - 6   長さのない Handle 判定基準 検証( Shade 16 )



12 - 3 項に記した [ script 12 - 3 ] ~ [ script 12 - 5 ] では sample 形状の出力と bezier patch 出力を同時に行っていますが、ここでは sample 形状の出力のみの script とします。

これらの script で形状出力し、マニュアルで DXF file を出力、上記の [ script 12 - 6 ] で検証します。

「 長さのない handle 判定 」が 12 - 4 項で示したように DXF file と ポリゴン分割で異なっていることが示されます。



[ script 12 - 3a ]     sample 形状出力

import labo
from vec3 import *


scene = xshade.scene()
obL = []

for h in [0.039, 0.04, 0.041] :		#  h : handle  長さ
	#  sample 自由曲面		
	bz = []
	bz.append([[0, 10000, 0], [0, 10000, 5000], [0, 5000, 10000], [0, 0, 10000]])
	bz.append([[h, 10000, 0], None, None, [5000, 0, 10000]])
	bz.append([[5000, 10000, -5000], None, None, [15000, 0, 0]])
	bz.append([[5000, 10000, -5000], [13000, 10000, -5000], [15000, 5000, -5000], [15000, 0, -5000]])
	bz = vec3list(bz)

	#  bz の bounding box size と handle size の比
	bb = labo.vec3_bounding_box_size(bz)
	ratio = h/bb[3]					

	ob = scene.create_part(str(ratio))

	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)

	
	scene.select_parent(1)
	obL.append(ob)
	
scene.active_shapes = obL


[ script 12 - 4a ]     sample 形状出力

import labo
from vec3 import *


scene = xshade.scene()
obL = []

for [h1, h2] in [[0.039, 0.39], [0.039, 0.4], [0.039, 0.039], [0.039, 0.04]] :		#  h1, h2 : handle  長さ
	#  sample 自由曲面		
	bz = []
	bz.append([[0, 10000, 0], [0, 10000, h2], [0, 5000, 10000], [0, 0, 10000]])
	bz.append([[h1, 10000, 0], None, None, [5000, 0, 10000]])
	bz.append([[5000, 10000, -5000], None, None, [15000, 0, 0]])
	bz.append([[5000, 10000, -5000], [13000, 10000, -5000], [15000, 5000, -5000], [15000, 0, -5000]])
	bz = vec3list(bz)

	#  bz の bounding box size と handle size の比
	bb = labo.vec3_bounding_box_size(bz)
	ratio1 = float(h1)/bb[3]					
	ratio2 = float(h2)/bb[3]
	
	if ratio1 >1e-6 :
		t1 = str(ratio1*100000) + 'e-5'
	else :
		t1 = str(ratio1*1000000) + 'e-6'	
	if ratio2 >1e-6 :
		t2 = str(ratio2*100000) + 'e-5'
	else :
		t2 = str(ratio2*1000000) + 'e-6'

	ob = scene.create_part(t1 + ' - ' +  t2)

	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)

	
	scene.select_parent(1)
	obL.append(ob)
	
scene.active_shapes = obL


[ script 12 - 5a ]     sample 形状出力

import labo
from vec3 import *


scene = xshade.scene()
obL = []

h1 = 0.04*1.325
h2 =  0.04001*1.335


for h in [h1, h2] :		#  h1, h2 : handle  長さ
	#  sample 自由曲面		
	bz = []
	bz.append([[0, 10000, 0], [0, 10000, 5000], [0, 5000, 10000], [0, 0, 10000]])
	bz.append([[h, 10000, 0], None, None, [5000, 0, 10000]])
	bz.append([[5000, 10000, -5000], None, None, [15000, 0, 0]])
	bz.append([[5000, 10000, -5000], [13000, 10000, -5000], [15000, 5000, -5000], [15000, 0, -5000]])
	bz = vec3list(bz)

	ob = scene.create_part()

	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)
	
	#  自由曲面を回転
	scene.move_object([0, 0, 0], None, [0, -115, 0], None)
	scene.move_object([0, 0, 0], None, [40, 0, 0], None)
	scene.move_object([0, 0, 0], None, [0, 0, 48], None)

	#  選択自由曲面の 0 0 ブロックの bezier patch 制御点座標を取得 ( 中央4点は除く )	
	bP = labo.get_bezier_patch_base( xshade, 0, 0)

	#  bz の bounding box size と handle size の比
	bb = labo.vec3_bounding_box_size(bP)
	ratio = h/bb[3]
	
	scene.select_parent(1)
	scene.active_shape().name = str(ratio)
	
	obL.append(ob)
	
scene.active_shapes = obL



/ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /




12 - 7   Bezier Patch 内挿法( Shade 17 以降 )



Shade 17 より、bezier patch 中央4点の 内挿法 及び 長さのない handle 判定基準 が全面的に変更されています。

     pic%2016      [ fig 12 - 8 ]




< bezier patch script >


labo に登録している自由曲面から bezier ptch を取得する関数 get_bezier_patch( ) を改訂、Shade 17 ベースの patch も得られるようにしました。


labo.get_bezier_patch ( xshade as xshade, m as int, n as int, uuid as string = None, regacy as bool = None )

      legacy :   内挿基準を指定

     regacy = True :       Shade 15 ベース
     regacy = False :       Shade 17 ベース
     regacy = None :       使用する Shade version に合わせる( Shade 16 の場合は Shade 15 ベース )

     長さのない handle 判定基準を レンダリング時のポリゴン分割 のそれに合わせる




< 長さのない handle 判定基準 >


     判定時の基準座標:

           レンダリング時のポリゴン分割 : world 座標
           DXF file 出力時 : local 座標


     判定基準:

           bounding box size x 1e-5 以下




< Patch 内挿法 - Step 1 >

< Case - 1 >

     pic%2017
                pic%2018      [ fig 12 - 9 ]


< Case - 2 , 3 >

     pic%2019
                pic%2020      [ fig 12 - 10 ]


< Case - 4 >

     pic%2021
                pic%2022      [ fig 12 - 11 ]


< Case - 4 > において、| U1 | / | U3 | には limitter が与えられていない。

よって 0 < | U3 | << | U1 | なるとき、| U1 | / | U3 | は極めて大きな値となり、 P11 座標は bezier patch の bounding box 外の遙か遠くに位置することになってしまう。




< Patch 内挿法 - Step 2 >

     


     [ fig 12 - 12 ]






よって bezier patch を求める関数は次のようになり、labo module に登録してあります。

[ scripr 12 - 7 ]

#  set_bezier_patch_2(bP as vec3 matrix, bb as float list)
#	中央4点の control point 座標未定義の patch base bP の未定義座標をセット ( Shade 17 以降 )
#
def set_bezier_patch_2(bP) :
	if bP == None :
		return
		
	bb = vec3_bounding_box_size(bP)			#  bP の bounding box size
	epsilon = bb[3]/100000						#  0 handle 判定しきい値
	
	#  overlap :	patch の一点収束 ( 極 ) 存在状態
	pL = []
	pL.append(bP[0])										#   第一行
	pL.append([bP[0][0], bP[1][0], bP[2][0], bP[3][0]])		#   第一列
	pL.append([bP[0][3], bP[1][3], bP[2][3], bP[3][3]])		#   第四列
	pL.append(bP[3])										#   第四行
	
	overlap = []
	for p in pL :
		result = True
		for i in range(1,4) :
			v = p[i] - p[i - 1]
			if v.abs() > epsilon :
				result = False						
				break
		if result :
			overlap.append(True)				#  一点収束している
		else :
			overlap.append(False)				#  一点収束していない
			
	
	#  4 つの bezier patch 座標をセット ( step 1 )
	for [[i, j], [ii, jj], k1, k2] in [  [[0, 0],[1, 1], 0, 1]  ,  [[0, 3],[1, -1], 0, 2]  ,  [[3, 0], [-1, 1], 3, 1]  ,  [[3, 3], [-1, -1], 3, 2]  ] :
		
		p00 = bP[i][j]
		
		p01 = bP[i][j + jj]
		p10 = bP[i + ii][j]
		
		p30 = bP[i + 3*ii][j]
		p31 = bP[i + 3*ii][j + jj]
		
		p03 = bP[i][j + 3*jj]
		p13 = bP[i + ii][j + 3*jj]

		u1 = p01 - p00
		u2 = p31 - p30
		v1 = p10 - p00
		v2 = p13 - p03	
		
		
		#  u, v
		if overlap[k1] and overlap[k2] :					#  行, 列 両方向が一点収束 ( 極 )
			u = vec3(0, 0, 0)
			v = vec3(0, 0, 0)
		
		elif overlap[k1] :									#  行方向が一点収束 ( 極 )
			p20 = bP[i + 2*ii][j]
			v3 = p20 - p00
			r = v3.abs()
			if r > epsilon :
				u = u2*v1.abs()/r
			else :
				u = vec3(0, 0, 0)
			v = v1
			
		elif overlap[k2] :									#  列方向が一点収束 ( 極 )
			p02 = bP[i][j + 2*jj]
			u3 = p02 - p00
			r = u3.abs()
			if r > epsilon :
				v = v2*u1.abs()/r
			else :
				v = vec3(0, 0, 0)
			u = u1
			
		else :											#  行 / 列 方向が一点収束 ( 極 ) ではない
			if u1.abs() > epsilon :						#  行方向 handle あり
				if u2.abs() > epsilon :					#  反対側の行方向 handle あり
					u = (5*u1 + u2)/6
				else :									#  反対側の行方向 handle なし
					u = u1
			else :
				u = u2/3
				
			if v1.abs() > epsilon :						#  列方向 handle あり
				if v2.abs() > epsilon :					#  反対側の列方向 handle あり
					v = (5*v1 + v2)/6
				else :									#  反対側の列方向 handle なし
					v = v1
			else :
				v = v2/3
		
		bP[i + ii][j + jj] = p00 + u + v	
			
			
	#  4 つの bezier patch 座標をセット ( step 2 )	
	vL = [vec3(0, 0, 0), vec3(0, 0, 0), vec3(0, 0, 0), vec3(0, 0, 0)]		#  座標補正 vector
	
	pL = []
	pL.append([bP[0][3], bP[1][3], bP[0][0], bP[1][0], 1, 0, overlap[0]])
	pL.append([bP[0][0], bP[0][1], bP[3][0], bP[3][1], 0, 2, overlap[1]])
	pL.append([bP[3][0], bP[2][0], bP[3][3], bP[2][3], 2, 3, overlap[3]])
	pL.append([bP[3][3], bP[3][2], bP[0][3], bP[0][2], 3, 1, overlap[2]])
	
	for [p1, h1, p2, h2, k1, k2, overlapping] in pL :
		if not overlapping :
			u1 = h1 - p1
			u2 = h2 - p2
			U1 = vec3(u1)
			U2 = vec3(u2)
			if (U1.norm() > epsilon) and (U2.norm() > epsilon) :
				if (U1*U2).abs() > 1e-8 :
					U = U1 - U2
					U.norm()
					L1 = u1.dot(U)
					L2 = u2.dot(U)
					v = -(L1 + L2)/3*U
			
					vL[k1] = vL[k1] + v
					vL[k2] = vL[k2] + v
			
	bP[1][1] = bP[1][1] + vL[0]
	bP[1][2] = bP[1][2] + vL[1]
	bP[2][1] = bP[2][1] + vL[2]
	bP[2][2] = bP[2][2] + vL[3]



/ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /




12 - 8   Patch 内挿法 検証 ( Shade 17 以降 )



[ script 12 - 9 ] ~ [ script 12 - 13 ] で sample 形状を出力し、マニュアルで DXF file を出力、[ script 12 - 8 ] で検証します。

[ script 12 - 8 ]       検証用 script

import labo
from vec3 import *
from matrix import *



#  get_bezier_patch_by_DXF_2(m as int, n as int) as vec3 matrix	
#
# 	自由曲面の指定する区間 m, n ( 0基数 )の bezier patch 座標を 予め保存しておいた DXF file 経由で vec3 matrix として返す
	
def get_bezier_patch_by_DXF_2(m, n) :	
	import os

	scene = xshade.scene()
	
	bP = labo.get_bezier_patch_base(xshade, m, n)	 #  選択自由曲面の区間 m, n の bezier patch 座標を matrix として取得
	if bP == None :
		return None
		
		
	#  dialog uuid は Online GUID Generator により生成		https://www.guidgenerator.com
	dialog = xshade.create_dialog_with_uuid('ab2a1365-0b51-4cf2-8577-c42d5881d3da')

	file_path = dialog.append_string('DXF file path')

	if dialog.ask('DXF file path') :
		path = dialog.get_value(file_path)			#  予めマニュアルで保存しておいた DXF file の path を取得
	else :
		return None
	

	if not os.path.exists :
		print ' 指定した file が見つからない - get_bezier_patch_by_DXF_2'
		return None
	else :
		f = open(path)									#  DXF file 読み込み
		t = f.read()
		f.close()

	
	if shade.version >= 510000 :						#  Shade 18 以降
		idx1 = 51
		idx2 = 59
		k = 63
	else :												#  Shade 17 まで
		idx1 = 11
		idx2 = 19
		k = 23

	s = t.split()
	if (s[idx1] !='16') or (s[idx2] != 'VERTEX') :
		print 'DXF エクスポートの設定として、「ベジェ曲面」にチェックを入れておいて下さい。'
		return None
	else :
		bP = matrix()
		for i in range(4) :
			v = []
			for j in range(4) :
				v.append(vec3(float(s[k]), float(s[k + 4]), -float(s[k + 2])))	#  DXF file の座標は傾いている
				k = k + 12
			bP.append(v)

		return bP

	
	
		

scene = xshade.scene()
obL = []

#  選択自由曲面の copy を作成、ポリゴン分割を実施
ob = scene.active_shape()
scene.copy_object(None, None, None, None)
ob2 = scene.active_shape()
ob2.convert_to_polygon_mesh_with_subdivision_level(2)
obL.append(scene.active_shape())


#  選択自由曲面の 0, 0 ブロックの bezier patch を DXF 経由で取得
ob.activate()
bP = get_bezier_patch_by_DXF_2(0, 0)
#bP = labo.get_bezier_patch_by_DXF(xshade, 0, 0)			#  Shade 18 で script の不具合が解消されたら、これが使用可能に

#  bezier patch  bP を出力
if bP != None :
	labo.make_bezier_patch(xshade, bP, 'bezier patch by DXF')
	obL.append(scene.active_shape())
	
#  選択自由曲面の 0 0 ブロックの bezier patch 制御点座標を取得
ob.activate()
bP2 = labo.get_bezier_patch(xshade, 0, 0, None, False) 		#  Shade 17 ベースの patch	
#bP2 = labo.get_bezier_patch(xshade, 0, 0, None, True) 		#  Shade 15 ベースの patch
#bP2 = labo.get_bezier_patch(xshade, 0, 0) 					#  使用する Shade version に合わせた patch

#  bezier patch  bP2 を出力
labo.make_bezier_patch(xshade, bP2, 'bezier patch')
obL.append(scene.active_shape())

	
scene.active_shapes = obL


[ script 12 - 9 ]       sample 形状出力

import labo
from vec3 import *


scene = xshade.scene()
obL = []

scene.create_part()

#  sample 自由曲面
scale = scene.user_to_native_unit
x = 1500*scale
z = 1000*scale
hx = 1000*scale
hz = 2000/3.*scale
bb = 2*x + 2*z					#  bounding box size

for h in [hx/2, hx/5, bb*0.000011, bb*0.00001] :			#  h : handle 長さ
	bz = []
	bz.append([[-x, 0, -z], [-x + h, 0, -z], [x - hx, 0, -z], [x, 0, -z]])
	bz.append([[-x, 0, -z + hz], None, None, [x, 0, -z + hz/3]])
	bz.append([[-x, 0, z - hz], None, None, [x, 0, z - hz]])
	bz.append([[-x, 0, z], [-x + hx, 0, z], [x - hx, 0, z], [x, 0, z]])
	bz = vec3list(bz)
	
	#  bz の bounding box size と handle size の比
	bb = 2*x + 2*z
	ratio = h/bb				

	ob = scene.create_part(str(ratio))
	
	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)
	
	scene.select_parent(1)
	obL.append(ob)
	
scene.active_shapes = obL


[ script 12 - 10 ]       sample 形状出力

i mport labo
from vec3 import *

scene = xshade.scene()
obL = []

scene.create_part()

#  sample 自由曲面
scale = scene.user_to_native_unit
x = 1500*scale
z = 1000*scale
hx = 1000*scale
hz = 2000/3.*scale
bb = 2*x + 2*z					#  bounding box size

for [h1, h2] in [[bb*0.00001, bb*0.00001] , [bb*0.000011, bb*0.00001] , [bb*0.00001, bb*0.000011] , [bb*0.000011, bb*0.000011]] :			#  h1, h2 : handle 長さ
	bz = []
	bz.append([[-x, 0, -z], [-x + h1, 0, -z], [x - hx, 0, -z], [x, 0, -z]])
	bz.append([[-x, 0, -z + h2], None, None, [x, 0, -z + hz]])
	bz.append([[-x, 0, z - hz], None, None, [x, 0, z - hz]])
	bz.append([[-x, 0, z], [-x + hx, 0, z], [x - hx, 0, z], [x, 0, z]])
	bz = vec3list(bz)
	
	#  bz の bounding box size と handle size の比
	bb = 2*x + 2*z
	ratio = h/bb				

	ob = scene.create_part(str(ratio))
	
	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)
	
	scene.select_parent(1)
	obL.append(ob)
	
scene.active_shapes = obL


[ script 12 - 11 ]       sample 形状出力

import labo
from vec3 import *


scene = xshade.scene()
obL = []

scene.create_part()

#  sample 自由曲面
scale = scene.user_to_native_unit
x = 1500*scale
z = 1000*scale
hx = 1000*scale
hz = 2000/3.*scale


for h in [0.795, 0.785] :			#  h : handle 長さ
	bz = []
	bz.append([[-x, 0, -z], [-x + h, 0, -z], [x - hx, 0, -z], [x, 0, -z]])
	bz.append([[-x, 0, -z + hz], None, None, [x, 0, -z + hz]])
	bz.append([[-x, 0, z - hz], None, None, [x, 0, z - hz]])
	bz.append([[-x, 0, z], [-x + hx, 0, z], [x - hx, 0, z], [x, 0, z]])
	bz = vec3list(bz)

	ob = scene.create_part()
	
	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)
	
	scene.move_object([0, 0, 0], None, [0, -115, 0], None)
	scene.move_object([0, 0, 0], None, [40, 0, 0], None)
	scene.move_object([0, 0, 0], None, [0, 0, 48], None)
	
	#  回転後の bounding box size と handle size の比
	bz2 = labo.get_bezier_patch_base(xshade, 0, 0)
	bb = labo.vec3_bounding_box_size(bz2)
	ratio = h/bb[3]

	scene.select_parent(1)
	ob.name = (str(ratio))
	obL.append(ob)
	
scene.active_shapes = obL


[ script 12 - 12 ]       sample 形状出力

import labo
from vec3 import *


scene = xshade.scene()
#obL = []

scene.create_part()

#  sample 自由曲面
scale = scene.user_to_native_unit
x = 1500*scale
z = 1000*scale
hx = 1000*scale
hz = 2000/3.*scale
#bb = 2*x + 2*z					#  bounding box size

bz = []
bz.append([[-x, 0, -z], [-x + hx/2, 0, -z], [x - hx*1.5, 0, -z], [x, 0, -z]])
bz.append([[-x, 0, -z + hz], None, None, [x, 0, -z + hz/3]])
bz.append([[-x*1.25, 0, z - hz], None, None, [x*1.8, 0, z - hz*1.5]])
bz.append([[-x, 0, z], [-x + hx*1.5, 0, z], [x - hx/2, 0, z], [x, 0, z]])

#  sample 自由曲面を出力
labo.make_bezier_surface(xshade, bz)


[ script 12 - 13 ]       sample 形状出力

import labo
from vec3 import *


scene = xshade.scene()
obL = []

scene.create_part()

#  sample 自由曲面
scale = scene.user_to_native_unit
x = 1000*scale
z = 1000*scale
bb = 2*(x + z)								#  bounding box size

for d in [bb*0.0000099, bb*0.00001] :
	bz = []
	bz.append([[-d, 0, -z], [0, 0, -z], [0, 0, -z], [d, 0, -z]])
	bz.append([[-x*2/3, 0, 0], None, None, [x*2/3, 0, 0]])
	bz.append([[-x, 0, z*2/3], None, None, [x, 0, z*2/3]])
	bz.append([[-x, 0, z], [-x/3, 0, z], [x/3, 0, z], [x, 0, z]])
	bz = vec3list(bz)
	
	#  bz の bounding box size と handle size の比
	ratio = d/bb				

	ob = scene.create_part(str(ratio))
	
	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)
	
	scene.select_parent(1)
	obL.append(ob)
	
	
for d in [bb*0.0000099/2, bb*0.00001/2] :
	bz = []
	bz.append([[-d*1.5, 0, -z], [-d, 0, -z], [d, 0, -z], [d*1.5, 0, -z]])
	bz.append([[-x*2/3, 0, 0], None, None, [x*2/3, 0, 0]])
	bz.append([[-x, 0, z*2/3], None, None, [x, 0, z*2/3]])
	bz.append([[-x, 0, z], [-x/3, 0, z], [x/3, 0, z], [x, 0, z]])
	bz = vec3list(bz)
	
	#  bz の bounding box size と handle size の比
	ratio = d/bb				

	ob = scene.create_part(str(ratio))
	
	#  sample 自由曲面を出力
	labo.make_bezier_surface(xshade, bz)
	
	scene.select_parent(1)
	obL.append(ob)
	
	
scene.active_shapes = obL

#10

A lot to unpack :smiley:

I tested the new labo.set_bezier_patch_2 function and verified that it works.
However, I find a difference with the polygon output of Shade 17 and the new function.

The sample surface I attached has case 2 at both cross curves, where P(10)-P(00)=0 and P(13)-P(03)=0

wing1_patch_m2_n8.shd (207.1 KB)


#11

My report is just for modeling and it shows correct patch interpolate algorithm supported by Shade 17.

Rectangle polygon shapes in your sample file wing1_patch_m2_n8.shd have small difference.

It is due to the difference between normal bezier patch subdivision and bezier surface subdivision at rendering, mentioned before.

It remains unsolved.

orange : normal patch subdivision
green : subdivision at rendering

test.shd


#12

I fiddled around with the inner 4 control points of a Shade 16 patch, and got really close to the true result.

I have a feeling the actual control points are not too far off.

If it helps any, I found two interesting functions listed in the Shade 13 SDK manual:

calculate_internal_points
I believe this includes the unsolved last step, if the following footnote is any indication:
(Shade独自変形)ベジエ曲面の内部コントロールポイントを計算する。

calculate_blending_weights
Hmm… changing the weights of the inner 4 control points, creating a rational bezier surface?


#13

I’m planning an entirely visual explanation of the initial surface generation methods, for those who might not understand at the moment.
I’ve figured out Shade 17 Step 1, but I’m having trouble getting the correct result of step 2.
Below is my understanding of step 2:

What determines which point is P1 or P2? Your step 2 graphic doesn’t seem to show what Ũ1 and Ũ2 should be for each corner of the bezier surface.

Attached is an Excel worksheet of my current progress:

shade17_coord_rig_2.zip (3.9 KB)


#14

For the upcoming topic 16-5 Bezier Surface Subdivision at Rendering, we might be dealing with significantly more control points, possibly a degree 16 patch, if I had to guess. From my field tests in Autodesk Alias, a surface of degree 9 has too few internal control points to replicate the final surface.

Aligning the circled region…

Produces a wacky, incorrect result everywhere else:

The attached scene helped me rule out rational control points. The V-direction in this scene for any subdivision level is perfectly straight, and changing the weight for any control point breaks alignment with the V-direction of the rendered result.

I await to see if you also came to the same conclusion.

subdivision_test_8.shd (167.3 KB)


#15

I’m sorry, I can’t understand what you want to say.

I don’t have Autodesk, so I can’t verify its internal logic


#16

In Alias, you can hand-edit the internal control points like so:
Aliasでは、次のように内部の制御点を手動で編集できます。

The above is the same test scene attached in the previous post.
The white curves will never match the brown curves using only a tertiary bezier surface.
This is what led me to consider using de Casteljau’s algorithm to increase the number of control points to help obtain the final surface.
上記は前の記事に添付されているのと同じテストシーンです。
白い曲線は、3次ベジェ曲面のみを使用している茶色の曲線とは一致しません。
これが、最終的な曲面を得るのを助けるために制御点の数を増やすためにde Casteljauのアルゴリズムを使用することを私に考えさせたものです。


#17

Well, I understand Autodesk can control patch position manually, and you try to use de Casteljau’s algorithm.

I can guess your trial, but I don’t know any Autodesk function and user interface at all.

So, I can’t judge your trial is correct or not.

If you would like to continue this topic, please use just Shade only.