From 93baef650700062fc4459704abaa52f42f74438a Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Thu, 20 Jun 2024 13:23:45 -0700 Subject: [PATCH] Working wheel and weight design save --- .../src/Parser/ExporterOptions.py | 63 ++++++++-------- .../src/Parser/SynthesisParser/Joints.py | 13 +++- .../src/Parser/SynthesisParser/Parser.py | 5 +- .../src/UI/ConfigCommand.py | 72 ++++++++++--------- 4 files changed, 81 insertions(+), 72 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py index 68313c38ca..fe26fb48f5 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py +++ b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py @@ -1,10 +1,6 @@ -""" ParserOptions - - - This module targets the creation of the parser used for actually parsing the data - - Since the parsing can be recursive you can pass a low overhead options construction into each function to detail the parsing - - Should have factory methods to convert from a given configuration possibly - - or maybe a configuration should replace this im not certain - - this is essentially a flat configuration file with non serializable objects +""" +Container for all options pertaining to the Fusion Exporter. +These options are saved per-design and are passed to the parser upon design export. """ import json @@ -17,11 +13,13 @@ from ..strings import INTERNAL_ID +# Not 100% sure what this is for - Brandon +JointParentType = Enum("JointParentType", ["ROOT", "END"]) -JointParentType = Enum("JointParentType", ["ROOT", "END"]) # Not 100% sure what this is for - Brandon WheelType = Enum("WheelType", ["STANDARD", "OMNI"]) SignalType = Enum("SignalType", ["PWM", "CAN", "PASSIVE"]) -ExportMode = Enum("ExportMode", ["ROBOT", "FIELD"]) # Dynamic / Static export +ExportMode = Enum("ExportMode", ["ROBOT", "FIELD"]) # Dynamic / Static export +PreferredUnits = Enum("PreferredUnits", ["METRIC", "IMPERIAL"]) @dataclass @@ -38,7 +36,7 @@ class Joint: signalType: SignalType = field(default=None) speed: float = field(default=None) force: float = field(default=None) - + @dataclass class Gamepiece: @@ -48,39 +46,30 @@ class Gamepiece: class PhysicalDepth(Enum): - """ - Depth at which the Physical Properties are generated and saved - This is mostly dictated by export type as flattening or any hierarchical modification takes precedence - """ - - """ No Physical Properties are generated """ + # No Physical Properties are generated NoPhysical = 0 - """ Only Body Physical Objects are generated """ + # Only Body Physical Objects are generated Body = 1 - """ Only Occurrence that contain Bodies and Bodies have Physical Properties """ + # Only Occurrence that contain Bodies and Bodies have Physical Properties SurfaceOccurrence = 2 - """ Every Single Occurrence has Physical Properties even if empty """ + # Every Single Occurrence has Physical Properties even if empty AllOccurrence = 3 class ModelHierarchy(Enum): - """ - Enum Class to describe how the model format should look on export to suit different needs - """ - - """ Model exactly as it is shown in Fusion 360 in the model view tree """ + # Model exactly as it is shown in Fusion 360 in the model view tree FusionAssembly = 0 - """ Flattened Assembly with all bodies as children of the root object """ + # Flattened Assembly with all bodies as children of the root object FlatAssembly = 1 - """ A Model represented with parented objects that are part of a jointed tree """ + # A Model represented with parented objects that are part of a jointed tree PhysicalAssembly = 2 - """ Generates the root assembly as a single mesh and stores the associated data """ + # Generates the root assembly as a single mesh and stores the associated data SingleMesh = 3 @@ -95,14 +84,19 @@ class ExporterOptions: wheels: list[Wheel] = field(default=None) joints: list[Joint] = field(default=None) gamepieces: list[Gamepiece] = field(default=None) - robotWeight: float = field(default=0.0) # kg + preferredUnits: PreferredUnits = field(default=PreferredUnits.IMPERIAL) + robotWeight: float = field(default=0.0) # Always stored in kg regardless of 'preferredUnits' compressOutput: bool = field(default=True) exportAsPart: bool = field(default=False) hierarchy: ModelHierarchy = field(default=ModelHierarchy.FusionAssembly) - visualQuality: TriangleMeshQualityOptions = field(default=TriangleMeshQualityOptions.LowQualityTriangleMesh) + visualQuality: TriangleMeshQualityOptions = field( + default=TriangleMeshQualityOptions.LowQualityTriangleMesh + ) physicalDepth: PhysicalDepth = field(default=PhysicalDepth.AllOccurrence) - physicalCalculationLevel: CalculationAccuracy = field(default=CalculationAccuracy.LowCalculationAccuracy) + physicalCalculationLevel: CalculationAccuracy = field( + default=CalculationAccuracy.LowCalculationAccuracy + ) def read(self) -> None: designAttributes = adsk.core.Application.get().activeProduct.attributes @@ -143,16 +137,15 @@ def write(self) -> None: # TODO: There should be a way to clean this up - Brandon def _makeObjectFromJson(self, objectType: type, data: any) -> any: primitives = (bool, str, int, float, type(None)) - if ( + if isinstance(objectType, EnumType): + return objectType(data) + elif ( objectType in primitives or type(data) in primitives ): # Required to catch `fusion.TriangleMeshQualityOptions` return data - elif isinstance(objectType, EnumType): - return objectType(data) elif get_origin(objectType) is list: return [ - self._makeObjectFromJson(objectType.__args__[0], item) - for item in data + self._makeObjectFromJson(objectType.__args__[0], item) for item in data ] newObject = objectType() diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py index ec9359dd6f..b443c7d3a6 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py @@ -116,12 +116,19 @@ def populateJoints( # really could just map the enum to a friggin string if ( - parse_joints.signalType != ExporterOptions.SignalType.PASSIVE + parse_joints.signalType + != ExporterOptions.SignalType.PASSIVE and assembly.dynamic ): - if parse_joints.signalType == ExporterOptions.SignalType.CAN: + if ( + parse_joints.signalType + == ExporterOptions.SignalType.CAN + ): signal.device_type = signal_pb2.DeviceType.CANBUS - elif parse_joints.signalType == ExporterOptions.SignalType.PWM: + elif ( + parse_joints.signalType + == ExporterOptions.SignalType.PWM + ): signal.device_type = signal_pb2.DeviceType.PWM motor = joints.motor_definitions[joint.entityToken] diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py index f010cb950d..7fddd89b54 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py @@ -14,6 +14,7 @@ from .Utilities import * from .. import ExporterOptions + class Parser: def __init__(self, options: ExporterOptions): """Creates a new parser with the supplied options @@ -37,7 +38,9 @@ def export(self) -> bool: ) # set int to 0 in dropdown selection for dynamic - assembly_out.dynamic = self.exporterOptions.exportMode == ExporterOptions.ExportMode.ROBOT + assembly_out.dynamic = ( + self.exporterOptions.exportMode == ExporterOptions.ExportMode.ROBOT + ) # Physical Props here when ready diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index 21da004932..db39c8de71 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -17,6 +17,7 @@ Joint, Wheel, JointParentType, + PreferredUnits, ) from .Configuration.SerialCommand import SerialCommand @@ -162,6 +163,7 @@ def __init__(self, configure): def notify(self, args): try: exporterOptions = ExporterOptions().read() + # exporterOptions = ExporterOptions() if not Helper.check_solid_open(): return @@ -172,14 +174,6 @@ def notify(self, args): NOTIFIED = True write_configuration("analytics", "notified", "yes") - # Transition: AARD-1687 - # designCompress = self.designAttrs.itemByName("SynthesisExporter", "compress") - # global compress - # if designCompress: - # compress = True if designCompress.value == "True" else False - # else: - # compress = True - if A_EP: A_EP.send_view("export_panel") @@ -226,9 +220,7 @@ def notify(self, args): dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle, ) - # TODO - # dynamic = exporterOptions.exportMode == ExporterOptions.ExportMode.ROBOT - dynamic = True + dynamic = exporterOptions.exportMode == ExportMode.ROBOT dropdownExportMode.listItems.add("Dynamic", dynamic) dropdownExportMode.listItems.add("Static", not dynamic) @@ -271,11 +263,18 @@ def notify(self, args): auto_calc_weight.resourceFolder = IconPaths.stringIcons["calculate-enabled"] auto_calc_weight.isFullWidth = True + imperialUnits = exporterOptions.preferredUnits == PreferredUnits.IMPERIAL + if imperialUnits: + # ExporterOptions always contains the metric value + displayWeight = exporterOptions.robotWeight * 2.2046226218 + else: + displayWeight = exporterOptions.robotWeight + weight_input = inputs.addValueInput( "weight_input", "Weight Input", "", - adsk.core.ValueInput.createByString("0.0"), # TODO + adsk.core.ValueInput.createByReal(displayWeight), ) weight_input.tooltip = "Robot weight" weight_input.tooltipDescription = """(in pounds)
This is the weight of the entire robot assembly.""" @@ -285,11 +284,12 @@ def notify(self, args): "Weight Unit", adsk.core.DropDownStyles.LabeledIconDropDownStyle, ) + weight_unit.listItems.add( - "‎", True, IconPaths.massIcons["LBS"] + "‎", imperialUnits, IconPaths.massIcons["LBS"] ) # add listdropdown mass options weight_unit.listItems.add( - "‎", False, IconPaths.massIcons["KG"] + "‎", not imperialUnits, IconPaths.massIcons["KG"] ) # add listdropdown mass options weight_unit.tooltip = "Unit of mass" weight_unit.tooltipDescription = ( @@ -401,6 +401,11 @@ def notify(self, args): 3, ) + for wheel in exporterOptions.wheels: + wheelEntity = gm.app.activeDocument.design.findEntityByToken(wheel.jointToken)[0] + typeWheel = type(wheelEntity) + addWheelToTable(wheelEntity) + # ~~~~~~~~~~~~~~~~ JOINT CONFIGURATION ~~~~~~~~~~~~~~~~ """ Joint configuration group. Container for joint selection table @@ -530,6 +535,7 @@ def notify(self, args): 5, ) + # Fill the table with all joints in current design for joint in list( gm.app.activeDocument.design.rootComponent.allJoints ) + list(gm.app.activeDocument.design.rootComponent.allAsBuiltJoints): @@ -710,7 +716,7 @@ def notify(self, args): "compress", "Compress Output", exporter_settings, - checked=compress, + checked=exporterOptions.compressOutput, tooltip="Compress the output file for a smaller file size.", tooltipadvanced="
Use the GZIP compression system to compress the resulting file which will be opened in the simulator, perfect if you want to share the file.
", enabled=True, @@ -720,7 +726,7 @@ def notify(self, args): "export_as_part", "Export As Part", exporter_settings, - checked=False, + checked=exporterOptions.exportAsPart, tooltip="Use to export as a part for Mix And Match", enabled=True, ) @@ -1078,10 +1084,6 @@ def notify(self, args): .children.itemById("exporter_settings") .children.itemById("export_as_part") ).value - # parserOptions.exportAsPart = export_as_part_boolean - - # Transition: AARD-1687 - # self.designAttrs.add("SynthesisExporter", "export_as_part", str(export_as_part_boolean.value)) processedFileName = gm.app.activeDocument.name.replace(" ", "_") dropdownExportMode = INPUTS_ROOT.itemById("mode") @@ -1090,9 +1092,6 @@ def notify(self, args): elif dropdownExportMode.selectedItem.index == 1: isRobot = False - # Transition: AARD-1687 - # self.designAttrs.add("SynthesisExporter", "mode", str(isRobot)) - if platform.system() == "Windows": if isRobot: if export_as_part_boolean: @@ -1183,8 +1182,8 @@ def notify(self, args): _exportWheels.append( Wheel( WheelListGlobal[row - 1].entityToken, - wheelTypeIndex, - signalTypeIndex, + wheelTypeIndex + 1, # TODO: More explicit conversion to 'enum' - Brandon + signalTypeIndex + 1, # TODO: More explicit conversion to 'enum' - Brandon # onSelect.wheelJointList[row-1][0] # GUID of wheel joint. if no joint found, default to None ) ) @@ -1220,7 +1219,7 @@ def notify(self, args): Joint( JointListGlobal[row - 1].entityToken, JointParentType.ROOT, - signalTypeIndex, # index of selected signal in dropdown + signalTypeIndex + 1, # TODO: More explicit conversion to 'Enum' - Brandon jointSpeed, jointForce / 100.0, ) # parent joint GUID @@ -1244,7 +1243,7 @@ def notify(self, args): Joint( JointListGlobal[row - 1].entityToken, parentJointToken, - signalTypeIndex, + signalTypeIndex + 1, # TODO: More explicit conversion to 'Enum' - Brandon jointSpeed, jointForce, ) @@ -1285,8 +1284,10 @@ def notify(self, args): weight_unit = INPUTS_ROOT.itemById("weight_unit") if weight_unit.selectedItem.index == 0: + selectedUnits = PreferredUnits.IMPERIAL _robotWeight = float(weight_input.value) / 2.2046226218 else: + selectedUnits = PreferredUnits.METRIC _robotWeight = float(weight_input.value) """ @@ -1305,9 +1306,6 @@ def notify(self, args): .children.itemById("compress") ).value - # Transition: AARD-1687 - # self.designAttrs.add("SynthesisExporter", "compress", str(compress)) - exporterOptions = ExporterOptions( savepath, name, @@ -1316,13 +1314,14 @@ def notify(self, args): joints=_exportJoints, wheels=_exportWheels, gamepieces=_exportGamepieces, + preferredUnits=selectedUnits, robotWeight=_robotWeight, exportMode=_mode, compressOutput=compress, ) - exporterOptions.write() Parser(exporterOptions).export() + exporterOptions.write() except: if gm.ui: gm.ui.messageBox("Failed:\n{}".format(traceback.format_exc())) @@ -2347,7 +2346,15 @@ def addWheelToTable(wheel: adsk.fusion.Joint) -> None: wheel (adsk.fusion.Occurrence): wheel Occurrence object to be added. """ try: - onSelect = gm.handlers[3] + try: + onSelect = gm.handlers[3] + onSelect.allWheelPreselections.append(wheel.entityToken) + except IndexError: + # Not 100% sure what we need the select handler here for however it should not run when + # first populating the saved wheel configs. This will naturally throw a IndexError as + # we do this before the initialization of gm.handlers[] + pass + wheelTableInput = wheelTable() # def addPreselections(child_occurrences): # for occ in child_occurrences: @@ -2359,7 +2366,6 @@ def addWheelToTable(wheel: adsk.fusion.Joint) -> None: # if wheel.childOccurrences: # addPreselections(wheel.childOccurrences) # else: - onSelect.allWheelPreselections.append(wheel.entityToken) WheelListGlobal.append(wheel) cmdInputs = adsk.core.CommandInputs.cast(wheelTableInput.commandInputs)