Skip to content

Commit

Permalink
Refactored for new exporter options
Browse files Browse the repository at this point in the history
  • Loading branch information
BrandonPacewic committed Jun 20, 2024
1 parent 2f66cda commit 0cd2e39
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 161 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@
from typing import get_origin
from enum import Enum, EnumType
from dataclasses import dataclass, fields, field
import adsk.core, adsk.fusion
import adsk.core

from adsk.fusion import CalculationAccuracy, TriangleMeshQualityOptions

from ..strings import INTERNAL_ID


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"])
ExportMode = Enum("ExportMode", ["ROBOT", "FIELD"]) # Dynamic / Static export


@dataclass
Expand All @@ -46,89 +48,42 @@ 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
"""
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
"""

NoPhysical = 0
""" No Physical Properties are generated """
Body = 1
NoPhysical = 0

""" Only Body Physical Objects are generated """
SurfaceOccurrence = 2
Body = 1

""" Only Occurrence that contain Bodies and Bodies have Physical Properties """
AllOccurrence = 3
SurfaceOccurrence = 2

""" 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
"""

FusionAssembly = 0
""" Model exactly as it is shown in Fusion 360 in the model view tree """
FusionAssembly = 0

FlatAssembly = 1
""" Flattened Assembly with all bodies as children of the root object """
FlatAssembly = 1

PhysicalAssembly = 2
""" A Model represented with parented objects that are part of a jointed tree """
PhysicalAssembly = 2

SingleMesh = 3
""" Generates the root assembly as a single mesh and stores the associated data """
SingleMesh = 3


class ParseOptions:
"""Options to supply to the parser object that will generate the output file"""

def __init__(
self,
fileLocation: str,
name: str,
version: str,
hierarchy=ModelHierarchy.FusionAssembly,
visual=adsk.fusion.TriangleMeshQualityOptions.LowQualityTriangleMesh,
physical=adsk.fusion.CalculationAccuracy.LowCalculationAccuracy,
physicalDepth=PhysicalDepth.AllOccurrence,
materials=1,
mode=ExportMode.ROBOT,
wheels=list[Wheel],
joints=list[Joint], # [{Occurrence, wheeltype} , {entitytoken, wheeltype}]
gamepieces=list[Gamepiece],
weight=float,
compress=bool,
):
"""Generates the Parser Options for the given export
Args:
- fileLocation (str): Location of file with file name (given during file explore action)
- name (str): name of the assembly
- version (str): root assembly version
- hierarchy (ModelHierarchy.FusionAssembly, optional): The exported model hierarchy. Defaults to ModelHierarchy.FusionAssembly
- visual (adsk.fusion.TriangleMeshQualityOptions, optional): Triangle Mesh Export Quality. Defaults to adsk.fusion.TriangleMeshQualityOptions.HighQualityTriangleMesh.
- physical (adsk.fusion.CalculationAccuracy, optional): Calculation Level of the physical properties. Defaults to adsk.fusion.CalculationAccuracy.MediumCalculationAccuracy.
- physicalDepth (PhysicalDepth, optional): Enum to define the level of physical attributes exported. Defaults to PhysicalDepth.AllOccurrence.
- materials (int, optional): Export Materials type: defaults to STANDARD 1
- joints (bool, optional): Export Joints. Defaults to True.
- wheels (list (strings)): List of Occurrence.entityTokens that
"""
self.fileLocation = fileLocation
self.name = name
self.version = version
self.hierarchy = hierarchy
self.visual = visual
self.physical = physical
self.physicalDepth = physicalDepth
self.materials = materials
self.mode = mode
self.wheels = wheels
self.joints = joints
self.gamepieces = gamepieces
self.weight = weight # full weight of robot in KG
self.compress = compress


# TODO: This should be the only parse option class - Brandon
@dataclass
class ExporterOptions:
# TODO: Clean up all these `field(default=None)` things. This could potentially be better - Brandon
Expand All @@ -145,26 +100,48 @@ class ExporterOptions:
exportAsPart: bool = field(default=False)

hierarchy: ModelHierarchy = field(default=ModelHierarchy.FusionAssembly)
visual: adsk.fusion.TriangleMeshQualityOptions = field(
default=adsk.fusion.TriangleMeshQualityOptions.LowQualityTriangleMesh
)
visualQuality: TriangleMeshQualityOptions = field(default=TriangleMeshQualityOptions.LowQualityTriangleMesh)
physicalDepth: PhysicalDepth = field(default=PhysicalDepth.AllOccurrence)
physicalCalculationLevel: CalculationAccuracy = field(default=CalculationAccuracy.LowCalculationAccuracy)

