Notifications
Clear all

[Closed] Adjust vert normals between two meshes

I there a better way to correct vertex normals between two or more separated objects.
I want to achive result like this.
Using some of Denis suggestions for earlier threads I ‘ve tied with below method but without luck.
Max throws this message:
– Runtime error: Modifier is not appropriate: Edit_Normals:Edit Normals

fn setCommandPanelRedraw act =
(
	WM_SETREDRAW = 0x000B
	windows.sendmessage (windows.getchildhwnd #max "Command Panel")[1] WM_SETREDRAW act 0
	if act == 0 then disableRefMsgs() else enableRefMsgs()
)
fn fixNormals objA objB = with redraw off
(
	local getOpenEdge = meshop.getOpenEdges
	local getVertByEdge = meshop.getVertsUsingEdge
	local objA_borderE = getOpenEdge objA
	local objA_borderV = getVertByEdge objA objA_borderE
	local objB_borderE = getOpenEdge objB
	local objB_borderV = getVertByEdge objB objB_borderE
	local vData = #()
	
	for i in objA_borderV do (for j in objB_borderV where (distance (getVert objA i) (getVert objB j)) < 0.1 do join vData #(#{i},#{j}))
	setCommandPanelRedraw 0
	max modify mode
	select #(objA,objB)
	norm = Edit_Normals()
	modPanel.addModToSelection norm
	for i = 1 to vData.count by 2 do
	(
		n = #{}
		nData = ((join (norm.ConvertVertexSelection vData[i] &n node:objA ; n) (norm.ConvertVertexSelection vData[i+1] &n node:objB ; n)) as array)
		norm.AverageTwo objA nData[1] objB nData[2]
	)
	converttomesh #(objA,objB)
	setCommandPanelRedraw 0
)

delete objects
sp = converttopoly (sphere name:"A" radius:50 segs:24 wirecolor:[30,110,0])
polyop.detachFaces sp #{145..288} delete:on asNode:on name:"B"
objA = $A ; objB = $B
converttomesh #($A,$B)
fixNormals objA objB ; CompleteRedraw()

BTW Is this possible without the use of EditNormals modifier?

25 Replies

BTW Is this possible without the use of EditNormals modifier?

yes but requires creating script extensions using the sdk.

as for techniques I found copying the normal data into a arbitrary map channel and the working with the channel data and then copying it back to the normals gives the best results. An example of it here(sorry no code examples) but it uses the base normals -> map channel technique in the original challenge thread.

1 Reply
(@gazybara)
Joined: 11 months ago

Posts: 0

Hmm. Thanks for suggestion! Interesting method and really looks perfect.
I will explore this soon, but for now I need simple mxs solution

I’ve collected some old bits an pieces to demo something that will handle say a seam it’s very contrived ! but it shows the principle

fn addNormalModifier obj displaylen:10.0 = 
      (	
      	if obj.modifiers[#Edit_Normals] != undefined then
      		editnorm = obj.modifiers[#Edit_Normals];
      	else
      	(
      		editnorm = Edit_Normals();
      		editnorm.displayLength = displaylen;
      		if validModifier obj editnorm then			
      			addmodifier obj editnorm;
      		else
      			editnorm = undefined;
      	)	
      	editnorm;
      )
      
      fn ExplodeMapChannel obj mapchannel = 
      (
      	polyop.setMapSupport obj mapchannel on
      	polyop.defaultMapFaces obj mapchannel
      	polyOp.applyUVWMap obj #face channel:mapchannel
      )
      
      fn CopyNormalsToMapChannel obj mapchannel inworldsys =
      (
      	editnorm = addNormalModifier obj;
      	ExplodeMapChannel obj mapchannel;	
      
      	for i = 1 to editnorm.GetNumFaces() do
      	(
      		mface = polyOp.getMapFace obj  mapchannel  i;
      		for j = 1 to mface.count do
      		(
      			if inworldsys then in coordsys world
      			(
      				n = editnorm.GetNormal (editnorm.GetNormalID i j);
      				polyOp.setmapvert obj mapchannel mface[j] n;
      			)
      			else in coordsys local
      			(
      				n = editnorm.GetNormal (editnorm.GetNormalID i j);
      				polyOp.setmapvert obj mapchannel mface[j] n;
      			)
      		)	
      	)
      	update obj
      )
      
      fn CopyMapChannelToNormals obj mapchannel inworldsys =
      (
      	editnorm = addNormalModifier obj;
      	editnorm.reset selection:#{1..editnorm.GetNumNormals()}
      
      	disableRefMsgs();
      	for i = 1 to obj.numFaces  do
      	(
      		mface = polyOp.getMapFace obj  mapchannel  i;
      		for j = 1 to mface.count do
      		(		
      			mapnormal = (polyOp.getmapvert obj mapchannel mface[j]);
      			if inworldsys then in coordsys world
      				editnorm.SetNormal (editnorm.GetNormalID i j) mapnormal;	
      			else in coordsys local
      				editnorm.SetNormal (editnorm.GetNormalID i j) mapnormal;
      		)	
      	)
      	editnorm.MakeExplicit selection:#{1..editnorm.GetNumNormals()}
      	enableRefMsgs();
      )	
      
      fn GeoVertToMapVert obj v mapCh =
      (
      	faces = (polyop.getFacesUsingVert obj v) as array 
      	faceIndices = for i in faces collect finditem (polyop.getFaceVerts obj i) v;
      	makeUniqueArray (for i = 1 to faces.count collect (polyop.getMapFace obj mapCh faces[i])[faceIndices[i]]);
      )
      
      num_segs = 32;
  MAPCH = 5;
  delete objects;
  sph1 = sphere segs:num_segs;
  converttoPoly sph1;
  polyop.detachFaces sph1 #{1..sph1.numfaces/2}  asNode:true name:"Sphere02";
  sph2= $Sphere02;
  
  select  sph1
  CopyNormalsToMapChannel sph1 MAPCH false;
  select  sph2
  CopyNormalsToMapChannel sph2 MAPCH false;
  
  top_start = sph2.numverts - num_segs;
  for i = 1 to num_segs do
  (
  	mapverts1 = (GeoVertToMapVert sph1 i MAPCH);
  	mapverts2 = (GeoVertToMapVert sph2 (top_start + i) MAPCH);
  	blended = (polyOp.getmapvert sph1 MAPCH mapverts1[1] + polyOp.getmapvert sph2 MAPCH mapverts2[1]) * 0.5;
  	for mv in mapverts1 do polyOp.setmapvert sph1 MAPCH mv blended;
  	for mv in mapverts2 do polyOp.setmapvert sph2 MAPCH mv blended;
  )	
  
  select  sph1
  CopyMapChannelToNormals sph1 MAPCH false;
  select  sph2
  CopyMapChannelToNormals sph2 MAPCH false;
  
  converttoPoly sph1;
  converttoPoly sph2;
      

edited to make it work for any sphere

another edit removing

	polyop.setFaceSmoothGroup $ $.faces 0;

no idea what that was doing in there :surprised the code was pulled from a 2012 backup

Wow. This is a lot I never played with this stuffs (normals and mapping related) before but you provided me a good piece of information that I will start to analyze right now.
Klunk, thanks you very much.

it’s all a bit general, and could be optimized to work just on the seam verts as opposed to the whole mesh. The real bottleneck is edit normals modifier and having to have the node selected in the modifier panel for the functions to work. As I said the sdk mxs extension function can work around this and are a real performance gain for stuff like this.

1 Reply
(@gazybara)
Joined: 11 months ago

Posts: 0

I understand you. I’m not programmer and I’m not familiar with sdk methods but I already saw your sdk extensions for mxs (normals related).
About your mxs fn’s you are right , for sure can be optimized started from first one

fn addNormalModifier obj displaylen:10.0 = 
 (
 	local mody = for m in obj.modifiers where isKindOf m Edit_Normals do exit with m
 	if not (isKindOf mody modifier and validModifier obj mody) do
 	(
 		addmodifier obj (mody = Edit_Normals displayLength:displaylen)
 	) ; mody
 )
 addNormalModifier $

how did you work that one out ? :curious: I find them Identical timewise !



fn addNormalModifier1 obj ndisplaylen:10.0 = 
(	
	if obj.modifiers[#Edit_Normals] != undefined then
		editnorm = obj.modifiers[#Edit_Normals];
	else
	(
		editnorm = Edit_Normals();
		if validModifier obj editnorm then			
			addmodifier obj editnorm;
		else
			editnorm = undefined;
	)	
	editnorm.displayLength = ndisplaylen;
	editnorm;
)

fn addNormalModifier2 obj displaylen:10.0 = 
(
	local mody = for m in obj.modifiers where isKindOf m Edit_Normals do exit with m
	if not (isKindOf mody modifier and validModifier obj mody) do
	(
		addmodifier obj (mody = Edit_Normals displayLength:displaylen)
	) ; mody
)

count = 1000;
sph = sphere();
converttopoly sph;

t = timestamp()
for i = 1 to count do addNormalModifier1 sph;
print (timestamp() - t)

converttopoly sph;
t = timestamp()
for i = 1 to count do addNormalModifier2 sph;
print (timestamp() - t)
1 Reply
(@gazybara)
Joined: 11 months ago

Posts: 0

“Exit with” cause slowdown. Last night I fix this with 3rd fn
See results

fn addNormalModifier1 obj ndisplaylen:10.0 = 
(	
	if obj.modifiers[#Edit_Normals] != undefined then
		editnorm = obj.modifiers[#Edit_Normals];
	else
	(
		editnorm = Edit_Normals();
		if validModifier obj editnorm then			
			addmodifier obj editnorm;
		else
			editnorm = undefined;
	)	
	editnorm.displayLength = ndisplaylen;
	editnorm;
)

fn addNormalModifier2 obj displaylen:10.0 = 
(
	local mody = for m in obj.modifiers where isKindOf m Edit_Normals do exit with m
	if not (isKindOf mody modifier and validModifier obj mody) do
	(
		addmodifier obj (mody = Edit_Normals displayLength:displaylen)
	) ; mody
)
fn addNormalModifier3 obj displaylen:10.0 =
(
	local mody, notFound = on
	for m in obj.modifiers while notFound where isKindOf m Edit_Normals do (mody = m ; notFound = off)
	if mody == undefined do addmodifier obj (mody = Edit_Normals displayLength:displaylen) ; mody
)

count = 1000
delete objects
sph = converttopoly (sphere())
gc() ; t1 = timestamp() ; m1 = heapfree
for i = 1 to count do addNormalModifier1 sph
format "RESULT1 >>> time:% memory:%
" ((timestamp()-t1 as float)/1000) (m1-heapfree)

delete objects
sph = converttopoly (sphere())
gc() ; t1 = timestamp() ; m1 = heapfree
for i = 1 to count do addNormalModifier2 sph
format "RESULT2 >>> time:% memory:%
" ((timestamp()-t1 as float)/1000) (m1-heapfree)

delete objects
sph = converttopoly (sphere())
gc() ; t1 = timestamp() ; m1 = heapfree
for i = 1 to count do addNormalModifier3 sph
format "RESULT3 >>> time:% memory:%
" ((timestamp()-t1 as float)/1000) (m1-heapfree)

RESULT1 >>> time:0.036 memory:3368L
RESULT2 >>> time:0.06 memory:67304L
RESULT3 >>> time:0.02 memory:3368L

Also this is interesting. Use less memory and time is better

fn addNormalModifier4 obj editnorm: notFound:on =
(
	for m in obj.modifiers while notFound where isKindOf m Edit_Normals do (editnorm = m ; notFound = off)
	if editnorm == undefined do addmodifier obj (editnorm = copy editnorm) ; editnorm
)
delete objects
sph = converttopoly (sphere())
mody = Edit_Normals displayLength:10
gc() ; t1 = timestamp() ; m1 = heapfree
for i = 1 to count do addNormalModifier4 sph editnorm:mody
format "RESULT4 >>> time:% memory:%
" ((timestamp()-t1 as float)/1000) (m1-heapfree)

RESULT4 >>> time:0.008 memory:2144L

… deleted as doubled

#1 the using node.modifiers[#ModifierName] is wrong technique because in this case you are searching not by class but by name

#2 addModifier function is better to break on two, like:

fn getEditNormal node = 
  (
  	local modi
  	for m in node.modifiers while modi == undefined where iskindof m Edit_Normals do modi = m 
  	modi
  )
  fn addEditNormal node  =
  (
  	if (modi = getEditNormal node) == undefined do addmodifier node (modi = Edit_Normals()) 
  	modi
  )

because in practice the getModifier would be much more utilized than addModifier

#3 for most modifiers the way how a modifier adds doesn’t really effect speed and memory. so try to make a function just clear. (the using both mody and notFound doesn’t help it :))

#4 using edit normal modifier is so last year.

Page 1 / 2