Notifications
Clear all

[Closed] UVMapping mesh polygons without modifiers/nondestructively

I’m trying to develop a PainterInterface script that will take the polygons that it hits, do a face map on it and place the map faces on certain UVW coordinates. Right now I’m still stuck on the UVW mapping part. I’ve attempted two approaches to this but they don’t seem to be right for me.

First I tried using a Uvwmap modifier which works fine if I manually invoke my custom unwrap function. However it doesn’t work very well when combined with PainterInterface because when I select the faces in the script, the PainterInterface stops. Is there a way to use a Uvwmap modifier on certain faces of a mesh without selecting them first? That is the main problem I have with this approach.


 fn polyUVunwrap node faceIndex =
 (
 	local polyFaces = meshop.getPolysUsingFace node faceIndex
 	setFaceSelection node polyFaces
 	local selectionArray = node.selectedFaces
 	
 	local planeMap = Uvwmap()
 	modPanel.addModToSelection planeMap ui:on maptype:5
 	collapseStack node -- Collapse the UVW map modifier
 	-- subObjectLevel = 3
 )
 

Next I tried figuring out a way to UVWmap without modifiers and came across meshop.applyUVW. Since this only works on trimeshes I tried extracting the faces I’m interested in, applying the mapping and then booleaning it back into the mesh. It sorta works but I get weird booleans when the faces I’m operating on aren’t planar. Another problem with this approach is I can’t build a list of faces to apply the operation on since the face ID’s get renumbered when the faces are deleted and boolean’d.


 fn polyUVunwrap node faceIndex =
 (
 	-- Find the other faces in the polygon
 	local polyFaces = meshop.getPolysUsingFace node faceIndex
 	-- Detach these faces and store it as a TriMesh that can then be meshop.uvwunwrap
 	local opTriMesh = meshop.detachFaces node polyFaces delete:true asMesh:true
 	meshop.applyUVWMap opTriMesh #face 
 	-- Clean up the map channels of both
 	meshop.deleteIsoMapVertsAll node
 	-- Boolean the detached mesh back onto the parent mesh. Tah dah!
 	node.mesh+opTriMesh
 )
 

Thanks for any help here!

2 Replies

Ah, figured it out except for one little problem. I did not want face mapping, rather I needed planar mapping. meshop seems to be inconsistent between plain max9 and max9 SP3, had to work out some bugs on operations using meshop when I got home.

The planar map is giving me a bit of a problem since I my math knowledge is a little lacking, I tried using the code from the maxscript reference on aligning the gizmo to a face but it doesn’t seem to be working out for me. Here is the script I have right now, can someone please help me correct the face normal code? Thanks.


-- Given a face index, this will faceMap the polygon of that face
fn polyUVunwrap node faceIndex =
(
	-- 1.Store a copy of the target mesh as a triMesh
	local opTriMesh = copy node
	-- 2.Get the other faces in the same poly
	local polyFaces = meshop.getPolysUsingFace node faceIndex
	--   >iterate over poly bitarray, get an array of faceIndex
	local polyIndex = for i in polyFaces collect i
	
	-- 3.Delete faces from the poly by using the negated bitArray of poly
	meshop.deleteFaces opTriMesh -polyFaces delIsoVerts:true
	meshop.deleteIsoMapVertsAll opTriMesh -- cleanup triMesh mapping
	-- Perfoming a planar map might be more reliable, have to perform normal averaging transform here
	local avgNormals = [0,0,0]
	for i=1 to meshop.getNumFaces opTriMesh do
	(
		avgNormals = getFaceNormal node i
	)
	worldUpVector = [0,0,1]
	rightVector = normalize (cross worldUpVector avgNormals) 
	upVector = normalize ( cross rightVector avgNormals)
	theMatrix = matrix3 rightVector upVector avgNormals [0,0,0]
	meshop.applyUVWMap opTriMesh #planar tim:theMatrix
	-- 5.Hopefully vertex order between trimesh and target poly remains the same
	-- find the number of tverts in trimesh
	local triMeshVCount = meshop.getNumMapVerts opTriMesh 1
	-- store the number of tverts in targetNode
	local nodeVCount = node.numTVerts
	-- newVertNum = trimeshTvert num + targetNode tvertNum
	local newVertNum = nodeVCount + triMeshVCount
	-- setNumMapVerts of targetNode to newVertNum
	setNumTVerts node newVertNum true
	--buildTVFaces node false
	
	-- This loop copies the positions of the tVerts in the trimesh
	-- to the newly created tVerts in the targetNode
	-- loop i=1 to trimeshTvert.num
	for i=1 to triMeshVCount do
	( 
		newTVertPos = meshop.getMapVert opTriMesh 1 i
		targetVert = nodeVCount+i
		print targetVert
		setTVert node targetVert newTVertPos
	)
	
	-- Loop through triMesh faces, get the tVerts that are associated
	-- to those faces. Add the original tVertex count of targetNode 
	-- to those tVert ID's and hopefully arrive at the new tVert indices
	-- Then setMapFace 
	-- loop faces in triMesh
	for i=1 to meshop.getNumFaces opTriMesh do
	(
		-- faceVerts = getMapFace triMesh faceindex
		faceVerts = meshop.getMapFace opTriMesh 1 i 
		-- to each part of point3, add targetNode.tvertNum
		faceVerts += nodeVCount
		-- The polyIndex and the triMesh should have 1-1 mapping in terms of faces. I think.
		-- setMapFace targetNode 1 polyIndex[i] newPoint3
		print faceVerts
		setTVFace node.mesh polyIndex[i] faceVerts
	)
	-- 6. Cleanup map of targetNode
	meshop.deleteIsoMapVertsAll node
	-- 7. Delete triMesh
	delete opTriMesh
	-- ?? PROFIT!
)