def read(self):
def read(self) -> None:
designAttributes = adsk.core.Application.get().activeProduct.attributes
for field in fields(self):
attribute = designAttributes.itemByName(INTERNAL_ID, field.name)
if attribute:
setattr(
self,
field.name,
self.makeObjectFromJson(field.type, json.loads(attribute.value)),
self._makeObjectFromJson(field.type, json.loads(attribute.value)),
)

return self

def write(self) -> None:
designAttributes = adsk.core.Application.get().activeProduct.attributes
for field in fields(self):
data = json.dumps(
getattr(self, field.name),
default=lambda obj: (
obj.value
if isinstance(obj, Enum)
else {
key: (
lambda value: value
if not isinstance(value, Enum)
else value.value
)(value)
for key, value in obj.__dict__.items()
}
if hasattr(obj, "__dict__")
else obj
),
indent=4,
)
designAttributes.add(INTERNAL_ID, field.name, data)

# TODO: There should be a way to clean this up - Brandon
def makeObjectFromJson(self, objectType, data):
def _makeObjectFromJson(self, objectType: type, data: any) -> any:
primitives = (bool, str, int, float, type(None))
if (
objectType in primitives or type(data) in primitives
Expand All @@ -174,7 +151,7 @@ def makeObjectFromJson(self, objectType, data):
return objectType(data)
elif get_origin(objectType) is list:
return [
self.makeObjectFromJson(objectType.__args__[0], item)
self._makeObjectFromJson(objectType.__args__[0], item)
for item in data
]

Expand All @@ -191,36 +168,13 @@ def makeObjectFromJson(self, objectType, data):
newObject,
attr,
[
self.makeObjectFromJson(currType.__args__[0], item)
self._makeObjectFromJson(currType.__args__[0], item)
for item in data[attr]
],
)
elif currType in primitives:
setattr(newObject, attr, data[attr])
elif isinstance(currType, object):
setattr(newObject, attr, self.makeObjectFromJson(currType, data[attr]))
setattr(newObject, attr, self._makeObjectFromJson(currType, data[attr]))

return newObject

def write(self):
designAttributes = adsk.core.Application.get().activeProduct.attributes
for field in fields(self):
data = json.dumps(
getattr(self, field.name),
default=lambda obj: (
obj.value
if isinstance(obj, Enum)
else {
key: (
lambda value: value
if not isinstance(value, Enum)
else value.value
)(value)
for key, value in obj.__dict__.items()
}
if hasattr(obj, "__dict__")
else obj
),
indent=4,
)
designAttributes.add(INTERNAL_ID, field.name, data)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from proto.proto_out import assembly_pb2, types_pb2, material_pb2, joint_pb2

from .Utilities import *
from .. import ParseOptions
from .. import ExporterOptions
from typing import *

