[Closed] UVW Seem with GW
Someone on scriptspot wants to have the ability to toggle on and off the UV seems of an object.
I figured I would take a gander at the situation and see what I could come up with.
I’ve only spent a few minutes so far on it but I’m going to go with the GW.Polyline route. I guess you could also do splines with scripted controllers on the knots but that gets to be very taxing on the computer.
This script just does lines for all verts. Next I’ll add in the verts based on the UVW.
delete objects
myBox = box width:10 height:10 length:10 pos:[0,0,50] wirecolor:blue
convertToPoly myBox
unRegisterRedrawViewsCallback showObjectNames
fn showObjectNames = if (gw.wTransPoint [0,0,0]) != undefined do
(
gw.setTransform (Matrix3 1)
gw.setColor #line green
-- gw.polyLine #([5,5,5],[10,10,10],[50,50,50]) true
for o in objects where classof o == Editable_Poly do
(
vertsPts = for i = 1 to o.numVerts collect polyop.getVert o i
gw.polyLine vertsPts true
)
-- Update the viewports
gw.enlargeUpdateRect #whole
gw.updateScreen()
)
registerRedrawViewsCallback showObjectNames
forceCompleteRedraw()
You seem to be insinuating that gw is not the way go to on this one? then what would you suggest?
try first to check how long takes to find all uvw (map) seam edges for… let’s say 10,000 faces mesh.
Yeah GW doesn’t see to great when it comes to high volumes. That is a bummer.
The rough of splines could work but may become taxing depending on the functionality…
hmmm
If you dont need to update the model position or topology, then you could gather the vertices positions outside the callback, which will give the callback a huge boost.
Could also use a trigger on mouse up/down if position or topology change and renew the array of points.
However, as Denis said, the biggest drop in performance is going to be in getting the uvw open edges.
Unfortunately Max does not provide a function for that, so you need to do some dirty work to get them. Not really hard, but painfully slow.
Once you get the open edges they would need either to be converted to a sorted array of point (per cluster I guess) or you could draw one line at time, which also adds some performance drop.
In this example, 60,000 faces, I do not see any mayor performance drop, but it is completely different than gathering and drawing just the uvw open edges.
delete objects
myBox = box width:10 height:10 length:10 lengthsegs:100 widthsegs:100 heightsegs:100 pos:[0,0,50] wirecolor:red
convertToPoly myBox
unRegisterRedrawViewsCallback showObjectNames
gw.setTransform (Matrix3 1)
vertsPts = for i = 1 to myBox.numVerts collect polyop.getVert myBox i
fn showObjectNames =
(
gw.setColor #line green
gw.polyLine vertsPts true
)
registerRedrawViewsCallback showObjectNames
forceCompleteRedraw()
Here is a script that might be useful. Havent tested too much, but it seems to work, although its very slow.
There is room for optimization and improvements so I hope someone can get their hands on it. Meanwhile Ill try to work on it a little more.
Hope someone find it useful.
(
unRegisterRedrawViewsCallback cbDrawOpenEdges
global RO_DISPLAY_UV_OE
global cbDrawOpenEdges
global cbSelectionChanged
local vertexPos = #()
local uvChannel
local edgesColor = [0,255,0]
fn cbDrawOpenEdges = (
gw.setTransform (Matrix3 1)
gw.setColor #line edgesColor
for j in vertexPos do gw.polyLine j false
)
fn cbSelectionChanged mForce:false = (
unRegisterRedrawViewsCallback cbDrawOpenEdges
if ($ != undefined) do (
setwaitcursor()
st = timeStamp()
obj = snapshotasmesh selection[1]
allChannels = #()
for j = 1 to meshop.getNumMaps obj do (
if (meshop.getMapSupport obj j == true) do append allChannels j
)
RO_DISPLAY_UV_OE.ddl_channel.items = for j in allChannels collect j as string
if (mForce == false) do (
uvChannel = allChannels[1]
RO_DISPLAY_UV_OE.ddl_channel.selection = 1
)
numTFaces = meshop.getNumMapFaces obj uvChannel
numTVerts = meshop.getNumMapVerts obj uvChannel
facesTVertsIdx = for j = 1 to numTFaces collect (meshop.getMapFace obj uvChannel j)
emesh = trimesh()
emesh.numverts = numTVerts
emesh.numfaces = numTFaces
for j = 1 to numTFaces do (
objFaceTVerts = facesTVertsIdx[j]
setFace emesh j objFaceTVerts.x objFaceTVerts.y objFaceTVerts.z
)
meshOpenEdges = meshop.getOpenEdges emesh
sharedFaces = meshop.getFacesUsingEdge emesh meshOpenEdges
vertexPos = #()
foundEdges = #()
for j in sharedFaces do (
objFaceVerts = getFace obj j
edge1 = j*3 - 2
edge2 = j*3 - 1
edge3 = j*3
edges = #{edge1, edge2, edge3} * meshOpenEdges
if (finditem edges edge1) > 0 do (
v1Idx = objFaceVerts.x
v2Idx = objFaceVerts.y
if (findItem foundEdges [v1Idx, v2Idx] == 0) do (
v1 = getVert obj v1Idx
v2 = getVert obj v2Idx
append foundEdges [v2Idx, v1Idx]
append vertexPos #(v1, v2)
)
)
if (finditem edges edge2) > 0 do (
v1Idx = objFaceVerts.y
v2Idx = objFaceVerts.z
if (findItem foundEdges [v1Idx, v2Idx] == 0) do (
v1 = getVert obj v1Idx
v2 = getVert obj v2Idx
append foundEdges [v2Idx, v1Idx]
append vertexPos #(v1, v2)
)
)
if (finditem edges edge3) > 0 do (
v1Idx = objFaceVerts.z
v2Idx = objFaceVerts.x
if (findItem foundEdges [v1Idx, v2Idx] == 0) do (
v1 = getVert obj v1Idx
v2 = getVert obj v2Idx
append foundEdges [v2Idx, v1Idx]
append vertexPos #(v1, v2)
)
)
)
format "Found % UV Open Edges in %s
" vertexPos.count ((timeStamp() - st) / 1000.0)
registerRedrawViewsCallback cbDrawOpenEdges
delete obj
delete emesh
gc light:true
setArrowCursor()
)
forceCompleteRedraw()
)
try(destroyDialog RO_DISPLAY_UV_OE) catch()
rollout RO_DISPLAY_UV_OE "UV Open Edges" width:160 height:164
(
checkbutton bt_enable "Display UV Open Edges" pos:[8,16] width:144 height:32
dropDownList ddl_channel "UV Channel:" pos:[8,56] width:144 height:40 enabled:false
button bt_update "Update" pos:[8,128] width:144 height:28 enabled:false
fn destroy = (
callbacks.removeScripts #selectionSetChanged
unRegisterRedrawViewsCallback cbDrawOpenEdges
forceCompleteRedraw()
)
on RO_DISPLAY_UV_OE close do destroy()
on bt_enable changed arg do
(
bt_update.enabled = arg
ddl_channel.enabled = arg
destroy()
if (arg == true) do (
callbacks.addscript #selectionSetChanged "cbSelectionChanged()"
cbSelectionChanged()
)
)
on bt_update pressed do cbSelectionChanged()
on ddl_channel selected arg do (
uvChannel = (ddl_channel.selected as integer)
cbSelectionChanged mForce:true
)
)
createDialog RO_DISPLAY_UV_OE
)
Have you looked into the XViewChecker Interface? It supports edge display and can have custom coded tools applied.
-Eric
Not deeply. I just gave it a quick overview but couldnt find a ready to use function to display the uv open edges, so I went the gw direction.
I thought it couldnt be customized, but now that you mention it Ill see what I can do.
There are limits in what you can do, but you can create scripted functions and execute them through the XViewChecker interface.
-Eric
Nicely done. That is a pretty nice script you have there Rodríguez
Thanks John,
I wish I could get it working at least four times faster for getting the edges, but I burned a few neurons already, have to recover them before a new attempt.
But I do hope some smart guys here and at ScriptSpot can tackle it.
Here is an improved version that should work in Max 9+.
Finding the UV open Edges:
90,000 polygons 12,000 open edges around 3X faster
20,000 polygons 6,000 open edges around 2X faster
Displaying the edges is still slow, but although it is drawing 10 times more edges, I havent noticed any mayor drop in performance. The biggest impact is because it is drawing one set of edges at a time.
(
unRegisterRedrawViewsCallback cbDrawOpenEdges
global RO_DISPLAY_UV_OE
global cbDrawOpenEdges
global cbSelectionChanged
local vertexPos = #()
local uvChannel = 1
local edgesColor = [0,255,0]
local edgeOffset = 0.03
local forceRedraw = false
fn cbDrawOpenEdges = (
gw.setTransform (Matrix3 1)
gw.setColor #line edgesColor
for j in vertexPos do gw.polyLine j true
-- Needed in some Max versions or used dirvers
if forceRedraw do (
gw.enlargeUpdateRect #whole
gw.updateScreen()
)
)
fn cbSelectionChanged mForce:false = (
unRegisterRedrawViewsCallback cbDrawOpenEdges
if ($ != undefined) do (
setwaitcursor()
st = timeStamp()
obj = snapshotasmesh selection[1]
allChannels = for j = 1 to meshop.getNumMaps obj where (meshop.getMapSupport obj j) collect j
RO_DISPLAY_UV_OE.ddl_channel.items = for j in allChannels collect j as string
if (mForce == false) do (
uvChannel = allChannels[1]
RO_DISPLAY_UV_OE.ddl_channel.selection = 1
)
numTFaces = meshop.getNumMapFaces obj uvChannel
numTVerts = meshop.getNumMapVerts obj uvChannel
facesTVertsIdx = for j = 1 to numTFaces collect (meshop.getMapFace obj uvChannel j)
emesh = trimesh()
setMesh emesh numverts:numTVerts numfaces:numTFaces
setMesh emesh faces:facesTVertsIdx
objOpenEdges = meshop.getOpenEdges obj
meshOpenEdges = (meshop.getOpenEdges emesh) - objOpenEdges
vertexPos = #()
sharedFaces = meshop.getFacesUsingEdge emesh meshOpenEdges
foundEdges = #()
for j in sharedFaces do (
objFaceVerts = getFace obj j
edge1 = j*3 - 2
edge2 = j*3 - 1
edge3 = j*3
n1 = (getFaceNormal obj j) * edgeOffset
if (finditem meshOpenEdges edge1) > 0 do (
v1Idx = objFaceVerts.x
v2Idx = objFaceVerts.y
if (findItem foundEdges [v1Idx, v2Idx] == 0) do (
v1 = getVert obj v1Idx
v2 = getVert obj v2Idx
n3 = (normalize (cross n1 (v1-v2))) * edgeOffset
append vertexPos #(v1+n1+n3, v2+n1+n3, v1-n1-n3, v2-n1-n3, v1-n1+n3, v2-n1+n3, v1+n1-n3, v2+n1-n3)
append foundEdges [v2Idx, v1Idx]
)
)
if (finditem meshOpenEdges edge2) > 0 do (
v1Idx = objFaceVerts.y
v2Idx = objFaceVerts.z
if (findItem foundEdges [v1Idx, v2Idx] == 0) do (
v1 = getVert obj v1Idx
v2 = getVert obj v2Idx
n3 = (normalize (cross n1 (v1-v2))) * edgeOffset
append vertexPos #(v1+n1+n3, v2+n1+n3, v1-n1-n3, v2-n1-n3, v1-n1+n3, v2-n1+n3, v1+n1-n3, v2+n1-n3)
append foundEdges [v2Idx, v1Idx]
)
)
if (finditem meshOpenEdges edge3) > 0 do (
v1Idx = objFaceVerts.z
v2Idx = objFaceVerts.x
if (findItem foundEdges [v1Idx, v2Idx] == 0) do (
v1 = getVert obj v1Idx
v2 = getVert obj v2Idx
n3 = (normalize (cross n1 (v1-v2))) * edgeOffset
append vertexPos #(v1+n1+n3, v2+n1+n3, v1-n1-n3, v2-n1-n3, v1-n1+n3, v2-n1+n3, v1+n1-n3, v2+n1-n3)
append foundEdges [v2Idx, v1Idx]
)
)
)
sharedFaces = meshop.getFacesUsingEdge obj objOpenEdges
for j in sharedFaces do (
objFaceVerts = getFace obj j
edge1 = j*3 - 2
edge2 = j*3 - 1
edge3 = j*3
n1 = (getFaceNormal obj j) * edgeOffset
if (finditem objOpenEdges edge1) > 0 do (
v1Idx = objFaceVerts.x
v2Idx = objFaceVerts.y
v1 = getVert obj v1Idx
v2 = getVert obj v2Idx
n3 = (normalize (cross n1 (v1-v2))) * edgeOffset
append vertexPos #(v1+n1+n3, v2+n1+n3, v1-n1-n3, v2-n1-n3, v1-n1+n3, v2-n1+n3, v1+n1-n3, v2+n1-n3)
)
if (finditem objOpenEdges edge2) > 0 do (
v1Idx = objFaceVerts.y
v2Idx = objFaceVerts.z
v1 = getVert obj v1Idx
v2 = getVert obj v2Idx
n3 = (normalize (cross n1 (v1-v2))) * edgeOffset
append vertexPos #(v1+n1+n3, v2+n1+n3, v1-n1-n3, v2-n1-n3, v1-n1+n3, v2-n1+n3, v1+n1-n3, v2+n1-n3)
)
if (finditem objOpenEdges edge3) > 0 do (
v1Idx = objFaceVerts.z
v2Idx = objFaceVerts.x
v1 = getVert obj v1Idx
v2 = getVert obj v2Idx
n3 = (normalize (cross n1 (v1-v2))) * edgeOffset
append vertexPos #(v1+n1+n3, v2+n1+n3, v1-n1-n3, v2-n1-n3, v1-n1+n3, v2-n1+n3, v1+n1-n3, v2+n1-n3)
)
)
format "Found % UV Open Edges in %s
" vertexPos.count ((timeStamp() - st) / 1000.0)
registerRedrawViewsCallback cbDrawOpenEdges
delete obj
delete emesh
gc light:true
setArrowCursor()
)
forceCompleteRedraw()
)
try(destroyDialog RO_DISPLAY_UV_OE) catch()
rollout RO_DISPLAY_UV_OE "UV Open Edges" width:160 height:198
(
checkbutton bt_enable "Display UV Open Edges" pos:[8,8] width:144 height:32
dropdownList ddl_channel "UV Channel:" pos:[8,72] width:120 height:40 enabled:false
colorPicker cp1 "" pos:[131,90] width:21 height:21 enabled:true color:edgesColor modal:false
checkbox chk_redraw "Force Redraw" pos:[8,46] width:100 height:16 enabled:false
spinner spn_width "Edge Width:" pos:[8,120] width:120 height:16 range:[1,100,4] type:#integer fieldwidth:50
button bt_update "Update" pos:[8,160] width:144 height:28 enabled:false
fn destroy = (
callbacks.removeScripts #selectionSetChanged
unRegisterRedrawViewsCallback cbDrawOpenEdges
forceCompleteRedraw()
)
on RO_DISPLAY_UV_OE close do destroy()
on bt_enable changed arg do
(
bt_update.enabled = arg
ddl_channel.enabled = arg
chk_redraw.enabled = arg
destroy()
if (arg == true) do (
callbacks.addscript #selectionSetChanged "cbSelectionChanged()"
cbSelectionChanged()
)
)
on bt_update pressed do cbSelectionChanged()
on ddl_channel selected arg do (
uvChannel = (ddl_channel.selected as integer)
cbSelectionChanged mForce:true
)
on cp1 changed arg do edgesColor = arg
on spn_width changed arg do edgeOffset = (arg-1)/100.0
on chk_redraw changed arg do forceRedraw = arg
)
createDialog RO_DISPLAY_UV_OE
)
after optimizing your algorithm overall i made the performance 2.5 times faster and memory use 2 times less.
also this little trick:
fn cbDrawOpenEdges = (
local polyLine = gw.polyLine
gw.setTransform (Matrix3 1)
gw.setColor #line edgesColor
for j in vertexPos do polyLine j true
-- Needed in some Max versions or used dirvers
if forceRedraw do (
gw.enlargeUpdateRect #whole
gw.updateScreen()
)
)
… stops memory leaking during gw polylines drawing
Its a pleasure to know you could get it working that fast.
The only way I can get that speed is by not removing the duplicated edges, but then I get double edges for each internal edge, so displaying them drops the performance.
I cant wait to see your sorting algorithm.
Thanks for the caching function tip, the difference that one line can make always amazes me.
When the obvious is not so obvious.
This optimization runs an average of 2.5 times faster and uses half the memory than previous version.
I feel this could still be optimized and that a different approach would lead to better results, especially for the drawing routine. But for what it is, I think it works pretty well.
getFaceNormal was a big surprise.
(
unRegisterRedrawViewsCallback cbDrawOpenEdges
global RO_DISPLAY_UV_OE
global cbDrawOpenEdges
global cbSelectionChanged
local vertexPos = #()
local uvChannel = 1
local edgesColor = [0,255,0]
local edgeOffset = 0.03
local forceRedraw = false
local polyLine = gw.polyLine
fn cbDrawOpenEdges = (
gw.setTransform (Matrix3 1)
gw.setColor #line edgesColor
for j in vertexPos do polyLine j true
-- Needed in some Max versions or used dirvers
if forceRedraw do (
gw.enlargeUpdateRect #whole
gw.updateScreen()
)
)
fn cbSelectionChanged mForce:false = (
unRegisterRedrawViewsCallback cbDrawOpenEdges
if ($ != undefined) do (
setwaitcursor()
st = timeStamp()
cGetMapFace = meshop.getMapFace
obj = snapshotasmesh selection[1]
allChannels = for j = 1 to meshop.getNumMaps obj where (meshop.getMapSupport obj j) collect j
RO_DISPLAY_UV_OE.ddl_channel.items = for j in allChannels collect j as string
if (mForce == false) do (
uvChannel = allChannels[1]
RO_DISPLAY_UV_OE.ddl_channel.selection = 1
)
numTFaces = meshop.getNumMapFaces obj uvChannel
numTVerts = meshop.getNumMapVerts obj uvChannel
emesh = trimesh()
setMesh emesh numverts:numTVerts numfaces:numTFaces
setMesh emesh faces:(for j = 1 to numTFaces collect (cGetMapFace obj uvChannel j))
objOpenEdges = meshop.getOpenEdges obj
meshOpenEdges = (meshop.getOpenEdges emesh) - objOpenEdges
delete emesh
sharedFaces = meshop.getFacesUsingEdge obj meshOpenEdges
foundEdges = #()
vertexPos = #()
for j in sharedFaces do (
objFaceVerts = getFace obj j
edge1 = j*3 - 2
edge2 = j*3 - 1
edge3 = j*3
v1Idx = objFaceVerts.x
v2Idx = objFaceVerts.y
v3Idx = objFaceVerts.z
v1 = getVert obj v1Idx
v2 = getVert obj v2Idx
v3 = getVert obj v3Idx
n1 = (normalize (cross (v1 - v2) (v2 - v3))) * edgeOffset
if (finditem meshOpenEdges edge1) > 0 do (
found = findItem foundEdges [v1Idx, v2Idx]
if (found == 0) then (
n2 = (normalize (cross n1 (v1-v2))) * edgeOffset
append vertexPos #(v1+n1+n2, v2+n1+n2, v1-n1-n2, v2-n1-n2, v1-n1+n2, v2-n1+n2, v1+n1-n2, v2+n1-n2)
append foundEdges [v2Idx, v1Idx]
)else(
deleteitem foundEdges found
)
)
if (finditem meshOpenEdges edge2) > 0 do (
found = findItem foundEdges [v2Idx, v3Idx]
if (found == 0) then (
n2 = (normalize (cross n1 (v2-v3))) * edgeOffset
append vertexPos #(v2+n1+n2, v3+n1+n2, v2-n1-n2, v3-n1-n2, v2-n1+n2, v3-n1+n2, v2+n1-n2, v3+n1-n2)
append foundEdges [v3Idx, v2Idx]
)else(
deleteitem foundEdges found
)
)
if (finditem meshOpenEdges edge3) > 0 do (
found = findItem foundEdges [v3Idx, v1Idx]
if (found == 0) then (
n2 = (normalize (cross n1 (v3-v1))) * edgeOffset
append vertexPos #(v3+n1+n2, v1+n1+n2, v3-n1-n2, v1-n1-n2, v3-n1+n2, v1-n1+n2, v3+n1-n2, v1+n1-n2)
append foundEdges [v1Idx, v3Idx]
)else(
deleteitem foundEdges found
)
)
)
sharedFaces = meshop.getFacesUsingEdge obj objOpenEdges
for j in sharedFaces do (
objFaceVerts = getFace obj j
edge1 = j*3 - 2
edge2 = j*3 - 1
edge3 = j*3
v1Idx = objFaceVerts.x
v2Idx = objFaceVerts.y
v3Idx = objFaceVerts.z
v1 = getVert obj v1Idx
v2 = getVert obj v2Idx
v3 = getVert obj v3Idx
n1 = (normalize (cross (v1 - v2) (v2 - v3))) * edgeOffset
if (finditem objOpenEdges edge1) > 0 do (
n2 = (normalize (cross n1 (v1-v2))) * edgeOffset
append vertexPos #(v1+n1+n2, v2+n1+n2, v1-n1-n2, v2-n1-n2, v1-n1+n2, v2-n1+n2, v1+n1-n2, v2+n1-n2)
append foundEdges [v2Idx, v1Idx]
)
if (finditem objOpenEdges edge2) > 0 do (
n2 = (normalize (cross n1 (v2-v3))) * edgeOffset
append vertexPos #(v2+n1+n2, v3+n1+n2, v2-n1-n2, v3-n1-n2, v2-n1+n2, v3-n1+n2, v2+n1-n2, v3+n1-n2)
append foundEdges [v3Idx, v2Idx]
)
if (finditem objOpenEdges edge3) > 0 do (
n2 = (normalize (cross n1 (v3-v1))) * edgeOffset
append vertexPos #(v3+n1+n2, v1+n1+n2, v3-n1-n2, v1-n1-n2, v3-n1+n2, v1-n1+n2, v3+n1-n2, v1+n1-n2)
append foundEdges [v1Idx, v3Idx]
)
)
format "Found % UV Open Edges in %s
" vertexPos.count ((timeStamp() - st) / 1000.0)
registerRedrawViewsCallback cbDrawOpenEdges
delete obj
gc light:true
setArrowCursor()
)
forceCompleteRedraw()
)
try(destroyDialog RO_DISPLAY_UV_OE) catch()
rollout RO_DISPLAY_UV_OE "UV Open Edges" width:160 height:198
(
checkbutton bt_enable "Display UV Open Edges" pos:[8,8] width:144 height:32
dropdownList ddl_channel "UV Channel:" pos:[8,72] width:120 height:40 enabled:false
colorPicker cp1 "" pos:[131,90] width:21 height:21 enabled:true color:edgesColor modal:false
checkbox chk_redraw "Force Redraw" pos:[8,46] width:100 height:16 enabled:false
spinner spn_width "Edge Width:" pos:[8,120] width:120 height:16 range:[1,100,4] type:#integer fieldwidth:50
button bt_update "Update" pos:[8,160] width:144 height:28 enabled:false
fn destroy = (
callbacks.removeScripts #selectionSetChanged
unRegisterRedrawViewsCallback cbDrawOpenEdges
forceCompleteRedraw()
)
on RO_DISPLAY_UV_OE close do destroy()
on bt_enable changed arg do
(
bt_update.enabled = arg
ddl_channel.enabled = arg
chk_redraw.enabled = arg
destroy()
if (arg == true) do (
callbacks.addscript #selectionSetChanged "cbSelectionChanged()"
cbSelectionChanged()
)
)
on bt_update pressed do cbSelectionChanged()
on ddl_channel selected arg do (
uvChannel = (ddl_channel.selected as integer)
cbSelectionChanged mForce:true
)
on cp1 changed arg do edgesColor = arg
on spn_width changed arg do edgeOffset = (arg-1)/100.0
on chk_redraw changed arg do forceRedraw = arg
)
createDialog RO_DISPLAY_UV_OE
)