[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!
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:
- Create an editable mesh object, assigning a material to it to see the effects.
- Call up my Rollout, click on the Paint checkbox.
- Paint one stroke (mouse down and up).
- Paint a second stroke.
- Uncheck the paint checkbox.
- Move/edit the editable mesh.
- 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!