Notifications
Clear all

[Closed] Attach node to patch

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
	)
1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

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.

Well I’m glad that my version turns out to not be a waste of time. 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?

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…

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

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.

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!

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
Page 3 / 3