from . import PhysicalProperties
Expand All @@ -17,7 +17,7 @@
@TimeThis
def _MapAllComponents(
design: adsk.fusion.Design,
options: ParseOptions,
options: ExporterOptions,
progressDialog: PDMessage,
partsData: assembly_pb2.Parts,
materials: material_pb2.Materials,
Expand All @@ -40,7 +40,7 @@ def _MapAllComponents(
component, partDefinition.physical_data
)

if options.mode == 3:
if options.exportMode == ExporterOptions.ExportMode.FIELD:
partDefinition.dynamic = False
else:
partDefinition.dynamic = True
Expand Down Expand Up @@ -78,7 +78,7 @@ def processBody(body: adsk.fusion.BRepBody | adsk.fusion.MeshBody):
def _ParseComponentRoot(
component: adsk.fusion.Component,
progressDialog: PDMessage,
options: ParseOptions,
options: ExporterOptions,
partsData: assembly_pb2.Parts,
material_map: dict,
node: types_pb2.Node,
Expand Down Expand Up @@ -111,7 +111,7 @@ def _ParseComponentRoot(
def __parseChildOccurrence(
occurrence: adsk.fusion.Occurrence,
progressDialog: PDMessage,
options: ParseOptions,
options: ExporterOptions,
partsData: assembly_pb2.Parts,
material_map: dict,
node: types_pb2.Node,
Expand Down Expand Up @@ -153,7 +153,7 @@ def __parseChildOccurrence(
part.part_definition_reference = compRef

# TODO: Maybe make this a separate step where you dont go backwards and search for the gamepieces
if options.mode == ParseOptions.ExportMode.FIELD:
if options.exportMode == ExporterOptions.ExportMode.FIELD:
for x in options.gamepieces:
if x.occurrenceToken == mapConstant:
partsData.part_definitions[
Expand Down Expand Up @@ -193,13 +193,13 @@ def GetMatrixWorld(occurrence):

def _ParseBRep(
body: adsk.fusion.BRepBody,
options: ParseOptions,
options: ExporterOptions,
trimesh: assembly_pb2.TriangleMesh,
) -> any:
try:
meshManager = body.meshManager
calc = meshManager.createMeshCalculator()
calc.setQuality(options.visual)
calc.setQuality(options.visualQuality)
mesh = calc.calculate()

fill_info(trimesh, body)
Expand All @@ -219,7 +219,7 @@ def _ParseBRep(

def _ParseMesh(
meshBody: adsk.fusion.MeshBody,
options: ParseOptions,
options: ExporterOptions,
trimesh: assembly_pb2.TriangleMesh,
) -> any:
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from ...general_imports import *
import adsk.core, adsk.fusion, traceback, logging, enum
from typing import *
from .. import ParseOptions
from ..ExporterOptions import ExporterOptions
from .PDMessage import PDMessage
from proto.proto_out import types_pb2, joint_pb2

Expand Down Expand Up @@ -461,7 +461,7 @@ def searchForGrounded(
def BuildJointPartHierarchy(
design: adsk.fusion.Design,
joints: joint_pb2.Joints,
options: ParseOptions,
options: ExporterOptions,
progressDialog: PDMessage,
):
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from ...general_imports import *
from .Utilities import fill_info, construct_info, guid_occurrence
from .PDMessage import PDMessage
from ..ParseOptions import ParseOptions, JointParentType
from ..ExporterOptions import ExporterOptions, JointParentType


# Need to take in a graphcontainer
Expand Down Expand Up @@ -59,7 +59,7 @@ def populateJoints(
joints: joint_pb2.Joints,
signals: signal_pb2.Signals,
progressDialog: PDMessage,
options: ParseOptions,
options: ExporterOptions,
assembly: assembly_pb2.Assembly,
):
fill_info(joints, None)
Expand Down Expand Up @@ -116,12 +116,12 @@ def populateJoints(

# really could just map the enum to a friggin string
if (
parse_joints.signalType != ParseOptions.SignalType.PASSIVE
parse_joints.signalType != ExporterOptions.SignalType.PASSIVE
and assembly.dynamic
):
if parse_joints.signalType == ParseOptions.SignalType.CAN:
if parse_joints.signalType == ExporterOptions.SignalType.CAN:
signal.device_type = signal_pb2.DeviceType.CANBUS
elif parse_joints.signalType == ParseOptions.SignalType.PWM:
elif parse_joints.signalType == ExporterOptions.SignalType.PWM:
signal.device_type = signal_pb2.DeviceType.PWM

motor = joints.motor_definitions[joint.entityToken]
Expand Down Expand Up @@ -177,7 +177,7 @@ def _addJointInstance(
joint_instance: joint_pb2.JointInstance,
joint_definition: joint_pb2.Joint,
signals: signal_pb2.Signals,
options: ParseOptions,
options: ExporterOptions,
):
fill_info(joint_instance, joint)
# because there is only one and we are using the token - should be the same
Expand Down Expand Up @@ -222,10 +222,10 @@ def _addJointInstance(
signal.io = signal_pb2.IOType.OUTPUT
joint_instance.signal_reference = signal.info.GUID

if wheel.signalType != ParseOptions.SignalType.PASSIVE:
if wheel.signalType == ParseOptions.SignalType.CAN:
if wheel.signalType != ExporterOptions.SignalType.PASSIVE:
if wheel.signalType == ExporterOptions.SignalType.CAN:
signal.device_type = signal_pb2.DeviceType.CANBUS
elif wheel.signalType == ParseOptions.SignalType.PWM:
elif wheel.signalType == ExporterOptions.SignalType.PWM:
signal.device_type = signal_pb2.DeviceType.PWM
else:
joint_instance.signal_reference = ""
Expand Down
Loading

0 comments on commit 0cd2e39

Please sign in to comment.