Attach node to patch


#15

you can do the same to attach splines. it works almost for everything that needs picking a node in the viewport.

timestemp() before and after.


#16

That’s interesting to know, although I just used addAndWeld for attaching selected splines together. I’ll have to try that timestamp function.

Who knows what and where Autodesk changes things under the hood right now… My main beef is with older things like edit mesh and patch. I think that if you are going to keep older things in max for whatever legacy reasons, then UPDATE THEM! Or at least give better access to them in maxscript… I don’t really use edit mesh for modeling, but patches still can have their uses from time to time. I think it would be really nice to have the same ribbon tools for patches as well, which is why I’m trying to create a lot of them myself, but it definitely seems like a challenge. Anyways that’s my rant for today. :slight_smile:


#17

Okay so I finally finished my version, which works decently. I haven’t recorded the times on this, and I’ve only tried it on a few objects. I figured it was worth a try because it seems like your function is dependent on the version, and if autodesk moves things around for 2014, then your function ends up getting broken. And I don’t know anything really about dot net to constantly update it for my other attachment scripts. I would have to say that the set of scripts is for max 2013 only, which I think would be a major drawback.

Anyways, here’s the function -

fn AttachPatch Obj:$ AttachNode:undefined = 
   (
   	if (Obj != undefined and AttachNode != undefined) then
   	(
   		if (classof Obj != Editable_Patch) then 
   		(
   			if (classof Obj == Editable_Poly or classof Obj == PolyMeshObject or classof Obj == Editable_Mesh) then 
   			(
   				select Obj ; modPanel.addModToSelection (Turn_to_Patch ()) ; macros.run "Modifier Stack" "Convert_to_Patch" -- Convert to patch, retain quads
   			) else macros.run "Modifier Stack" "Convert_to_Patch" -- Convert to patch
   		) -- Convert current object to patch if its not already a patch 
   		if (classof AttachNode != Editable_Patch) then 
   		(
   			if (classof AttachNode == Editable_Poly or classof AttachNode == PolyMeshObject or classof AttachNode == Editable_Mesh) then 
   			(
   				select AttachNode ; modPanel.addModToSelection (Turn_to_Patch ()) ; macros.run "Modifier Stack" "Convert_to_Patch" -- Convert to patch, retain quads
   			) else macros.run "Modifier Stack" "Convert_to_Patch" -- Convert to patch
   		) -- Convert attached object to patch if its not already a patch 
   		Loops2Sided1 = #{} -- Closed loops with only 2 edges
   		Loops2Sided2 = #{} -- Closed loops with only 2 edges
   		for i = 1 to (patch.getNumEdges Obj) do
   		(
   			local Edges1 = ((patch.getVertEdges Obj ((patch.getEdgeVert1 Obj i) + 1)) as bitarray) -- Get edges from first vertex
   			local Edges2 = ((patch.getVertEdges Obj ((patch.getEdgeVert2 Obj i) + 1)) as bitarray) -- Get edges from second vertex
   			if ((Edges1 * Edges2).numberset == 2) then Loops2Sided1 += (Edges1 * Edges2) -- Get both edges
   		) -- Get 2-sided closed loops on current object
   		for i = 1 to (patch.getNumEdges AttachNode) do
   		(
   			local Edges1 = ((patch.getVertEdges AttachNode ((patch.getEdgeVert1 AttachNode i) + 1)) as bitarray) -- Get edges from first vertex
   			local Edges2 = ((patch.getVertEdges AttachNode ((patch.getEdgeVert2 AttachNode i) + 1)) as bitarray) -- Get edges from second vertex
   			if ((Edges1 * Edges2).numberset == 2) then Loops2Sided2 += (Edges1 * Edges2) -- Get both edges
   		) -- Get 2-sided closed loops on attached object
   		patch.deletePatchParts Obj Loops2Sided1 #{} -- Delete 2-sided loops on current object to avoid vertex count mismatch
   		patch.deletePatchParts AttachNode Loops2Sided2 #{} -- Delete 2-sided loops on attached object to avoid vertex count mismatch
   		AllVerts1 = #() -- Initial vertex array
   		AllVecPositions1 = #() -- Initial position array
   		AllVertTypes1 = #() -- Initial handle array
   		AllVerts2 = #() -- Initial vertex array
   		AllVecPositions2 = #() -- Initial position array
   		AllVertTypes2 = #() -- Initial handle array
   		if ((patch.getNumVerts Obj) >= 3) then
   		(
   			AllVerts1 = #{1..(patch.getNumVerts Obj)} as array -- Get all vertices
   			AllVecPositions1 = (for i = 1 to AllVerts1.count collect (for j = 1 to (patch.getVertVecs Obj i).count collect (patch.getVec Obj (patch.getVertVecs Obj i)[j]))) as array -- Get 3d array of handle positions of current object
   			AllVertTypes1 = (for i = 1 to AllVerts1.count collect patch.getVertType Obj i) as array -- Get array of vertex tangent types of current object
   		) -- If there is at least three vertices in the current object i.e. a face exists
   		if ((patch.getNumVerts AttachNode) >= 3) then
   		(
   			AllVerts2 = #{1..(patch.getNumVerts AttachNode)} as array -- Get all vertices
   			AllVecPositions2 = (for i = 1 to AllVerts2.count collect (for j = 1 to (patch.getVertVecs AttachNode i).count collect (patch.getVec AttachNode (patch.getVertVecs AttachNode i)[j]))) as array -- Get 3d array of handle positions of attached node
   			AllVertTypes2 = (for i = 1 to AllVerts2.count collect patch.getVertType AttachNode i) as array -- Get array of vertex tangent types of attached node
   		) -- If there is at least three vertices in the attached object i.e. a face exists
   		AllVerts3 = AllVerts1 + AllVerts2 -- Add vertice selections
   		AllVecPositions3 = AllVecPositions1 + AllVecPositions2 -- Add positions together
   		AllVertTypes3 = AllVertTypes1 + AllVertTypes2 -- Add vert types together
   		Steps = getPatchSteps Obj -- Get patch steps of original object
   		ShowInterior = patch.getShowInterior Obj
   		setPatchSteps Obj 0 -- Set patch steps of current object to 0
   		setPatchSteps AttachNode 0 -- Set patch steps of attached node to 0
   		for i = 1 to AllVerts2.count do AllVerts2[i] += AllVerts1.count -- Offset vertice indexes
   		select Obj ; macros.run "Modifier Stack" "Convert_to_Poly" -- Convert to editable poly, using turn to poly mod in order to retain quads
   		select AttachNode ; macros.run "Modifier Stack" "Convert_to_Poly" -- Convert to editable poly, using turn to poly mod in order to retain quads
   		Obj.deleteIsoVerts() -- Delete isolated vertices
   		polyop.attach Obj AttachNode -- Attach node to current object
   		select Obj -- Select original object
   		modPanel.addModToSelection (Turn_to_Patch ()) ; macros.run "Modifier Stack" "Convert_to_Patch" -- Convert to editable patch, using turn to patch mod in order to retain quads
   		patch.update Obj -- Update patch
   		if ((patch.getNumVerts Obj) >= 3 and AllVecPositions3.count == (patch.getNumVerts Obj) and AllVerts3.count == (patch.getNumVerts Obj)) then 
   		(
   			for i = 1 to (patch.getNumVerts Obj) do 
   			(
   				if (AllVecPositions3[i] != undefined and AllVecPositions3[i].count == (patch.getVertVecs Obj i).count) then
   				(
   					for j = 1 to (patch.getVertVecs Obj i).count do 
   					(
   						if (AllVecPositions3[i][j] != undefined) then patch.setVec Obj (patch.getVertVecs Obj i)[j] AllVecPositions3[i][j] -- Return handles to what they were previously
   					) -- Loop through handles
   				) -- If vertices match and vert's handle numbers match
   			) -- Loop through vertices
   		) -- If vertex counts match
   		if ((patch.getNumVerts Obj) >= 3 and AllVertTypes3.count == (patch.getNumVerts Obj)) then (for i = 1 to (patch.getNumVerts Obj) do (if (AllVertTypes3[i]) != undefined do patch.changeVertType Obj i (AllVertTypes3[i]))) -- Return vertex tangent types to what they were previously
   		setPatchSteps Obj Steps -- Set patch steps to what they were previously
   		patch.setShowInterior Obj ShowInterior
   		patch.update Obj -- Update patch
   		return Obj
   	) -- If objects exist
   )

