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

899 lines
24 KiB
Plaintext
Executable File

struct RsSelectionToolsStruct
(
private
storedVertPositions = #(),
-- Abstracted methods to deal with differences between Editable_Mesh and Editable_Poly. Thanks max.
fn _getVertSelection obj =
(
case (classOf obj) of
(
Editable_Poly:(polyop.getVertSelection obj)
Editable_Mesh:(getVertSelection obj)
)
),
fn _setFaceSelection obj faceList =
(
case (classOf obj) of
(
Editable_Poly:(polyop.setFaceSelection obj faceList)
Editable_Mesh:(setFaceSelection obj faceList)
)
completeRedraw()
),
fn _getFaceSelection obj =
(
case (classOf obj) of
(
Editable_Poly:(polyop.getFaceSelection obj)
Editable_Mesh:(getFaceSelection obj)
)
),
fn _setVertSelection obj vertList =
(
case (classOf obj) of
(
Editable_Poly:(polyop.setVertSelection obj vertList)
Editable_Mesh:(setVertSelection obj vertList)
)
completeRedraw()
),
fn _getVertsUsingEdge obj edgeList =
(
local objOp = RsMeshPolyOp obj
if (objOp == undefined) do return undefined
objOp.getVertsUsingEdge obj edgeList
),
fn _getEdgesUsingFace obj faceIdx =
(
local objOp = RsMeshPolyOp obj
if (objOp == undefined) do return undefined
objOp.getEdgesUsingFace obj faceIdx
),
fn _setEdgeSelection obj edgeList =
(
case (classOf obj) of
(
Editable_Poly:(polyop.setEdgeSelection obj edgeList)
Editable_Mesh:(setEdgeSelection obj edgeList)
)
),
fn _getEdgeSelection obj =
(
case (classOf obj) of
(
Editable_Poly:(polyop.getEdgeSelection obj)
Editable_Mesh:(getEdgeSelection obj)
)
),
public
--------------------------------------
-- SUBOBJECT CONVERSION --
--------------------------------------
fn convertMapVertsToGeom obj chan vertNums =
(
vertNums = (vertNums as bitArray)
local objGetMapFace = RsGetMapFaceFunc obj
local objGetFace = RsGetFaceFunc obj
local faceCount = obj.numFaces
local polyVerts = for faceNum = 1 to faceCount collect (objGetFace obj faceNum)
local polyBitVerts = for item in polyVerts collect (item as bitArray)
local retVal = #{}
retVal.count = obj.numVerts
for faceNum = 1 to faceCount do
(
local mapFaceVerts = objGetMapFace obj chan faceNum
local usedMapFaceVerts = (vertNums * (mapFaceVerts as bitArray))
-- Bitarray will overlap with vertNums if face includes some of its verts:
if (usedMapFaceVerts.numberSet != 0) do
(
local geomFaceVerts = (objGetFace obj faceNum)
for vertIdx = 1 to mapFaceVerts.count do
(
local mapVertNum = mapFaceVerts[vertIdx]
if usedMapFaceVerts[mapVertNum] do
(
local geomVertNum = geomFaceVerts[vertIdx]
retVal[geomVertNum] = True
)
)
)
)
return retVal
),
fn getMaterialIds obj = (
if obj != undefined then (
local faces = #()
if ( classOf obj == Editable_Mesh ) then (
local numFaces = meshop.getNumFaces obj
for idx = 1 to numFaces do (
local faceMatId = getFaceMatId obj idx
if faceMatId != undefined do (
appendIfUnique faces faceMatId
)
)
) else if ( classof obj == Editable_Poly ) then (
local numFaces = polyOp.getNumFaces obj
for idx = 1 to numFaces do (
local faceMatId = polyop.getFaceMatId obj idx
if faceMatId != undefined do (
appendIfUnique faces faceMatId
)
)
)
sort faces
faces
)
),
fn getFacesUsingMaterialId obj matId = (
local faces = #()
if ( classOf obj == Editable_Mesh ) then (
local numFaces = meshop.getNumFaces obj
for idx = 1 to numFaces do (
local faceMatId = getFaceMatId obj idx
if faceMatId == matId then append faces idx
)
) else if ( classof obj == Editable_Poly ) then (
local numFaces = polyOp.getNumFaces obj
for idx = 1 to numFaces do (
local faceMatId = polyop.getFaceMatId obj idx
if faceMatId == matId then append faces idx
)
)
faces
),
fn growSelectionByMatId obj matId:undefined = (
-- Hayes: This code is from '\wildwest\script\max\rockstar_north\growSelectMatID.ms'
if ( classOf obj == Editable_Poly ) then (
local faces = #{}
local aSel = #()
local aBorder = #()
local aChecked = #()
sel = polyop.getFaceSelection obj
sel = sel as array
append aChecked sel[1]
--??what if you have faces with various matID selected at first??
--first add this selection to the final aSel array and get the matID for testing with
local selMatID = undefined
if matId != undefined then (
selMatID = matId
) else (
selMatID = polyop.getFaceMatID obj sel[1]
)
aSel = for item in sel where polyop.getFaceMatID obj item == selMatID collect item
--append aSel sel[1]
--Find the faces connected to this face
faceEdges = #()
for item in aSel do
(
edges = polyop.getFaceEdges obj item
join faceEdges edges
)
--faces from those faceEdges
connFaces = polyop.getFacesUsingEdge obj faceEdges
--set the first border faces
aBorder = connFaces as array
aMatch = #()
do
(
--fvFaces = #()
aNewBorder = #()
for f in aBorder do
(
--test this face for a match
if polyop.getFaceMatID obj f == selMatID then
(
--this face is the right matID so add it to the match array
append aMatch f
--Find the faces connected to this face
borderFaceEdges = polyop.getFaceEdges obj f
edgeFaces = polyop.getFacesUsingEdge obj borderFaceEdges
appendIfUnique aChecked f
--test if they've already been checked - or remove the already aSel values from this new array
aTesting = (edgeFaces - aChecked as BitArray ) as array
--the tested ones to checked
join aChecked aTesting
--check which if any are the same matID
aBorderMatch = for item in aTesting where polyop.getFaceMatID obj item == selMatID collect item
--Add the new mtahcing matID faces to the match array
join aMatch aBorderMatch
--add matching matID faces to new border
join aNewBorder aBorderMatch
)
)
--strip out duplicate array entries
aNewBorder = makeUniqueArray aNewBorder
--aMatch = makeUniqueArray aMatch
--ones that are the same add to aSel
join aSel aMatch
--the new border to check is the one we found
aBorder = aNewBorder
)
while aNewBorder.count > 0
--selectFaces on the model
obj.selectedFaces = aSel
faces = _getFaceSelection obj
faces
)
),
fn getBorderEdgesUsingMaterialId obj matId selectAllBorders:false = (
-- Collect all faces using the supplied material id.
local faces = #{}
if selectAllBorders == true then (
faces = getFacesUsingMaterialId obj matId
) else (
faces = growSelectionByMatId obj matId:matId
)
-- Select faces using the supplied material id.
_setFaceSelection obj faces
-- Collect all of the selected faces.
local materialFaces = _getFaceSelection obj
-- Now select every edge using the material faces.
_setEdgeSelection obj ( _getEdgesUsingFace obj materialFaces )
-- Keep track of which edges to remove.
local edgesToRemove = #{}
-- Collect all edges using the material faces.
local borderEdges = _getEdgeSelection obj
local objOp = RsMeshPolyOp obj
local objGetFaceMatID = RsGetFaceMatIDFunc obj
-- Iterate over each selected edge and determine if it is on the border. If not, deselect the edge.
for edgeIdx in borderEdges do (
local edgeFaces = ( objOp.getFacesUsingEdge obj #{ edgeIdx } ) as array
-- The faces on either side of this edge are using the same material id we are looking for. We don't
-- want this edge if it is.
local isInteriorEdge = false
-- The faces on either side of this edge are not the same material id we are looking for, so it
-- must be trying to connect island borders.
local isIslandEdge = false
local faceMatId1 = objGetFaceMatID obj edgeFaces[ 1 ]
if edgeFaces.count == 2 then (
local faceMatId2 = objGetFaceMatID obj edgeFaces[ 2 ]
if faceMatId1 == matId and faceMatId2 == matId then isInteriorEdge = true
if faceMatId1 != matId and faceMatId2 != matId then isIslandEdge = true
) else (
if faceMatId1 != matId then isIslandEdge = true
)
if isInteriorEdge or isIslandEdge then append edgesToRemove edgeIdx
)
borderEdges -= edgesToRemove
borderEdges
),
fn selectMaterialBorderVerts obj matId selectAllBorders:false = (
if obj != undefined do (
local borderEdges = getBorderEdgesUsingMaterialId obj matId selectAllBorders:selectAllBorders
if borderEdges.count > 0 do (
_setVertSelection obj ( _getVertsUsingEdge obj borderEdges )
subObjectLevel = 1
)
)
),
fn selectMaterialBorderEdges obj matId selectAllBorders:false = (
if obj != undefined do (
local borderEdges = getBorderEdgesUsingMaterialId obj matId selectAllBorders:selectAllBorders
if borderEdges.count > 0 do (
_setEdgeSelection obj borderEdges
subObjectLevel = 2
)
)
),
fn selectMaterialBorderFromFaceSelection obj type:#edges selectAllBorders:false = (
if obj != undefined do (
local selectedFaces = _getFaceSelection obj
if selectedFaces.count > 0 do (
local matIds = #()
local objOp = (RsMeshPolyOp obj)
local objGetFaceMatID = RsGetFaceMatIDFunc obj
for faceIdx in selectedFaces do (
appendIfUnique matIds ( objGetFaceMatID obj faceIdx )
)
local borderEdges = #{}
for matId in matIds do (
local currentBorderEdges = getBorderEdgesUsingMaterialId obj matId selectAllBorders:selectAllBorders
join borderEdges currentBorderEdges
)
local edgesToRemove = #{}
-- The union of borderEdges will likely result in interior edges still being selected if more than one material id was selected.
-- Attempt to deal with this now.
for edgeIdx in borderEdges do (
local edgeFaces = ( objOp.getFacesUsingEdge obj #{ edgeIdx } ) as array
if edgeFaces.count == 2 then (
local faceMatId1 = objGetFaceMatID obj edgeFaces[ 1 ]
local faceMatId2 = objGetFaceMatID obj edgeFaces[ 2 ]
if ( findItem matIds faceMatId1 ) != 0 and ( findItem matIds faceMatId2 ) != 0 then append edgesToRemove edgeIdx
)
)
borderEdges -= edgesToRemove
if type == #edges then (
_setEdgeSelection obj borderEdges
subObjectLevel = 2
) else if type == #verts then (
local borderVerts = _getVertsUsingEdge obj borderEdges
_setVertSelection obj borderVerts
subObjectLevel = 1
)
)
)
),
fn randomlyDeselectVerts obj pct = (
if obj != undefined do (
local selectedVerts = _getVertSelection obj as array
if selectedVerts.count > 1 then (
subObjectLevel = 1
local numVertsToDeselect = ( selectedVerts.count * ( pct / 100 as float ) + 0.5 ) as integer
local vertsToDeselect = #()
local finished = false
local numIterations = 0
-- Attempt to avoid an infinite loop, to avoid losing work. Theoretically, iterating over the number of selected vertices
-- three times should be sufficient to hit the number of verts to remove.
local maxNumIterations = selectedVerts.count * 3
while not finished do (
-- Avoid infinite loop. Don't want anyone to lose work.
if numIterations >= maxNumIterations then finished = true
if vertsToDeselect.count == numVertsToDeselect then (
finished = true
) else (
local vertIdx = random 1 selectedVerts.count
if ( findItem vertsToDeselect selectedVerts[ vertIdx ] ) == 0 then (
append vertsToDeselect selectedVerts[ vertIdx ]
)
)
numIterations += 1
)
if vertsToDeselect.count != numVertsToDeselect do (
format "Selection Tools: Did not hit the required percentage of verts to deselect! Only deselected % out of %.\n" vertsToDeselect.count numVertsToDeselect
)
selectedVerts = selectedVerts as bitarray
vertsToDeselect = vertsToDeselect as bitarray
selectedVerts -= vertsToDeselect
_setVertSelection obj selectedVerts
) else (
messageBox "Please select more than 1 vertex!" title:"R* Selection Tools"
)
)
),
fn snapVertsToTarget objA objB tolerance:0.05 =
(
-- Hayes: This code is from '\wildwest\script\max\rockstar_north\maps\sm_snap_verts.ms'
--Get exterior vertices
OpenEdges = polyOp.getOpenEdges objA
srcVerts = (polyOp.getVertsUsingEdge objA OpenEdges) as array
OpenEdges = polyOp.getOpenEdges objB
tarVerts = (polyOp.getVertsUsingEdge objB OpenEdges) as array
SrcNumVerts = srcVerts.count
TarNumVerts = tarVerts.count
For x=1 to TarNumVerts do
(
--get vert
tv = polyOp.getvert objB tarVerts[x]
rv = polyOp.getvert objA srcVerts[1]
targetVertDist = tolerance * tolerance
For i=1 to SrcNumVerts do (
try ( rv = getvert objA srcVerts[i] ) catch ( rv = polyOp.getvert objA srcVerts[i] )
dist = (tv.x - rv.x) * (tv.x - rv.x) + (tv.y - rv.y) * (tv.y - rv.y) + (tv.z - rv.z) * (tv.z - rv.z)
if dist < targetVertDist then (
polyOp.setVert objB tarVerts[x] rv
)
)
)
),
fn snapVertsToMidpoint objA objB tolerance:0.05 =
(
-- Hayes: This code is from '\wildwest\script\max\rockstar_north\maps\sm_snap_verts.ms'
--Get exterior vertices
OpenEdges = polyOp.getOpenEdges objA
srcVerts = (polyOp.getVertsUsingEdge objA OpenEdges) as array
OpenEdges = polyOp.getOpenEdges objB
tarVerts = (polyOp.getVertsUsingEdge objB OpenEdges) as array
SrcNumVerts = srcVerts.count
TarNumVerts = tarVerts.count
For x=1 to TarNumVerts do
(
--get vert
tv = polyOp.getvert objB tarVerts[x]
rv = polyOp.getvert objA srcVerts[1]
targetVertDist = tolerance * tolerance
For i=1 to SrcNumVerts do (
try ( rv = getvert objA srcVerts[i] ) catch ( rv = polyOp.getvert objA srcVerts[i] )
dist = (tv.x - rv.x) * (tv.x - rv.x) + (tv.y - rv.y) * (tv.y - rv.y) + (tv.z - rv.z) * (tv.z - rv.z)
if dist < targetVertDist then (
newpos = [0, 0, 0]
newpos.x = (tv.x + rv.x)/2
newpos.y = (tv.y + rv.y)/2
newpos.z = (tv.z + rv.z)/2
polyOp.setVert objB tarVerts[x] newpos
polyOp.setVert objA srcVerts[i] newpos
)
)
)
),
fn storeVertexPositions selObj =
(
-- Hayes: This code is from '\wildwest\script\3dsmax\maps\mesh_edits\reset_vert_position.ms'
storedVertPositions =#()
select selObj
max modify mode
modPanel.setCurrentObject selection[1].baseObject
-- detect Edge or Border selection
local vertSelection = undefined
if ( subobjectlevel == 1 ) then
(
vertSelection = true
)
if ( subobjectlevel == 2 or subobjectlevel == 3 ) then
(
vertSelection = false
)
edgeList = #()
vertList = #()
if ( vertSelection == false ) then
(
edgeList = polyop.getEdgeSelection selection[1]
vertList = (polyop.getVertsUsingEdge selection[1] edgeList) as array
)
else
(
vertList = (selection[1].GetSelection #Vertex ) as array
)
if (vertList.count < 1) do
(
MessageBox "Nothing selected - you need to select either edges or verts"
return false
)
-- Store these vert positions
SelectedVertData =#()
for v = 1 to vertList.count do
(
VertData =#()
append VertData vertList[v]
append VertData (polyop.getvert $ vertList[v])
append SelectedVertData VertData
)
-- Append this vert data to the main store
for vd = 1 to SelectedVertData.count do
(
append storedVertPositions SelectedVertData[vd]
)
),
fn restoreVertexPositions selObj =
(
-- Hayes: This code is from '\wildwest\script\3dsmax\maps\mesh_edits\reset_vert_position.ms'
select selObj
max modify mode
modPanel.setCurrentObject selection[1].baseObject
subobjectlevel == 1
-- Loop through the stored verts
for svd = 1 to storedVertPositions.count do
(
-- Read the stored data
theVertnumber = storedVertPositions[svd][1]
theVertPosition = storedVertPositions[svd][2]
-- Set the vert back to the original position
polyop.setvert $ theVertnumber theVertPosition
)
),
fn convertMapVertSelectionToMeshVertSelection obj =
(
if (isEditPolyMesh obj) then
(
local uvMod
for thisMod in obj.modifiers while (uvMod == undefined) do
(
if (isKindOf thisMod Unwrap_UVW) do
(
uvMod = thisMod
)
)
if (uvMod != undefined) then
(
local selectedMapVerts = uvMod.getSelectedVertices()
local mapChannel = ( uvmod.getMapChannel() + 1 ) --Unlike just about everything else in MXS that is 1-based, Autodesk decided to make this return a 0-based integer.
local geomVerts = convertMapVertsToGeom obj mapChannel selectedMapVerts
modPanel.setCurrentObject obj.baseObject
subObjectLevel = 1
_setVertSelection obj geomVerts
)
else
(
messageBox "You must have a 'Unwrap UVW' modifier applied to the selected object!" title:"Rockstar"
)
)
else
(
messageBox "You must have an object selected!" title:"Rockstar"
)
),
fn getFacesByArea obj sizeLimit inverted:False =
(
local objOp = (RsMeshPolyOp obj)
if (objOp == undefined) do
return Undefined
local numFaces = objOp.getNumFaces obj
local outFaces = #{}
outFaces.count = numFaces
for faceNum = 1 to numFaces do
(
local faceArea = objOp.getFaceArea obj faceNum
if (faceArea <= sizeLimit) do
outFaces[faceNum] = True
)
return outFaces
),
-- Returns list of faces on obj for elements where all dimensions are smaller than sizeLimit:
fn GetFacesByElemSize obj sizeLimit node: showProgress:True inverted:False =
(
local objOp = RsMeshPolyOp obj
-- Only return values for poly/mesh objects:
if (objOp == undefined) do
return Undefined
if (node == Unsupplied) do
node = obj
local vertCount = objOp.GetNumVerts obj
local faceCount = objOp.GetNumFaces obj
local getFaces = #{}
getFaces.count = faceCount
if showProgress do
(
progressStart "Finding small elements..."
progressUpdate (1.0)
)
local success = True
local doneFaces = #{}
doneFaces.count = faceCount
for faceNum = 1 to faceCount where (not doneFaces[faceNum]) while (success = progressUpdate (100.0 * faceNum / faceCount)) do
(
-- Get element-faces:
local elemFaces = objOp.getElementsUsingFace obj #{faceNum}
doneFaces += elemFaces
-- Get verts using element-faces:
local elemVerts = (objOp.getVertsUsingFace obj elemFaces)
-- Mark element-faces for deletion if their bounding-box is too small:
(
local elemVertPosList = (for vertNum = elemVerts collect (objOp.getVert obj vertNum))
local elemBox = RsFindMinBox elemVertPosList node:node
local boxDims = [elemBox.width, elemBox.length, elemBox.height]
local smallElem = True
for n = 1 to 3 while smallElem do
(
smallElem = (boxDims[n] < sizeLimit)
)
-- Mark faces for deletion if box was too small:
if smallElem do
(
getFaces += elemFaces
)
)
)
if showProgress do
ProgressEnd()
-- Return False if cancelled
if not success do
return False
return getFaces
),
-- Select faces on chosen nodes that are returned by 'FindFacesFunc'
-- If a selection-modifier is added, it will be named 'modName'
fn SelObjFaces FindFacesFunc objs: params:#() inverted:False modName:"Poly Select" =
(
-- Get suitable nodes from selection
if (objs == Unsupplied) do
objs = Selection
objs = for obj in objs where (IsEditPolyMesh obj) collect obj
if (objs.count == 0) do
return OK
undo "Select Faces" on
(
-- Get existing matching selection-modifier(s)
local selMods = MakeUniqueArray (for thisNode in objs collect thisNode.modifiers[modName])
selMods = for item in selMods where (item != Undefined) collect item
-- Reuse selection-modifier if a matching one (and only one) was found
local selMod = Undefined
if (selMods.count == 1) do
(
selMod = selMods[1]
-- We only want to use this modifier if it's the topmost modifier for the nodes that use it
-- (otherwise we can't show that specific modifier for multiple nodes)
for thisNode in objs while (selMod != Undefined) do
(
-- 'modIdx' will be 0 if missing, or 1 if it's at top of list
local modIdx = FindItem thisNode.modifiers selMod
if (modIdx > 1) do
selMod = Undefined
)
)
-- Create new selection-modifier if valid existing one wasn't found
if (objs.count > 1) and (selMod == Undefined) do
selMod = Poly_Select name:modName
if (GetCommandPanelTaskMode() != #Modify) do
(SetCommandPanelTaskMode #Modify)
-- Collect total number of found faces
local foundFacesCount = 0
fn _RunFindFacesFunc inObj inNode Func params inverted =
(
local outFaces = Undefined
case params.count of
(
0:(outFaces = Func inObj node:inNode)
1:(outFaces= Func inObj params[1] node:inNode)
Default:(Throw (params.count as String) + "params are not supported yet. You'll wanna fix that here!")
)
if inverted and (IsKindOf outFaces BitArray) do
outFaces = -outFaces
return outFaces
)
if (selMod == Undefined) then
(
-- SINGLE-NODE FACE-SELECTION (NO MODIFIER)
PushPrompt "Finding Faces..."
SetWaitCursor()
-- Get matching faces on baseObject (i.e. under any modifiers)
local thisNode = objs[1]
local baseObj = thisNode.baseObject
local foundFaces = _RunFindFacesFunc baseObj thisNode FindFacesFunc params inverted
if (not IsKindOf foundFaces BitArray) then
(
-- If function was cancelled
if (foundFaces == Face) do
success = False
)
else
(
-- Show baseObject, in Face mode
Select thisNode
ModPanel.SetCurrentObject baseObj node:thisNode
SetSelectionLevel baseObj #Face
SetFaceSelection thisNode foundFaces
Update thisNode
foundFacesCount += foundFaces.numberSet
)
SetArrowCursor()
PopPrompt()
)
else
(
-- FACE-SELECTION USING MODIFIER
-- Add selection-modifier to objects that don't have it yet
local addModNodes = for thisNode in objs where (FindItem thisNode.modifiers selMod == 0) collect thisNode
AddModifier addModNodes selMod
Select objs
-- Set face-selection in modifier for all nodes
ProgressStart "Finding Faces..."
local success = True
for nodeNum = 1 to objs.count while (success = ProgressUpdate (100 * nodeNum / objs.count)) do
(
local thisNode = objs[nodeNum]
-- Get faces-selection from a temp collapsed copy of object
undo off
(
local tempNode = (Copy thisNode)
tempNode.name = ("TEMPCOPY_" + thisNode.name)
ConvertToPoly tempNode
local foundFaces = _RunFindFacesFunc tempNode tempNode FindFacesFunc params inverted
Delete tempNode
)
if (foundFaces == False) then
(
success = False
)
else if (IsKindOf foundFaces BitArray) do
(
SetFaceSelection thisNode selMod foundFaces
foundFacesCount += foundFaces.numberSet
)
)
ProgressEnd()
-- Show modifier, which will be at top of all nodes' stacks
SetSelectionLevel selMod #Face
)
)
-- Print results, and briefly show them in status-prompt
local promptMsg = ((foundFacesCount as String) + " faces selected")
Format "%\n" promptMsg
DisplayTempPrompt promptMsg 5000
return OK
),
fn SelectFacesBySize inObjs sizeLimit elements:False inverted:False =
(
local FindFacesFunc = if elements then this.GetFacesByElemSize else this.GetFacesByArea
this.SelObjFaces FindFacesFunc objs:Selection params:#(sizeLimit) inverted:inverted
return OK
)
)
global gRsSelectionTools = RsSelectionToolsStruct()