430 lines
12 KiB
Plaintext
Executable File
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
|