A couple of things to note, I had to make it so that the function deletes edges that make up a 2 sided loop i.e. edges that form 2 sided cylinders. So when you convert a teapot to patch and run the function using the teapot as the attach node, the handle and spout end up getting deleted. It was something that I couldn’t figure out a way around. This is because the geometry gets weird when you end up with double sided faces in edit poly. Also, you end up with a higher vertex count afterwards, since you can’t weld edges together with double sided faces. So if you use this function try to avoid those cases. Another thing I noticed is that sometimes, if you run the function and undo, then the patch steps on the source object end up staying at 0.

This is the single node version, I also made a function to loop through a selection of objects -

fn AttachSelectedPatch Sel:(selection as array) = 
   (
   	if (Sel != undefined) then
   	(
   		Object1 = Sel[1] -- Get first object in selection
   		ObjectSel = for i = 1 to Sel.count where (Sel[i] != Object1) collect Sel[i] -- Get selected objects excluding the first one
   		for i = 1 to ObjectSel.count do AttachPatch Obj:Object1 AttachNode:ObjectSel[i] -- Attach objects
   		patch.update Object1 ; CenterPivot Object1 ; WorldAlignPivot Object1 ; select Object1 -- Select object, update it, and fix its transforms
   		return Object1 -- Get object
   	) -- If objects exist
   )

It will probably be really slow for a large selection of objects because it loops through the selection and runs the attach function for each node. I plan on trying to make this method faster by just having one function, which is a multi-node version which somehow appends the position and vertex type arrays from each object, and just attaches them in edit poly and reapplies them after going back to patch.

If you know of any ways of making this faster and more efficient, please let me know.


#18

Success! I was able to make one function to attach multiple patches. Just as I thought I got the first object, and looped through the rest of the selection. Got the first objects positions and vert types, and appended each object’s positions and types to those arrays. then I attached those same objects in edit poly, then converted to patch and reapplied those positions. I fixed some problems I missed and made it a little more efficient.