Hi, I’ve hit upon a showstopping bug in the development of my script. I don’t quite know how to debug this and I’m not sure where the fault is in my code. In certain cases after using my UVW mapping function, when I move or edit my editable mesh new objects are created. They don’t appear to be from the copy command since they’re all named Object01 but they have the same mesh as my original object. I’ve checked that I’ve deleted the copy object in the script so I don’t think it is that either.

Full script below:


-- Script for "painting" tiled UV onto an environment. Helpful for
-- low res scenes with strict texture restrictions.

macroScript TilePainter category:"zbTools"
(
	
	global TilePainter_CanvasRollout
	try(destroyDialog TilePainter_CanvasRollout) catch()
	
	-- Palette rollout, will eventually be somehow used --
	--global rci = rolloutCreator "tilePalette" "Tile Palette"
	--rci.begin()
	-- Some way to store the tile defs so that we can build the palette? --
	
	global targetNode -- Stores the mesh being edited
	global donePainting -- Stores the poly bit array

	rollout TilePainter_CanvasRollout "Tile Painter"
	(
		-- Tool simulation stuff, plug in dynamic picking later
		group "Pos 1"
		(
		spinner pos1x "x" range:[0,1,0.0] type:#float scale:0.01
		spinner pos1y "y" range:[0,1,0.0] type:#float scale:0.01
		)
		group "Pos 2"
		(
		spinner pos2x "x" range:[0,1,0.5] type:#float scale:0.01
		spinner pos2y "y" range:[0,1,0.5] type:#float scale:0.01
		)
		
		button fillTile "Fill"
		checkbutton paintTile "Paint"
		
		edittext faceId "Face ID"
		button polyUn "Unwrap Poly"

		-- Given a face index, this will faceMap the polygon of that face
		fn polyUVunwrap node faceIndex =
		(
			-- 1.Store a copy of the target mesh as a triMesh
			local opTriMesh = copy node
			-- 2.Get the other faces in the same poly
			local polyFaces = meshop.getPolysUsingFace node faceIndex
			--   >iterate over poly bitarray, get an array of faceIndex
			local polyIndex = for i in polyFaces collect i
			
			-- 3.Delete faces from the poly by using the negated bitArray of poly
			meshop.deleteFaces opTriMesh -polyFaces delIsoVerts:true
			meshop.deleteIsoMapVertsAll opTriMesh -- cleanup triMesh mapping
			-- 4.Perform face map on the triMesh
			--meshop.applyUVWMap opTriMesh #face
			-- Perfoming a planar map might be more reliable, have to perform normal averaging transform here
			local avgNormals = [0,0,0]
			for i=1 to meshop.getNumFaces opTriMesh do
			(
				avgNormals = getFaceNormal node i
			)
			worldUpVector = [0,0,1]
			rightVector = normalize (cross worldUpVector avgNormals) 
			upVector = normalize ( cross rightVector avgNormals)
			theMatrix = matrix3 rightVector upVector avgNormals [0,0,0]
			meshop.applyUVWMap opTriMesh #planar tm:theMatrix
			-- 5.Hopefully vertex order between trimesh and target poly remains the same
			-- find the number of tverts in trimesh
			local triMeshVCount = meshop.getNumMapVerts opTriMesh 1
			-- store the number of tverts in targetNode
			local nodeVCount = node.numTVerts
			-- newVertNum = trimeshTvert num + targetNode tvertNum
			local newVertNum = nodeVCount + triMeshVCount
			-- setNumMapVerts of targetNode to newVertNum
			setNumTVerts node newVertNum true
			--buildTVFaces node false
			
			local centerX = 0
			local centerY = 0
			local maxX, maxY, minX, minY
			for i=1 to triMeshVCount do
			(
				newTVertPos = meshop.getMapVert opTriMesh 1 i
				print newTVertPos
				-- while we're looping through the find min/max
				if maxX == undefined do maxX = newTVertPos.x
				if minX == undefined do minX = newTVertPos.x
				if maxY == undefined do maxY = newTVertPos.y
				if minY == undefined do minY = newTVertPos.y
				
				if newTVertPos.x > maxX do (
					maxX = newTVertPos.x
				)
				if newTVertPos.x < minX do (
					minX = newTVertPos.x
				)
				if newTVertPos.y > maxY do (
					maxY = newTVertPos.y
				)
				if newTVertPos.y < minY do (
					minY = newTVertPos.y
				)
				
				centerX += newTVertPos.x
				centerY += newTVertPos.y
			)
			
			-- Calculate the width and height
			height = maxY - minY
			width = maxX - minX
			targetWidth = pos2x.value-pos1x.value
			targetHeight = pos2y.value-pos1y.value
			scaleX = targetWidth/height
			scaleY = targetHeight/width
			centerX /= triMeshVCount
			centerY /= triMeshVCount
			
			-- This loop copies the positions of the tVerts in the trimesh
			-- to the newly created tVerts in the targetNode
			-- loop i=1 to trimeshTvert.num
			for i=1 to triMeshVCount do
			(
				newTVertPos = meshop.getMapVert opTriMesh 1 i
				targetVert = nodeVCount+i
				--Move to 0,0
				newTVertPos += [-centerX,-centerY,0]
				
				newTVertPos *= [scaleX,scaleY,1]
				
				--Move to Pos
				targetX = pos1x.value+targetWidth/2
				targetY = pos1y.value+targetHeight/2
				newTVertPos += [targetX,targetY,0]
				
				setTVert node targetVert newTVertPos
			)
			
			
			-- Loop through triMesh faces, get the tVerts that are associated
			-- to those faces. Add the original tVertex count of targetNode 
			-- to those tVert ID's and hopefully arrive at the new tVert indices
			-- Then setMapFace 
			   -- loop faces in triMesh
			for i=1 to meshop.getNumFaces opTriMesh do
			(
				-- faceVerts = getMapFace triMesh faceindex
				faceVerts = meshop.getMapFace opTriMesh 1 i 
				-- to each part of point3, add targetNode.tvertNum
				faceVerts += nodeVCount
				-- The polyIndex and the triMesh should have 1-1 mapping in terms of faces. I think.
				-- setMapFace targetNode 1 polyIndex[i] newPoint3
				--print faceVerts
				try
				(
				setTVFace node polyIndex[i] faceVerts
				)
				catch
				(
				buildTVFaces node
				setTVFace node polyIndex[i] faceVerts
				)
			)
			-- 6. Cleanup map of targetNode
			meshop.deleteIsoMapVertsAll node
			update node
			-- 7. Delete triMesh
			delete opTriMesh
			-- ?? PROFIT!
		)
		
		-- Painting stuff
		fn fitFaceToTile node target tile =
		(
		)
		
		-- Unwraps the target face, then fits the UV on the tile target
		fn tilePainter node target tile=
		(
			-- Unwrap node target
			-- Tile fit function
		)
		
		fn StartStroke =
		(
			thePainterInterface.undoStart()
		)
		fn PaintStroke =
		(
			faceIndex = 1
			bary = [0,0,0]
			thePainterInterface.getHitFaceData &bary &faceIndex targetNode 0
			thePainterInterface.clearStroke()
			
			--print faceIndex
			if targetNode.numFaces < faceIndex do return false
			local donePolys = meshop.getPolysUsingFace targetNode faceIndex -- Polys being done on this pass
			if donePainting[faceIndex] == false then
			(
				--print faceIndex
				polyUVunwrap targetNode faceIndex
			)
			donePainting = donePainting + donePolys -- Mark these polys as being done
			-- check donePainting to see if this face has already been painted.
		)
		fn EndStroke =
		(
			 donePainting = -#{1..getNumFaces targetNode}
		)
		fn CancelStroke =
		(
		)
		fn SystemEndPaintSession =
		(
			paintTile.checked = false
		)
		fn startPainting3d =
		(
			local theObj = getCurrentSelection()
			if thePainterInterface.InPaintMode() or theObj.count != 1 then
			(
				thePainterInterface.EndPaintSession()
				paintTile.checked = false
			)
			else
			(
				theObj = theObj[1]
				targetNode = theObj
				max modify mode
				select targetNode
				subObjectLevel = 0
				update theObj
				-- Code to start 3d paint goes here
				paintTile.checked = true
				thePainterInterface.pointGatherEnable = TRUE
				thePainterInterface.initializeNodes 0 #(theObj)
				thePainterInterface.offMeshHitType = 2
				thePainterInterface.ScriptFunctions startStroke paintStroke endStroke cancelStroke SystemEndPaintSession
				thePainterInterface.drawTrace = false
				thePainterInterface.startPaintSession()
				
				-- Store the polys already painted
				faceCount = theObj.numFaces
				donePainting = #{1..faceCount}
				donePainting = -donePainting
			)
		)
		
		on paintTile changed state do startPainting3d()
		
		-- Fill tile stuff
		fn fillSelected node =
		(
			faceCount = getNumFaces node
			polyBitArray = #{1..faceCount}
			polyBitArray = -polyBitArray
			-- Loop that gets the indexes of all faces in the selection, makes a new array out of them
			indexOfFaces = #()
			for face in node.selectedFaces do
			(
				append indexOfFaces face.index
			)
			for i=1 to indexOfFaces.count do
			(
				-- Check if we've gone through this face yet
				if polyBitArray[indexOfFaces[i]] == false then
				(
					polyFaces = meshop.getPolysUsingFace node indexOfFaces[i]
					polyBitArray = polyBitArray + polyFaces
					
					--print polyFaces
					setFaceSelection node polyFaces
					--print node.selectedFaces
					polyUVunwrap node indexOfFaces[i]
				)
			)
			--collapestack node
		)
		
		on fillTile pressed do
		(
			fillSelected $
		)
		
		-- test func
		on polyUn pressed do
		(
			--print faceId.text
			val = execute faceId.text
			polyUVunwrap $ val
		)
		
		-- This function checks if a poly is unwrapped as a whole
		
		-- When mousedown, start storing the polygons that we've already painted
	)
	
	createDialog TilePainter_CanvasRollout 300 250
)

To use the script, create an editable mesh and assign a material to it. Select the object and press paint in the rollup. You can “paint” faces into the UV coordinates assigned by Pos 1 and Pos 2. You can also select faces and press the fill button to achieve the same effect as painting them in.

The bug occurs in both fill and paint mode, although I can’t figure out how to reproduce it in fill mode. The paint mode bug is the only one I’ve figured out how to reproduce. Steps for reproducing the bug:

  1. Create an editable mesh object, assigning a material to it to see the effects.
  2. Call up my Rollout, click on the Paint checkbox.
  3. Paint one stroke (mouse down and up).
  4. Paint a second stroke.
  5. Uncheck the paint checkbox.
  6. Move/edit the editable mesh.
  7. More editable meshes, all named Object01 will now appear.

The number of editable meshes that appear are related to the number of faces that have been painted over. I really can’t see where in the code I have gone wrong to produce this bug. Thanks greatly for any help in squashing this bug!