MaxScript Editable Poly - Collapse vertices

MaxScript Editable Poly - Collapse vertices

Anonymous
Not applicable
4,219 Views
11 Replies
Message 1 of 12

MaxScript Editable Poly - Collapse vertices

Anonymous
Not applicable
Good morning everybody.
I am starting with MaxScript, but I already know develop codes (I come from c#)
I want to create a script which collapse all vertices which has the same position or their distance is close then a value. I thought this logic:


SelectedMesh = $
ConvertTo SelectedMesh Editable_Poly



for (v in "Vertices of this editable poly") do
for (v1 in "Other vertices of this poly but not this") do
// if (v.position = v1.position) Collapse These two


How can I do this? Thanks Good Bye.
0 Likes
4,220 Views
11 Replies
Replies (11)
Message 2 of 12

Steve_Curley
Mentor
Mentor
Avoid "$" in scripts. $ is the entire current selection whereas you need a single object.
selectedMesh = selection

Or if you need to iterate over all the selected objects:-
 for selectedMesh in selection do


for (v in “Vertices of this editable poly") do
No. for (v in "all but the last vertex of this editable poly") do
Reason being you correctly identified the need to omit the current v vertex in the v1 loop (but it needs to omit all vertices up to and including the current v) therefore you must omit the last vertex in the first line otherwise you'll end up comparing the last vertex with itself.
And as you're converting it, it will no longer be a mesh. Probably better to simply call it "obj" or "thisObj".

obj = selection
convertToPoly obj

vertexCount = polyOp.getNumVerts obj

for v = 1 to vertexCount - 1 do
(
for v1 = v + 1 to vertexCount do
(
...
)
)

The above, as it stands, will not work. If you collapse 2 vertices of a single quad poly (4 vertices) you end up with a triangle (3 vertices). The conditions of a for loop, as in most languages, are set in the initial for statement and cannot be changed within the loop therefore you'll be trying to test vertex 3(last iteration of the outer v loop) with vertex 4 (last iteration of the inner v1 loop) which no longer exists. This will cause an exception and the script will fail.

Note. This isn't specifically a Maxscript issue - it can occur in almost any language where you attempt to compare, for example, 2 arrays of values and remove values from those arrays when a match is found. The count (number of elements in the arrays) decreases causing either an exception, an "index out of range" or some similar error, depending on the language. For arrays it can sometimes be overcome by working DOWN the arrays rather than UP them. This may not work in this specific case because you may remove more than 1 vertex per pass. It will need more than just iterating over the vertices which exist at the beginning in order to overcome this.

Max 2016 (SP1/EXT1)
Win7Pro x64 (SP1). i5-3570K @ 4.4GHz, 8Gb Ram, DX11.
nVidia GTX760 (2GB) (Driver 430.86).

0 Likes
Message 3 of 12

Anonymous
Not applicable
Yes you are right, because in this way we are taking an object at an index which is out of length so a Not Existing Object,
I think that this can be resolved updating vertexCount every time we collapse two vertex
for v = 1 to vertexCount-1 do (
for v1 = v+1 to vertexCount do (
if vertex.position = vertex do (
Collapse (v, v1)
vertexCount = polyOp.getNumVerts obj
)
)
)
What do you thing? Thanks.
0 Likes
Message 4 of 12

Steve_Curley
Mentor
Mentor
Unfortunately not - as I mentioned, the conditions for a for loop are set once when the actual for loop is executed. That actual statement is never executed again (at least the outer one isn't) so changing the value of vertexCount within the loop will not affect the number of times the looped code is executed.

(
V = 5
for t = 1 to V do
(
format "t=%, V=%\n" t V
V -= 1
)
)

results in

t=1, V=5
t=2, V=4
t=3, V=3
t=4, V=2
t=5, V=1
OK

Next up - the actual collapsing of the vertices. There are 2 methods for collapsing vertices in an Editable Poly:-
polyOp.collapseVerts editable_poly_object vertexlist
and
editable_poly_object.collapse #vertex

The first requires a list of vertex numbers (actually a bitArray) to be collapsed, the second requires that the vertices to be collapsed are selected. In both cases the vertices must be directly connected to each other - merely determining that their positions are the same will not be sufficient to guarantee that they will be collapsed. That will require a fair bit of work - that which you can easily identify by simply looking at the object can be quite difficult (or at least long-winded) to determine programatically.

Max 2016 (SP1/EXT1)
Win7Pro x64 (SP1). i5-3570K @ 4.4GHz, 8Gb Ram, DX11.
nVidia GTX760 (2GB) (Driver 430.86).

0 Likes
Message 5 of 12

Anonymous
Not applicable
I am having trouble manipulating with vertexes.

This works great

for selectedMesh in selection do (
print (selectedMesh.name as String)
vertexCount = polyOp.getNumVerts selectedMesh
collapsableVertex = 0
print (vertexCount as String)
for v = 1 to vertexCount-1 do (
for v1 = v+1 to vertexCount do (
vertexA = polyOp.getVert selectedMesh v
vertexB = polyOp.getVert selectedMesh v1
)
)
print (collapsableVertex)
)


But when I check if they has the same position I get error.
This is the code I have used to check the position

if vertexA.pos = vertexB.pos do (
collapsableVertex = collapsableVertex+1
)

This is the error

"Box001"
"6146"
-- Error occurred in v1 loop; filename: C:\Users\Gurpreet Singh\Documents\3dsMax\scenes\CollapseVertexWithSamePosition.ms; position: 886; line: 26
-- Frame:
-- v1: 2
-- vertexA:
-- vertexB:
-- called in V loop; filename: C:\Users\Gurpreet Singh\Documents\3dsMax\scenes\CollapseVertexWithSamePosition.ms; position: 945; line: 29
-- Frame:
-- V: 1
-- called in selectedMesh loop; filename: C:\Users\Gurpreet Singh\Documents\3dsMax\scenes\CollapseVertexWithSamePosition.ms; position: 949; line: 30
-- Frame:
-- selectedMesh: $Box001
-- collapsableVertex: 0
-- vertexCount: 6146
-- No ""="" function for (Global:distance Local:vertexA Local:vertexB)
0 Likes
Message 6 of 12

Steve_Curley
Mentor
Mentor
You need to read the help a little more carefully.

if vertexA.pos = vertexB.pos
Is wrong on 2 counts.

1) polyOp.getVert returns a position (a Point3 value) - it IS a position. It does not have a pos property.
2) A test for Equals is == not = (= is for assignment only).

I would suggest you put this project on hold temporarily. Load the Maxscript help > contents > Maxscript Introduction. Read all the sections down to (and including) "Maxscript for new and casual users". That should help you avoid making what are quite basic mistakes. Every language has its own distinct syntax and is rarely applicable, without modification, to another language. Knowing C# will help with the general principles of programming, but not with the specifics.

Max 2016 (SP1/EXT1)
Win7Pro x64 (SP1). i5-3570K @ 4.4GHz, 8Gb Ram, DX11.
nVidia GTX760 (2GB) (Driver 430.86).

0 Likes
Message 7 of 12

Swordslayer
Advisor
Advisor
I've done a similar script by request quite a while ago, the barebones version would be:

try destroyDialog ::vertexFuse catch()
rollout vertexFuse "Vertex Fuse" width:150 height:65
(
spinner spnDist "Distance: " range: fieldWidth:50 type:#worldunits
button btnFuse "Fuse Vertices" width:135 height:25 offset:

local setMeshVert = meshOp.setVert

fn getNearbyVertices index list dist verts queue =
for v in queue
where distance verts.pos verts.pos <= dist do
(
append list v
queue = false
getNearbyVertices v list dist verts queue
)

on btnFuse pressed do with undo off, redraw off
(
if selection.count != 1 do
return messageBox "Select one object."

local obj = selection
local objTM = obj.transform
if NOT isKindOf obj Editable_mesh do convertToMesh obj

setCommandPanelTaskMode mode:#create

local verts = obj.verts as bitArray
local vertCount = verts.count
local vertList = join #() obj.verts
local vertDist = spnDist.value
local vertQueue = #{}

local matchedVerts = #{}
local fuseList = #()

for i = 1 to vertCount do
(
local v = vertList

for j = i + 1 to vertCount
where NOT matchedVerts AND
distance v.pos vertList.pos <= vertDist do
(
append vertQueue i
append vertQueue j
append matchedVerts j
)
)

local vertToSelect = copy vertQueue

for v in vertQueue do
(
local vertGroup = #{}
getNearbyVertices v vertGroup vertDist vertList vertQueue
append fuseList vertGroup
)

for each in fuseList do
(
obj.selectedVerts = each
setMeshVert obj each ((averageSelVertCenter obj) * objTM)
)

obj.selectedVerts = vertToSelect

setCommandPanelTaskMode mode:#modify
subObjectLevel = 1
)
)
createDialog vertexFuse


Hopefully it's short enough for you to comprehend while still applicable (with a few changes here and there - you might want to use 3d grid for lookup for complex meshes, for example). Instead of collapsing the verts, it positions them at the same place - one loop to find all close verts, another to merge them in groups. It uses a threshold (btw. note that equality comparison on two Point3 values, effectively vectors consisting of single precision floats, will often return false even when the two points are by design on the same position - look up float comparison for further info).

Using mesh instead of poly methods is just my personal preference, if you'd like to use polyop methods, only averageSelVertCenter would need to be replaced by a simple function - sum of all positions divided by their count.
0 Likes
Message 8 of 12

Anonymous
Not applicable
It's oke, got it. I have just now studied Values at: http://docs.autodesk.com/3DSMAX/15/ENU/MAXScript-Help/index.html?url=files/GUID-B59088DE-AC0B-4926-8...
So only now I am able to understand what is Point3 and how to compare these two.
Now I am studying "Maxscript for new and casual users" at http://docs.autodesk.com/3DSMAX/15/ENU/MAXScript-Help/index.html?url=files/GUID-35CB7918-A86D-465E-A... as you said.
The problem is that this script would help me in a game development project, so I without this I will lost a lot of time.
0 Likes
Message 9 of 12

Anonymous
Not applicable
Does Editable_Mesh to this better then Editable_Poly?
I was using Editable_Poly because this has also the possibility to remove a vertex without losing a face and the UV.
You are suggesting me to use one loop to position all vertex with less then a distance at the same position, and use a second loop to collapse them. Yes this looks good, now I have another idea passing through my mind. With this I can implement in this way:
Detect which is the vertex a the border.
Position all closest vertex to it's position.
How can I detect if a vertex is at border?
What is averageSelVertCenter? Is it the mid point between vertexes or a method like polyOp?
I see at line 10 you used " where distance verts.pos verts.pos <= dist do". Does this loop automatically and check the if statement?
Thanks
0 Likes
Message 10 of 12

Anonymous
Not applicable
It looks I am getting near to the target but not sure.
I am using this Logic
Loop from 1 to vertexCount to check how many vertex are collapsable
Loop from 1 to vertexCount-collapsableVertex to collapse this vertex.
I have created a box with Length = 0, Width = 10, Height = 10, I have converted it to Editable_Poly and then Tesselate a lot of time. So now this Box has at 17 vertex per each vertex at the border, and 2 vertex on per each vertex on the front face.
In total I have 1538 Vertex, this script detects 1248 Vertex Collapsable
Looping in this way to second loop (the loop which has the function to collapse) finishes at 1538-1248 = 290. This should be correct, but the problem is that all collapse returns false, I don't know why?.
I have tried to do it from Editable_Poly Options, and is possible to collapse this 17 vertex to 1 vertex.
This is the script:

distanceToCollapse = 0.1
for selectedMesh in selection do (
vertexCount = polyOp.getNumVerts selectedMesh
print (selectedMesh.name as String + "; Vertex: " + vertexCount as String + ";")
print ("Detection started ...")
collapsableVertex = 0
disabledVertex = #()
for a = 1 to vertexCount-1 do (
for b = a+1 to vertexCount-1 do (
vertexA = polyOp.getVert selectedMesh a
vertexB = polyOp.getVert selectedMesh b
if (distance vertexA vertexB) < distanceToCollapse do (
availableVertex = true
for c = 1 to disabledVertex.count do (
if disabledVertex as Integer == b as Integer then (
availableVertex = false
print ("STATE " + a as String + "; DETECTED " + b as String + "; AT INDEX " + c as String + "; TARGET " + vertexCount as String + "; RETURN " + availableVertex as String + ";")
)
)
if availableVertex then (
collapsableVertex = collapsableVertex+1
disabledVertex = disabledVertex + #(b)
) else (
print ("AVAILABLE RETURNED FALSE")
)
)
)
)
print (disabledVertex.count)
print ("Detection finished. Detected " + collapsableVertex as String + " collapsable vertex over " + vertexCount as String + " vertex")
if collapsableVertex > vertexCount do (
print ("Failed")
continue
)
for d = 1 to vertexCount-collapsableVertex-1 do (
for g = d+1 to vertexCount-collapsableVertex do (
vertexD = polyOp.getVert selectedMesh d
vertexG = polyOp.getVert selectedMesh g
if (distance vertexD vertexG) < distanceToCollapse do (
polyOp.SetVertSelection selectedMesh #{d, g}
if (selectedMesh.EditablePoly.collapse #Vertex) then (
print ("Collapsed Vertex " + d as String + " and " + g as String)
) else (
print ("Failed collapsing " + d as String + " and " + g as String)
)
)
)
)
vertexCount = polyOp.getNumVerts selectedMesh
print (vertexCount as String)
)

How can I fix this?
Thanks, good bye!
0 Likes
Message 11 of 12

Swordslayer
Advisor
Advisor
You are free to use whatever suits your purpose best, meshop methods are in some cases faster but sometimes the conversion from poly to mesh beats the purpose.
I'm not sure what you mean by the border here. In a subobject sense, finding out if a vertex lies on a border means that the vertex is one of the border verts - you'd get the border verts before entering the loop - borderVerts = polyOp.getVertsUsingEdge obj (polyOp.getOpenEdges obj) - and then in the loop test for it - borderVerts.
As for averageSelVertCenter, just look it up in the reference. Same applies for where condition - look up for loop syntax in the reference.
0 Likes
Message 12 of 12

Swordslayer
Advisor
Advisor
No, it won't work like this, look at the code I posted before - the second loop is not just iterating over all the candidates, it's recursively searching for the neighbor candidates to collapse. To see what would happen in you case compare it with for example max teapot, 4 segments, 120-unit radius - run my code on it converted to editable mesh, run this code on it converted to editable poly:

(
local distanceToCollapse = 10.0
local objs = for obj in selection where isKindOf obj Editable_Poly collect obj
local getVertPos = polyOp.getVert

for obj in objs do
(
local disabledVerts = #{}
local vertexCount = polyOp.getNumVerts obj
format "Object: %, Vertex count: %\n" obj.name vertexCount
print ("Detection started ...")

for a = 1 to vertexCount-1 do
for b = a + 1 to vertexCount
where distance (getVertPos obj a) (getVertPos obj b) < distanceToCollapse do
(
append disabledVerts a
append disabledVerts b
)

format "Detection finished. Detected % collapsible vertices of %.\n" disabledVerts.numberSet vertexCount

polyOp.collapseVerts obj disabledVerts

format "Vertex count after collapse: %\n" (polyOp.getNumVerts obj)
)
)


That's basically your code cleaned up a bit with some erroneous parts removed.

Have a look at bitarrays and how they work, and study polyOp structure for a while to see what you can use. Don't write new code until you are familiar with them, otherwise you end up reinventing the whell which makes your code both slower and harder to read (and introduces some bugs here and there as well). Take for example this part:

for c = 1 to disabledVertex.count do
(
if disabledVertex as Integer == b as Integer then
(
availableVertex = false
print (...)
)
)


Using bitarray for disabledVerts, it can be rewritten as
if disabledVerts do print (...)
or rather using this line together with the original condition ((...) AND (...)). Apart from the debug print, it's not needed here as bitarrays don't have duplicate entries anyway so I left it out in the end.

Note that in another line,
disabledVertex = disabledVertex + #(b)
is actually
append disabledVerts b


And a minor nitpick would be changing
collapsableVertex = collapsableVertex + 1
to
collapsableVertex += 1
although you don't actually need it at all.

Also don't use EditablePoly methods, many of them require modify mode and redraw the UI, which slows things down. Another problem with them is that you often have to select verts and "push the buttons" instead of passing some arbitrary vertex bitarray directly to a function.
0 Likes