fn AttachObjectsPatch AttachNodes:(selection as array) AttachName:(uniquename ((selection as array)[1].name)) = 
         (
         	if (AttachNodes != undefined and AttachNodes.count != 0) then
         	(
         		Object1 = AttachNodes[1] -- Get first object in selection
         		ObjectSel = for i = 1 to AttachNodes.count where (AttachNodes[i] != Object1) collect AttachNodes[i] -- Get selected objects excluding the first one
         		AllVecPositions = #() -- Initial position array
         		AllVertTypes = #() -- Initial handle array
         		select Object1 -- Select first object
         		if (classof Object1 != Editable_Patch) then 
         		(
         			if (classof Object1 == Editable_Poly or classof Object1 == PolyMeshObject or classof Object1 == Editable_Mesh) then 
         			(
         				modPanel.addModToSelection (Turn_to_Patch ()) ; macros.run "Modifier Stack" "Convert_to_Patch" -- Convert to patch, retain quads
         			) else macros.run "Modifier Stack" "Convert_to_Patch" -- Convert to patch
         		) -- Convert first object to patch if its not already a patch 
         		Steps = getPatchSteps Object1 -- Get patch steps of first object
         		ShowInterior = patch.getShowInterior Object1 -- Get show interior edges
         		Loops2Sided = #{} -- Closed loops with only 2 edges
         		for i = 1 to (patch.getNumEdges Object1) do
         		(
         			local Edges1 = ((patch.getVertEdges Object1 ((patch.getEdgeVert1 Object1 i) + 1)) as bitarray) -- Get edges from first vertex
         			local Edges2 = ((patch.getVertEdges Object1 ((patch.getEdgeVert2 Object1 i) + 1)) as bitarray) -- Get edges from second vertex
         			if ((Edges1 * Edges2).numberset == 2) then Loops2Sided += #{((patch.getEdgeVert1 Object1 i) + 1), ((patch.getEdgeVert2 Object1 i) + 1)} -- Get both edges
         		) -- Get 2-sided closed loops on first object
         		patch.deletePatchParts Object1 Loops2Sided #{} -- Delete 2-sided loops on first object to avoid vertex count mismatch
         		setPatchSteps Object1 0 -- Set patch steps of first object to 0
         		AllVertsNum = (patch.getNumVerts Object1) -- Get vertex number of first object
         		if (AllVertsNum >= 3) then
         		(
         			AllVecPositions = (for i = 1 to AllVertsNum collect (for j = 1 to (patch.getVertVecs Object1 i).count collect (patch.getVec Object1 (patch.getVertVecs Object1 i)[j]))) as array -- Get 3d array of handle positions of first object
         			AllVertTypes = (for i = 1 to AllVertsNum collect patch.getVertType Object1 i) as array -- Get array of vertex tangent types of first object
         		) -- If there is at least three vertices in the first object i.e. a face exists
         		macros.run "Modifier Stack" "Convert_to_Poly" -- Convert to editable poly, using turn to poly mod in order to retain quads
         		for i = 1 to ObjectSel.count do
         		(
         			if (classof ObjectSel[i] != Editable_Patch) then 
         			(
         				select ObjectSel[i] -- Select current object
         				if (classof ObjectSel[i] == Editable_Poly or classof ObjectSel[i] == PolyMeshObject or classof ObjectSel[i] == Editable_Mesh) then 
         				(
         					modPanel.addModToSelection (Turn_to_Patch ()) ; macros.run "Modifier Stack" "Convert_to_Patch" -- Convert to patch, retain quads
         				) else macros.run "Modifier Stack" "Convert_to_Patch" -- Convert to patch
         			) -- Convert current object to patch if its not already a patch 
         			local Loops2Sided1 = #{} -- Closed loops with only 2 edges
         			local AllVertsNum1 = (patch.getNumVerts ObjectSel[i]) -- Get vertex number of current object
         			for j = 1 to (patch.getNumEdges ObjectSel[i]) do
         			(
         				local Edges1 = ((patch.getVertEdges ObjectSel[i] ((patch.getEdgeVert1 ObjectSel[i] j) + 1)) as bitarray) -- Get edges from first vertex
         				local Edges2 = ((patch.getVertEdges ObjectSel[i] ((patch.getEdgeVert2 ObjectSel[i] j) + 1)) as bitarray) -- Get edges from second vertex
         				if ((Edges1 * Edges2).numberset == 2) then Loops2Sided1 += #{((patch.getEdgeVert1 ObjectSel[i] i) + 1), ((patch.getEdgeVert2 ObjectSel[i] i) + 1)} -- Get both edges
         			) -- Get 2-sided closed loops on current object
         			patch.deletePatchParts ObjectSel[i] Loops2Sided1 #{} -- Delete 2-sided loops on attached object to avoid vertex count mismatch
         			if (AllVertsNum1 >= 3) then
         			(
         				local AllVecPositions1 = (for j = 1 to AllVertsNum1 collect (for k = 1 to (patch.getVertVecs ObjectSel[i] j).count collect (patch.getVec ObjectSel[i] (patch.getVertVecs ObjectSel[i] j)[k]))) as array -- Get 3d array of handle positions of current node
         				local AllVertTypes1 = (for j = 1 to AllVertsNum1 collect patch.getVertType ObjectSel[i] j) as array -- Get array of vertex tangent types of current node
         				AllVertsNum += AllVertsNum1 -- Add vertice selections
         				AllVecPositions += AllVecPositions1 -- Add positions together
         				AllVertTypes += AllVertTypes1 -- Add vert types together
         			) -- If there is at least three vertices in the current object i.e. a face exists
         			setPatchSteps ObjectSel[i] 0 -- Set patch steps of current node to 0
         		) -- Loop through object selection
         		select Object1 ; macros.run "Modifier Stack" "Convert_to_Poly" -- Select first object and convert to poly
         		for i = 1 to ObjectSel.count do polyop.attach Object1 ObjectSel[i] -- Attach objects to first object
         		Object1.deleteIsoVerts() -- Delete isolated vertices
         		modPanel.addModToSelection (Turn_to_Patch ()) ; macros.run "Modifier Stack" "Convert_to_Patch" -- Convert to editable patch, using turn to patch mod in order to retain quads
         		patch.update Object1 -- Update patch
         		if ((patch.getNumVerts Object1) >= 3 and AllVecPositions.count == (patch.getNumVerts Object1) and AllVertsNum == (patch.getNumVerts Object1)) then 
         		(
         			for i = 1 to (patch.getNumVerts Object1) do 
         			(
         				if (AllVecPositions[i] != undefined and AllVecPositions[i].count == (patch.getVertVecs Object1 i).count) then
         				(
         					for j = 1 to (patch.getVertVecs Object1 i).count do 
         					(
         						if (AllVecPositions[i][j] != undefined) then patch.setVec Object1 (patch.getVertVecs Object1 i)[j] AllVecPositions[i][j] -- Return handles to what they were previously
         					) -- Loop through handles
         				) -- If vertices match and vert's handle numbers match
         			) -- Loop through vertices
         		) -- If vertex counts match
         		if ((patch.getNumVerts Object1) >= 3 and AllVertTypes.count == (patch.getNumVerts Object1)) then (for i = 1 to (patch.getNumVerts Object1) do (if (AllVertTypes[i]) != undefined do patch.changeVertType Object1 i (AllVertTypes[i]))) -- Return vertex tangent types to what they were previously
         		setPatchSteps Object1 Steps -- Set patch steps to what they were previously
         		patch.setShowInterior Object1 ShowInterior -- Set show interior edges to what it was previously
         		patch.update Object1 -- Update patch
         		CenterPivot Object1 ; WorldAlignPivot Object1 -- Fix object's transforms
         		Object1.name = AttachName -- Update patch and fix its transforms
         		return Object1 --Get node
         	) -- If objects exist
         )
     Its a little slower than I would've wanted, but its not too bad.  I did timestamp tests using my first method and my second method and also your pick method.  Here are the results on my machine attaching 100 spheres -
     
     Method 1 - Single attach, getting and resetting vertex handles, function ran on each object looping through selection.
     Method 1 = 68.3 seconds
     Method 2 - Multiple attach, getting and resetting vertex handles, appending arrays.
     Method 2 = 10.6 seconds
     Method 3 - Multiple attach, picking objects in viewport.
     Method 3 = 6.8 seconds
     
     Although the pick method is the fastest, I did notice a problem.  After a running this on large numbers of selections -
