my effort for what its worth, works after a fashion, would be nice if edit_normal scripting worked
under suspendEditing(); but these script don’t. Why max doesn’t handle normals as just another mapping channel is beyond me.
--******************************************************************************
fn VertIDtoNormals vertID editNormalMod =
(
normalselection = #{};
editNormalMod.ConvertVertexSelection #{vertID} normalselection;
(normalselection as array);
)
--***************************************************************************
fn CopyNormalsToMapChannel obj mapchannel =
(
if obj.modifiers[#Edit_Normals] != undefined then
editnorm = obj.modifiers[#Edit_Normals];
else
(
editnorm = Edit_Normals();
addmodifier obj editnorm;
)
-- explode the object and map channel
for i in obj.faces do
polyop.detachFaces obj i.index;
polyop.applyUVWMap obj #face channel:mapchannel;
for i in obj.faces do
(
mface = polyOp.getMapFace obj mapchannel i.index
vface = polyOp.getFaceVerts obj i.index
for j = 1 to mface.count do
(
in coordsys local
(
normalID = VertIDtoNormals vface[j] editnorm
if(normalID.count != 0) then
(
n = editnorm.GetNormal normalID[1];
polyOp.setmapvert obj mapchannel mface[j] n;
)
)
)
)
)
--******************************************************************************
fn CopyMapChannelToNormals obj mapchannel =
(
if obj.modifiers[#Edit_Normals] != undefined then
editnorm = obj.modifiers[#Edit_Normals];
else
(
editnorm = Edit_Normals();
addmodifier obj editnorm;
)
for i in obj.faces do
(
mface = polyOp.getMapFace obj mapchannel i.index
vface = polyOp.getFaceVerts obj i.index
for j = 1 to mface.count do
(
normal = (polyOp.getmapvert obj mapchannel mface[j]);
in coordsys local
(
normalIDs = VertIDtoNormals vface[j] editnorm
for k = 1 to normalIDs.count do
(
editnorm.SetNormal normalIDs[k] normal
editnorm.SetNormalExplicit normalIDs[k] explicit:true;
)
)
)
)
)
fn DoTheDirtyDeed obj =
(
mapchannel = 1;
setCommandPanelTaskMode mode:#modify;
-- save the normals
CopyNormalsToMapChannel obj mapchannel;
convertto obj editable_poly;
-- tesselate
obj.tessellate #Object;
-- trust in the max's interpolator
--suspendEditing();
CopyMapChannelToNormals obj mapchannel;
--resumeEditing();
)
p.s. the resulting object has about trillion verts so you need to put an edit poly mod on top of the edit_normal mod, weld the verts, copy the edit_normal mod and paste above the edit_poly and collapse the stack, couldn’t be bothered to script that
Bingo! We have a Leader!
First of all – it’s absolutely right direction. But there is a way to do the same without face detaching. It will be dramatically faster.
To suspendEditing in situation where editing modifier doesn’t allow to do it (UVW_Unwrap, Edit_Normals, Skin, etc.) at least we can disable UI (not viewport) redraw.
windows.sendmessage (windows.getmaxhwnd()) 0x000B 0 1 – sets whole max window redraw to disable
windows.sendmessage (windows.getmaxhwnd()) 0x000B 1 1 – set to enable
the same way we can do it for modifiers panel
those windows commands did nothing on my machine except send a browser window a bit screwy.
these commands work as expected and don’t screw anything up. but setting of enabling window redraw doesn’t mean the window’s automatic redraw. i don’t know a way to do it with mxs, but how to do it with c# see http://forums.cgsociety.org/showpost.php?p=6186103&postcount=24
as i said… it looks as the right direction for me… but the idea definitely needs some perfection. my version doesn’t detach faces and takes ~0.8-1.0 sec.
the time taken has nothing to do with the detached faces and every thing to do with the edit_normal modifier and as your windows hack codes don’t work here I’m not going to waste mine time looking for solutions that will make no difference. You would be right to say using the detached method is not very elegant as it requires some trickery at the end to weld the object back together.
disabling windows redraw doesn’t really effect timing.
detaching itself is not too bad for timing but attaching will cause a problem. every attach operation forces poly to rebuild.
there’s no attaching required, slap an edit poly onto the edit_normal, weld the verts copy the edit_normal on top of the edit poly… collapse the stack.
re TzMtn, have you tried making the normals explicit before collapsing the stack ?
You’re right, it does solve the problem, thanks
and here is the fixed code:
(
struct s_VertData
(
index,
OriginalNormals = #()
)
struct s_EdgeData
(
owner,
index,
verts,
midVert,
fn divide =
(
midVert = owner.divideEdge index 0.5
-- format "Edge % divided
" index
),
fn init =
(
verts = polyop.getEdgeVerts owner index
not polyop.isEdgeDead owner index
),
valid = init()
)
struct s_NormalData
(
owner,
faceIndex,
Vert,
index,
dir,
pos,
fn init =
(
local faceNorms = #{}
local vertNorms = #{}
owner.ConvertFaceSelection #{faceIndex} faceNorms
owner.ConvertVertexSelection #{Vert.index} vertNorms
faceNorms = faceNorms as array
vertNorms = vertNorms as array
for i in faceNorms where findItem vertNorms i > 0 do exit with index = i
if index != undefined then (
dir = owner.getNormal index
pos = owner.getVertex Vert.index
true
) else
false
),
fn sotrByDist N1 N2 =
(
local d1 = distance pos N1.pos
local d2 = distance pos N2.pos
case of (
(d1 < d2): -1
(d1 > d2): 1
default:0
)
),
fn reconstruct =
(
dir = [0,0,0]
for N in Vert.OriginalNormals do
dir += N.dir
dir = normalize dir
owner.setNormal index dir
),
valid = init()
)
struct s_FaceData
(
owner,
index,
center,
MidVert,
originalVerts = #(),
OriginalVertsData = #(),
originalEdges = #(),
OriginalNormals = #(),
EdgesData = #(),
faceChildren = #{},
midEdgeVerts = #(),
MidEdgeVertsData = #(),
fn getFaceNormals face:index =
(
local faceVerts = polyop.getFaceVerts owner face
local OVerts = for i = 1 to originalVerts.count where findItem faceVerts originalVerts[i] > 0 collect OriginalVertsData[i]
local EVerts = for i = 1 to midEdgeVerts.count where findItem faceVerts midEdgeVerts[i] > 0 collect MidEdgeVertsData[i]
local Verts = #()
join Verts OVerts
join Verts EVerts
if MidVert != undefined then
append Verts MidVert
local NormsData = #()
for V in Verts do (
local NewNormData = s_NormalData owner:owner.norms faceIndex:face Vert:V
if NewNormData.valid then
append NormsData NewNormData
)
NormsData
),
fn getNormalByVert v =
(
for N in OriginalNormals where N.Vert.index == v do
exit with N
),
fn init =
(
if not polyop.isFaceDead owner index then (
center = polyop.getFaceCenter owner index
originalVerts = polyop.getFaceVerts owner index
originalEdges = polyop.getFaceEdges owner index
OriginalVertsData = for v in originalVerts collect s_VertData index:v
OriginalNormals = getFaceNormals()
for V in OriginalVertsData do
V.OriginalNormals = #(getNormalByVert V.index)
faceChildren[index] = true
-- format "Face % initialized
" index
true
) else
false
),
fn setEdgesData Edges =
(
EdgesData = for i in originalEdges collect Edges[i]
),
fn divide =
(
local bit1 = bit.set 0 1 true
local numFacesBefore = polyop.getNumFaces owner
for D in EdgesData do (
local OrigNorms = for v in D.verts collect OriginalNormals[findItem originalVerts v]
append midEdgeVerts D.midVert
append MidEdgeVertsData (s_VertData index:D.midVert OriginalNormals:OrigNorms)
)
polyop.setVertSelection owner #{midEdgeVerts[1], midEdgeVerts[2]}
owner.ConnectVertices vertexFlag:bit1
local newEdge = polyop.getNumEdges owner
MidVert = s_VertData index:(owner.divideEdge newEdge 0.5) OriginalNormals:OriginalNormals
polyop.setVert owner MidVert.index center
for i = 3 to MidEdgeVerts.count do (
polyop.setVertSelection owner #{midEdgeVerts[i], MidVert.index}
owner.ConnectVertices vertexFlag:bit1
)
local numFacesAfter = polyop.getNumFaces owner
for i = numFacesBefore + 1 to numFacesAfter do
faceChildren[i] = true
-- format "Face % divided
" index
),
fn reconstructNormals =
(
for f in faceChildren do (
local NewFaceNormals = getFaceNormals face:f
for N in NewFaceNormals do
N.reconstruct()
)
-- format "Face % normals reconstructed
" index
),
valid = init()
)
fn tessellateObject obj =
(
if canConvertTo obj Editable_Poly then (
local ts = timeStamp()
max modify mode
obj = convertToPoly (copy obj)
addModifier obj (edit_normals name:"norms")
modPanel.setCurrentObject obj.norms
local EdgesData = #()
local FacesData = #()
/* Edge Data collection loop */
for i = 1 to polyop.getNumEdges obj do (
local NewEdgeData = s_EdgeData owner:obj index:i
if NewEdgeData.valid then
append EdgesData NewEdgeData
)
/* Face Data collection loop */
for i = 1 to polyop.getNumFaces obj do (
local NewFaceData = s_FaceData owner:obj index:i
if NewFaceData.valid then (
NewFaceData.setEdgesData EdgesData
append FacesData NewFaceData
)
)
/* Edges Subdivision loop */
for D in EdgesData do
D.divide()
/* Faces Subdivision loop */
for F in FacesData do
F.divide()
/* Normals loop */
for F in FacesData do
F.reconstructNormals()
obj.norms.SelLevel = #Object
obj.norms.MakeExplicit()
collapseStack obj
max create mode
format "% tessellation took %
" obj.name (timeStamp() - ts)
gc()
obj
) else
undefined
)
local newObjs = #()
for o in selection do (
local newObj = tessellateObject o
if newObj != undefined then
append newObjs newObj
)
select newObjs
redrawViews()
)
my bad. we don’t need to attach.
but anyway a solution has to be complete. how the algorythm knows what verts have to be welded? the mesh could have some broken verts for a purpose. (I’ll make another sample)
Here is my attempt (unencrypted )
It’s VERY slow but it’s a very low level solution.
Anyhow claude666 solution with the vertex color information is a much better one if you are looking for a practical solution, mine is more of a study case.
I am still unable to solve one problem though, when I try to collapse the stack in the end to get rid of the Edit_Normals modifier, the normals still go to hell… Any ideas on how to solve it?
(
struct s_VertData
(
index,
OriginalNormals = #()
)
struct s_EdgeData
(
owner,
index,
verts,
midVert,
fn divide =
(
midVert = owner.divideEdge index 0.5
-- format "Edge % divided
" index
),
fn init =
(
verts = polyop.getEdgeVerts owner index
not polyop.isEdgeDead owner index
),
valid = init()
)
struct s_NormalData
(
owner,
faceIndex,
Vert,
index,
dir,
pos,
fn init =
(
local faceNorms = #{}
local vertNorms = #{}
owner.ConvertFaceSelection #{faceIndex} faceNorms
owner.ConvertVertexSelection #{Vert.index} vertNorms
faceNorms = faceNorms as array
vertNorms = vertNorms as array
for i in faceNorms where findItem vertNorms i > 0 do exit with index = i
if index != undefined then (
dir = owner.getNormal index
pos = owner.getVertex Vert.index
true
) else
false
),
fn sotrByDist N1 N2 =
(
local d1 = distance pos N1.pos
local d2 = distance pos N2.pos
case of (
(d1 < d2): -1
(d1 > d2): 1
default:0
)
),
fn reconstruct =
(
dir = [0,0,0]
for N in Vert.OriginalNormals do
dir += N.dir
dir = normalize (dir / Vert.OriginalNormals.count)
owner.setNormal index dir
),
valid = init()
)
struct s_FaceData
(
owner,
index,
center,
MidVert,
originalVerts = #(),
OriginalVertsData = #(),
originalEdges = #(),
OriginalNormals = #(),
EdgesData = #(),
faceChildren = #{},
midEdgeVerts = #(),
MidEdgeVertsData = #(),
fn getFaceNormals face:index =
(
local faceVerts = polyop.getFaceVerts owner face
local OVerts = for i = 1 to originalVerts.count where findItem faceVerts originalVerts[i] > 0 collect OriginalVertsData[i]
local EVerts = for i = 1 to midEdgeVerts.count where findItem faceVerts midEdgeVerts[i] > 0 collect MidEdgeVertsData[i]
local Verts = #()
join Verts OVerts
join Verts EVerts
if MidVert != undefined then
append Verts MidVert
local NormsData = #()
for V in Verts do (
local NewNormData = s_NormalData owner:owner.norms faceIndex:face Vert:V
if NewNormData.valid then
append NormsData NewNormData
)
NormsData
),
fn getNormalByVert v =
(
for N in OriginalNormals where N.Vert.index == v do
exit with N
),
fn init =
(
if not polyop.isFaceDead owner index then (
center = polyop.getFaceCenter owner index
originalVerts = polyop.getFaceVerts owner index
originalEdges = polyop.getFaceEdges owner index
OriginalVertsData = for v in originalVerts collect s_VertData index:v
OriginalNormals = getFaceNormals()
for V in OriginalVertsData do
V.OriginalNormals = #(getNormalByVert V.index)
faceChildren[index] = true
-- format "Face % initialized
" index
true
) else
false
),
fn setEdgesData Edges =
(
EdgesData = for i in originalEdges collect Edges[i]
),
fn divide =
(
local bit1 = bit.set 0 1 true
local numFacesBefore = polyop.getNumFaces owner
for D in EdgesData do (
local OrigNorms = for v in D.verts collect OriginalNormals[findItem originalVerts v]
append midEdgeVerts D.midVert
append MidEdgeVertsData (s_VertData index:D.midVert OriginalNormals:OrigNorms)
)
polyop.setVertSelection owner #{midEdgeVerts[1], midEdgeVerts[2]}
owner.ConnectVertices vertexFlag:bit1
local newEdge = polyop.getNumEdges owner
MidVert = s_VertData index:(owner.divideEdge newEdge 0.5) OriginalNormals:OriginalNormals
polyop.setVert owner MidVert.index center
for i = 3 to MidEdgeVerts.count do (
polyop.setVertSelection owner #{midEdgeVerts[i], MidVert.index}
owner.ConnectVertices vertexFlag:bit1
)
local numFacesAfter = polyop.getNumFaces owner
for i = numFacesBefore + 1 to numFacesAfter do
faceChildren[i] = true
-- format "Face % divided
" index
),
fn reconstructNormals =
(
for f in faceChildren do (
local NewFaceNormals = getFaceNormals face:f
for N in NewFaceNormals do
N.reconstruct()
)
-- format "Face % normals reconstructed
" index
),
valid = init()
)
fn tessellateObject obj =
(
if canConvertTo obj Editable_Poly then (
local ts = timeStamp()
obj = convertToPoly (copy obj)
addModifier obj (edit_normals name:"norms")
modPanel.setCurrentObject obj.norms
local EdgesData = #()
local FacesData = #()
/* Edge Data collection loop */
for i = 1 to polyop.getNumEdges obj do (
local NewEdgeData = s_EdgeData owner:obj index:i
if NewEdgeData.valid then
append EdgesData NewEdgeData
)
/* Face Data collection loop */
for i = 1 to polyop.getNumFaces obj do (
local NewFaceData = s_FaceData owner:obj index:i
if NewFaceData.valid then (
NewFaceData.setEdgesData EdgesData
append FacesData NewFaceData
)
)
/* Edges Subdivision loop */
for D in EdgesData do
D.divide()
/* Faces Subdivision loop */
for F in FacesData do
F.divide()
/* Normals loop */
for F in FacesData do
F.reconstructNormals()
-- obj.norms.RecomputeNormals()
-- obj.norms.RebuildNormals()
-- forceCompleteRedraw()
-- collapseStack obj
max create mode
format "% tessellation took %
" obj.name (timeStamp() - ts)
obj
) else
undefined
)
local newObjs = #()
for o in selection do (
local newObj = tessellateObject o
if newObj != undefined then
append newObjs newObj
)
select newObjs
redrawViews()
)
you may want to change this line
[i]the task is very simple – tessellate the ball
[/i]to
the task is to create the fastest production robust script to tessellate any object while maintaining the normal integrity and all vertex boundaries.
tesselate is not a point… it might be any poly operation that needs poly rebuild:
delete objects
(
setCommandPanelTaskMode mode:#modify
target = OilTank name:"target" radius:10 Cap_Height:10 height:30 Blend:0 sides:24 Height_Segments:1 Smooth_On:0 isSelected:on wirecolor:(color 150 160 165)
converttopoly target
polyop.detachfaces target #{193..216}
modpanel.addmodtoselection (norm = edit_normals())
for v=1 to norm.GetNumVertices() do
(
normals = #{}
norm.ConvertVertexSelection #{v} &normals
norm.Average selection:normals
)
for v in #{170..193, 194..217} do
(
normals = #{}
norm.ConvertVertexSelection #{v, v+217} &normals
norm.Average selection:normals
)
update target.mesh
norm.MakeExplicit selection:#{1..norm.GetNumNormals()}
norm.SetSelection #{}
converttopoly target
)
completeredraw()
gc()
weld for example…
and here is a small variation:
(
struct s_VertData
(
index,
OriginalNormals = #()
)
struct s_EdgeData
(
owner,
index,
verts,
midVert,
fn divide =
(
midVert = owner.divideEdge index (random 0.0 1.0)
-- format "Edge % divided
" index
),
fn init =
(
verts = polyop.getEdgeVerts owner index
not polyop.isEdgeDead owner index
),
valid = init()
)
struct s_NormalData
(
owner,
faceIndex,
Vert,
index,
dir,
pos,
fn init =
(
local faceNorms = #{}
local vertNorms = #{}
owner.ConvertFaceSelection #{faceIndex} faceNorms
owner.ConvertVertexSelection #{Vert.index} vertNorms
faceNorms = faceNorms as array
vertNorms = vertNorms as array
for i in faceNorms where findItem vertNorms i > 0 do exit with index = i
if index != undefined then (
dir = owner.getNormal index
pos = owner.getVertex Vert.index
true
) else
false
),
fn sotrByDist N1 N2 =
(
local d1 = distance pos N1.pos
local d2 = distance pos N2.pos
case of (
(d1 < d2): -1
(d1 > d2): 1
default:0
)
),
fn reconstruct =
(
dir = [0,0,0]
for N in Vert.OriginalNormals do (
local dist = amax (distance N.pos pos) 0.00001
dir += N.dir / dist
)
dir = normalize dir
owner.setNormal index dir
),
valid = init()
)
struct s_FaceData
(
owner,
index,
center,
MidVert,
originalVerts = #(),
OriginalVertsData = #(),
originalEdges = #(),
OriginalNormals = #(),
EdgesData = #(),
faceChildren = #{},
midEdgeVerts = #(),
MidEdgeVertsData = #(),
fn getFaceNormals face:index =
(
local faceVerts = polyop.getFaceVerts owner face
local OVerts = for i = 1 to originalVerts.count where findItem faceVerts originalVerts[i] > 0 collect OriginalVertsData[i]
local EVerts = for i = 1 to midEdgeVerts.count where findItem faceVerts midEdgeVerts[i] > 0 collect MidEdgeVertsData[i]
local Verts = #()
join Verts OVerts
join Verts EVerts
if MidVert != undefined then
append Verts MidVert
local NormsData = #()
for V in Verts do (
local NewNormData = s_NormalData owner:owner.norms faceIndex:face Vert:V
if NewNormData.valid then
append NormsData NewNormData
)
NormsData
),
fn getNormalByVert v =
(
for N in OriginalNormals where N.Vert.index == v do
exit with N
),
fn init =
(
if not polyop.isFaceDead owner index then (
center = polyop.getFaceCenter owner index
originalVerts = polyop.getFaceVerts owner index
originalEdges = polyop.getFaceEdges owner index
OriginalVertsData = for v in originalVerts collect s_VertData index:v
OriginalNormals = getFaceNormals()
for V in OriginalVertsData do
V.OriginalNormals = #(getNormalByVert V.index)
faceChildren[index] = true
-- format "Face % initialized
" index
true
) else
false
),
fn setEdgesData Edges =
(
EdgesData = for i in originalEdges collect Edges[i]
),
fn divide =
(
local bit1 = bit.set 0 1 true
local numFacesBefore = polyop.getNumFaces owner
for D in EdgesData do (
local OrigNorms = for v in D.verts collect OriginalNormals[findItem originalVerts v]
append midEdgeVerts D.midVert
append MidEdgeVertsData (s_VertData index:D.midVert OriginalNormals:OrigNorms)
)
polyop.setVertSelection owner #{midEdgeVerts[1], midEdgeVerts[2]}
owner.ConnectVertices vertexFlag:bit1
local newEdge = polyop.getNumEdges owner
MidVert = s_VertData index:(owner.divideEdge newEdge (random 0.0 1.0)) OriginalNormals:OriginalNormals
polyop.setVert owner MidVert.index center
for i = 3 to MidEdgeVerts.count do (
polyop.setVertSelection owner #{midEdgeVerts[i], MidVert.index}
owner.ConnectVertices vertexFlag:bit1
)
local numFacesAfter = polyop.getNumFaces owner
for i = numFacesBefore + 1 to numFacesAfter do
faceChildren[i] = true
-- format "Face % divided
" index
),
fn reconstructNormals =
(
for f in faceChildren do (
local NewFaceNormals = getFaceNormals face:f
for N in NewFaceNormals do
N.reconstruct()
)
-- format "Face % normals reconstructed
" index
),
valid = init()
)
fn tessellateObject obj =
(
if canConvertTo obj Editable_Poly then (
local ts = timeStamp()
max modify mode
obj = convertToPoly (copy obj)
addModifier obj (edit_normals name:"norms")
modPanel.setCurrentObject obj.norms
local EdgesData = #()
local FacesData = #()
/* Edge Data collection loop */
for i = 1 to polyop.getNumEdges obj do (
local NewEdgeData = s_EdgeData owner:obj index:i
if NewEdgeData.valid then
append EdgesData NewEdgeData
)
/* Face Data collection loop */
for i = 1 to polyop.getNumFaces obj do (
local NewFaceData = s_FaceData owner:obj index:i
if NewFaceData.valid then (
NewFaceData.setEdgesData EdgesData
append FacesData NewFaceData
)
)
/* Edges Subdivision loop */
for D in EdgesData do
D.divide()
/* Faces Subdivision loop */
for F in FacesData do
F.divide()
/* Normals loop */
for F in FacesData do
F.reconstructNormals()
obj.norms.SelLevel = #Object
obj.norms.MakeExplicit()
collapseStack obj
max create mode
format "% tessellation took %
" obj.name (timeStamp() - ts)
gc()
obj
) else
undefined
)
local newObjs = #()
for o in selection do (
local newObj = tessellateObject o
if newObj != undefined then
append newObjs newObj
)
select newObjs
redrawViews()
)
the method works. it takes 32 sec on my machine. I can make it faster (~20 sec) just adding two lines of code in one of your functions.