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()