/*--//////////////////////////////////////////////////////////////////////////////////////////////// Cable generator Generate cables or wires between two points either from nought or retrieved from an existing mesh --////////////////////////////////////////////////////////////////////////////////////////////////*/ --filein (RsConfigGetWildWestDir() + "script/3dsmax/_common_functions/CatenaryCurve.ms") global cableMaker filein (RsConfigGetWildWestDir() + "script/3dsmax/Maps/LegacyCableUpgrade.ms") filein (RsConfigGetWildWestDir() + "script/3dsMax/_config_files/Wildwest_header.ms") --create LegacyCableUpgrade() instance global cableUpgrade = LegacyCableUpgrade() -- Flag to favour the creation of material presets instead of sps files in certain utility tools global RsFavourPresetCreation = (RsProjectConfig.GetBoolParameter "Favour Material Preset Creation") if (RsFavourPresetCreation == undefined ) then RsFavourPresetCreation = false --////////////////////////////////////////////////////////////////////////////////////////////// -- STRUCTS --////////////////////////////////////////////////////////////////////////////////////////////// struct CableConnectorsUtilStruct ( connectionConfig = (RsConfigGetWildWestDir() + "etc/config/props/cableConnectors.xml"), XML = dotNetObject "System.Xml.XmlDocument", fn hashPoint3 n = ( hash = (n.x * 73856093) as integer; hash = bit.xor hash ((n.y * 19349663) as integer ); bit.xor hash ((n.z * 83492791) as integer ); ), fn createCableConnectorElement connectPoint prop propNode sibling:unsupplied = ( --get the connection position relative to the prop pivot local connectPos = connectPoint.pos - prop.pos format "prop: % connectPos: % \n" prop connectPos local connectName = hashPoint3 connectPos local siblingPos = undefined local siblingName = undefined if(sibling != unsupplied) then ( siblingPos = sibling.pos - prop.pos siblingName = hashPoint3 siblingPos ) --////////////////////////////////////////// --Create Connector definition --////////////////////////////////////////// local cableConnector = XML.createElement "CableConnector" local theName = XML.createAttribute "Name" theName.value = connectName as String cableConnector.setAttributeNode theName --position local position = XML.createElement "Position" local xAttr = XML.createAttribute "X" xAttr.value = connectPos.x as string position.setAttributeNode xAttr local yAttr = XML.createAttribute "Y" yAttr.value = connectPos.y as string position.setAttributeNode yAttr local zAttr = XML.createAttribute "Z" zAttr.value = connectPos.z as string position.setAttributeNode zAttr cableConnector.appendChild position if (siblingPos != undefined) and (siblingName != undefined) then ( local siblingElem = XML.createElement "Sibling" siblingElem.InnerText = siblingName as String cableConnector.appendChild siblingElem ) --add nodes propNode.appendChild cableConnector ), --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- Function to create the cable connector xml config used to generate the helpers on props for creating cable proxies --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// fn createCableAttachPointsXML = ( --local configPath = (RsConfigGetWildWestDir() + "etc/config/props/cableConnectors.xml") --create config --local XML = dotNetObject "System.Xml.XmlDocument" local declaration = XML.CreateXmlDeclaration "1.0" "utf-8" "yes" XML.appendChild declaration local rootElem = xml.createElement "CableConnections" XML.appendChild rootElem toolMode.coordsys #world --iterate selected props for prop in selection where superclassof prop == GeometryClass do ( local theProp = undefined local theNode = undefined if (prop.children != undefined) then ( --create the Node local propNode = XML.createElement prop.name for connectPoint in prop.children do ( --ok, if this connectPoint has children then its a connector pair if (connectPoint.children.count != 0) then ( --this connectPoint should have a connection point pair for item in connectPoint.children do ( local connectors = connectPoint.children if (item == connectors[1]) then ( createCableConnectorElement connectors[1] prop propNode sibling:connectors[2] ) else ( createCableConnectorElement connectors[2] prop propNode sibling:connectors[1] ) ) ) else --its a singular connection ( createCableConnectorElement connectPoint prop propNode ) ) --append to root rootElem.appendChild propNode ) ) --print XML.innerXML --save the xml --gRsPerforce.add_or_edit #(configPath) silent:false XML.save connectionConfig ) ) struct CableConnector ( propName = "", name = "", position = [0,0,0], sibling = "" ) struct CablePresetsStruct ( p4 = RsPerforce(), presetLocation = (RsConfigGetWildWestDir()+"etc/config/maps/cableMakerPresets.ini"), presets, presetKeys, presetValues, --Load Presets fn loadPresets = ( /* if p4.connected() then --sync the presets ini file ( --get depot loc from location depotLoc = p4.local2depot presetLocation try ( p4.sync depotLoc print "Sync success!" ) catch ( print "Sync Fail" messageBox "Couldn't sync cable presets" return false ) ) */ --check presets file exits if doesFileExist presetLocation then ( presets = getINISetting presetLocation ) else ( messageBox "Can't load cable presets\n Check synced from perforce" title:"Error" ) ), fn getPreset chosen = ( presetKeys = getINISetting presetLocation chosen presetValues = for key in presetKeys collect getINISetting presetLocation chosen key ), on create do loadPresets() ) cablePresets = CablePresetsStruct() --////////////////////////////////////////////////////////////////////////////// -- Export mesh vertex --////////////////////////////////////////////////////////////////////////////// struct CableMeshVert ( position, tangency, radius, lineDist, texCoordX ) --////////////////////////////////////////////////////////////////////////////// -- Cable setup --////////////////////////////////////////////////////////////////////////////// struct CableMakerStruct ( slackAttr = undefined, weight = undefined, weightVariance = undefined, line = undefined, numSegs = undefined, DEBUGMESH = true, meshPoints = #(), tangentVecs = #(), radius = 0.01, --metres abrv = undefined, cableColour = orange, cable = undefined, cableStartEndNodes = undefined, cableTexturePath = (RsConfigGetTextureSourceDir() + "testbed/cable.tga"), cableShader = undefined, cableTextSize = 1, startNode = undefined, endNode = undefined, connectionConfig = (RsConfigGetWildWestDir() + "etc/config/props/cableConnectors.xml"), cableConnectors = #(), availableContainers = #(), UIHandle = undefined, legacyMode = false, fn createCableAttachPointsXML = ( local ccs = CableConnectorsUtilStruct() ccs.createCableAttachPointsXML() ), --////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// fn addToContainer items = ( local selContainer = UIHandle.ddlContainers.selected --check against scene container to make sure its there local sceneContainerNames = for o in $objects where classOf o == Container collect o.name if findItem sceneContainerNames selContainer == 0 then ( MessageBox "Can't find the container chosen in the list, \n in the current scene" title:"Error" ) else ( --clean the continer array local tempArray = for c in availableContainers where isValidNode c collect c local containerNames = for c in tempArray collect c.name if findItem containerNames selContainer == 0 then --notify that theres a mismatch if we got different count ( messagebox "The chosen container doesnt exist anymore, delete the cable and try again,\n or add to a container manually" return false ) availableContainers = tempArray local theContainer = (for c in availableContainers where (isValidNode c) == true and c.name == selContainer collect c)[1] if theContainer != undefined then ( success = theContainer.AddNodesToContent &items if not success then ( format "oh dear :(\n" MessageBox "Failed to add the cable to the chosen container\n" title:"Error" ) ) ) ), --////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- Loads the cable connector xml config generated by createCableAttachPointsXML() --////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// fn loadCableConnectorConfig = ( if doesFileExist connectionConfig == false then ( messagebox "Missing the cableConnectors.xml file" return false ) local XML = dotNetObject "System.Xml.XmlDocument" XML.load connectionConfig local root = XML.SelectSingleNode "CableConnections" local propNodes = root.childNodes --read the props for i = 0 to (propNodes.count - 1) do ( local thisPropNode = propNodes.Item[i] local thisPropName = thisPropNode.name local connectors = #() --get the CableConnector nodes local cableConnectorNodes = thisPropNode.childNodes for i=0 to (cableConnectorNodes.count - 1) do ( local currentNode = cableConnectorNodes.item[i] --get the child nodes local cableConnectorKids = currentNode.childNodes local positionVal = undefined local siblingVal = undefined local connector = CableConnector() connector.propName = thisPropName connector.name = currentNode.getAttribute "Name" for n=0 to (cableConnectorKids.count - 1) do ( local dataNode = cableConnectorKids.item[n] --print dataNode.name case dataNode.name of ( "Position": ( connector.position = [(dataNode.getAttribute "X") as Float, (dataNode.getAttribute "Y") as Float, (dataNode.getAttribute "Z") as Float] ) "Sibling": ( connector.sibling = dataNode.innerText ) ) ) append connectors connector ) --get the positions --local posNodes = thisPropNode.selectNodes "./CableConnector/Position" -- local posValues = for p = 0 to (posNodes.count - 1) collect -- ( -- [(posNodes.ItemOf[p].getAttribute "X") as Float, (posNodes.ItemOf[p].getAttribute "Y") as Float, (posNodes.ItemOf[p].getAttribute "Z") as Float] -- ) --append cableConnectors (DataPair prop:thisPropName connectionPoints:posValues) append cableConnectors (DataPair prop:thisPropName connectionPoints:connectors) ) ), --///////////////////////////////////////////////////////////////////////////// -- Populate list with current scene containers --///////////////////////////////////////////////////////////////////////////// fn getAvailableContainers = ( availableContainers = for o in objects where classOf o == Container collect o ), --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- Check the continer chosen in the UI still exists in the scene --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// fn checkContainerExists = ( --Check if there is any containers in the scene first if (getAvailableContainers()).count == 0 then return true local selContainer = UIHandle.ddlContainers.selected local tempArray = for c in availableContainers where isValidNode c collect c local containerNames = for c in tempArray collect c.name if findItem containerNames selContainer == 0 then --notify that theres a mismatch if we got different count ( messagebox "The chosen container doesnt exist anymore, choose again\n" getAvailableContainers() UIHandle.ddlContainers.items = for c in availableContainers collect c.name return false ) return true ), --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// fn discoverCableStartEndNodes cable = ( startEndNodes = for c in cable.children where (classOf c) == text collect c.children startNode = startEndNodes[1] endNode = startEndNodes[2] ), --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// fn getEdgeLength theMesh theEdge = ( --verts from edge verts = (polyOp.getVertsUsingEdge theMesh theEdge) as array edgeLength = distance (polyOp.getvert theMesh verts[1]) (polyOp.getvert theMesh verts[2]) ), --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// fn getVectorFromEdge theMesh theEdge = ( verts = (polyOp.getVertsUsingEdge theMesh theEdge) as array vec = normalize((polyOp.getvert theMesh verts[1]) - (polyOp.getvert theMesh verts[2])) ), --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// fn getVectorFromVerts theMesh vert1 vert2 = ( out = undefined if (classOf theMesh) == Editable_Mesh then ( out = normalize ((meshOp.getvert theMesh vert1) - (meshOp.getvert theMesh vert2)) ) else ( out = normalize ((polyOp.getvert theMesh vert1) - (polyOp.getvert theMesh vert2)) ) out ), --///////////////////////////////////////////////////////////////////////////// -- Set the cable mesh GTA Attributes --///////////////////////////////////////////////////////////////////////////// fn fixCableGtaAttrs cableMesh = ( --export degenerate Polys attrIdx = GetAttrIndex "Gta Object" "Remove Degenerate Polys" setAttr cableMesh attrIdx false --weld mesh attrIdx = GetAttrIndex "Gta Object" "Weld Mesh" setAttr cableMesh attrIdx true --set txd name attrIdx = GetAttrIndex "Gta Object" "TXD" setAttr cableMesh attrIdx "cablemesh" --set appdata on mesh to mark it out as a cable setAppData cableMesh 1 "cableMesh" ), --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// fn fixMergedCableNormals theMesh:undefined = ( coordsys #world undo off --with redraw off --( with undo off ( --print "FIX MERGER NORMALS" if theMesh == undefined then ( theMesh = selection[1] ) --resetXForm of theMesh ResetXForm theMesh collapseStack theMesh local theTriMesh = snapshotasmesh theMesh --get element verts elemFaces = ::cableUpgrade.getSubElements theMesh --print elemFaces elemVerts = #() theCopy = snapshotasmesh theMesh for elem in elemFaces do ( --format "elem: % \n" elem faceVerts = #() -- --convertToMesh theCopy for f in (elem as array) do join faceVerts (meshOp.getVertsUsingFace theCopy f) as array --delete theCopy faceVertsTemp = makeUniqueArray faceVerts faceVerts = #() --find the pairs and order them as such for v=1 to faceVertsTemp.count do ( for x=1 to faceVertsTemp.count do ( if v != x and (findItem faceVerts faceVertsTemp[x]) == 0 then ( if distance (getVert theCopy faceVertsTemp[x]) (getVert theCopy faceVertsTemp[v]) < 0.00001 then ( append faceVerts faceVertsTemp[v] append faceVerts faceVertsTemp[x] ) ) ) ) --add the verts for this element to the master array append elemVerts faceVerts ) --setup normal mod normalMod = Edit_Normals() addModifier theMesh normalMod normalMod.MakeExplicit() --Short the name for easier reading normalMod = theMesh.modifiers[#Edit_Normals] --Make sure were in modify mode and the current edit_normals modifier is set max modify mode modPanel.setCurrentObject normalMod --generate new normals newNormals = #() for verts in elemVerts do ( newNormals = #() --head --check for inconsistent vert indexes producing bad normals headNormal = getVectorFromVerts theMesh verts[3] verts[1] --format "headNormal: % \n" headNormal if headNormal == [1, 0 ,0] then ( headNormal = getVectorFromVerts theMesh verts[2] verts[3] --format "headNormal: % \n" headNormal append newNormals headNormal append newNormals headNormal ) else ( append newNormals headNormal append newNormals headNormal ) --body for v = 3 to (verts.count - 2) do ( --get vec behind behindVec = getVectorFromVerts theMesh verts[v] verts[v-2] if behindVec == [1, 0 ,0] then behindVec = getVectorFromVerts theMesh verts[v] verts[v-1] --get vec in front frontVec = getVectorFromVerts theMesh verts[v+2] verts[v] if frontVec == [1, 0 ,0] then frontVec = getVectorFromVerts theMesh verts[v+1] verts[v] --add and normalize append newNormals (normalize(frontVec+behindVec)) ) --tail append newNormals ( getVectorFromVerts theMesh verts[verts.count] verts[verts.count-2] ) append newNormals ( getVectorFromVerts theMesh verts[verts.count-1] verts[verts.count-3] ) --apply newNormals for v = 1 to verts.count do ( --get vertex normals from vertex id local vtx = #{verts[v]} local normals = #{} normalMod.EditNormalsMod.ConvertVertexSelection &vtx &normals normals = normals as array --format "vtx: % normals: % \n" vtx normals --set them to tangency for n = 1 to normals.count do ( --format "Tangent: % \n" meshPoints[v].tangency normalMod.SetNormalExplicit normals[n] normalMod.SetNormal normals[n] newNormals[v] ) ) ) collapseStack theMesh --fix the gta attrs fixCableGtaAttrs theMesh ) ), --///////////////////////////////////////////////////////////////////////////// -- Validate the curve looking for any null values -1.#IND --///////////////////////////////////////////////////////////////////////////// fn validateCurve startPos endPos numSegs = ( --generate points catenaryCurve.slack = 0.03 catenaryCurve.simulateHangingWire startPos endPos numSegs --format "start: % end: % segs: % \n" startPos endPos numSegs --trawl the points looking for invalid values --return false on the first bad one --print "----------" --print catenaryCurve.points --print "----------" for pnt in catenaryCurve.points do ( --print pnt if (pnt.z as String) == "-1.#IND" then ( messageBox "Invalid curve generated\nTry moving the start or end slightly in the Z and try again." title:"Invalid Curve" return false ) ) return true ), --//////////////////////////////////////////////////////////////////////////////--////////////////////////////////////////////////////////////////////////////// --Create cable connector helpers at locations defined in the cable connectors xml config --//////////////////////////////////////////////////////////////////////////////--////////////////////////////////////////////////////////////////////////////// fn createPropCableConnectors = ( local selected = getCurrentSelection() --get the props out of the selection local props = for obj in selected where classOf obj == RSrefObject collect obj --create cables connectors for known props local connectionList = #() for prop in props do ( --match with cable connectors definition? local connections = (for def in cableConnectors where def.prop == prop.objectName collect def.connectionPoints)[1] append connectionList connections ) --create the cable connection helpers for p = 1 to connectionList.count do ( local aConnectors = #() local thisProp = connectionList[p] local propTM = props[p].transform for c = 1 to thisProp.count do ( local thisPropPos = props[p].position + thisProp[c] --format "connection: % \n" thisPropPos local connector = Point pos:thisPropPos name:(props[p].objectName + "_CableConnector") constantscreensize:true wirecolor:(color 225 88 199) append aConnectors connector ) --transform by linking etc props[p].transform = matrix3 [1, 0, 0] [0, 1, 0] [0, 0, 1] propTM.translation --link helpers to the prop for cabcon in aConnectors do cabcon.parent = props[p] --reapply transform props[p].transform = propTM --unlink helpers for cabcon in aConnectors do cabcon.parent = undefined ) ), --////////////////////////////////////////////////////////////////////////////// --Create an artist adjustable cable representation --////////////////////////////////////////////////////////////////////////////// fn createCableProxy startNode endNode numSegs inSlack:0.003 = ( --check container exists to add to local success = checkContainerExists() if not success then return false --set catenaary curve slack catenaryCurve.slack = inSlack local cable = CableProxy() cable.startNode = startNode cable.endNode = endNode cable.pntCount = numSegs + 1 cable.slack = inSlack --return the cable cable ), --////////////////////////////////////////////////////////////////////////////// --Create cables between props automatically --////////////////////////////////////////////////////////////////////////////// fn AutoGenPropCables propSel = ( clearListener() --use the first one and collect any in the selection that are of the same type local firstProp = propSel[1].objectName undo "Create Cables" on ( --get the cable connectors for this type local connections = (for def in cableConnectors where (def.prop == firstProp) collect def.connectionPoints)[1] if (connections == undefined) then ( messagebox ("no connections defined for this prop type\n" + firstProp) title:"Connections missing" return false ) local slackMin = CableGenerationUI.spnMinSlack.value local slackMax = CableGenerationUI.spnMaxSlack.value local createdCables = #() --while we are not 1 away from the end connect the matching connector name between this and the next prop in the list for i=1 to (propSel.count - 1) do ( local currentProp = propSel[i] local currentPropTM = currentProp.transform local nextProp = propSel[i+1] local nextPropTM = nextProp.transform --match up connections between current and next prop for cnx in connections do ( --check if it has a sibling paring. --if it has then check we are not trying to connect back past its sibling --hopefully this way we can we dont get any crossover connections local ignore = false --get the sibling position if this connector has one local siblingPos = undefined if (cnx.sibling != undefined) then ( siblingPos = (for item in connections where (item.name == cnx.sibling) collect item.position)[1] ) local currentPropPos = currentProp.position + cnx.position --if there is a closer sibling then ignore this cable if (siblingPos != undefined) then ( --if it has a closer sibling to the next prop then ignore = true and dont make a cable if (distance currentPropPos nextProp.position) < (distance (currentProp.position + siblingPos) nextProp.position) then ( --print "Closer Sibling connector ignoring" ignore = true ) ) local nextPropPos = if (siblingPos != undefined) then ( nextProp.position + siblingPos ) else ( nextProp.position + cnx.position ) if not ignore then ( local currentConnector = Point pos:currentPropPos size:5.0 constantscreensize:true wirecolor:(color 0 255 0) \ name:"CableStart" local nextConnector = Point pos:nextPropPos size:5.0 constantscreensize:true wirecolor:(color 255 0 0) \ name:"CableEnd" local cable = CableProxy() append createdCables cable cable.startNode = currentConnector cable.endNode = nextConnector cable.pntCount = 10 + 1 cable.slack = (random slackMin slackMax) cable.points = genCablePoints currentConnector nextConnector 10 cable.uid = genGuid() cable.editing = true currentProp.transform = matrix3 [1, 0, 0] [0, 1, 0] [0, 0, 1] currentPropTM.translation nextProp.transform = matrix3 [1, 0, 0] [0, 1, 0] [0, 0, 1] nextPropTM.translation currentConnector.parent = currentProp nextConnector.parent = nextProp currentProp.transform = currentPropTM nextProp.transform = nextPropTM ) ) ) --add all the cables to the container addToContainer createdCables ) ), --////////////////////////////////////////////////////////////////////////////// -- Point to Line distance --////////////////////////////////////////////////////////////////////////////// fn pointLineDist2 pA pB pC = ( local vAB=pB-pA local vAC=pC-pA (length (cross vAB vAC))/(length vAB) ), --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// fn meshReport theCable = ( format "--------------- MESH REPORT ----------------\n" --report the vert positions aVtxPos = #() for vtx = 1 to getnumverts theCable do ( pos = meshop.getVert theCable vtx append aVtxPos pos --format "vtx: % pos: %\n" vtx pos ) --normals aNormals = #() --Create the mod and apply normalMod = Edit_Normals() addModifier theCable normalMod normalMod.MakeExplicit() --Short the name for easier reading normalMod = theCable.modifiers[#Edit_Normals] --Make sure were in modify mode and the current edit_normals modifier is set max modify mode modPanel.setCurrentObject normalMod for vtx = 1 to getnumverts theCable do ( for v = 1 to theCable.numVerts do ( nrmlVal = normalMod.EditNormalsMod.GetNormal v append aNormals nrmlVal ) ) --report the uv maps1 values aTverts = #() for tv = 1 to getNumTVerts theCable do ( append aTverts (getTVert theCable tv) --format "tvert: % value: %\n" tv (getTVert theCable tv) ) --report the vertex color values aVtxCols = #() for cpv = 1 to (getNumCPVVerts theCable) do ( append aVtxCols (getVertColor theCable cpv) --format "vtx: % colour: %\n" cpv (getVertColor theCable cpv) ) for i = 1 to getnumverts theCable do ( format "vtx: % Pos: % Normal: % UV: % CPV: %\n" i aVtxPos[i] aNormals[i] aTverts[i] aVtxCols[i] ) format "--------------- MESH REPORT ----------------\n" ), fn getCableMaterialShaderName = ( if RsFavourPresetCreation then "templates/cable.mpt" else "cable.sps" ), --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// fn createCableMaterial = ( local cableShaderName = getCableMaterialShaderName() --lets check if a cable material exists in the scene alreadythen we can cut down no dupes local cableMaterials = for mat in sceneMaterials where classOf m == Rage_Shader and RstGetShaderName mat == cableShaderName collect mat if cableMaterials.count != 0 then ( cableShader = cableMaterials[1] return false ) cableShader = Rage_Shader() cableShader.name = "CableMaterial" RstSetShaderName cableShader cableShaderName --assign to the mat RstSetVariable cableShader 1 cableTexturePath ------------------------------------ --assign colours from UI ------------------------------------ --get shader param indices local diffuseIdx = 0 local diffuse2Idx = 0 local ambientIdx = 0 local emissiveIdx = 0 for i=1 to (RstGetVariableCount cableShader) do ( case (RstGetVariableName cableShader i) of ( "Diffuse": ( local colour = 0.00392157 * (UIHandle.cprDiffOne.color as Point3) --normalized Point3 colour RstSetVariable cableShader i colour ) "Diffuse2": ( local colour = 0.00392157 * (UIHandle.cprDiffTwo.color as Point3) --normalized Point3 colour RstSetVariable cableShader i colour ) "Ambient": ( local ambientNatural = UIHandle.spnAmbientNatural.value local ambientArtificial = UIHandle.spnAmbientArtificial.value RstSetVariable cableShader i [ambientNatural, ambientArtificial] ) "Emissive" : RstSetVariable cableShader i UIHandle.spnEmissive.value ) ) ), --////////////////////////////////////////////////////////////////////////////// -- Create an export mesh from the proxy representation. --////////////////////////////////////////////////////////////////////////////// /* NOTES struct vertexInput { float3 pos : POSITION; float3 tan : NORMAL; // tangent float4 col : COLOR0; // xy=phase, z=texcoord.x, w=unused float2 r_d : TEXCOORD0; }; that's what it expects the mesh to contain i haven't hooked up the micromovements yet so the phase xy can be 0, but the texcoord.x (stored in the COLOR blue channel) should go 0 to 255 along the length of the wire r_d (stored in TEXCOORD0) should contain the radius in the x component and the distance to line in the y component actually y is really "distance to line times micromovement amplitude scale" .. and can be 0 for now */ fn createCableMesh cable startPos endPos numSegs = ( --check container exists to add to local success = checkContainerExists() if not success then return false local cableClass = classOf cable --check if theres a mesh already and prompt to replace it or not local cableMeshes = #() case cableClass of ( cableProxy: ( cableMeshes = for o in objects where (superClassOf o == GeometryClass) and ((getAppData o 1) != undefined) and ((getAppData o 2) != undefined) and ((getAppData o 2) == cable.uid) collect o ) SplineShape: ( cableMeshes = for o in objects where (superClassOf o == GeometryClass) and ((getAppData o 1) != undefined) and ((getAppData o 2) != undefined) and ((getAppData o 2) as IntegerPtr == cable.handle) collect o ) ) if cableMeshes.count == 1 and (not legacyMode) then --we have a mesh ( --if it exists ask if user wants to replace answer = queryBox "This cable already has a mesh\n\nYou want to replace it?" title:"Replace cable mesh" if answer == true then --delete the old mesh ( delete cablemeshes[1] ) else --no dont delete the old one, we'll skip out for now ( return false ) ) --use selected cable spline --sel = $selection[1] --if classOf sel != Editable_Spline then return false --*********************************** --Generate the catenary curve --*********************************** --if the ui opt 'TAUT' is set then set slack to very low to make a taut cable mesh taut = UIHandle.chkTaut.checked if taut == true then ( catenaryCurve.slack = 0.00000001 cable.slack = 0.00000001 ) --catenaryCurve.slack = catenaryCurve.simulateHangingWire startPos endPos numSegs --generate tangent vector array local curvePointsCount = catenaryCurve.points.count tangentVecs = #() --first tangent append tangentVecs ( normalize(catenaryCurve.points[2] - catenaryCurve.points[1])) --middle --0.5 * ((n – n-1) + (n+1 – n)) for p=2 to (curvePointsCount - 1) do ( append tangentVecs ( normalize(catenaryCurve.points[p+1] - catenaryCurve.points[p-1])) ) --add the last tangent append tangentVecs ( normalize(catenaryCurve.points[curvePointsCount] - catenaryCurve.points[curvePointsCount - 1])) -------------------------------- -- Generate Mesh Data -------------------------------- --start of mesh --get first point firstPoint = CableMeshVert() firstPoint.position = startPos firstPoint.tangency = tangentVecs[1] firstPoint.radius = -radius -- neg radius firstPoint.lineDist = 1.0 firstPoint.texCoordX = 0 append meshPoints firstPoint firstPoint = undefined --now another but radius opposite firstPoint = CableMeshVert() firstPoint.position = startPos firstPoint.tangency = tangentVecs[1] firstPoint.lineDist = 1.0 firstPoint.texCoordX = 0 firstPoint.radius = radius -- pos radius append meshPoints firstPoint --Now for the cat points pointCount = catenaryCurve.points.count normPointCount = 1.0 / pointCount --iterate the catenary curve creating points as we go for p = 1 to pointCount do ( --make 2 mesh points for each position to create faces local pos = catenaryCurve.points[p] --local tangency = lengthTangent cable (p * normPointCount) --lerp local lineDist = (pointLineDist2 startPos endPos pos) --point1 local catPoint = CableMeshVert() catPoint.position = pos catPoint.tangency = tangentVecs[p] --neg radius first catPoint.radius = -radius catPoint.lineDist = 1.0 - lineDist if taut == true then catPoint.lineDist = 1.0 catPoint.texCoordX = (p * (255.0 / pointCount)) as Integer append meshPoints catPoint catPoint = undefined --point2 local catPoint = CableMeshVert() catPoint.position = pos catPoint.tangency = tangentVecs[p] --pos radius second catPoint.radius = radius catPoint.lineDist = 1.0 - lineDist if taut == true then catPoint.lineDist = 1.0 catPoint.texCoordX = (p * (255.0 / pointCount)) as Integer append meshPoints catPoint ) -------------------------------- -- /Generate Mesh Data -------------------------------- -------------------------------- --Generate Mesh -------------------------------- --tverts are currently broken so have to work around meshVertices = for cmv in meshPoints collect cmv.position --format "Vert Positions: % \n" meshVertices --work out the meshFaces leave out first and last verts meshFaces = #() matIDs = #() meshTVertices = #() for p = 1 to (meshVertices.count - 2) do ( if (mod p 2) == 0 then ( append meshFaces [p, p+1, p+2] --format "face: % indices: %\n" p [p, p+1, p+2] ) else ( append meshFaces [p, p+2, p+1] --format "face: % indices: %\n" p [p, p+2, p+1] ) append matIDs 1 ) --texture verts will hold extra info - --meshTVert_tangent = for p = 2 to (meshPoints.count - 1) collect meshPoints[p].tangency --textureverts for [radius, linedist, unused] meshTVert_rad_dist = for p = 1 to meshPoints.count collect [meshPoints[p].radius, meshPoints[p].lineDist, 0] --format "uv: % \n" meshTVert_rad_dist cableMesh = mesh vertices:meshVertices faces:meshFaces materialIDs:matIDs tverts:meshTVert_rad_dist --Generate mesh name append preset abbreviation on the end cableMesh.name = uniqueName ("CableMesh" + (cableMesh.handle as String) + "_" + abrv) tmesh = cableMesh.mesh buildTVFaces tmesh for i = 1 to tmesh.numfaces do(setTVFace tmesh i (getFace tmesh i)) update cableMesh -------------------------------- -- /Generate Mesh -------------------------------- ----------------------------------------------------------------- --set the mesh normals to the curve tangency ----------------------------------------------------------------- --for p = 1 to meshPoints.count do format "tangency: %\n" meshPoints[p].tangency --Create the mod and apply normalMod = Edit_Normals() addModifier cableMesh normalMod normalMod.MakeExplicit() --Short the name for easier reading normalMod = cableMesh.modifiers[#Edit_Normals] --Make sure were in modify mode and the current edit_normals modifier is set max modify mode modPanel.setCurrentObject normalMod for v = 1 to cableMesh.numVerts do ( --get vertex normals from vertex id local vtx = #{v} local normals = #{} normalMod.EditNormalsMod.ConvertVertexSelection &vtx &normals normals = normals as array --format "vtx: % normals: % \n" vtx normals --set them to tangency for n = 1 to normals.count do ( --format "Tangent: % \n" meshPoints[v].tangency normalMod.SetNormalExplicit normals[n] normalMod.SetNormal normals[n] meshPoints[v].tangency ) ) collapseStack cableMesh ----------------------------------------------------------------- -- /set the mesh normals to the curve tangency ----------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------- --Set the vertex BLUE colour along the length of the cable 0-255 ---------------------------------------------------------------------------------------------------------------- --for p = 1 to meshPoints.count do format "TexCoordX: % \n" meshPoints[p].texCoordX local tX = for p = 1 to meshPoints.count collect meshPoints[p].texCoordX --Set the red and green to random values for phase along the length of the cable redPhase = random 0 255 greenPhase = random 0 255 --setNumCPVVerts cableMesh meshPoints.count --meshop.setNumCPVVerts cableMesh meshPoints.count --first flush the cpv for v = 1 to cableMesh.numVerts do ( meshop.setVertColor cableMesh 0 v (color 0 0 0) ) --now set it for v = 1 to tX.count do ( if mod v 2 == 0 then ( meshop.setVertColor cableMesh 0 #{v} (color redPhase greenPhase tX[v-1]) ) else ( meshop.setVertColor cableMesh 0 #{v} (color redPhase greenPhase tX[v]) ) --setVertColor cableMesh v (color 0 0 tX[v]) --setVertColor cabeMesh v+1 (color 0 0 tX[v]) ) --Apply Cable Shader cableMaker.createCableMaterial() cableMesh.material = cableShader --Fix up GTA attrs fixCableGtaAttrs cableMesh --set appdata on mesh to link it back to its proxy cable spline case cableClass of ( cableProxy: ( setAppData cableMesh 2 cable.uid ) SplineShape: ( setAppData cableMesh 2 (cable.handle as string) setAppData cable 1 "CableProxy" ) ) --centre pivot CenterPivot cableMesh --set the wirecolor cableMesh.wireColor = cableColour -- DO A MESH REPORT FOR DEBUGGING PURPOSES --meshReport cableMesh --------------- --clean up --------------- meshPoints = #() tangentVecs = #() --remove temp spline --delete cable --add the cable to the selected container in the UI dropdown? if availableContainers.count != 0 then ( addToContainer #(cableMesh) ) cableMesh ), --///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// fn createCableMeshFromSpline theSpline numSegs = ( --check container exists to add to local success = checkContainerExists() if not success then return false --theSpline = convertToSplineShape theSpline --check if theres a mesh already and prompt to replace it or not --generate tangent vector array local normPointCount = 1.0 / numSegs local normU = 1.0 / (numSegs + 1) tangentVecs = for pt=1 to (numSegs + 1) collect pathTangent theSpline 1 (pt * normU) tangentVecs[1] = pathTangent theSpline 1 0.01 tangentVecs[tangentVecs.count] = pathTangent theSpline 1 0.99 -------------------------------- -- Generate Mesh Data -------------------------------- --start of mesh --get first point local firstPoint = CableMeshVert() firstPoint.position = pathInterp theSpline 1 0.0 firstPoint.tangency = tangentVecs[1] firstPoint.radius = -radius -- neg radius firstPoint.lineDist = 1.0 firstPoint.texCoordX = 0 append meshPoints firstPoint firstPoint = undefined --now another but radius opposite firstPoint = CableMeshVert() firstPoint.position = pathInterp theSpline 1 0.0 firstPoint.tangency = tangentVecs[1] firstPoint.lineDist = 1.0 firstPoint.texCoordX = 0 firstPoint.radius = radius -- pos radius append meshPoints firstPoint --Now for the cat points pointCount = catenaryCurve.points.count --iterate the catenary curve creating points as we go for p = 1 to numSegs do ( --make 2 mesh points for each position to create faces local pos = pathInterp theSpline 1 (p * normPointCount) local lineDist = 0 --point1 local catPoint = CableMeshVert() catPoint.position = pos catPoint.tangency = tangentVecs[p+1] --neg radius first catPoint.radius = -radius catPoint.lineDist = 1.0 catPoint.texCoordX = 0 append meshPoints catPoint catPoint = undefined --point2 local catPoint = CableMeshVert() catPoint.position = pos catPoint.tangency = tangentVecs[p+1] --pos radius second catPoint.radius = radius catPoint.lineDist = 1.0 catPoint.texCoordX = 0 append meshPoints catPoint ) -------------------------------- -- /Generate Mesh Data -------------------------------- -------------------------------- --Generate Mesh -------------------------------- --tverts are currently broken so have to work around local meshVertices = for cmv in meshPoints collect cmv.position --format "Vert Positions: % \n" meshVertices --work out the meshFaces leave out first and last verts local meshFaces = #() local matIDs = #() local meshTVertices = #() for p = 1 to (meshVertices.count - 2) do ( if (mod p 2) == 0 then ( append meshFaces [p, p+1, p+2] --format "face: % indices: %\n" p [p, p+1, p+2] ) else ( append meshFaces [p, p+2, p+1] --format "face: % indices: %\n" p [p, p+2, p+1] ) append matIDs 1 ) --texture verts will hold extra info - --meshTVert_tangent = for p = 2 to (meshPoints.count - 1) collect meshPoints[p].tangency --textureverts for [radius, linedist, unused] local meshTVert_rad_dist = for p = 1 to meshPoints.count collect [meshPoints[p].radius, meshPoints[p].lineDist, 0] --format "uv: % \n" meshTVert_rad_dist local cableMesh = mesh vertices:meshVertices faces:meshFaces materialIDs:matIDs tverts:meshTVert_rad_dist --Generate mesh name append preset abbreviation on the end cableMesh.name = "CableMesh" + (cableMesh.handle as String) + "_" + abrv local tmesh = cableMesh.mesh buildTVFaces tmesh for i = 1 to tmesh.numfaces do(setTVFace tmesh i (getFace tmesh i)) update cableMesh -------------------------------- -- /Generate Mesh -------------------------------- ----------------------------------------------------------------- --set the mesh normals to the curve tangency ----------------------------------------------------------------- --for p = 1 to meshPoints.count do format "tangency: %\n" meshPoints[p].tangency --Create the mod and apply local normalMod = Edit_Normals() addModifier cableMesh normalMod normalMod.MakeExplicit() --Short the name for easier reading normalMod = cableMesh.modifiers[#Edit_Normals] --Make sure were in modify mode and the current edit_normals modifier is set max modify mode modPanel.setCurrentObject normalMod for v = 1 to cableMesh.numVerts do ( --get vertex normals from vertex id local vtx = #{v} local normals = #{} normalMod.EditNormalsMod.ConvertVertexSelection &vtx &normals normals = normals as array --format "vtx: % normals: % \n" vtx normals --set them to tangency for n = 1 to normals.count do ( --format "Tangent: % \n" meshPoints[v].tangency normalMod.SetNormalExplicit normals[n] normalMod.SetNormal normals[n] meshPoints[v].tangency ) ) fixMergedCableNormals theMesh:cableMesh collapseStack cableMesh ----------------------------------------------------------------- -- /set the mesh normals to the curve tangency ----------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------- --Set the vertex BLUE colour along the length of the cable 0-255 ---------------------------------------------------------------------------------------------------------------- --for p = 1 to meshPoints.count do format "TexCoordX: % \n" meshPoints[p].texCoordX local tX = for p = 1 to meshPoints.count collect meshPoints[p].texCoordX --Set the red and green to random values for phase along the length of the cable local redPhase = 0 local greenPhase = 0 --setNumCPVVerts cableMesh meshPoints.count --meshop.setNumCPVVerts cableMesh meshPoints.count --first flush the cpv for v = 1 to cableMesh.numVerts do ( meshop.setVertColor cableMesh 0 v (color 0 0 0) ) --now set it for v = 1 to tX.count do ( if mod v 2 == 0 then ( meshop.setVertColor cableMesh 0 #{v} (color redPhase greenPhase tX[v-1]) ) else ( meshop.setVertColor cableMesh 0 #{v} (color redPhase greenPhase tX[v]) ) --setVertColor cableMesh v (color 0 0 tX[v]) --setVertColor cabeMesh v+1 (color 0 0 tX[v]) ) --Apply Cable Shader cableMaker.createCableMaterial() cableMesh.material = cableShader --Fix up GTA attrs fixCableGtaAttrs cableMesh --set appdata on mesh to link it back to its proxy cable spline if spline != undefined then setAppData cableMesh 2 (spline.handle as string) --set the appdata on the spline back to "CableProxy" for id if the mesh needs to be regenerated for it --setAppData spline 1 "CableProxy" --Parent the mesh to the spline. --cableMesh.parent = spline --centre pivot CenterPivot cableMesh --set the wirecolor cableMesh.wireColor = cableColour -- DO A MESH REPORT FOR DEBUGGING PURPOSES --meshReport cableMesh --------------- --clean up --------------- meshPoints = #() tangentVecs = #() --remove temp spline --delete cable --add the cable to the selected container in the UI dropdown? if availableContainers.count != 0 then ( addToContainer #(cableMesh) ) cableMesh ) ) --END STRUCT --////////////////////////////////////////////////////////////////////////////// -- Cable Diagnostics --////////////////////////////////////////////////////////////////////////////// struct VisObjCacheStruct ( vPos = #(), vCol = #() ) struct CableMakerDiagnosticsStruct ( objectSelectionList = #(), cachedSelectionHash = #(), visObj = undefined, -- visObjCache = (DataPair vpos:#() vChannel:#()), visObjCache = VisObjCacheStruct(), visChannel, cacheDirty = true, --////////////////////////////////////////////////////////////////////////////// -- Find singularities --////////////////////////////////////////////////////////////////////////////// fn findCoLocatedVerts obj subObjectDisplay:true = ( local epsilon = 0.000001 local selectionList = #() local meshPolyOpFn = RsMeshPolyOp obj local doneVerts = #{} local coLocatedVtx = #() for vert in obj.verts where (not doneverts[vert.index]) do ( local matches = 0 local coLocatedCandidates = #{} for other in obj.verts do ( if other.index != vert.index then ( if (distance vert.pos other.pos) < epsilon then ( append coLocatedCandidates other.index matches += 1 ) ) ) if (matches > 1) do ( append coLocatedVtx (DataPair vtx:vert.index coVtx:coLocatedCandidates) doneVerts + coLocatedCandidates ) ) --now test connectivity for item in coLocatedVtx do ( --find the element its in local element = meshPolyOpFn.getElementsUsingFace obj (meshPolyOpFn.getFacesUsingVert obj item.vtx) --now match against the other colocated verts --if more than 2 match then its a problem local matches = 0 for coVtx in item.coVtx do ( local coVtxElem = meshPolyOpFn.getElementsUsingFace obj (meshPolyOpFn.getFacesUsingVert obj coVtx) if ((element - coVtxElem).numberSet == 0) then matches += 1 ) if (matches > 1) then ( append selectionList item.vtx ) ) if selectionList.count == 0 then ( --messagebox "Cable is clean, No singularities found" print "Clean" ) else ( gRsUlog.LogError (obj.name + " has singularities") obj.selectedVerts = selectionList SetCommandPanelTaskMode #modify if subObjectDisplay do ( subobjectLevel = 1 ) ) (selectionList.count == 0) ), --////////////////////////////////////////////////////////////////////////////// --func to test normal deviation --////////////////////////////////////////////////////////////////////////////// fn isDeviantNormal vec normal = ( local maxDev = 0.1 local deviation = 1.0 - (dot vec normal) --return (deviation > maxDev) ), --////////////////////////////////////////////////////////////////////////////// -- Find bad normals --////////////////////////////////////////////////////////////////////////////// fn findBadNormals obj subObjectDisplay:true = ( local maxDev = 0.1 local selectionList = #() local objElements = cableUpgrade.getSubElements obj local editNormalsMod = EditNormals() addModifier obj editNormalsMod --go through each element for elem in objElements do ( local elemArray = elem as Array --head local vec = normalize (obj.verts[elemArray[3]].pos - obj.verts[elemArray[1]].pos) local vtxIndex = #{1} local vtxNormals = #{} editNormalsMod.ConvertVertexSelection &vtxIndex &vtxNormals local normalVec = editNormalsMod.GetNormal (vtxNormals as Array)[1] if (isDeviantNormal vec normalVec) then ( append selectionList elemArray[1] ) --body for vtx=3 to (elemArray.count - 2) do ( --use 2 verts to get a vector local vec = normalize(obj.verts[elemArray[vtx+2]].pos - obj.verts[elemArray[vtx-2]].pos) --get the normal for the vertex local vtxIndex = #{vtx} local vtxNormals = #{} editNormalsMod.ConvertVertexSelection &vtxIndex &vtxNormals local normalVec = editNormalsMod.GetNormal (vtxNormals as Array)[1] --dot them and if they dont agree within some margin mark them as problematic if (isDeviantNormal vec normalVec) then ( append selectionList elemArray[vtx] ) ) --tail local vec = normalize (obj.verts[elemArray[elemArray.count]].pos - obj.verts[elemArray[(elemArray.count - 1)]].pos) local vtxIndex = #{elemArray.count} local vtxNormals = #{} editNormalsMod.ConvertVertexSelection &vtxIndex &vtxNormals local normalVec = editNormalsMod.GetNormal (vtxNormals as Array)[1] if (isDeviantNormal vec normalVec) then ( append selectionList elemArray[elemArray.count] ) ) if selectionList.count == 0 then ( --messagebox "Cable is clean, No singularities found" print "Clean" deleteModifier obj editNormalsMod ) else ( gRsUlog.LogError (obj.name + " has deviant normals") deleteModifier obj editNormalsMod obj.selectedVerts = selectionList SetCommandPanelTaskMode #modify if subObjectDisplay do ( subobjectLevel = 1 ) ) (selectionList.count == 0) ), --////////////////////////////////////////////////////////////////////////////// -- Find Bad Materials --////////////////////////////////////////////////////////////////////////////// fn findBadMaterials obj subObjectDisplay:true = ( local matIDs = (RsGetMatIdsUsedByObj obj) as Array local cableMaterialAssigned = true local nonCableMatIDs = undefined if (isKindOf obj.material Multimaterial) then ( for id in matIDs while not cableMaterialAssigned do ( cableMaterialAssigned = ((RstGetShaderName obj.material.materialList[id]) == getCableMaterialShaderName()) if not cableMaterialAssigned then nonCableMatIDs = id ) faceMatId = RsGetFaceMatIDFunc obj obj.selectedFaces = (for face in obj.faces where (faceMatId obj face.index) == nonCableMatIDs collect face.index) ) else ( if (not ((RstGetShaderName obj.material) == getCableMaterialShaderName())) then cableMaterialAssigned = false ) if (not cableMaterialAssigned) and subObjectDisplay then ( subobjectlevel = 4 --face ) cableMaterialAssigned ), --////////////////////////////////////////////////////////////////////////////// -- Find Bad Attrs --////////////////////////////////////////////////////////////////////////////// fn findBadAttrs obj = ( --export degenerate Polys attrIdx = GetAttrIndex "Gta Object" "Remove Degenerate Polys" local removeDegeneratePolys = (not (getAttr obj attrIdx)) if not removeDegeneratePolys do gRsUlog.LogError (obj.name + " has remove Degenerate Polys attribute set, this is wrong") --weld mesh attrIdx = GetAttrIndex "Gta Object" "Weld Mesh" local weldMesh = getAttr obj attrIdx if not weldMesh do gRsUlog.LogError (obj.name + " does not have weld mesh attribute set, this is wrong") --set txd name --attrIdx = GetAttrIndex "Gta Object" "TXD" --local txd = ((getAttr obj attrIdx) == "cablemesh") (removeDegeneratePolys and weldMesh) ), /*** Get the vert colour from a given map channel for a mesh vert index ***/ fn getVertColourFromVert theMesh vIndex vChannel = ( -- Get all faces using the vert as array faceArray = (meshop.getFacesUsingVert theMesh vIndex) as array local colours = #() --iterate over the faces for f in faceArray do ( --Get geometry face - vertex is either its first, second or third component geoFace = getFace theMesh f -- Get map face (index corresponds with geometry face) --There may not be a map channel set for this, so catch it accordingly try ( mapFace = meshop.getMapFace theMesh vChannel f ) catch ( --add a pure white for a missing channel colour append colours #(255, 255, 255) --onto the next continue ) -- Find order inside the mesh face - vertex (component) order will be the same in both mapVert = case of ( (geoFace.x == vIndex): mapFace.x (geoFace.y == vIndex): mapFace.y (geoFace.z == vIndex): mapFace.z ) --collect the mapVert values found local mapVal = meshop.getMapVert theMesh vChannel mapVert --print mapVal appendifunique colours mapVal )--end f loop colours ), /*** Get a hash value for a selection array ***/ fn getSelectionHash itemArray = ( local retHash local ss = StringStream "" for item in itemArray do ( format "%" item.name to:ss ) getHashValue (ss as String) 1 ), /*** Build the vertex marker cache ***/ fn buildMarkerCache = ( print "buildcache" visObjCache.vPos = #() visObjCache.vCol = #() --find the object positions and channel values for obj in objectSelectionList where (isValidNode obj) do ( if (obj != visObj) do ( --get the cable colour from the object material local cableShader = undefined if (classOf obj.material == Multimaterial) then ( visObj = obj.mesh local matIds = RsGetMatIdsUsedByObj obj local matIdArray = matIds as Array if (matIds.numberSet > 0) then ( -- -- --get the material diffs in a list -- matDiffs = #() for matID in (matIds as Array) do ( local cableShader = obj.material[matID] local diff1 = (RstGetVariable cableShader 4) * 255.0 local diff2 = (RstGetVariable cableShader 5) * 255.0 append matDiffs (DataPair diff1:diff1 diff2:diff2) ) --break() --for each material get a bitarray of verts --so for each material we want to get the diffuse values to tint via the alpha channel for vtx in visObj.verts do ( faces = (meshop.getFacesUsingVert visObj vtx.index) as Array local faceMatLookup = findItem matIdArray (getFaceMatID visObj faces[1]) local faceMat = matDiffs[faceMatLookup] --get the verts append visObjCache.vPos ( meshop.getvert visObj vtx.index node:obj ) local vAlpha = (getVertColourFromVert visObj vtx.index -2)[1] if (visChannel == #alpha) then ( append visObjCache.vCol (vAlpha * 255.0) ) else ( if (vAlpha.x >= 1.0) then ( append visObjCache.vCol faceMat.diff2 ) else ( append visObjCache.vCol faceMat.diff1 ) ) ) ) else ( local idx = (matIds as Array)[1] local mat = obj.material[idx] if (classOf mat == Rage_Shader) and (RstGetShaderName mat == getCableMaterialShaderName()) then ( cableShader = mat ) ) ) else if (classOf obj.material == Rage_Shader) and (RstGetShaderName obj.material == getCableMaterialShaderName()) then ( visObj = obj.mesh cableShader = obj.material local diff1 = (RstGetVariable cableShader 4) * 255.0 local diff2 = (RstGetVariable cableShader 5) * 255.0 for vtx in obj.verts do ( --get the verts append visObjCache.vPos ( meshop.getvert visObj vtx.index node:obj ) local vAlpha = (getVertColourFromVert visObj vtx.index -2)[1] if (visChannel == #alpha) then ( append visObjCache.vCol vAlpha ) else ( if (vAlpha.x >= 1.0) then ( append visObjCache.vCol diff2 ) else ( append visObjCache.vCol diff1 ) ) ) ) ) ) --remove duplicate position entries in the cache local leanVPos = for vtx = 1 to visObjCache.vPos.count by 2 collect visObjCache.vPos[vtx] local leanVCol = for vtx = 1 to visObjCache.vCol.count by 2 collect visObjCache.vCol[vtx] visObjCache.vPos = leanVPos visObjCache.vCol = leanVCol cachedSelectionHash = getSelectionHash objectSelectionList ), /*** Visualise the vertex colours using viewport drawing methods ***/ fn visualiseVerts = ( if (selection.count == 0) do ( objectSelectionList = #() unregisterRedrawViewsCallback cableMakerDiagnostics.visualiseVerts return false ) --rebuild the cache if the selection list has changed local selectionHash = getSelectionHash objectSelectionList if (selectionHash != cachedSelectionHash) then ( cacheDirty = true ) if cacheDirty then ( buildMarkerCache() cacheDirty = false ) gw.setTransform (Matrix3 1) --draw the markers for v=1 to visObjCache.vPos.count do ( local vPos = visObjCache.vPos[v] local vCol = (color visObjCache.vCol[v].x visObjCache.vCol[v].y visObjCache.vCol[v].z) -- Draw filled circles at vert-points: local drawPos = gw.htranspoint vPos + [1,1,0] gw.hRect (box2 [drawPos.x - 3, drawPos.y - 3] [drawPos.x + 4, drawPos.y + 3]) vCol gw.hMarker drawPos #circle color:vCol ) gw.enlargeUpdateRect #whole gw.updateScreen() ) )--END STRUCT --struct instance cableMaker = CableMakerStruct() cableMakerDiagnostics = CableMakerDiagnosticsStruct() --////////////////////////////////////////////////////////////////////////////////////////////// -- UI --////////////////////////////////////////////////////////////////////////////////////////////// if CableMakerUI != undefined then destroyDialog CableMakerUI rollout CableMakerUI "Cable Maker" ( --////////////////////////////////////////////////////////////////////////////// -- VARIABLES --////////////////////////////////////////////////////////////////////////////// local tabRollouts = #(::CableGenerationUI, ::LegacyCableUpgradeUI, ::CableDiagnosticsUI, ::CableUtilUI) --////////////////////////////////////////////////////////////////////////////// -- CONTROLS --////////////////////////////////////////////////////////////////////////////// dotNetControl rsBannerPanel "Panel" pos:[0,0] height:32 width:CableMakerUI.width local banner = makeRsBanner dn_Panel:rsBannerPanel versionName:"Pumpkin Flumpkin" versionNum:1.05 wiki:"CableMaker" filename:(getThisScriptFilename()) dotNetControl dnTabs "system.windows.forms.tabControl" width:CableMakerUI.width height:25 offset:[-12, 0] subRollout theSubRollout width:(CableMakerUI.width - 2) height:(CableMakerUI.height - 60) offset:[-12, 0]--pos:[-1, dnTabs.pos.y + 50] --////////////////////////////////////////////////////////////////////////////// -- FUNCTIONS --////////////////////////////////////////////////////////////////////////////// fn SetPage index = ( if index > tabRollouts.count do messageBox "invalid tab" for roll in theSubRollout.rollouts do ( removeRollout roll ) AddSubRollout theSubRollout tabRollouts[index] rolledup:false border:true ) --////////////////////////////////////////////////////////////////////////////// -- EVENTS --////////////////////////////////////////////////////////////////////////////// on dnTabs Click do ( local tabNum = dnTabs.SelectedIndex + 1 local tabName = tabRollouts[tabNum].name SetPage tabNum ) on CableMakerUI open do ( banner.setup() for tab in tabRollouts do dnTabs.tabPages.Add tab.title SetPage 1 ) ) rollout CableGenerationUI "Generation" ( --Body /* group "Cable Settings" ( spinner spnSlack "Slack" tooltip:"How slack the cable is" range:[0.001, 5.0, 0.003] spinner spnNumSegs "Segments" tooltip:"Number of segments in proxy" range:[3, 100, 10] type:#integer scale:1 --spinner spnRadius "Radius" tooltip:"Cable radius in METERS 0.01 = 1 cm" range:[0.01, 1.0, 0.01] scale:0.01 ) */ dropdownlist ddlContainers "Scene Containers: " width:150 across:2 tooltip:"Container to add cables to" button btnRefreshContainers "R" width:25 offset:[37, 18] tooltip:"Refresh Container List" group "Generate Proxies" ( button btnMakeConnectors "Create Cable Connectors" tooltip:"Create connection helpers for props" width:200 --button btnMakeProxy "Create Cable Helper" tooltip:"Create a cable representation" width:200 button btnToggleLinks "Toggle Selected Cable Links" tooltip:"Toggles visibilty of setup structure" width:200 ) group "AutoGenerate Prop Cables" ( button btnAutoPropCables "Create Prop Cables" tooltip:"Make a selection of similar props to connect" width:200 spinner spnMinSlack "Slack Min" tooltip:"Minimum slack variation" range:[0.001, 0.1, 0.003] type:#float scale:0.001 across:2 spinner spnMaxSlack "Max" tooltip:"Maximum slack variation" range:[0.001, 0.1, 0.003] type:#float scale:0.001 ) group "Mesh Options" ( dropdownlist ddlPreset "Cable Type" items:cablePresets.presets width:200 spinner spnMeshSegs "Segments" tooltip:"Final mesh segments" range:[3, 100, 6] type:#integer scale:1 width:80 across:2 align:#left checkBox chkTaut "Taut" align:#right ) group "Shader Options" ( groupbox grpColour width:(CableGenerationUI.width - 25) height:50 label lblColour " Colour " align:#left offset:[5, -55] colorPicker cprDiffOne "1:" color:(color 0 0 0 ) across:2 offset:[15, 0] width:70 modal:false colorPicker cprDiffTwo "2:" color:(color 0 0 0 ) modal:false width:70 groupbox grpAmbient width:(CableGenerationUI.width - 25) height:50 offset:[0, 10] label lblAmbient " Ambient " align:#left offset:[5, -55] spinner spnAmbientNatural "Natural" range:[0.0, 1.0, 0.4] type:#float across:2 offset:[-5, 5] spinner spnAmbientArtificial "Artificial" range:[0.0, 1.0, 0.4] type:#float offset:[-5, 5] spinner spnEmissive "Emissive" range:[0.0, 32.0, 0.0] type:#float align:#left width:60 offset:[0, 10] ) group "Generate Mesh" ( button btnGenerateMesh "Create Cable Mesh" tooltip:"Build the mesh" width:200 button btnGenFromSpline "From Spline" tooltip:"Build a cable mesh from a spline" width:200 button btnFixCombinedNormals "Fix Combined Normals" width:200 ) --/////////////////////////////////// -- EVENTS dear boy --/////////////////////////////////// on btnHelp pressed do ( process = dotnetobject "System.Diagnostics.Process" process.start "https://devstar.rockstargames.com/wiki/index.php/CableMaker" process.dispose() ) --///////////////////////////////////////////////////////////////////////////// -- Make the cable Connectors --///////////////////////////////////////////////////////////////////////////// on btnMakeConnectors pressed do ( cableMaker.createPropCableConnectors() ) --///////////////////////////////////////////////////////////////////////////// -- Make the cable proxies --///////////////////////////////////////////////////////////////////////////// on btnMakeProxy pressed do ( if selection.count == 2 then ( --Use the current settings values for mesh creation cablePresets.getPreset ddlPreset.selected cableMaker.radius = cablePresets.presetValues[3] as float cableMaker.abrv = cablePresets.presetValues[1] as string --the cableMakerColour theColour = filterString cablePresets.presetValues[2] " " cableMaker.cableColour = (color (theColour[1] as float) (theColour[2] as float) (theColour[3] as float)) catenaryCurve.slack = spnSlack.value cableMaker.createCableProxy selection[1] selection[2] (spnNumSegs.value as Integer) inSlack:spnSlack.value ) else ( messagebox "Bad selection\nExiting!" title:"Error" ) ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on btnToggleLinks pressed do ( if selection.count != 0 then ( if classOf selection[1] == Dummy then ( if selection[1].showLinks == false then ( selection[1].showLinks = true ) else ( selection[1].showLinks = false ) ) ) ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on btnAutoPropCables pressed do ( --get the prop selection local userSel = for item in selection where (classof item == RSrefObject) collect item if (userSel.count < 2) then ( messageBox "Need more than 1 props selected" title:"Invalid Selection" return false ) --get props types, look for non-uniform selection local propTypes = #() for item in selection where (classof item == RSrefObject) do ( appendifunique propTypes item.objectName ) if (propTypes.count > 1) then ( messagebox "More than 1 prop type in selection" title:"Invalid Selection" return false ) cableMaker.AutoGenPropCables userSel ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on btnGenerateMesh pressed do ( if selection.count == 0 then ( messagebox "Nothing selected!\nNo mesh created." title:"Error" return false ) --Use the current settings values for mesh creation cablePresets.getPreset ddlPreset.selected cableMaker.radius = cablePresets.presetValues[3] as float cableMaker.abrv = cablePresets.presetValues[1] as string --the cableMakerColour theColour = filterString cablePresets.presetValues[2] " " cableMaker.cableColour = (color (theColour[1] as float) (theColour[2] as float) (theColour[3] as float)) --format "********************preset vals: % \n" cablePresets.presetValues --iterate through all the cable proxy splines selected and create meshes for them cableProxies = for o in selection collect o if cableProxies.count == 0 then ( messagebox "This doesn't seem to be a cable proxy spline" title:"Error!" return false ) for cable in cableProxies do ( local startPos = undefined local endPos = undefined --branch on cable type local validCable = false case (classof cable.baseObject) of ( cableProxy: --new system ( --check properties for a cableProxy helper if (isProperty cable #startNode) and (isProperty cable #endNode) and (isProperty cable #slack) do ( --get spline start and end startPos = cable.startNode.pos endPos = cable.endNode.pos --get the child label to change its text to the preset type cable.nameTag = ddlPreset.selected --make sure the slack is set as the proxy cable setting catenaryCurve.slack = cable.slack validCable = true ) ) SplineShape: --old system ( --get spline start and end startPos = interpCurve3D cable 1 0.0 endPos = interpCurve3D cable 1 1.0 --make sure the slack is set as the proxy cable setting local cableStartEndNodes = for c in cable.children where (classOf c) == text collect c.children local thisSlack = for c in cableStartEndNodes[1] where isProperty c "slack" == true collect c.slack --print thisSlack if thisSlack.count == 0 then break() catenaryCurve.slack = thisSlack[1] validCable = true ) default: --not a valid cable proxy ( print (classof cable.baseObject) as String print "invalid type" ) ) --trigger the mesh build if we have a valid cable proxy if validCable do ( --set the spline to the preset name for future use setAppData cable 1 ddlPreset.selected --set iteration count higher for better curve accuracy catenaryCurve.maxIter = 200 -- --CREATE THE MESH -- cableMaker.createCableMesh cable startPos endPos (spnMeshSegs.value as Integer) --set iteration count back for better interactivity catenaryCurve.maxIter = 75 ) ) ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on btnGenFromSpline pressed do ( if selection.count == 0 then ( messagebox "Nothing selected!\nNo mesh created." title:"Error" return false ) theSplines = for o in selection where superclassOf o == shape collect o for theSpline in theSplines do ( cableMaker.createCableMeshFromSpline theSpline (spnMeshSegs.value as Integer) ) ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on btnFixCombinedNormals pressed do ( local selObjs = for item in selection collect item for item in selObjs where (getAttrClass item == "Gta Object") do ( --check it has a cable shader applied local mat = item.material local isCableMesh = false if (isKindOf mat Multimaterial) then ( local usedMatIDs = RsGetMatIdsUsedByObj item for id = usedMatIDs while (not isCableMesh) do ( if (RsIsCableTexture mat.materialList[id]) then ( isCableMesh = true ) ) ) else if (isKindOf mat Rage_Shader) then ( if (RsIsCableTexture mat) then ( isCableMesh = true ) ) if isCableMesh then ( cableMaker.fixMergedCableNormals theMesh:item ) else print "not cable" ) ) --///////////////////////////////////////////////////////////////////////////// -- Set the catenary struct slack based on the spinner val --///////////////////////////////////////////////////////////////////////////// on spnSlack changed val do ( catenaryCurve.slack = val ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on spnRadius changed val do ( cableMaker.radius = val ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on btnRefreshContainers pressed do ( --find availableContainers in the current scene cableMaker.getAvailableContainers() ddlContainers.items = (for c in cableMaker.availableContainers collect c.name) ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on CableGenerationUI open do ( cableMaker.UIHandle = CableGenerationUI cablePresets.getPreset ddlPreset.selected cableMaker.abrv = cablePresets.presetValues[1] as string --sync and load the cable connections file if gRsPerforce.connected() == false then gRsPerforce.connect() gRsPerforce.sync cableMaker.connectionConfig silent:true cableMaker.loadCableConnectorConfig() --find availableContainers in the current scene cableMaker.getAvailableContainers() ddlContainers.items = (for c in cableMaker.availableContainers collect c.name) ) ) --///////////////////////////////////////////////////////////////////////////// -- Legacy cable upgrade rollout --///////////////////////////////////////////////////////////////////////////// rollout LegacyCableUpgradeUI "Cable Upgrade" width:200 height:40 ( button btnUpgradeSelected "Upgrade Selected" width:180 button btnUpgradeCableProxy "Upgrade Scene Cable Proxies" width:180 --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on btnUpgradeSelected pressed do ( max hold success = cableUpgrade.doUpgrade() if success then ( cableMaker.fixMergedCableNormals() ) ) /*** ***/ on btnUpgradeCableProxy pressed do ( local success = cableUpgrade.upgradeSceneCableProxies() if success then ( messagebox "Cable Proxies Upgraded" title:"Done" ) ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// /* on LegacyCableUpgradeUI rolledUp arg do ( if arg then ( CableMakerUI.height = 670 CableMakerUI.cableUpgrade.height = 60 ) else ( CableMakerUI.height = 630 CableMakerUI.cableUpgrade.height = 25 ) ) */ ) --///////////////////////////////////////////////////////////////////////////// -- Diagnostics rollout --///////////////////////////////////////////////////////////////////////////// rollout CableDiagnosticsUI "Diagnostics" ( local greenBitmap = (bitmap 17 17 color:green) local redBitmap = bitmap 17 17 color:red local neutralBitmap = bitmap 17 17 color:((colorMan.getColor #background) * 255.0) local runAllTests = false group "Object Selection:" ( radiobuttons rdoSelection labels:#("Selected", "All") ) button btnAllTests "Run All Tests" tooltip:"Run all the test below" width:(CableDiagnosticsUI.width - 10) button btnShowSingularities "Show Singularities" tooltip:"Show co-located verts" width:(CableDiagnosticsUI.width - 30) align:#left offset:[-8, 0] across:2 bitmap btnShowSingularitiesResult bitmap:neutralBitmap align:#right offset:[9, 0] button btnShowBadNormals "Show Bad Normals" tooltip:"Show bad vertex normals" width:(CableDiagnosticsUI.width - 30) align:#left offset:[-8, 0] across:2 bitmap btnShowBadNormalsResult bitmap:neutralBitmap align:#right offset:[9, 0] button btnShowBadMaterials "Show Bad Materials" tooltip:"Show bad face material assignments" width:(CableDiagnosticsUI.width - 30) align:#left offset:[-8, 0] across:2 bitmap btnShowBadMaterialsResult bitmap:neutralBitmap align:#right offset:[9, 0] button btnBadAttrs "Bad Attributes" tooltip:"Select cables with bad attributes set" width:(CableDiagnosticsUI.width - 30) align:#left offset:[-8, 0] across:2 bitmap btnBadAttrsResult bitmap:neutralBitmap align:#right offset:[9, 0] group "Show Vertex Colours" ( --checkbox chkVisColour "Colour" checked:true across:2 --checkbox chkVisAlpha "Alpha" radiobuttons rdoVisChannel labels:#("Colour", "Alpha") button btnShowVertexColours "Visualise" width:(CableDiagnosticsUI.width - 30) ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// fn getCables inSelection:true = ( local objSel = if inSelection then objSel = selection else objSel = objects local filterSel = #() for obj in objSel where (getAttrClass obj == "Gta Object") and (obj.material != undefined) do ( local excludedClassTypes = for type in #(RsInternalRef, RsRefObject, RsContainerLodRef) \ collect if not (isKindOf obj type) then dontCollect if excludedClassTypes.count == 0 do ( if (isKindOf obj.material Multimaterial) then ( local matIDs = (RsGetMatIdsUsedByObj obj) as Array local cableMaterialAssigned = false for id in matIDs while not cableMaterialAssigned do ( cableMaterialAssigned = ((RstGetShaderName obj.material.materialList[id]) == getCableMaterialShaderName()) ) if cableMaterialAssigned do append filterSel obj ) else if RstGetShaderName obj.material == getCableMaterialShaderName() then ( append filterSel obj ) ) ) --retval filterSel ) --///////////////////////////////////////////////////////////////////////////// -- Reset all test states to neutral --///////////////////////////////////////////////////////////////////////////// fn clearResultSwatches = ( btnShowSingularitiesResult.bitmap = neutralBitmap btnShowBadNormalsResult.bitmap = neutralBitmap btnShowBadMaterialsResult.bitmap = neutralBitmap btnBadAttrsResult.bitmap = neutralBitmap ) --///////////////////////////////////////////////////////////////////////////// -- Execute the desired test on the selection --///////////////////////////////////////////////////////////////////////////// fn executeTest testFn = ( local result = false local cableMeshes = getCables inSelection:(rdoSelection.state == 1) if cableMeshes.count > 1 then ( for item in cableMeshes do ( if (testFn item subObjectDisplay:false) then result = true ) ) else ( if (testFn cableMeshes[1] subObjectDisplay:true) then result = true ) --return result ) --///////////////////////////////////////////////////////////////////////////// -- EVENTS --///////////////////////////////////////////////////////////////////////////// --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on btnShowVertexColours pressed do ( unregisterRedrawViewsCallback cableMakerDiagnostics.visualiseVerts cableMakerDiagnostics.objectSelectionList = getCables() cableMakerDiagnostics.visChannel = case of ( (rdoVisChannel.state == 1):#colour (rdoVisChannel.state == 2):#alpha ) cableMakerDiagnostics.visualiseVerts() registerRedrawViewsCallback cableMakerDiagnostics.visualiseVerts ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on btnAllTests pressed do ( --radSelection runAllTests = true clearResultSwatches() gRsUlog.init "Cable tests" local pass = undefined --SINGULARITIES pass = executeTest cableMakerDiagnostics.findCoLocatedVerts if pass then ( btnShowSingularitiesResult.bitmap = greenBitmap ) else ( btnShowSingularitiesResult.bitmap = redBitmap ) --BAD NORMALS pass = executeTest cableMakerDiagnostics.findBadNormals if pass then ( btnShowBadNormalsResult.bitmap = greenBitmap ) else ( btnShowBadNormalsResult.bitmap = redBitmap ) --BAD MATERIALS pass = executeTest cableMakerDiagnostics.findBadMaterials if pass then ( btnShowBadMaterialsResult.bitmap = greenBitmap ) else ( btnShowBadMaterialsResult.bitmap = redBitmap ) --BAD ATTRS pass = executeTest cableMakerDiagnostics.findBadAttrs if pass then ( btnBadAttrsResult.bitmap = greenBitmap ) else ( btnBadAttrsResult.bitmap = redBitmap ) --finalise log gRsUlog.Validate() runAllTests = false ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on btnShowSingularities pressed do ( gRsUlog.init "Show cable singularities" clearResultSwatches() local pass = executeTest cableMakerDiagnostics.findCoLocatedVerts if pass then ( btnShowSingularitiesResult.bitmap = greenBitmap ) else ( btnShowSingularitiesResult.bitmap = redBitmap ) --finalise log gRsUlog.Validate() ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on btnShowBadNormals pressed do ( gRsUlog.init "Show deviant cable normals" clearResultSwatches() local pass = executeTest cableMakerDiagnostics.findBadNormals if pass then ( btnShowBadNormalsResult.bitmap = greenBitmap ) else ( btnShowBadNormalsResult.bitmap = redBitmap ) --finalise log gRsUlog.Validate() ) --///////////////////////////////////////////////////////////////////////////// -- find any faces that do not have a cable shader applied --///////////////////////////////////////////////////////////////////////////// on btnShowBadMaterials pressed do ( gRsUlog.init "Show bad cable materials" clearResultSwatches() local pass = executeTest cableMakerDiagnostics.findBadMaterials if pass then ( btnShowBadMaterialsResult.bitmap = greenBitmap ) else ( btnShowBadMaterialsResult.bitmap = redBitmap ) --finalise log gRsUlog.Validate() ) --///////////////////////////////////////////////////////////////////////////// -- --///////////////////////////////////////////////////////////////////////////// on btnBadAttrs pressed do ( gRsUlog.init "Show bad cable attributes" clearResultSwatches() local pass = executeTest cableMakerDiagnostics.findBadAttrs if pass then ( btnBadAttrsResult.bitmap = greenBitmap ) else ( btnBadAttrsResult.bitmap = redBitmap ) --finalise log gRsUlog.Validate() ) ) --///////////////////////////////////////////////////////////////////////////// -- Util rollout --///////////////////////////////////////////////////////////////////////////// rollout CableUtilUI "Utils" ( button btnMakePropConnectors "Export Prop Connectors" on btnMakePropConnectors pressed do ( cableMaker.createCableAttachPointsXML() ) ) --////////////////////////////////////////////////////////////////////////////////////////////// -- MAIN --////////////////////////////////////////////////////////////////////////////////////////////// createDialog CableMakerUI width:275 height:580 style:#(#style_titlebar, #style_border, #style_sysmenu, #style_minimizebox) --addSubRollout CableMakerUI.cableUpgrade LegacyCableUpgradeUI