Skip to content

Refactor Parser Error Handling #1172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 12 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions exporter/SynthesisFusionAddin/Synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

from src.Dependencies import resolveDependencies
from src.Logging import logFailure, setupLogger

logger = setupLogger()
from src.ErrorHandling import Err, ErrorSeverity

try:
# Attempt to import required pip dependencies to verify their installation.
Expand All @@ -32,17 +32,9 @@


from src import APP_NAME, DESCRIPTION, INTERNAL_ID, gm
from src.UI import (
HUI,
Camera,
ConfigCommand,
MarkingMenu,
ShowAPSAuthCommand,
ShowWebsiteCommand,
)
from src.UI import (HUI, Camera, ConfigCommand, MarkingMenu, ShowAPSAuthCommand, ShowWebsiteCommand)
from src.UI.Toolbar import Toolbar


@logFailure
def run(_context: dict[str, Any]) -> None:
"""## Entry point to application from Fusion.
Expand All @@ -51,6 +43,7 @@ def run(_context: dict[str, Any]) -> None:
**context** *context* -- Fusion context to derive app and UI.
"""


# Remove all items prior to start just to make sure
unregister_all()

Expand All @@ -70,6 +63,7 @@ def stop(_context: dict[str, Any]) -> None:
Arguments:
**context** *context* -- Fusion Data.
"""

unregister_all()

app = adsk.core.Application.get()
Expand Down
Empty file.
55 changes: 55 additions & 0 deletions exporter/SynthesisFusionAddin/src/ErrorHandling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from .Logging import getLogger
from enum import Enum
from typing import Generic, TypeVar

logger = getLogger()

# NOTE
# Severity refers to to the error's affect on the parser as a whole, rather than on the function itself
# If an error is non-fatal to the function that generated it, it should be declared but not return, which prints it to the screen
class ErrorSeverity(Enum):
Fatal = 50 # Critical Error
Warning = 30 # Warning

T = TypeVar('T')

class Result(Generic[T]):
def is_ok(self) -> bool:
return isinstance(self, Ok)

def is_err(self) -> bool:
return isinstance(self, Err)

def unwrap(self) -> T:
if self.is_ok():
return self.value # type: ignore
raise Exception(f"Called unwrap on Err: {self.message}") # type: ignore

def unwrap_err(self) -> tuple[str, ErrorSeverity]:
if self.is_err():
return (self.message, self.severity) # type: ignore
raise Exception(f"Called unwrap_err on Ok: {self.value}") # type: ignore

class Ok(Result[T]):
value: T
def __init__(self, value: T):
self.value = value

def __repr__(self):
return f"Ok({self.value})"

class Err(Result[T]):
message: str
severity: ErrorSeverity
def __init__(self, message: str, severity: ErrorSeverity):
self.message = message
self.severity = severity

self.write_error()

def __repr__(self):
return f"Err({self.message})"

def write_error(self) -> None:
# Figure out how to integrate severity with the logger
logger.log(self.severity.value, self.message)
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Contains all of the logic for mapping the Components / Occurrences
from requests.models import parse_header_links
import adsk.core
import adsk.fusion

from src.ErrorHandling import Err, ErrorSeverity, Ok, Result
from src.Logging import logFailure
from src.Parser.ExporterOptions import ExporterOptions
from src.Parser.SynthesisParser import PhysicalProperties
Expand All @@ -15,15 +17,13 @@
from src.Types import ExportMode

# TODO: Impelement Material overrides


def _MapAllComponents(
def MapAllComponents(
design: adsk.fusion.Design,
options: ExporterOptions,
progressDialog: PDMessage,
partsData: assembly_pb2.Parts,
materials: material_pb2.Materials,
) -> None:
) -> Result[None]:
for component in design.allComponents:
adsk.doEvents()
if progressDialog.wasCancelled():
Expand All @@ -32,31 +32,42 @@ def _MapAllComponents(

comp_ref = guid_component(component)

fill_info(partsData, None)
fill_info_result = fill_info(partsData, None)
if fill_info_result.is_err():
return fill_info_result


partDefinition = partsData.part_definitions[comp_ref]

fill_info(partDefinition, component, comp_ref)
fill_info_result = fill_info(partDefinition, component, comp_ref)
if fill_info_result.is_err():
return fill_info_result


PhysicalProperties.GetPhysicalProperties(component, partDefinition.physical_data)

if options.exportMode == ExportMode.FIELD:
partDefinition.dynamic = False
else:
partDefinition.dynamic = True
partDefinition.dynamic = options.exportMode != ExportMode.FIELD

def processBody(body: adsk.fusion.BRepBody | adsk.fusion.MeshBody) -> None:
def processBody(body: adsk.fusion.BRepBody | adsk.fusion.MeshBody) -> Result[None]:
if progressDialog.wasCancelled():
raise RuntimeError("User canceled export")
if body.isLightBulbOn:
part_body = partDefinition.bodies.add()
fill_info(part_body, body)
part_body.part = comp_ref

fill_info_result = fill_info(part_body, body)
if fill_info_result.is_err():
return fill_info_result

if isinstance(body, adsk.fusion.BRepBody):
_ParseBRep(body, options, part_body.triangle_mesh)
parse_result = ParseBRep(body, options, part_body.triangle_mesh)
if parse_result.is_err() and parse_result.unwrap_err()[0] == ErrorSeverity.Fatal:
return parse_result
else:
_ParseMesh(body, options, part_body.triangle_mesh)
parse_result = ParseMesh(body, options, part_body.triangle_mesh)
if parse_result.is_err() and parse_result.unwrap_err()[0] == ErrorSeverity.Fatal:
return parse_result


appearance_key = "{}_{}".format(body.appearance.name, body.appearance.id)
# this should be appearance
Expand All @@ -66,27 +77,33 @@ def processBody(body: adsk.fusion.BRepBody | adsk.fusion.MeshBody) -> None:
part_body.appearance_override = "default"

for body in component.bRepBodies:
processBody(body)

process_result = processBody(body)
if process_result.is_err() and process_result.unwrap_err()[0] == ErrorSeverity.Fatal:
return process_result
for body in component.meshBodies:
processBody(body)
process_result = processBody(body)
if process_result.is_err() and process_result.unwrap_err()[0] == ErrorSeverity.Fatal:
return process_result



def _ParseComponentRoot(
def ParseComponentRoot(
component: adsk.fusion.Component,
progressDialog: PDMessage,
options: ExporterOptions,
partsData: assembly_pb2.Parts,
material_map: dict[str, material_pb2.Appearance],
node: types_pb2.Node,
) -> None:
) -> Result[None]:
mapConstant = guid_component(component)

part = partsData.part_instances[mapConstant]

node.value = mapConstant

fill_info(part, component, mapConstant)
fill_info_result = fill_info(part, component, mapConstant)
if fill_info_result.is_err() and fill_info_result.unwrap_err()[1] == ErrorSeverity.Fatal:
return fill_info_result

def_map = partsData.part_definitions

Expand All @@ -99,20 +116,25 @@ def _ParseComponentRoot(

if occur.isLightBulbOn:
child_node = types_pb2.Node()
__parseChildOccurrence(occur, progressDialog, options, partsData, material_map, child_node)

parse_child_result = parseChildOccurrence(occur, progressDialog, options, partsData, material_map, child_node)
if parse_child_result.is_err():
return parse_child_result

node.children.append(child_node)
return Ok(None)


def __parseChildOccurrence(
def parseChildOccurrence(
occurrence: adsk.fusion.Occurrence,
progressDialog: PDMessage,
options: ExporterOptions,
partsData: assembly_pb2.Parts,
material_map: dict[str, material_pb2.Appearance],
node: types_pb2.Node,
) -> None:
) -> Result[None]:
if occurrence.isLightBulbOn is False:
return
return Ok(None)

progressDialog.addOccurrence(occurrence.name)

Expand All @@ -124,7 +146,9 @@ def __parseChildOccurrence(

node.value = mapConstant

fill_info(part, occurrence, mapConstant)
fill_info_result = fill_info(part, occurrence, mapConstant)
if fill_info_result.is_err() and fill_info_result.unwrap_err() == ErrorSeverity.Fatal:
return fill_info_result

collision_attr = occurrence.attributes.itemByName("synthesis", "collision_off")
if collision_attr != None:
Expand All @@ -134,11 +158,15 @@ def __parseChildOccurrence(
try:
part.appearance = "{}_{}".format(occurrence.appearance.name, occurrence.appearance.id)
except:
_ = Err("Failed to format part appearance", ErrorSeverity.Warning); # ignore: type
part.appearance = "default"
# TODO: Add phyical_material parser

# TODO: I'm fairly sure that this should be a fatal error
if occurrence.component.material:
part.physical_material = occurrence.component.material.id
else:
_ = Err(f"Component Material is None", ErrorSeverity.Warning)

def_map = partsData.part_definitions

Expand All @@ -165,8 +193,13 @@ def __parseChildOccurrence(

if occur.isLightBulbOn:
child_node = types_pb2.Node()
__parseChildOccurrence(occur, progressDialog, options, partsData, material_map, child_node)

parse_child_result = parseChildOccurrence(occur, progressDialog, options, partsData, material_map, child_node)
if parse_child_result.is_err():
return parse_child_result

node.children.append(child_node)
return Ok(None)


# saw online someone used this to get the correct context but oh boy does it look pricey
Expand All @@ -180,12 +213,11 @@ def GetMatrixWorld(occurrence: adsk.fusion.Occurrence) -> adsk.core.Matrix3D:
return matrix


@logFailure
def _ParseBRep(
def ParseBRep(
body: adsk.fusion.BRepBody,
options: ExporterOptions,
trimesh: assembly_pb2.TriangleMesh,
) -> None:
) -> Result[None]:
meshManager = body.meshManager
calc = meshManager.createMeshCalculator()
# Disabling for now. We need the user to be able to adjust this, otherwise it gets locked
Expand All @@ -196,26 +228,36 @@ def _ParseBRep(
# calc.surfaceTolerance = 0.5
mesh = calc.calculate()

fill_info(trimesh, body)
fill_info_result = fill_info(trimesh, body)
if fill_info_result.is_err() and fill_info_result.unwrap_err()[1] == ErrorSeverity.Fatal:
return fill_info_result

trimesh.has_volume = True

plainmesh_out = trimesh.mesh

plainmesh_out.verts.extend(mesh.nodeCoordinatesAsFloat)
plainmesh_out.normals.extend(mesh.normalVectorsAsFloat)
plainmesh_out.indices.extend(mesh.nodeIndices)
plainmesh_out.uv.extend(mesh.textureCoordinatesAsFloat)

return Ok(None)


@logFailure
def _ParseMesh(
def ParseMesh(
meshBody: adsk.fusion.MeshBody,
options: ExporterOptions,
trimesh: assembly_pb2.TriangleMesh,
) -> None:
) -> Result[None]:
mesh = meshBody.displayMesh
if mesh is None:
return Err("Component Mesh was None", ErrorSeverity.Fatal)


fill_info_result = fill_info(trimesh, meshBody)
if fill_info_result.is_err() and fill_info_result.unwrap_err()[1] == ErrorSeverity.Fatal:
return fill_info_result


fill_info(trimesh, meshBody)
trimesh.has_volume = True

plainmesh_out = trimesh.mesh
Expand All @@ -225,8 +267,10 @@ def _ParseMesh(
plainmesh_out.indices.extend(mesh.nodeIndices)
plainmesh_out.uv.extend(mesh.textureCoordinatesAsFloat)

return Ok(None)


def _MapRigidGroups(rootComponent: adsk.fusion.Component, joints: joint_pb2.Joints) -> None:
def MapRigidGroups(rootComponent: adsk.fusion.Component, joints: joint_pb2.Joints) -> None:
groups = rootComponent.allRigidGroups
for group in groups:
mira_group = joint_pb2.RigidGroup()
Expand Down
Loading
Loading