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