with redraw off
         (
         	viewport.resetAllViews() 
         	ObjectSel1 = (selection as array)
         	target = ObjectSel1[1]
         	sources = for i = 1 to ObjectSel1.count where (ObjectSel1[i] != target) collect ObjectSel1[i]
         	convertto target Editable_Patch
         	select target
         	max modify mode
         	pickPatchAttach sources
         )
     It looks like its missing about a third of the objects. -

 [[img]http://img694.imageshack.us/img694/7474/patchattachingerror.jpg[/img]](http://imageshack.us/photo/my-images/694/patchattachingerror.jpg/)

I tried then, reselected the attached patches then selecting the rest and trying to evaluate again. I did this over and over a bunch of times. It attached a couple of spheres, but eventually stopped attaching anything…Not sure what’s going on, but maybe this method isn’t so good for a large selection…


#19

check a c++/sdk solution http://forums.cgsociety.org/showpost.php?p=7508910&postcount=27


#20

you don’t need all these stack operations. just simply use


if iskindof node Editable_Patch then node else convertto node Editable_Patch
if iskindof node Editable_Poly then node else convertto node Editable_Poly

convertto returns undefined if it can’t convert to the specified class.

also i don’t see any reason to select every processing node.


#21

Yeah…that is way over my head, much more then your picking method. I have very limited c++ knowledge. I’ve written some basic mental ray shaders in c++, but not much beyond that. Do you have to compile anything with that? How do you call that in a function? I tried evaluating it and the listener spits out -

-- Error occurred in anonymous codeblock; filename: ; position: 35; line: 1
-- Syntax error: at ,, expected <factor>
--  In line: def_visible_primitive(patchAttach, "

Yeah I probably could just use convertto for converting to poly, but I need to use the Turn_to_Patch modifier before conversion to convert a mesh with quads to a patch with quads, otherwise using convertto node Editable_Patch converts the whole model to tri patches. Is there a conversion function that I’m missing, which keeps quads?

I was thinking though, maybe you could use a hybrid of our two methods by finding which nodes have closed 2-sided edge loops, and using the your method for those, and using my method for the rest. Or using mine, if the amount of nodes used, is over say, 20.


#22

it seams like your attach doesn’t work right. try this setup:


delete objects
target = Quadpatch name:"target" pos:[-10,0,0] length:10 lengthsegs:4 width:10 widthsegs:4
convertto target Editable_Patch
source = Quadpatch pos:[0,0,0] length:8 lengthsegs:2 width:8 widthsegs:2
convertto source Editable_Patch
patch.setvert source 5 [0,0,4]
patch.update source

-- AttachPatch obj:target attachnode:source


#23

btw. the c++ attach function attached 100 quad patches for 0.7 sec. you function after some optimization does the same for 30 secs.


#24

I have very limited c++ knowledge. I’ve written some basic mental ray shaders in c++, but not much beyond that. Do you have to compile anything with that? How do you call that in a function? I tried evaluating it and the listener spits out

you would need to create a dlx plugin to extend mxs using ms visual studio (version used depends on max version you need to compile for). The most difficult part is setting up the initial project for the build. Once it’s created the process is mostly painless, but it’s definitely worth it. (thisis quite old now but mostly still valid if you are interested in taking it further).


#25

Yeah that’s true, I’m going back in my multi-attach function, and make it so that the only object that’s selected, is the first node, which everything gets attached to. I switched to using -

addModifier node (Turn_to_Patch ()) ; convertto node Editable_Patch

to convert to patch, in order to not have to select anything, and keep quad faces. This will speed the function up a lot, thanks denis.


#26

Use this function instead -

fn AttachObjectsPatch AttachNodes:(selection as array) AttachName:(uniquename ((selection as array)[1].name)) = 
 (
 	if (AttachNodes != undefined and AttachNodes.count != 0) then
 	(
 		Object1 = AttachNodes[1] -- Get first object in selection
 		ObjectSel = for i = 1 to AttachNodes.count where (AttachNodes[i] != Object1) collect AttachNodes[i] -- Get selected objects excluding the first one
 		AllVecPositions = #() -- Initial position array
 		AllVertTypes = #() -- Initial handle array
 		select Object1 -- Select first object
 		if (classof Object1 == Editable_Poly or classof Object1 == PolyMeshObject or classof Object1 == Editable_Mesh) then addModifier Object1 (Turn_to_Patch ()) -- Use turn to patch mod to keep quads
 		convertto Object1 Editable_Patch -- Convert to patch
 		Steps = getPatchSteps Object1 -- Get patch steps of first object
 		ShowInterior = patch.getShowInterior Object1 -- Get show interior edges
 		Loops2Sided = #{} -- Closed loops with only 2 edges
 		for i = 1 to (patch.getNumEdges Object1) do
 		(
 			local Edges1 = ((patch.getVertEdges Object1 ((patch.getEdgeVert1 Object1 i) + 1)) as bitarray) -- Get edges from first vertex
 			local Edges2 = ((patch.getVertEdges Object1 ((patch.getEdgeVert2 Object1 i) + 1)) as bitarray) -- Get edges from second vertex
 			if ((Edges1 * Edges2).numberset == 2) then Loops2Sided += #{((patch.getEdgeVert1 Object1 i) + 1), ((patch.getEdgeVert2 Object1 i) + 1)} -- Get both edges
 		) -- Get 2-sided closed loops on first object
 		patch.deletePatchParts Object1 Loops2Sided #{} -- Delete 2-sided loops on first object to avoid vertex count mismatch
 		setPatchSteps Object1 0 -- Set patch steps of first object to 0
 		AllVertsNum = (patch.getNumVerts Object1) -- Get vertex number of first object
 		if (AllVertsNum >= 3) then
 		(
 			AllVecPositions = (for i = 1 to AllVertsNum collect (for j = 1 to (patch.getVertVecs Object1 i).count collect (patch.getVec Object1 (patch.getVertVecs Object1 i)[j]))) as array -- Get 3d array of handle positions of first object
 			AllVertTypes = (for i = 1 to AllVertsNum collect patch.getVertType Object1 i) as array -- Get array of vertex tangent types of first object
 		) -- If there is at least three vertices in the first object i.e. a face exists
 		convertto Object1 Editable_Poly ; update Object1 -- Convert to poly and update
 		for i = 1 to ObjectSel.count do
 		(
 			if (classof ObjectSel[i] == Editable_Poly or classof ObjectSel[i] == PolyMeshObject or classof ObjectSel[i] == Editable_Mesh) then addModifier ObjectSel[i] (Turn_to_Patch ()) -- Use turn to patch mod to keep quads
 			convertto ObjectSel[i] Editable_Patch -- Convert to patch
 			local Loops2Sided1 = #{} -- Closed loops with only 2 edges
 			local AllVertsNum1 = (patch.getNumVerts ObjectSel[i]) -- Get vertex number of current object
 			for j = 1 to (patch.getNumEdges ObjectSel[i]) do
 			(
 				local Edges1 = ((patch.getVertEdges ObjectSel[i] ((patch.getEdgeVert1 ObjectSel[i] j) + 1)) as bitarray) -- Get edges from first vertex
 				local Edges2 = ((patch.getVertEdges ObjectSel[i] ((patch.getEdgeVert2 ObjectSel[i] j) + 1)) as bitarray) -- Get edges from second vertex
 				if ((Edges1 * Edges2).numberset == 2) then Loops2Sided1 += #{((patch.getEdgeVert1 ObjectSel[i] i) + 1), ((patch.getEdgeVert2 ObjectSel[i] i) + 1)} -- Get both edges
 			) -- Get 2-sided closed loops on current object
 			patch.deletePatchParts ObjectSel[i] Loops2Sided1 #{} -- Delete 2-sided loops on attached object to avoid vertex count mismatch
 			if (AllVertsNum1 >= 3) then
 			(
 				local AllVecPositions1 = (for j = 1 to AllVertsNum1 collect (for k = 1 to (patch.getVertVecs ObjectSel[i] j).count collect (patch.getVec ObjectSel[i] (patch.getVertVecs ObjectSel[i] j)[k]))) as array -- Get 3d array of handle positions of current node
 				local AllVertTypes1 = (for j = 1 to AllVertsNum1 collect patch.getVertType ObjectSel[i] j) as array -- Get array of vertex tangent types of current node
 				AllVertsNum += AllVertsNum1 -- Add vertice selections
 				AllVecPositions += AllVecPositions1 -- Add positions together
 				AllVertTypes += AllVertTypes1 -- Add vert types together
 			) -- If there is at least three vertices in the current object i.e. a face exists
 			setPatchSteps ObjectSel[i] 0 -- Set patch steps of current node to 0
 		) -- Loop through object selection
 		for i = 1 to ObjectSel.count do polyop.attach Object1 ObjectSel[i] -- Attach objects to first object
 		Object1.deleteIsoVerts() -- Delete isolated vertices
 		addModifier Object1 (Turn_to_Patch ()) ; convertto Object1 Editable_Patch -- Use turn to patch mod to keep quads and convert to patch
 		patch.update Object1 -- Update patch
 		if ((patch.getNumVerts Object1) >= 3 and AllVecPositions.count == (patch.getNumVerts Object1) and AllVertsNum == (patch.getNumVerts Object1)) then 
 		(
 			for i = 1 to (patch.getNumVerts Object1) do 
 			(
 				if (AllVecPositions[i] != undefined and AllVecPositions[i].count == (patch.getVertVecs Object1 i).count) then
 				(
 					for j = 1 to (patch.getVertVecs Object1 i).count do 
 					(
 						if (AllVecPositions[i][j] != undefined) then patch.setVec Object1 (patch.getVertVecs Object1 i)[j] AllVecPositions[i][j] -- Return handles to what they were previously
 					) -- Loop through handles
 				) -- If vertices match and vert's handle numbers match
 			) -- Loop through vertices
 		) -- If vertex counts match
 		if ((patch.getNumVerts Object1) >= 3 and AllVertTypes.count == (patch.getNumVerts Object1)) then (for i = 1 to (patch.getNumVerts Object1) do (if (AllVertTypes[i]) != undefined do patch.changeVertType Object1 i (AllVertTypes[i]))) -- Return vertex tangent types to what they were previously
 		setPatchSteps Object1 Steps -- Set patch steps to what they were previously
 		patch.setShowInterior Object1 ShowInterior -- Set show interior edges to what it was previously
 		patch.update Object1 -- Update patch
 		CenterPivot Object1 ; WorldAlignPivot Object1 -- Fix object's transforms
 		Object1.name = AttachName -- Update patch and fix its transforms
 		return Object1 --Get node
 	) -- If objects exist
 )

It seems to work fine on the two quad patches. The only patches it doesn’t work good with is geometry with 2 sides, like the handle on a teapot -


#27

I think I’m pretty happy with my version now. I did some more optimizations and added an argument to attach current nodes, all geometry nodes, or geometry - current nodes. I also made it so that any 2 sided loops get subdivided before converting to edit poly. I was able to get the function to attach 100 patches in 0.911 seconds, which I guess is not too bad.

Here’s the function -

	fn AttachObjectsPatch Sel:(selection as array) SelType:#Current AttachName:(uniquename ((selection as array)[1].name)) = 
	(
		if (Sel != undefined and Sel.count != 0) then
		(
			AllNodeSel = (geometry as array) -- Get all objects
			InvNodeSel = for i in AllNodeSel where ((finditem Sel i) == 0) collect i -- Inverted selection
			NodeSel = #() -- Initial selection
			if (SelType == #Current) then NodeSel = Sel else -- Selection
			if (SelType == #Inverse) then NodeSel = InvNodeSel else -- Inverted selection
			if (SelType == #All) then NodeSel = AllNodeSel -- All objects
			Object1 = NodeSel[1] -- Get first object in selection
			ObjectSel = NodeSel -- Node selection
			AllVertsNum = 0 -- Initial vertex count
			AllVecPositions = #() -- Initial position array
			AllVertTypes = #() -- Initial handle array
			if (classof Object1 == Editable_Poly or classof Object1 == PolyMeshObject or classof Object1 == Editable_Mesh) then addModifier Object1 (Turn_to_Patch ()) -- Use turn to patch mod to keep quads
			convertto Object1 Editable_Patch -- Convert to patch
			ShowInterior = patch.getShowInterior Object1 -- Get show interior edges on first object
			Steps = getPatchSteps Object1 -- Get patch steps of first object
			for i = 1 to ObjectSel.count do
			(
				if (classof ObjectSel[i] == Editable_Poly or classof ObjectSel[i] == PolyMeshObject or classof ObjectSel[i] == Editable_Mesh) then addModifier ObjectSel[i] (Turn_to_Patch ()) -- Use turn to patch mod to keep quads
				convertto ObjectSel[i] Editable_Patch -- Convert to patch
				if ((patch.getNumVerts ObjectSel[i]) >= 3) then
				(
					local PoleVerts = #{} -- Vertices with 8 edges and 4 faces, usually on teapot patches
					local SubDEdges = #{} -- Edges used for subdivision
					for j = 1 to (patch.getNumEdges ObjectSel[i]) do
					(
						local Edges1 = ((patch.getVertEdges ObjectSel[i] ((patch.getEdgeVert1 ObjectSel[i] j) + 1)) as bitarray) -- Get edges from first vertex
						local Edges2 = ((patch.getVertEdges ObjectSel[i] ((patch.getEdgeVert2 ObjectSel[i] j) + 1)) as bitarray) -- Get edges from second vertex
						if ((Edges1 * Edges2).numberset == 2) then SubDEdges += (Edges1 * Edges2) -- Get both edges
					) -- Get 2-sided closed loops on current object
					for j = 1 to (patch.getNumVerts ObjectSel[i]) do
					(
						local VertEdges = patch.getVertEdges ObjectSel[i] j as bitarray -- Edges from vertex
						local VertFaces = patch.getVertPatches ObjectSel[i] j as bitarray -- Faces from vertex
						if (VertEdges.numberset == 8 and VertFaces.numberset == 4) then
						(
							local FaceVerts = #{} -- Vertices from faces, excluding vertex at pole
							for k = 1 to VertFaces.numberset do (for l = 1 to (patch.getNumVerts ObjectSel[i]) where ((patch.getVertPatches ObjectSel[i] l as bitarray)[(VertFaces as array)[k]] == true) do appendifunique FaceVerts l) -- Get the vertices from the face selection
							FaceVerts -= #{j} -- Subtract pole vertex
							append PoleVerts j -- Add vertex to bitarray
							for k = 1 to FaceVerts.numberset do append SubDEdges ((((patch.getVertEdges ObjectSel[i] (FaceVerts as array)[k]) as bitarray) * VertEdges) as array)[1] -- Get edges minus unwanted edges
						) -- Add edges to subdiviision bitarray
					)
					if (SubDEdges.numberset > 0) then 
					(
						ObjectSel[i].selectedvertices = PoleVerts -- Select pole verts
						ObjectSel[i].selectededges = SubDEdges -- Select edges for subdivision
						patch.subdivideEdges ObjectSel[i] false -- Subdivide edges
						PoleVerts = ObjectSel[i].selectedvertices as bitarray -- Get vertex selection after subdivision
					)
					local VecPositions = (for j = 1 to (patch.getNumVerts ObjectSel[i]) collect (for k = 1 to (patch.getVertVecs ObjectSel[i] j).count collect (patch.getVec ObjectSel[i] (patch.getVertVecs ObjectSel[i] j)[k]))) as array -- Get 3d array of handle positions of current node
					local VertTypes = (for j = 1 to (patch.getNumVerts ObjectSel[i]) collect patch.getVertType ObjectSel[i] j) as array -- Get array of vertex tangent types of current node
					if (PoleVerts.numberset > 0) then (for j = 1 to PoleVerts.numberset do VertTypes[((PoleVerts as array)[j])] = #coplanar) -- Chagne pole vertices to coplanar
					AllVertsNum += (patch.getNumVerts ObjectSel[i]) -- Add vertice selections
					AllVecPositions += VecPositions -- Add positions together
					AllVertTypes += VertTypes -- Add vert types together
					setPatchSteps ObjectSel[i] 0
				) -- If there is at least three vertices in the current object i.e. a face exists
			) -- Loop through object selection
			select Object1 ; convertto Object1 Editable_Poly ; update Object1 -- Select first object, onvert to poly and update
			for i = 1 to ObjectSel.count do polyop.attach Object1 ObjectSel[i] -- Attach objects to first object
			Object1.deleteIsoVerts() -- Delete isolated vertices
			addModifier Object1 (Turn_to_Patch ()) ; convertto Object1 Editable_Patch -- Use turn to patch mod to keep quads and convert to patch
			patch.update Object1 -- Update patch
			if ((patch.getNumVerts Object1) >= 3 and AllVecPositions.count == (patch.getNumVerts Object1) and AllVertsNum == (patch.getNumVerts Object1)) then 
			(
				for i = 1 to (patch.getNumVerts Object1) do 
				(
					if (AllVecPositions[i] != undefined and AllVecPositions[i].count == (patch.getVertVecs Object1 i).count) then
					(
						for j = 1 to (patch.getVertVecs Object1 i).count do (if (AllVecPositions[i][j] != undefined) then patch.setVec Object1 (patch.getVertVecs Object1 i)[j] AllVecPositions[i][j]) -- Return handles to what they were previously
					) -- If vertices match and vert's handle numbers match
				) -- Loop through vertices
				for i = 1 to (patch.getNumVerts Object1) where (AllVertTypes[i] != undefined) do patch.changeVertType Object1 i AllVertTypes[i] -- Return vertex tangent types to what they were previously
			) -- If vertex counts match
			setPatchSteps Object1 Steps ; patch.setShowInterior Object1 ShowInterior ; CenterPivot Object1 ; WorldAlignPivot Object1 ; Object1.name = AttachName-- Reset values, fix its transforms, and rename
			patch.update Object1 -- Update patch
			return Object1 --Get node
		) -- If objects exist
	)

#28

very good. actually it beats my c++ version! where is the problem of c++ function? it’s DoAttach.
i looked into the SDK code and got that they update internal patch data every time after attach. there is no way to disable this update. so when the patch grows it updates slower, slower, and slower.
in your multi-attach function you updates only once when you convert from poly to patch.
editable poly updates data much faster than patch. because its code and structures are most modern.
i cleaned your code a little to help me easier play with it… also i removed some unnecessary updates and selections
here is it:


fn AttachObjectsPatch Sel:(selection as array) SelType:#Current AttachName:(uniquename ((selection as array)[1].name)) = 
(
	max create mode
	if (Sel != undefined and Sel.count != 0) then
	(
		AllNodeSel = geometry as array -- Get all objects
		InvNodeSel = for i in AllNodeSel where ((finditem Sel i) == 0) collect i -- Inverted selection
		
		sources = case SelType of
		(
			#Current: Sel -- Selection
			#Inverse: InvNodeSel -- Inverted selection
				#All: AllNodeSel -- All objects
			 default: #()
		)
		
		target = sources[1] -- Get first object in selection
		if not iskindof target Editable_Patch do
		(
			addmodifier target (Turn_to_Patch()) -- Use turn to patch mod to keep quads
			convertto target Editable_Patch -- Convert to patch
		)
		
		AllVertsNum = 0 -- Initial vertex count
		AllVecPositions = #() -- Initial position array
		AllVertTypes = #() -- Initial handle array

		ShowInterior = patch.getShowInterior target -- Get show interior edges on first object
		Steps = getPatchSteps target -- Get patch steps of first object
			
		for source in sources do
		(
			if not iskindof source Editable_Patch do
			(
				addmodifier source (Turn_to_Patch ()) -- Use turn to patch mod to keep quads
				convertto source Editable_Patch -- Convert to patch
			)
			if ((patch.getNumVerts source) >= 3) then
			(
				local PoleVerts = #{} -- Vertices with 8 edges and 4 faces, usually on teapot patches
				local SubDEdges = #{} -- Edges used for subdivision
				for j = 1 to (patch.getNumEdges source) do
				(
					local Edges1 = (patch.getVertEdges source ((patch.getEdgeVert1 source j) + 1)) as bitarray -- Get edges from first vertex
					local Edges2 = (patch.getVertEdges source ((patch.getEdgeVert2 source j) + 1)) as bitarray -- Get edges from second vertex
					if ((Edges1 * Edges2).numberset == 2) then SubDEdges += (Edges1 * Edges2) -- Get both edges
				) -- Get 2-sided closed loops on current object
				for j = 1 to (patch.getNumVerts source) do
				(
					local VertEdges = patch.getVertEdges source j as bitarray -- Edges from vertex
					local VertFaces = patch.getVertPatches source j as bitarray -- Faces from vertex
					if (VertEdges.numberset == 8 and VertFaces.numberset == 4) then
					(
						local FaceVerts = #{} -- Vertices from faces, excluding vertex at pole
						for k = 1 to VertFaces.numberset do (for l = 1 to (patch.getNumVerts source) where ((patch.getVertPatches source l as bitarray)[(VertFaces as array)[k]] == true) do appendifunique FaceVerts l) -- Get the vertices from the face selection
						FaceVerts -= #{j} -- Subtract pole vertex
						append PoleVerts j -- Add vertex to bitarray
						for k = 1 to FaceVerts.numberset do append SubDEdges ((((patch.getVertEdges source (FaceVerts as array)[k]) as bitarray) * VertEdges) as array)[1] -- Get edges minus unwanted edges
					) -- Add edges to subdiviision bitarray
				)
				if (SubDEdges.numberset > 0) then 
				(
					source.selectedvertices = PoleVerts -- Select pole verts
					source.selectededges = SubDEdges -- Select edges for subdivision
					patch.subdivideEdges source false -- Subdivide edges
					PoleVerts = source.selectedvertices as bitarray -- Get vertex selection after subdivision
				)
				local VecPositions = (for j = 1 to (patch.getNumVerts source) collect (for k = 1 to (patch.getVertVecs source j).count collect (patch.getVec source (patch.getVertVecs source j)[k]))) as array -- Get 3d array of handle positions of current node
				local VertTypes = (for j = 1 to (patch.getNumVerts source) collect patch.getVertType source j) as array -- Get array of vertex tangent types of current node
				if (PoleVerts.numberset > 0) then (for j = 1 to PoleVerts.numberset do VertTypes[((PoleVerts as array)[j])] = #coplanar) -- Chagne pole vertices to coplanar
				AllVertsNum += (patch.getNumVerts source) -- Add vertice selections
				AllVecPositions += VecPositions -- Add positions together
				AllVertTypes += VertTypes -- Add vert types together
				setPatchSteps source 0
			) -- If there is at least three vertices in the current object i.e. a face exists
		) -- Loop through object selection
		convertto target Editable_Poly 

		for source in sources do polyop.attach target source -- Attach objects to first object
		target.deleteIsoVerts() -- Delete isolated vertices
		addmodifier target (Turn_to_Patch())
		convertto target Editable_Patch -- Use turn to patch mod to keep quads and convert to patch

			--patch.update target -- Update patch
		if ((patch.getNumVerts target) >= 3 and AllVecPositions.count == (patch.getNumVerts target) and AllVertsNum == (patch.getNumVerts target)) then 
		(
			for i = 1 to (patch.getNumVerts target) do 
			(
				if (AllVecPositions[i] != undefined and AllVecPositions[i].count == (patch.getVertVecs target i).count) then
				(
					for j = 1 to (patch.getVertVecs target i).count do (if (AllVecPositions[i][j] != undefined) then patch.setVec target (patch.getVertVecs target i)[j] AllVecPositions[i][j]) -- Return handles to what they were previously
				) -- If vertices match and vert's handle numbers match
			) -- Loop through vertices
			for i = 1 to (patch.getNumVerts target) where (AllVertTypes[i] != undefined) do patch.changeVertType target i AllVertTypes[i] -- Return vertex tangent types to what they were previously
		) -- If vertex counts match
		
		setPatchSteps target Steps ; patch.setShowInterior target ShowInterior ; CenterPivot target ; WorldAlignPivot target ; target.name = AttachName-- Reset values, fix its transforms, and rename
		patch.update target -- Update patch
		
		return target --Get node
	) -- If objects exist
)

it didn’t make the code faster…

now is about your problems. the first is big memory usage. for big patches i couldn’t complete the attachment because of out of memory. that’s the bottleneck.
the second problem is the loosing of tvHandles (which are MapVerts) .

but as i said more important is to solve the memory issue.


#29

Well I’m glad that my version turns out to not be a waste of time. :slight_smile: Although my version you reworked did crash max for me, so I’m not really sure what’s causing that.

I’m not really sure how I can make my version any less memory hungry. I would guess though, that if you are modeling with patches, odds are that you won’t need tons of vertices to define your models. And I can’t think of cases where you would need over tens of thousands of patches in one object. Hopefully though, some of the minor changes I just made today to the function will help a little as far as memory goes.

	fn AttachObjectsPatch Sel:(selection as array)  SelType:#Current AttachName:(uniquename ((selection as array)[1].name)) =  
   	(
   		if (Sel != undefined and Sel.count != 0) then
   		(
   			AllObjectSel = (geometry as array) -- Get all objects
   			InvObjectSel = for i in AllObjectSel where ((finditem Sel i) == 0) collect i -- Inverted selection
   			ObjectSel = #() -- Initial selection
   			if (SelType == #Current) then ObjectSel = Sel else -- Selection
   			if (SelType == #Inverse) then ObjectSel = InvObjectSel else -- Inverted selection
   			if (SelType == #All) then ObjectSel = AllObjectSel -- All objects
   			Object1 = ObjectSel[1] -- Get first object in selection
   			AllVecPositions = #() -- Initial position array
   			AllVertTypes = #() -- Initial vertex type array
   			AllPatchTypes = #() -- Initial patch type array
        		if (classof Object1 == Editable_Poly or classof Object1 ==  PolyMeshObject or classof Object1 == Editable_Mesh) then addModifier  Object1 (Turn_to_Patch ()) -- Use turn to patch mod to keep quads
   			convertto Object1 Editable_Patch -- Convert to patch
   			ShowInterior = patch.getShowInterior Object1 -- Get show interior edges on first object
   			Steps = getPatchSteps Object1 -- Get patch steps of first object
   			for Object2 in ObjectSel do
   			(
        			if (classof Object2 == Editable_Poly or classof Object2 ==  PolyMeshObject or classof Object2 == Editable_Mesh) then addModifier  Object2 (Turn_to_Patch ()) -- Use turn to patch mod to keep quads
   				convertto Object2 Editable_Patch -- Convert to patch
   				if ((patch.getNumVerts Object2) >= 3) then
   				(
   					local PoleVerts = #{} -- Vertices with 8 edges and 4 faces, usually on teapot patches
   					local SubDEdges = #{} -- Edges used for subdivision
   					for j = 1 to (patch.getNumEdges Object2) do
   					(
        					local Edges1 = ((patch.getVertEdges Object2  ((patch.getEdgeVert1 Object2 j) + 1)) as bitarray) -- Get edges from  first vertex
   						local Edges2 =  ((patch.getVertEdges Object2 ((patch.getEdgeVert2 Object2 j) + 1)) as  bitarray) -- Get edges from second vertex
   						if ((Edges1 * Edges2).numberset == 2) then SubDEdges += (Edges1 * Edges2) -- Get both edges
   					) -- Get 2-sided closed loops on current object
   					for j = 1 to (patch.getNumVerts Object2) do
   					(
   						local VertEdges = patch.getVertEdges Object2 j as bitarray -- Edges from vertex
   						local VertFaces = patch.getVertPatches Object2 j as bitarray -- Faces from vertex
   						if (VertEdges.numberset == 8 and VertFaces.numberset == 4) then
   						(
   							local FaceVerts = #{} -- Vertices from faces, excluding vertex at pole
        						for k = 1 to VertFaces.numberset do (for l = 1  to (patch.getNumVerts Object2) where ((patch.getVertPatches Object2 l as  bitarray)[(VertFaces as array)[k]] == true) do appendifunique FaceVerts  l) -- Get the vertices from the face selection
   							FaceVerts -= #{j} -- Subtract pole vertex
   							append PoleVerts j -- Add vertex to bitarray
        						for k = 1 to FaceVerts.numberset do append  SubDEdges ((((patch.getVertEdges Object2 (FaceVerts as array)[k]) as  bitarray) * VertEdges) as array)[1] -- Get edges minus unwanted edges
   						) -- Add edges to subdiviision bitarray
   					) -- Loop through vertices
   					if (SubDEdges.numberset > 0) then 
   					(
   						Object2.selectedvertices = PoleVerts -- Select pole verts
   						Object2.selectededges = SubDEdges -- Select edges for subdivision
   						patch.subdivideEdges Object2 false -- Subdivide edges
   						PoleVerts = Object2.selectedvertices as bitarray -- Get vertex selection after subdivision
   						patch.update Object2
   					) -- If there are any edges to subdivide
        				local VecPositions = for j = 1 to (patch.getNumVerts  Object2) collect (for k = 1 to (patch.getVertVecs Object2 j).count  collect (patch.getVec Object2 (patch.getVertVecs Object2 j)[k])) -- Get  array of handle positions of current node
   					local  VertTypes = for j = 1 to (patch.getNumVerts Object2) collect  patch.getVertType Object2 j -- Get array of vertex tangent types of  current node
   					local PatchTypes = for j = 1 to  (patch.getNumPatches Object2) collect patch.getPatchInteriorType Object2  j -- Get array of patch interior types of current node
   					 if (PoleVerts.numberset > 0) then (for j = 1 to  PoleVerts.numberset do VertTypes[((PoleVerts as array)[j])] = #coplanar)  -- Change pole vertices to coplanar
   					AllVecPositions += VecPositions -- Add vector positions together
   					AllVertTypes += VertTypes -- Add vert types together
   					AllPatchTypes += PatchTypes -- Add patch types together
   					setPatchSteps Object2 0
   				) -- If there is at least three vertices in the current object i.e. a face exists
   			) -- Loop through object selection
   			select Object1 ; convertto Object1 Editable_Poly -- Select first object, onvert to poly and update
   			for i = 1 to ObjectSel.count do polyop.attach Object1 ObjectSel[i] -- Attach objects to first object
   			Object1.deleteIsoVerts() -- Delete isolated vertices
        		addModifier Object1 (Turn_to_Patch ()) ; Object1.modifiers[#Turn_to_Patch].useSoftSelection = 0 ; convertto Object1  Editable_Patch -- Use turn to patch mod to keep quads and convert to  patch
   			for i = 1 to (patch.getNumVerts Object1) where  (AllVecPositions[i] != undefined and AllVecPositions[i].count ==  (patch.getVertVecs Object1 i).count) do 
   			(
   				local VertVecs = patch.getVertVecs Object1 i -- Get handle from vertex
        			for j = 1 to VertVecs.count where (AllVecPositions[i][j] !=  undefined) do patch.setVec Object1 VertVecs[j] AllVecPositions[i][j] --  Set handles positions
   			) -- Return tv vertex positions to what they were previously
        		for i = 1 to (patch.getNumVerts Object1) where (AllVertTypes[i]  != undefined) do patch.changeVertType Object1 i AllVertTypes[i] --  Return vertex tangent types to what they were previously
   			 for i = 1 to (patch.getNumPatches Object1) where (AllPatchTypes[i] !=  undefined) do patch.changePatchInteriorType Object1 i AllPatchTypes[i]  -- Return patch interior types to what they were previously
   			 setPatchSteps Object1 Steps ; patch.setShowInterior Object1  ShowInterior ; CenterPivot Object1 ; WorldAlignPivot Object1 ;  Object1.name = AttachName-- Reset values, fix its transforms, and rename
   			patch.update Object1 -- Update patch
   			return Object1 --Get node
   		) -- If objects exist
   	)

[/i]The main things I changed -[i]

  • Went from 3 updates to 1, there was an update before converting to poly, one after conversion, and another one after conversion back to patch and resetting everything. I just kept the last one.
  • I used variable Object2 to reference the current node in ObjectSel loop.
  • Cleaned up method for resetting vertex handles.
  • Added array for resetting interior types on each patch face
  • 100 spheres attached in 0.5 seconds!

[/i]The first two, it looks like you improved in your reworked version, so again I’m not sure why yours crashes max.

And yes I did notice that there are no tv handles after attachment. I actually have never unwrapped a patch object before, so I didn’t even know about them! I don’t even know how you can manually get them back. So I decided to try looping through the number of map verts, and getting their positions, then looping through the resulting patch and setting their positions, but when I chacked in unwrap, it was a huge mess… This is probably the same reason I needed to loop through each patch vertex to get the corresponding vectors, and reset them that way, because the handle order changes for the whole patch object, after conversion.

This is what I tried using -[i]

AllTVPositions = #() -- Initial tv map vertex position array
   
   local TVPositions = for j = 1 to (patch.getNumMapVerts Object2 1) collect (patch.getMapVert Object2 1 j) -- Get array of tv map vertex positions of current node
   
   AllTVPositions += TVPositions -- Add tv positions together
   
   for i = 1 to (patch.getNumMapVerts Object1 1) where (AllTVPositions[i] != undefined) do patch.setMapVert Object1 1 i AllTVPositions[i] -- Return tv map vertex positions to what they were previously
   

[/i]I’m not sure if I’m missing anything, but I don’t see a way of getting tv handles from a vertex. I looked in the MS reference and saw [i]patch.getMapPatch <obj> <map_chan> <index> What is the index? is that a patch number? It seems to always return a 4 number array, are those the handles or vertices?

Any ideas as to how I can store and reset the map handles?


#30

Hmmm…This is quite odd, I can’t figure out how to get back the patch map handles at all, they are totally gone after converting from poly…You can’t even edit them them at all in edit patch or uvw unwrap. I tried using

$.modifiers[#Unwrap_UVW].getHandleGeomIndexFromFace 1 1

in uvw unwrap and no matter what face or handle index I use it always returns 0. But if I try it on a patch that hasn’t been converted from poly it does return a different number.

Does anyone know how to reset the map handles? Very very odd…


#31

i remember this problem. until some last version of max when you tried to do anything with patch handles (either manually or from script) in the Unwrap_UVW the max just crashed. maybe the bug was fixed but no methods to work with handles were added.
in the SDK there is TVPatch Class which has all methods to work with map coordinates. But it’s not exposed to MXS.


#32

Oh well …I guess I’ll just have note that as a limitation, when I release it with other scripts for now. Although this might not even matter since you still have the same uv layout, and depending on the geometry and mapping you have on it, there might not be any difference in the shape of the uv faces. You would mainly notice the affect of not having the handles, if you had planar maps on the top and bottom of a cylinder. Anyways, thanks for your help denis.

Also, if you are interested, I also finished my DetachAllPatchElements script -

	fn DetachAllPatchElements Sel:(selection as array) SelType:#Current = 
  	(
  		if (Sel != undefined) then
  		(
  			  ObjArray = #() -- Initial object array
  			AllObjectSel = (geometry as array) -- Get all objects
  			InvObjectSel = for i in AllObjectSel where ((finditem Sel i) == 0) collect i -- Inverted selection
  			ObjectSel = #() -- Initial selection
  			if (SelType == #Current) then ObjectSel = Sel else -- Selection
  			if (SelType == #Inverse) then ObjectSel = InvObjectSel else -- Inverted selection
  			if (SelType == #All) then ObjectSel = AllObjectSel -- All objects
  			for i = 1 to ObjectSel.count do
  			(
  				local Object1 = ObjectSel[i] -- Get current object
  				if (classof Object1 == Editable_Poly or classof Object1 == PolyMeshObject or classof Object1 == Editable_Mesh) then addModifier Object1 (Turn_to_Patch ()) -- Use turn to patch mod to keep quads
  				convertto Object1 Editable_Patch -- Convert to patch
  				while (patch.getNumPatches ObjectSel[i]) != 0 do
  				(
  					local AllFaceSel = #{1..(patch.getNumPatches Object1)} -- Get all geometry
  					local NewObj = undefined -- Initial new object node
  					Element = #{1} -- Initial element face array
  					for j = 1 to (patch.getNumPatches Object1) do
  					(
  						local OldFaceNum = Element.numberset -- Get past selection
  						local Verts = #{}
  						for k = 1 to Element.numberset do (for l = 1 to (patch.getNumVerts Object1) where ((patch.getVertPatches Object1 l as bitarray)[(Element as array)[k]] == true) do appendifunique Verts l) -- Get vertices from faces
  						for k = 1 to Verts.numberset do Element += (patch.getVertPatches Object1 (Verts as array)[k] as bitarray) -- Get faces from vertices
  						if (Element.numberset == OldFaceNum) then exit -- Exit if selection doesn't change
  					) -- Grow faces to element
  					local NewObjName = uniquename (Object1.name) -- Get a new name for object
  					NewObj = copy Object1 name:NewObjName -- Clone object and give it a new name
  					patch.deletePatchParts Object1 #{} Element -- Delete selected faces on current object
  					patch.deletePatchParts NewObj #{} (AllFaceSel - Element) -- Delete unselected faces on new object
  					CenterPivot NewObj ; WorldAlignPivot NewObj ; NewObj.wirecolor = (color (random 0 255) (random 0 255) (random 0 255)) -- Fix new object's transforms, and give it a new wire color
  					setSelectionLevel NewObj #Object ; patch.update NewObj ; patch.update Object1 -- Go out of sub-object level, update current object, and update new object
  					append ObjArray NewObj -- Add to array
  				) -- Keep detaching elements to new objects until there is nothing left
  				delete Object1 -- Delete old empty object
  			) -- Loop over object selection
  			select ObjArray -- Select detached objects
  			return ObjArray -- Get array of detached objects
  		)
  	)

It works on multiple objects, and it uses a copy, and delete method to detach the faces. Now you can keep on attaching and detaching the same selected patch objects forever!


#33

Minor update - I changed the default option in turn to patch, Use Soft Selection, so that soft selection is off after everything’s done. I update the post to include the change. -

addModifier Object1 (Turn_to_Patch ()) ; Object1.modifiers[#Turn_to_Patch].useSoftSelection = 0 ; convertto Object1 Editable_Patch -- Use turn to patch mod to keep quads and convert to patch

#34

This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.