[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?
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.
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.
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)
“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
#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 :))