Files
2025-09-29 00:52:08 +02:00

430 lines
12 KiB
Plaintext
Executable File

try (destroyDialog RsUvElementExploder) catch ()
rollout RsUvElementExploder "UV-Element Exploder" width:260
(
local uvChan_source = 1
local uvChan_working = 20
local accuracy = 0.05
dotNetControl RsBannerPanel "Panel" pos:[0,0] height:32 width:RsUvElementExploder.width
local bannerStruct = makeRsBanner dn_Panel:RsBannerPanel wiki:"UV-Element Exploder" versionNum:1.01 versionName:"Cluttered Tiger" filename:(getThisScriptFilename())
spinner spnUvChan "UV Channel:" range:[1,99,uvChan_source] type:#integer fieldWidth:30 across:2 align:#left
spinner spnWorkChan "Store Channel:" range:[1,99,uvChan_working] type:#integer fieldWidth:30 align:#right
spinner spnAccuracy "UV-Match Accuracy:" range:[0,1.0,accuracy] type:#float scale:0.01 fieldWidth:40
local btnHeight = 30
button btnExplode "Explode Selected" width:(RsUvElementExploder.width - 10) height:btnHeight
button btnUnexplode "Un-Explode Selected" width:(RsUvElementExploder.width - 10) height:btnHeight offset:[-1,0]
fn explodeElements obj accuracy:accuracy sourceChan:uvChan_source workChan:uvChan_working =
(
local success = True
local selWas = getCurrentSelection()
undo "explode UV elements" on
(
-- Needs to be showing Modify tab when collapsing:
if (getCommandPanelTaskMode() != #modify) do (setCommandPanelTaskMode #modify)
local unwrapMod = Unwrap_UVW name:("UV.Testing...")
unwrapMod.setApplyToWholeObject true
-- Set source-channel on modifier:
case sourceChan of
(
1:() -- Do nothing, this is default
0:(unwrapMod.setMapChannel 1) -- Vertex-colours are on set on channel 1 for unwrapper, apparently...
Default:(unwrapMod.setMapChannel sourceChan)
)
addModifier obj unwrapMod -- Add modifier to top of stack
unwrapMod.setTVSubObjectMode 3
local uvElementData = #()
local faceCount = obj.numFaces
local checkFaces = #{}
checkFaces.count = faceCount
checkFaces = -checkFaces
progressStart "Processing UVs..."
for faceNum = 1 to faceCount where checkFaces[faceNum] while (success = progressUpdate (100.0 * (-checkFaces).numberSet / faceCount)) do
(
-- Select the segment's uncontested faces:
unwrapMod.selectFaces #{faceNum}
-- Expand selection to get full mapping-element:
local lastSelCount
local curSelCount = 0
local selFaces = #{}
while (lastSelCount != curSelCount) do
(
lastSelCount = curSelCount
unwrapMod.expandSelection()
selFaces = unwrapMod.getSelectedFaces()
curSelCount = selFaces.numberSet
)
local elemVerts = #()
for elemFace in selFaces do
(
local faceVertCount = (unwrapMod.numberPointsInFace elemFace)
for vertIdx = 1 to faceVertCount do
(
local vertNum = (unwrapMod.getVertexIndexFromFace elemFace vertIdx)
-- Get vert uv-position; flatten W-axis, as it can confuse comparison.
local vertPos = (unwrapMod.GetVertexPositionByNode 0 vertNum obj)
vertPos.Z = 0
append elemVerts vertPos
)
)
local boundsMin = undefined
local boundsMax = undefined
RsGetBBox elemVerts &boundsMin &boundsMax
-- Collect uv-island faces:
append uvElementData (dataPair faces:selFaces bounds:#(boundsMin, boundsMax))
-- Mark faces as collected:
checkFaces -= selFaces
)
progressEnd()
-- Abort if cancelled:
if not success do
(
deleteModifier obj unwrapMod
select selWas
return False
)
-- Collect together uv-elements with matching vert-positions:
local matchedUvElements = #()
for elemData in uvElementData do
(
local elemBounds = elemData.bounds
local matchedData = undefined
-- Only need to compare elements with matching vert-counts...
for matchData in matchedUvElements while (matchedData == undefined) do
(
local matched = True
for idx = 1 to 2 while matched do
(
matched = ((distance elemBounds[idx] matchData.bounds[idx]) < accuracy)
)
-- If boundingbox matches:
if matched do
(
matchedData = matchData
)
)
-- Add new element if these verts didn't match any already found:
if (matchedData == undefined) do
(
matchedData = (dataPair bounds:elemBounds faceLists:#())
append matchedUvElements matchedData
)
append matchedData.faceLists elemData.faces
)
-- Order list by number of elements:
qsort matchedUvElements (fn sorter v1 v2 = (v2.faceLists.count - v1.faceLists.count))
local baseObj = obj.baseObject
local objOp = RsMeshPolyOp baseObj
if (objOp == meshOp) do
(
baseObj = baseObj.mesh
)
unwrapMod.name = ("UV.ExplodeRestore")
unwrapMod.setMapChannel workChan
-- Set default mapping:
unwrapMod.selectFaces #{1..faceCount}
unwrapMod.mappingMode 1
unwrapMod.mappingMode 0
progressStart "Exploding faces..."
-- Move uvs to [0,0,0]:
unwrapMod.faceToVertSelect()
unwrapMod.moveX 0.5
unwrapMod.moveY 0.5
unwrapMod.moveZ 0.5
local objGetMapFace = RsGetMapFaceFunc baseObj
local objGetFace = RsGetFaceFunc baseObj
local doneFaces = #{}
doneFaces.count = faceCount
local objSize = (obj.Max - obj.Min)
local objMidPos = obj.Min + (0.5 * objSize)
-- Same-UV-area elements are offset by this distance:
local zOffsetStep = (objSize.Z + 0.01)
local zOffset = 0
for itemNum = 1 to matchedUvElements.count while success do
(
local item = matchedUvElements[itemNum]
-- List of placed bounding-boxes for elements that have been placed:
local elemBoxes = #()
-- Detach all matching elements:
for faceList in item.faceLists while (success = progressUpdate (100.0 * doneFaces.numberSet / faceCount)) do
(
local elemOffset = [0, 0, zOffset]
-- Detach faces in baseObject:
objOp.detachFaces baseObj faceList
-- Get vert-numbers:
local elemGeomVertNums = #{}
for faceNum in faceList do
(
elemGeomVertNums += ((objGetFace baseObj faceNum) as bitArray)
)
-- True if boxA and boxB (dataPair min: max:) intersect or touch
fn boxesIntersect boxA boxB axes:3 =
(
local Intersecting = True
local boxesMin = undefined
local boxesMax = undefined
RsGetBBox #(boxA.min, boxA.max, boxB.min, boxB.max) &boxesMin &boxesMax
local sizeA = (boxA.max - boxA.min)
local sizeB = (boxB.max - boxB.min)
local sizeSum = (sizeA + sizeB)
local boxesSize = (boxesMax - boxesMin)
-- The two boxes are intersecting if boxesSize is smaller than sizeSum on all axes:
for n = 1 to axes while Intersecting do
(
Intersecting = (boxesSize[n] <= sizeSum[n])
)
return Intersecting
)
(
-- Get element's unexploded vert-positions:
local elemVertPosList = for vertNum in elemGeomVertNums collect
(
(objOp.GetVert baseObj vertNum) * obj.objectTransform
)
-- Find element's bounding-box:
local elemMin = undefined
local elemMax = undefined
RsGetBBox elemVertPosList &elemMin &elemMax
local elemBox = (dataPair min:elemMin max:elemMax)
-- Make box bigger, to give extra breathing-room:
elemBox.Min += 0.05
elemBox.Max += 0.05
local elemOffsetDir = undefined
local elemPlaced = False
while (not elemPlaced) do
(
elemPlaced = True
-- See if elemBox intersects with any of the other already-placed elements for this set of elements:
for thisBox in elemBoxes while elemPlaced and (not keyboard.escPressed) do
(
elemPlaced = not (boxesIntersect thisBox elemBox)
)
-- If element overlaps another one, shift it out a bit from the pivot, ready for another loop:
if not elemPlaced do
(
if (elemOffsetDir == undefined) do
(
-- Use this offset to move element if it's bounding-box intersects any already-placed element:
local elemMidPos = elemMin + (0.5 * (elemMax - elemMin))
local elemLength = (distance elemMin elemMax)
local elemOffsetDir = elemMidPos - objMidPos
elemOffsetDir.Z = 0
elemOffsetDir = (elemLength) * (normalize elemOffsetDir)
)
-- Offset element horizontally:
elemOffset += elemOffsetDir
elemBox.Min += elemOffsetDir
elemBox.Max += elemOffsetDir
)
)
append elemBoxes elemBox
)
-- Colour faces in unwrap modifier:
unwrapMod.selectFaces faceList
unwrapMod.breakSelected()
unwrapMod.faceToVertSelect()
unwrapMod.moveX elemOffset.X
unwrapMod.moveY elemOffset.Y
unwrapMod.moveZ elemOffset.Z
-- Sample just-stored data to get the actual offset, to be sure it'll match when we undo later:
local firstElemUvNum = (objGetMapFace obj workChan (faceList as array)[1])[1]
local moveOffset = objOp.GetMapVert obj workChan firstElemUvNum
-- Offset those geometry-verts:
objOp.moveVert baseObj elemGeomVertNums moveOffset
--format "%\n" (distance elemOffset moveOffset)
doneFaces += faceList
)
zOffset += zOffsetStep
)
unwrapMod.selectFaces #{}
unwrapMod.setTVSubObjectMode 0
progressEnd()
-- select selWas
)
if (not success) do
(
-- Undo, then clear redo-action:
max undo
undo "Select" on
(
select selWas
)
)
return success
)
fn unexplodeElements obj workChan:uvChan_working =
(
select obj
local objOp = RsMeshPolyOp obj
if not (objOp.getMapSupport obj workChan) do
(
messageBox ("Object has no stored offsets on channel " + (workChan as string)) title:"Invalid Selection"
return False
)
undo "unexplode UV elements" on
(
local objGetMapFace = RsGetMapFaceFunc obj
local baseObj = obj.baseObject
local baseObjOp = RsMeshPolyOp baseObj
local objGetFace = RsGetFaceFunc baseObj
if (baseObjOp == meshOp) do
(
baseObj = baseObj.mesh
)
local faceCount = baseObjOp.getNumFaces baseObj
-- Find face-elements, and move them based on uv-data from their first vert:
local facesDone = #{}
facesDone.count = faceCount
for faceNum = 1 to faceCount where not facesDone[faceNum] do
(
-- Get UV-data from object as a whole:
local firstElemUvNum = (objGetMapFace obj workChan faceNum)[1]
local movedOffset = objOp.GetMapVert obj workChan firstElemUvNum
-- Get element-faces from baseObject:
local elemFaces = baseObjOp.getElementsUsingFace baseObj #{faceNum}
-- Get vert-numbers:
local elemGeomVertNums = #{}
for faceNum in elemFaces do
(
elemGeomVertNums += ((objGetFace baseObj faceNum) as bitArray)
)
-- Revert offsets on those geometry-verts:
baseObjOp.moveVert baseObj elemGeomVertNums -movedOffset node:obj
facesDone += elemFaces
)
local unexplodeMod = obj.modifiers["UV.ExplodeRestore"]
if (unexplodeMod != undefined) do
(
deleteModifier obj unexplodeMod
)
)
return True
)
fn validSel =
(
local msg = case of
(
(selection.count == 0):("No objects selected")
(selection.count != 1):("Multiple objects selected")
(not isEditPolyMesh selection[1]):("Non-Poly/Mesh object selected")
Default:(undefined)
)
if (msg == undefined) then (return True) else
(
messageBox msg title:"Invalid Selection"
return False
)
)
on btnExplode pressed do
(
if validSel() do
(
explodeElements selection[1] sourceChan:spnUvChan.Value workChan:spnWorkChan.Value accuracy:spnAccuracy.value
)
)
on btnUnexplode pressed do
(
if validSel() do
(
unexplodeElements selection[1] workChan:spnWorkChan.Value
)
)
on RsUvElementExploder open do
(
bannerStruct.setup()
)
)
createDialog RsUvElementExploder