[Closed] Detach Specific Parts Of Object
Here is a different approach in a similar line of thought as the previous ones… poor
Its a little faster, and although it works with the test model, it is more sensitive to fail.
If it works, one way to make it faster for large models, could be by improving the “get elements” part. There is a code somewhere on this forum to do it.
(
gc()
st = timestamp(); sh = heapfree
fn GetAveragePos mesh verts:#{} =
(
avg = [0,0,0]
for j in verts do avg += getvert mesh j
avg /= verts.numberset
)
with undo off
(
/* GET MAIN PIPE */
tmesh = snapshotasmesh $
openedges = meshop.getopenedges tmesh
openverts = meshop.getvertsusingedge tmesh openedges
meshop.weldvertsbythreshold tmesh openverts 0.0001
update tmesh
allFaces = #{1..tmesh.numfaces}
largestArea = 0
mainPipeFaces = #{}
for j in allFaces do
(
element = meshop.getelementsusingface tmesh j
area = meshop.getfacearea tmesh element
if area > largestArea do
(
largestArea = area
mainPipeFaces = element
)
for i in element do deleteitem allFaces i
)
free tmesh
/* GET MAIN PIPE */
tmesh = snapshotasmesh $
mainElements = #()
addElementsFaces = #{1..tmesh.numfaces} - mainPipeFaces
for j in mainPipeFaces do
(
element = meshop.getelementsusingface tmesh j
verts = meshop.getvertsusingface tmesh element
avgPos = GetAveragePos tmesh verts:verts
append mainElements #(element, avgPos)
for i in element do deleteitem mainPipeFaces i
)
addElements = #()
for j in addElementsFaces do
(
element = meshop.getelementsusingface tmesh j
verts = meshop.getvertsusingface tmesh element
avgPos = GetAveragePos tmesh verts:verts
append addElements #(element, avgPos)
for i in element do deleteitem addElementsFaces i
)
for j in addElements do
(
pos = j[2]
dist = 1E9
found = 0
for i = 1 to mainElements.count do
(
d = distance pos mainElements[i][2]
if d < dist do
(
dist = d
found = i
)
)
join mainElements[found][1] j[1]
)
for j in mainElements do
(
verts = meshop.getvertsusingface tmesh j[1]
polys = polyop.getfacesusingvert $ verts
element = polyop.getelementsusingface $ polys
polyop.detachFaces $ element delete:false asNode:true
)
free tmesh
gc light:on
format "ELEMENTS: %
" mainElements.count
)
format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
)
I’ve been looking into the Splitting/Slicing process. If you wouldn’t want to use ProBooleans, another solution could be the following one.
You can change the radius of the virtual collision sphere, or you could use a box as a collision test if that geometry suits better your needs.
I tested it with the Snake file, and it worked well.
(
node = $Snake
radius = 25.0
planes = for j in $plane* collect #(ray j.pos j.dir, #{})
for j = 1 to node.numverts do
(
vpos = polyop.getvert node j
for i in planes where distance vpos i[1].pos < radius do i[2][j] = true
)
for j in planes do polyop.slice node (polyop.getfacesusingvert node j[2]) j[1]
polyop.splitedges node (polyop.getedgeselection node)
)
Hey Jorge, your last code (not poly slice method) works 99% which is great.
Let me explain why I can not use poly slice method. Imagine facade wall with few windows, doors, ornamens and some additional asets all attached as one object. Now I need to cut this wall to make modular objects which can be combined as a puzzle and export later to game engine. So objects will probably have average triangle count. Optimization of code is not main thing here.
I prepared new test file (see attachment) for testing your code. This is the screenshot of the scene
Also I make another version of “cutMesh” function using simple boolean operation but not works in this case (first commented function in the code) so I stay with proboolean version. I use max2014 for testing.
Just open the file and run the code and you’ll see your method as first from above. A small element
is not detached and crossing over other element. Model ei. result in middle is only cutted version.
You can run only commented fn to see what is the problem.
-- SIMPLE BOOLEAN OPERATION (Fast Method)
-- slice plane need to cover whole poligon to be cutted
-- but in this cace we use multi polygonal slice plane
-----------------------------------------------------------------------------
/*fn cutMesh mesh nodes = with redraw off
(
clearSelection()
if GetCommandPanelTaskMode() != #create do SetCommandPanelTaskMode #create
mesh = snapshot mesh ; mesh.selectedfaces = mesh.selectededges = #{}
object = Editable_Mesh pos:[0,0,0] wirecolor:yellow name:"Cards"
for i in nodes do attach object (snapshotasmesh i)
mesh - object ; node = converttopoly mesh ; delete object
polyop.deleteFaces node node.selectedfaces delIsoVerts:on
node.SelectEdgeLoop() ; node.wirecolor = yellow
polyop.splitEdges node (polyOp.getedgeselection node)
polyop.collapseDeadStructs node
polyop.deleteIsoVerts node
free nodes ; gc light:on ; node
)
obj = cutMesh $OBJ ($'CutPlanes*' as array)
move obj [0,0,100] ; select obj
SetCommandPanelTaskMode #modify
subobjectLevel = 2
*/
------------------------------------------------------------------
-- PRO BOOLEAN OPERATION (Precise method) *** works with max2017
fn cutMesh mesh nodes = with redraw off
(
clearSelection() ; hwnd = undefined
if GetCommandPanelTaskMode() != #modify do SetCommandPanelTaskMode #modify
node = ProBoolean ; mesh = snapshot mesh
object = Editable_Mesh pos:[0,0,0] wirecolor:yellow name:"Cards"
for i in nodes do attach object (snapshotasmesh i) ; nodes = #() --; update object
node.createBooleanObject mesh undefined 4 2 0 ; select mesh
node.SetImprint mesh on
node.SetPlanarEdgeRemoval mesh 2
local fixStrings = if (maxVersion())[1]/1000 >= 19 then #("CustButton","Change Operation") else #("RollupPanelTitle","Parameters")
-- press "Change operation" button to refresh current operation
for c in (windows.getChildrenHWND #max) while hwnd == undefined where c[4] == fixStrings[1] and c[5] == fixStrings[2] do
(
hwnd = UIAccessor.GetParentWindow c[1]
)
if hwnd != undefined do
(
node.SetOperandSel mesh 0 on
if (bt = windows.getChildHWND hwnd "Change Operation") != undefined do UIAccessor.PressButton bt[1]
)
node.SetBoolOp mesh 2
node.SetOperandB mesh object 2 0
SetCommandPanelTaskMode #modify
node = converttopoly mesh
node.wirecolor = black
polyop.splitEdges node (polyOp.getedgeselection node)
polyop.collapseDeadStructs node
polyop.deleteIsoVerts node
free nodes ; gc light:on ; node
)
obj = cutMesh $OBJ ($'CutPlanes*' as array)
move obj [0,0,100]
SetCommandPanelTaskMode #modify
subobjectLevel = 2
--PolyTools Detach formula
-----------------------------------------------------------------------------------
(
gc() ; obj = $
st = timestamp(); sh = heapfree
fn GetAveragePos mesh verts:#{} =
(
avg = [0,0,0]
for j in verts do avg += getvert mesh j
avg /= verts.numberset
)
with undo off
(
/* GET MAIN PIPE */
tmesh = snapshotasmesh obj
openedges = meshop.getopenedges tmesh
openverts = meshop.getvertsusingedge tmesh openedges
meshop.weldvertsbythreshold tmesh openverts 0.0001
update tmesh
local getElementsUsingFaceMesh = meshop.getelementsusingface
local getElementsUsingFacePoly = polyop.getelementsusingface
local getFaceArea = meshop.getfacearea
local getVertsUsingFace = meshop.getvertsusingface
local getFacesUsingVert = polyop.getfacesusingvert
local detachFaces = polyop.detachFaces
allFaces = #{1..tmesh.numfaces}
largestArea = 0
mainPipeFaces = #{}
for j in allFaces do
(
element = getElementsUsingFaceMesh tmesh j
area = getFaceArea tmesh element
if area > largestArea do
(
largestArea = area
mainPipeFaces = element
)
for i in element do deleteitem allFaces i
)
free tmesh
/* GET MAIN PIPE */
tmesh = snapshotasmesh obj
mainElements = #()
addElementsFaces = #{1..tmesh.numfaces} - mainPipeFaces
for j in mainPipeFaces do
(
element = getElementsUsingFaceMesh tmesh j
verts = getVertsUsingFace tmesh element
avgPos = GetAveragePos tmesh verts:verts
append mainElements #(element, avgPos)
for i in element do deleteitem mainPipeFaces i
)
addElements = #()
for j in addElementsFaces do
(
element = getElementsUsingFaceMesh tmesh j
verts = getVertsUsingFace tmesh element
avgPos = GetAveragePos tmesh verts:verts
append addElements #(element, avgPos)
for i in element do deleteitem addElementsFaces i
)
for j in addElements do
(
pos = j[2]
dist = 1E9
found = 0
for i = 1 to mainElements.count do
(
d = distance pos mainElements[i][2]
if d < dist do
(
dist = d
found = i
)
)
join mainElements[found][1] j[1]
)
for j in mainElements do
(
verts = getVertsUsingFace tmesh j[1]
polys = getFacesUsingVert obj verts
element = getElementsUsingFacePoly obj polys
detachFaces obj element delete:false asNode:true name:(uniquename "CUTOBJ")
objects[objects.count].wirecolor = random black white
)
free tmesh
gc light:on
select $'CUTOBJ*'
move selection [0,0,100]
format "ELEMENTS: %
" mainElements.count
)
format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
)
Thanks in advance!
I see. Well, these latest models and the original model are black and white. None of the code I proposed will work with these models.
Same as what Denis said, I don’t see a simple generic solution for all these different problems.
- Let the artist do the work.
- Create a tool like Probooleans, but much more complex, where you can specify any shape for the cuts and then find a way to break the elements apart.
The second method might be a nightmare to develop, but I guess it could be done. I don’t know how much of these work you need to do, but my feeling is that developing such a tool will take longer than what it will take to the artists to do the job manually. Unless this needs to be done millions of time.
Yup. Artists will do the job manually faster for 100 models probaly . After that start nightmares:)
there is no and can’t be good universal solution for searching and automatically finding all pieces for all cut parts.
my suggestion is –
because anyway an artist has to spend time making the geometry and setting cutting planes (objects) it’s not a big deal and time consuming to bind some vertices which in case of changing topology have to be in the same part (group of elements). after that the cutting and detaching process is trivial.
the binding shouldn’t take more than 5% of artists time.
I agree. Universal solution not exists and I like to spend time to solve problems in special cases where mxs can help and do it by hand. But for the easy cases why would speed up the whole process?
Another idea.
#1 collect and detach all elements detach that share selected edges (group A)
#2 collect and detach all other elements (group B)
#3 add push modifier with lover value on group B
#4 find which element in group B is collide with element in group A
#5 set value of push modifier to zero and attach with corresponding element
what a cut plane doesn’t really slice any face?
what if ‘free’ elements from B are pretty far from any element from A?
what if B is inside A?
there are many ‘ifs’ that can make a task very specific.
i don’t see a problem when an artist spends 7 hours making mesh and cut planes, and 10 minutes to bind pieces. You only need to make a friendly binding tool… which can use any “smart” searching mecanics
#what a cut plane doesn’t really slice any face?
Then nothing. We always starts if some edges are selected and are border edges.
#what if ‘free’ elements from B are pretty far from any element from A
Probaby will stay detached and later attach manually
#what if B is inside A
it will not happen, but if it happens then same thing as 2nd question but manually will be deleted
I changed the GetAveragePos() function to use faces centers and it seems to work now with the triangle model. However, I would bet it will fail with others.
(
gc() ; obj = $
st = timestamp(); sh = heapfree
fn GetAveragePos mesh faces:#{} =
(
avg = [0,0,0]
for j in faces do avg += meshop.getfacecenter mesh j
avg /= faces.numberset
)
with undo off
(
/* GET MAIN PIPE */
tmesh = snapshotasmesh obj
openedges = meshop.getopenedges tmesh
openverts = meshop.getvertsusingedge tmesh openedges
meshop.weldvertsbythreshold tmesh openverts 0.0001
update tmesh
local getElementsUsingFaceMesh = meshop.getelementsusingface
local getElementsUsingFacePoly = polyop.getelementsusingface
local getFaceArea = meshop.getfacearea
local getVertsUsingFace = meshop.getvertsusingface
local getFacesUsingVert = polyop.getfacesusingvert
local detachFaces = polyop.detachFaces
allFaces = #{1..tmesh.numfaces}
largestArea = 0
mainPipeFaces = #{}
for j in allFaces do
(
element = getElementsUsingFaceMesh tmesh j
area = getFaceArea tmesh element
if area > largestArea do
(
largestArea = area
mainPipeFaces = element
)
for i in element do deleteitem allFaces i
)
free tmesh
/* GET MAIN PIPE */
tmesh = snapshotasmesh obj
mainElements = #()
addElementsFaces = #{1..tmesh.numfaces} - mainPipeFaces
for j in mainPipeFaces do
(
element = getElementsUsingFaceMesh tmesh j
--verts = getVertsUsingFace tmesh element
--avgPos = GetAveragePos tmesh verts:verts
avgPos = GetAveragePos tmesh faces:element
append mainElements #(element, avgPos)
for i in element do deleteitem mainPipeFaces i
)
addElements = #()
for j in addElementsFaces do
(
element = getElementsUsingFaceMesh tmesh j
--verts = getVertsUsingFace tmesh element
--avgPos = GetAveragePos tmesh verts:verts
avgPos = GetAveragePos tmesh faces:element
append addElements #(element, avgPos)
for i in element do deleteitem addElementsFaces i
)
for j in addElements do
(
pos = j[2]
dist = 1E9
found = 0
for i = 1 to mainElements.count do
(
d = distance pos mainElements[i][2]
if d < dist do
(
dist = d
found = i
)
)
join mainElements[found][1] j[1]
)
for j in mainElements do
(
verts = getVertsUsingFace tmesh j[1]
polys = getFacesUsingVert obj verts
element = getElementsUsingFacePoly obj polys
detachFaces obj element delete:false asNode:true name:(uniquename "CUTOBJ")
objects[objects.count].wirecolor = random black white
)
($CUTOBJ*).pos.z += 100
select ($CUTOBJ*)
free tmesh
gc light:on
format "ELEMENTS: %
" mainElements.count
)
format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
)
It works now. This last example represents one of many problematic cases which is annoying to do it by hand. Large number of small element, selecting, attaching, detachind, UV, mat ID, SmoothingGroups etc. At least we have code that will speed up the process for this particular task.
Thank you Jorge. I really appreciate all your work and suggestions here.
Of course this also applies to DenisT.
BTW sorry for my english
according to Jorge’s code it’s better to find main element before splitting edges.
we can split after that.
but welding after splitting might make a mesh not exactly the same than before split.
Yes. I saw that and remove split and weld fn’s. Detach fn will split geometry after all