diff --git a/.github/workflows/FusionTyping.yml b/.github/workflows/FusionTyping.yml new file mode 100644 index 0000000000..13de49fc2f --- /dev/null +++ b/.github/workflows/FusionTyping.yml @@ -0,0 +1,23 @@ +name: Fusion - mypy Typing Validation + +on: + workflow_dispatch: {} + push: + branches: [ prod, dev ] + pull_request: + branches: [ prod, dev ] + +jobs: + mypy: + name: Run mypy + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./exporter/SynthesisFusionAddin + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - run: pip install -r requirements-mypy.txt + - run: mypy diff --git a/exporter/SynthesisFusionAddin/Synthesis.py b/exporter/SynthesisFusionAddin/Synthesis.py index c5befeddde..ee630a12ca 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.py +++ b/exporter/SynthesisFusionAddin/Synthesis.py @@ -1,5 +1,6 @@ import os import sys +from typing import Any import adsk.core @@ -43,7 +44,7 @@ @logFailure -def run(_): +def run(_context: dict[str, Any]) -> None: """## Entry point to application from Fusion. Arguments: @@ -63,7 +64,7 @@ def run(_): @logFailure -def stop(_): +def stop(_context: dict[str, Any]) -> None: """## Fusion exit point - deconstructs buttons and handlers Arguments: diff --git a/exporter/SynthesisFusionAddin/mypy.ini b/exporter/SynthesisFusionAddin/mypy.ini new file mode 100644 index 0000000000..057a1f78b5 --- /dev/null +++ b/exporter/SynthesisFusionAddin/mypy.ini @@ -0,0 +1,14 @@ +[mypy] +files = Synthesis.py, src +warn_unused_configs = True +check_untyped_defs = True +warn_unreachable = True +warn_redundant_casts = True +warn_unused_ignores = True +warn_no_return = True +warn_return_any = True +strict = True +ignore_missing_imports = True +follow_imports = skip +disallow_subclassing_any = False +disable_error_code = no-untyped-call diff --git a/exporter/SynthesisFusionAddin/proto/build.bat b/exporter/SynthesisFusionAddin/proto/build.bat index 08b776b634..2d44d07102 100644 --- a/exporter/SynthesisFusionAddin/proto/build.bat +++ b/exporter/SynthesisFusionAddin/proto/build.bat @@ -2,5 +2,5 @@ md .\proto_out\ @RD /S /Q "./proto_out/__pycache__" @echo on -protoc -I=../../../mirabuf --python_out=./proto_out ../../../mirabuf/*.proto +protoc -I=../../../mirabuf --python_out=./proto_out --mypy_out=./proto_out ../../../mirabuf/*.proto @echo off diff --git a/exporter/SynthesisFusionAddin/proto/build.sh b/exporter/SynthesisFusionAddin/proto/build.sh index bf5fdeda7e..b4c48da239 100755 --- a/exporter/SynthesisFusionAddin/proto/build.sh +++ b/exporter/SynthesisFusionAddin/proto/build.sh @@ -1,4 +1,4 @@ rm -rf -v ./proto_out mkdir ./proto_out git submodule update --init --recursive -protoc -I=../../../mirabuf --python_out=./proto_out ../../../mirabuf/*.proto \ No newline at end of file +protoc -I=../../../mirabuf --python_out=./proto_out --mypy_out=./proto_out ../../../mirabuf/*.proto diff --git a/exporter/SynthesisFusionAddin/requirements.txt b/exporter/SynthesisFusionAddin/requirements-dev.txt similarity index 73% rename from exporter/SynthesisFusionAddin/requirements.txt rename to exporter/SynthesisFusionAddin/requirements-dev.txt index 75a3dbe98a..89846d2e08 100644 --- a/exporter/SynthesisFusionAddin/requirements.txt +++ b/exporter/SynthesisFusionAddin/requirements-dev.txt @@ -1,2 +1,3 @@ black +isort pyminifier diff --git a/exporter/SynthesisFusionAddin/requirements-mypy.txt b/exporter/SynthesisFusionAddin/requirements-mypy.txt new file mode 100644 index 0000000000..ef8b8dff85 --- /dev/null +++ b/exporter/SynthesisFusionAddin/requirements-mypy.txt @@ -0,0 +1,3 @@ +mypy +types-protobuf +types-requests diff --git a/exporter/SynthesisFusionAddin/src/APS/APS.py b/exporter/SynthesisFusionAddin/src/APS/APS.py index a6f4f34a5b..65ab01e4f7 100644 --- a/exporter/SynthesisFusionAddin/src/APS/APS.py +++ b/exporter/SynthesisFusionAddin/src/APS/APS.py @@ -1,3 +1,4 @@ +import http.client import json import os import pathlib @@ -52,21 +53,21 @@ def getAPSAuth() -> APSAuth | None: return APS_AUTH -def _res_json(res): - return json.loads(res.read().decode(res.info().get_param("charset") or "utf-8")) +def _res_json(res: http.client.HTTPResponse) -> dict[str, Any]: + return dict(json.loads(res.read().decode(str(res.info().get_param("charset")) or "utf-8"))) def getCodeChallenge() -> str | None: endpoint = "https://synthesis.autodesk.com/api/aps/challenge/" - res = urllib.request.urlopen(endpoint) + res: http.client.HTTPResponse = urllib.request.urlopen(endpoint) data = _res_json(res) - return data["challenge"] + return str(data["challenge"]) def getAuth() -> APSAuth | None: global APS_AUTH if APS_AUTH is not None: - return APS_AUTH + return APS_AUTH # type: ignore[unreachable] currTime = time.time() if os.path.exists(auth_path): @@ -86,7 +87,7 @@ def getAuth() -> APSAuth | None: return APS_AUTH -def convertAuthToken(code: str): +def convertAuthToken(code: str) -> None: global APS_AUTH authUrl = f'https://synthesis.autodesk.com/api/aps/code/?code={code}&redirect_uri={urllib.parse.quote_plus("https://synthesis.autodesk.com/api/aps/exporter/")}' res = urllib.request.urlopen(authUrl) @@ -106,14 +107,14 @@ def convertAuthToken(code: str): _ = loadUserInfo() -def removeAuth(): +def removeAuth() -> None: global APS_AUTH, APS_USER_INFO APS_AUTH = None APS_USER_INFO = None pathlib.Path.unlink(pathlib.Path(auth_path)) -def refreshAuthToken(): +def refreshAuthToken() -> None: global APS_AUTH if APS_AUTH is None or APS_AUTH.refresh_token is None: raise Exception("No refresh token found.") @@ -178,6 +179,8 @@ def loadUserInfo() -> APSUserInfo | None: removeAuth() logger.error(f"User Info Error:\n{e.code} - {e.reason}") gm.ui.messageBox("Please sign in again.") + finally: + return None def getUserInfo() -> APSUserInfo | None: @@ -259,20 +262,30 @@ def upload_mirabuf(project_id: str, folder_id: str, file_name: str, file_content global APS_AUTH if APS_AUTH is None: gm.ui.messageBox("You must login to upload designs to APS", "USER ERROR") + return None + auth = APS_AUTH.access_token # Get token from APS API later new_folder_id = get_item_id(auth, project_id, folder_id, "MirabufDir", "folders") if new_folder_id is None: - folder_id = create_folder(auth, project_id, folder_id, "MirabufDir") + created_folder_id = create_folder(auth, project_id, folder_id, "MirabufDir") else: - folder_id = new_folder_id - (lineage_id, file_id, file_version) = get_file_id(auth, project_id, folder_id, file_name) + created_folder_id = new_folder_id + + if created_folder_id is None: + return None + + file_id_data = get_file_id(auth, project_id, created_folder_id, file_name) + if file_id_data is None: + return None + + (lineage_id, file_id, file_version) = file_id_data """ Create APS Storage Location """ - object_id = create_storage_location(auth, project_id, folder_id, file_name) + object_id = create_storage_location(auth, project_id, created_folder_id, file_name) if object_id is None: gm.ui.messageBox("UPLOAD ERROR", "Object id is none; check create storage location") return None @@ -297,10 +310,10 @@ def upload_mirabuf(project_id: str, folder_id: str, file_name: str, file_content return None if file_id != "": update_file_version( - auth, project_id, folder_id, lineage_id, file_id, file_name, file_contents, file_version, object_id + auth, project_id, created_folder_id, lineage_id, file_id, file_name, file_contents, file_version, object_id ) else: - _lineage_info = create_first_file_version(auth, str(object_id), project_id, str(folder_id), file_name) + _lineage_info = create_first_file_version(auth, str(object_id), project_id, str(created_folder_id), file_name) return "" @@ -376,7 +389,7 @@ def get_item_id(auth: str, project_id: str, parent_folder_id: str, folder_name: return "" for item in data: if item["type"] == item_type and item["attributes"]["name"] == folder_name: - return item["id"] + return str(item["id"]) return None @@ -500,7 +513,7 @@ def get_file_id(auth: str, project_id: str, folder_id: str, file_name: str) -> t elif not file_res.ok: gm.ui.messageBox(f"UPLOAD ERROR: {file_res.text}", "Failed to get file") return None - file_json: list[dict[str, Any]] = file_res.json() + file_json: dict[str, Any] = file_res.json() if len(file_json["data"]) == 0: return ("", "", "") id: str = str(file_json["data"][0]["id"]) diff --git a/exporter/SynthesisFusionAddin/src/Dependencies.py b/exporter/SynthesisFusionAddin/src/Dependencies.py index 3bb7261501..284a8ad942 100644 --- a/exporter/SynthesisFusionAddin/src/Dependencies.py +++ b/exporter/SynthesisFusionAddin/src/Dependencies.py @@ -21,10 +21,14 @@ @logFailure -def getInternalFusionPythonInstillationFolder() -> str: +def getInternalFusionPythonInstillationFolder() -> str | os.PathLike[str]: # Thank you Kris Kaplan # Find the folder location where the Autodesk python instillation keeps the 'os' standard library module. - pythonStandardLibraryModulePath = importlib.machinery.PathFinder.find_spec("os", sys.path).origin + pythonOSModulePath = importlib.machinery.PathFinder.find_spec("os", sys.path) + if pythonOSModulePath: + pythonStandardLibraryModulePath = pythonOSModulePath.origin or "ERROR" + else: + raise BaseException("Could not locate spec 'os'") # Depending on platform, adjust to folder to where the python executable binaries are stored. if SYSTEM == "Windows": @@ -36,10 +40,10 @@ def getInternalFusionPythonInstillationFolder() -> str: return folder -def executeCommand(*args: str) -> subprocess.CompletedProcess: +def executeCommand(*args: str) -> subprocess.CompletedProcess[str]: logger.debug(f"Running Command -> {' '.join(args)}") try: - result: subprocess.CompletedProcess = subprocess.run( + result: subprocess.CompletedProcess[str] = subprocess.run( args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True ) logger.debug(f"Command Output:\n{result.stdout}") diff --git a/exporter/SynthesisFusionAddin/src/GlobalManager.py b/exporter/SynthesisFusionAddin/src/GlobalManager.py index 6841a58750..bc9e23e6d3 100644 --- a/exporter/SynthesisFusionAddin/src/GlobalManager.py +++ b/exporter/SynthesisFusionAddin/src/GlobalManager.py @@ -1,62 +1,53 @@ """ Initializes the global variables that are set in the run method to reduce hanging commands. """ +from typing import Any + import adsk.core import adsk.fusion -class GlobalManager(object): - """Global Manager instance""" - - class __GlobalManager: - def __init__(self): - self.app = adsk.core.Application.get() - - if self.app: - self.ui = self.app.userInterface - - self.connected = False - """ Is unity currently connected """ - - self.uniqueIds = [] - """ Collection of unique ID values to not overlap """ - - self.elements = [] - """ Unique constructed buttons to delete """ - - self.palettes = [] - """ Unique constructed palettes to delete """ +class GlobalManager: + def __init__(self) -> None: + self.app = adsk.core.Application.get() - self.handlers = [] - """ Object to store all event handlers to custom events like saving. """ + if self.app: + self.ui = self.app.userInterface - self.tabs = [] - """ Set of Tab objects to keep track of. """ + self.connected = False + """ Is unity currently connected """ - self.queue = [] - """ This will eventually implement the Python SimpleQueue synchronized workflow - - this is the list of objects being sent - """ + self.uniqueIds: list[str] = [] # type of HButton + """ Collection of unique ID values to not overlap """ - self.files = [] + self.elements: list[Any] = [] + """ Unique constructed buttons to delete """ - def __str__(self): - return "GlobalManager" + # Transition: AARD-1765 + # Will likely be removed later as this is no longer used. Avoiding adding typing for now. + self.palettes = [] # type: ignore + """ Unique constructed palettes to delete """ - def clear(self): - for attr, value in self.__dict__.items(): - if isinstance(value, list): - setattr(self, attr, []) + self.handlers: list[adsk.core.EventHandler] = [] + """ Object to store all event handlers to custom events like saving. """ - instance = None + self.tabs: list[adsk.core.ToolbarPanel] = [] + """ Set of Tab objects to keep track of. """ - def __new__(cls): - if not GlobalManager.instance: - GlobalManager.instance = GlobalManager.__GlobalManager() + # Transition: AARD-1765 + # Will likely be removed later as this is no longer used. Avoiding adding typing for now. + self.queue = [] # type: ignore + """ This will eventually implement the Python SimpleQueue synchronized workflow + - this is the list of objects being sent + """ - return GlobalManager.instance + # Transition: AARD-1765 + # Will likely be removed later as this is no longer used. Avoiding adding typing for now. + self.files = [] # type: ignore - def __getattr__(self, name): - return getattr(self.instance, name) + def __str__(self) -> str: + return "GlobalManager" - def __setattr__(self, name): - return setattr(self.instance, name) + def clear(self) -> None: + for attr, value in self.__dict__.items(): + if isinstance(value, list): + setattr(self, attr, []) diff --git a/exporter/SynthesisFusionAddin/src/Logging.py b/exporter/SynthesisFusionAddin/src/Logging.py index 90f1d98d34..3b2bedb68c 100644 --- a/exporter/SynthesisFusionAddin/src/Logging.py +++ b/exporter/SynthesisFusionAddin/src/Logging.py @@ -7,7 +7,7 @@ import time import traceback from datetime import date, datetime -from typing import cast +from typing import Any, Callable, cast import adsk.core @@ -19,7 +19,7 @@ class SynthesisLogger(logging.Logger): - def timing(self, msg: str, *args: any, **kwargs: any) -> None: + def timing(self, msg: str, *args: Any, **kwargs: Any) -> None: return self.log(TIMING_LEVEL, msg, *args, **kwargs) def cleanupHandlers(self) -> None: @@ -49,27 +49,28 @@ def setupLogger() -> SynthesisLogger: logger = getLogger(INTERNAL_ID) logger.setLevel(10) # Debug logger.addHandler(logHandler) - return cast(SynthesisLogger, logger) + return logger def getLogger(name: str | None = None) -> SynthesisLogger: if not name: # Inspect the caller stack to automatically get the module from which the function is being called from. - name = f"{INTERNAL_ID}.{'.'.join(inspect.getmodule(inspect.stack()[1][0]).__name__.split('.')[1:])}" + pyModule = inspect.getmodule(inspect.stack()[1][0]) + name = f"{INTERNAL_ID}.{'.'.join(pyModule.__name__.split('.')[1:])}" if pyModule else INTERNAL_ID return cast(SynthesisLogger, logging.getLogger(name)) # Log function failure decorator. -def logFailure(func: callable = None, /, *, messageBox: bool = False) -> callable: - def wrap(func: callable) -> callable: +def logFailure(func: Callable[..., Any] | None = None, /, *, messageBox: bool = False) -> Callable[..., Any]: + def wrap(func: Callable[..., Any]) -> Callable[..., Any]: @functools.wraps(func) - def wrapper(*args: any, **kwargs: any) -> any: + def wrapper(*args: Any, **kwargs: Any) -> Any: try: return func(*args, **kwargs) except BaseException: excType, excValue, excTrace = sys.exc_info() - tb = traceback.TracebackException(excType, excValue, excTrace) + tb = traceback.TracebackException(excType or BaseException, excValue or BaseException(), excTrace) formattedTb = "".join(list(tb.format())[2:]) # Remove the wrapper func from the traceback. clsName = "" if args and hasattr(args[0], "__class__"): @@ -91,8 +92,8 @@ def wrapper(*args: any, **kwargs: any) -> any: # Time function decorator. -def timed(func: callable) -> callable: - def wrapper(*args: any, **kwargs: any) -> any: +def timed(func: Callable[..., Any]) -> Callable[..., Any]: + def wrapper(*args: Any, **kwargs: Any) -> Any: startTime = time.perf_counter() result = func(*args, **kwargs) endTime = time.perf_counter() diff --git a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py index 69e9bbef5d..92e739da77 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py +++ b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py @@ -36,13 +36,13 @@ class ExporterOptions: fileLocation: str | None = field( default=(os.getenv("HOME") if platform.system() == "Windows" else os.path.expanduser("~")) ) - name: str = field(default=None) - version: str = field(default=None) + name: str | None = field(default=None) + version: str | None = field(default=None) materials: int = field(default=0) exportMode: ExportMode = field(default=ExportMode.ROBOT) - wheels: list[Wheel] = field(default=None) - joints: list[Joint] = field(default=None) - gamepieces: list[Gamepiece] = field(default=None) + wheels: list[Wheel] = field(default_factory=list) + joints: list[Joint] = field(default_factory=list) + gamepieces: list[Gamepiece] = field(default_factory=list) preferredUnits: PreferredUnits = field(default=PreferredUnits.IMPERIAL) # Always stored in kg regardless of 'preferredUnits' diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py index 6190d55a99..dd8726ad82 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py @@ -45,7 +45,7 @@ def _MapAllComponents( else: partDefinition.dynamic = True - def processBody(body: adsk.fusion.BRepBody | adsk.fusion.MeshBody): + def processBody(body: adsk.fusion.BRepBody | adsk.fusion.MeshBody) -> None: if progressDialog.wasCancelled(): raise RuntimeError("User canceled export") if body.isLightBulbOn: @@ -77,7 +77,7 @@ def _ParseComponentRoot( progressDialog: PDMessage, options: ExporterOptions, partsData: assembly_pb2.Parts, - material_map: dict, + material_map: dict[str, material_pb2.Appearance], node: types_pb2.Node, ) -> None: mapConstant = guid_component(component) @@ -108,7 +108,7 @@ def __parseChildOccurrence( progressDialog: PDMessage, options: ExporterOptions, partsData: assembly_pb2.Parts, - material_map: dict, + material_map: dict[str, material_pb2.Appearance], node: types_pb2.Node, ) -> None: if occurrence.isLightBulbOn is False: @@ -172,10 +172,10 @@ def __parseChildOccurrence( # saw online someone used this to get the correct context but oh boy does it look pricey # I think if I can make all parts relative to a parent it should return that parents transform maybe # TESTED AND VERIFIED - but unoptimized -def GetMatrixWorld(occurrence): - matrix = occurrence.transform +def GetMatrixWorld(occurrence: adsk.fusion.Occurrence) -> adsk.core.Matrix3D: + matrix = occurrence.transform2 while occurrence.assemblyContext: - matrix.transformBy(occurrence.assemblyContext.transform) + matrix.transformBy(occurrence.assemblyContext.transform2) occurrence = occurrence.assemblyContext return matrix @@ -185,7 +185,7 @@ def _ParseBRep( body: adsk.fusion.BRepBody, options: ExporterOptions, trimesh: assembly_pb2.TriangleMesh, -) -> any: +) -> None: meshManager = body.meshManager calc = meshManager.createMeshCalculator() calc.setQuality(options.visualQuality) @@ -207,7 +207,7 @@ def _ParseMesh( meshBody: adsk.fusion.MeshBody, options: ExporterOptions, trimesh: assembly_pb2.TriangleMesh, -) -> any: +) -> None: mesh = meshBody.displayMesh fill_info(trimesh, meshBody) diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py index 26dae4f2a4..0b5be182c2 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py @@ -1,5 +1,5 @@ import enum -from typing import Union +from typing import Any, Iterator, cast import adsk.core import adsk.fusion @@ -18,12 +18,12 @@ # this is more of a tree - todo rewrite class GraphNode: - def __init__(self, data: any): + def __init__(self, data: Any) -> None: self.data = data self.previous = None - self.edges = list() + self.edges: list[GraphEdge] = list() - def iter(self, filter_relationship=[]): + def iter(self, filter_relationship: list[enum.Enum] = []) -> Iterator["GraphNode"]: """Generator for Node Iterator that does not have the given relationship Args: @@ -37,19 +37,35 @@ def iter(self, filter_relationship=[]): if edge.relationship not in filter_relationship: yield from edge.node.iter(filter_relationship=filter_relationship) - def __iter__(self): + def __iter__(self) -> Iterator["GraphNode"]: for edge in self.edges: yield edge.node - def allChildren(self): + def allChildren(self) -> list["GraphNode"]: nodes = [self] for edge in self.edges: - nodes.extend(edge.node.allNodes()) + nodes.extend(edge.node.allChildren()) return nodes +class RelationshipBase(enum.Enum): ... + + +class OccurrenceRelationship(RelationshipBase): + TRANSFORM = 1 # As in hierarchy parenting + CONNECTION = 2 # As in a rigid joint or other designator + GROUP = 3 # As in a Rigid Grouping + NEXT = 4 # As in next_joint in list + END = 5 # Orphaned child relationship + + +class JointRelationship(RelationshipBase): + GROUND = 1 # This currently has no bearing + ROTATIONAL = 2 # This currently has no bearing + + class GraphEdge: - def __init__(self, relationship: enum.Enum, node: GraphNode): + def __init__(self, relationship: RelationshipBase | None, node: GraphNode) -> None: """A GraphEdge representing a edge in the GraphNode Args: @@ -59,10 +75,15 @@ def __init__(self, relationship: enum.Enum, node: GraphNode): self.relationship = relationship self.node = node - def print(self): - print(f"Edge Containing {self.relationship.name} -> {self.node}") + def print(self) -> None: + if self.relationship is None: + name = "None" + else: + name = self.relationship.name - def __iter__(self): + print(f"Edge Containing {name} -> {self.node}") + + def __iter__(self) -> Iterator["GraphEdge"]: """Iterator for Edges within this edge Yields: @@ -71,34 +92,21 @@ def __iter__(self): return (edge for edge in self.node.edges) -class OccurrenceRelationship(enum.Enum): - TRANSFORM = 1 # As in hierarchy parenting - CONNECTION = 2 # As in a rigid joint or other designator - GROUP = 3 # As in a Rigid Grouping - NEXT = 4 # As in next_joint in list - END = 5 # Orphaned child relationship - - -class JointRelationship(enum.Enum): - GROUND = 1 # This currently has no bearing - ROTATIONAL = 2 # This currently has no bearing - - # ______________________ INDIVIDUAL JOINT CHAINS ____________________________ class DynamicOccurrenceNode(GraphNode): - def __init__(self, occurrence: adsk.fusion.Occurrence, isGround=False, previous=None): + def __init__(self, occurrence: adsk.fusion.Occurrence, isGround: bool = False, previous: GraphNode | None = None): super().__init__(occurrence) self.isGround = isGround self.name = occurrence.name - def print(self): + def print(self) -> None: print(f"\n\t-------{self.data.name}-------") for edge in self.edges: edge.print() - def getConnectedAxis(self) -> list: + def getConnectedAxis(self) -> list[Any]: """Gets all Axis with the NEXT relationship Returns: @@ -109,10 +117,10 @@ def getConnectedAxis(self) -> list: if edge.relationship == OccurrenceRelationship.NEXT: nextItems.append(edge.node.data) else: - nextItems.extend(edge.node.getConnectedAxis()) + nextItems.extend(cast(DynamicOccurrenceNode, edge.node).getConnectedAxis()) return nextItems - def getConnectedAxisTokens(self) -> list: + def getConnectedAxisTokens(self) -> list[str]: """Gets all Axis with the NEXT relationship Returns: @@ -123,18 +131,20 @@ def getConnectedAxisTokens(self) -> list: if edge.relationship == OccurrenceRelationship.NEXT: nextItems.append(edge.node.data.entityToken) else: - nextItems.extend(edge.node.getConnectedAxisTokens()) + nextItems.extend(cast(DynamicOccurrenceNode, edge.node).getConnectedAxisTokens()) return nextItems class DynamicEdge(GraphEdge): - def __init__(self, relationship: OccurrenceRelationship, node: DynamicOccurrenceNode): - super().__init__(relationship, node) - # should print all in this class - def print(self): - print(f"\t\t - {self.relationship.name} -> {self.node.data.name}") - self.node.print() + def print(self) -> None: + if self.relationship is None: + name = "None" + else: + name = self.relationship.name + + print(f"\t\t - {name} -> {self.node.data.name}") + cast(DynamicOccurrenceNode, self.node).print() # ______________________ ENTIRE SIMULATION STRUCTURE _______________________ @@ -143,10 +153,10 @@ def print(self): class SimulationNode(GraphNode): def __init__( self, - dynamicJoint: DynamicOccurrenceNode, + dynamicJoint: DynamicOccurrenceNode | None, joint: adsk.fusion.Joint, - grounded=False, - ): + grounded: bool = False, + ) -> None: super().__init__(dynamicJoint) self.joint = joint self.grounded = grounded @@ -156,30 +166,30 @@ def __init__( else: self.name = self.joint.name - def print(self): + def print(self) -> None: print(f"Simulation Node for joint : {self.name} ") - def printLink(self): + def printLink(self) -> None: if self.grounded: print(f"GROUND -- {self.data.data.name}") else: print(f"--> {self.data.data.name}") for edge in self.edges: - edge.node.printLink() + cast(SimulationNode, edge.node).printLink() -class SimulationEdge(GraphEdge): - def __init__(self, relationship: JointRelationship, node: SimulationNode): - super().__init__(relationship, node) +class SimulationEdge(GraphEdge): ... # ______________________________ PARSER ___________________________________ class JointParser: + grounded: adsk.fusion.Occurrence + @logFailure - def __init__(self, design): + def __init__(self, design: adsk.fusion.Design) -> None: # Create hierarchy with just joint assembly # - Assembly # - Grounded @@ -211,15 +221,16 @@ def __init__(self, design): gm.ui.messageBox("There is not currently a Grounded Component in the assembly, stopping kinematic export.") raise RuntimeWarning("There is no grounded component") - self.currentTraversal = dict() - self.groundedConnections = [] + self.currentTraversal: dict[str, DynamicOccurrenceNode | bool] = dict() + self.groundedConnections: list[adsk.fusion.Occurrence] = [] # populate the rigidJoints connected to a given occurrence - self.rigidJoints = dict() + # Transition: AARD-1765 + # self.rigidJoints = dict() # populate all joints - self.dynamicJoints = dict() + self.dynamicJoints: dict[str, adsk.fusion.Joint] = dict() - self.simulationNodesRef = dict() + self.simulationNodesRef: dict[str, SimulationNode] = dict() # TODO: need to look through every single joint and find the starting point that is connected to ground # Next add that occurrence to the graph and then traverse down that path etc @@ -243,13 +254,13 @@ def __init__(self, design): # self.groundSimNode.printLink() @logFailure - def __getAllJoints(self): + def __getAllJoints(self) -> None: for joint in list(self.design.rootComponent.allJoints) + list(self.design.rootComponent.allAsBuiltJoints): if joint and joint.occurrenceOne and joint.occurrenceTwo: occurrenceOne = joint.occurrenceOne occurrenceTwo = joint.occurrenceTwo else: - return None + return if occurrenceOne is None: try: @@ -286,19 +297,19 @@ def __getAllJoints(self): logger.error( f"Occurrences that connect joints could not be found\n\t1: {occurrenceOne}\n\t2: {occurrenceTwo}" ) - return None + return else: if oneEntityToken == self.grounded.entityToken: self.groundedConnections.append(occurrenceTwo) elif twoEntityToken == self.grounded.entityToken: self.groundedConnections.append(occurrenceOne) - def _linkAllAxis(self): + def _linkAllAxis(self) -> None: # looks through each simulation nood starting with ground and orders them using edges # self.groundSimNode is ground self._recurseLink(self.groundSimNode) - def _recurseLink(self, simNode: SimulationNode): + def _recurseLink(self, simNode: SimulationNode) -> None: connectedAxisNodes = [ self.simulationNodesRef.get(componentKeys, None) for componentKeys in simNode.data.getConnectedAxisTokens() ] @@ -309,7 +320,7 @@ def _recurseLink(self, simNode: SimulationNode): simNode.edges.append(edge) self._recurseLink(connectedAxis) - def _lookForGroundedJoints(self): + def _lookForGroundedJoints(self) -> None: grounded_token = self.grounded.entityToken rootDynamicJoint = self.groundSimNode.data @@ -322,7 +333,7 @@ def _lookForGroundedJoints(self): is_ground=False, ) - def _populateAxis(self, occ_token: str, joint: adsk.fusion.Joint): + def _populateAxis(self, occ_token: str, joint: adsk.fusion.Joint) -> None: occ = self.design.findEntityByToken(occ_token)[0] if occ is None: @@ -339,21 +350,21 @@ def _populateAxis(self, occ_token: str, joint: adsk.fusion.Joint): def _populateNode( self, occ: adsk.fusion.Occurrence, - prev: DynamicOccurrenceNode, - relationship: OccurrenceRelationship, - is_ground=False, - ): + prev: DynamicOccurrenceNode | None, + relationship: OccurrenceRelationship | None, + is_ground: bool = False, + ) -> DynamicOccurrenceNode | None: if occ.isGrounded and not is_ground: - return + return None elif (relationship == OccurrenceRelationship.NEXT) and (prev is not None): node = DynamicOccurrenceNode(occ) edge = DynamicEdge(relationship, node) prev.edges.append(edge) - return + return None elif ((occ.entityToken in self.dynamicJoints.keys()) and (prev is not None)) or self.currentTraversal.get( occ.entityToken ) is not None: - return + return None node = DynamicOccurrenceNode(occ) @@ -363,6 +374,7 @@ def _populateNode( self._populateNode(occurrence, node, OccurrenceRelationship.TRANSFORM, is_ground=is_ground) # if not is_ground: # THIS IS A BUG - OCCURRENCE ACCESS VIOLATION + # this is the current reason for wrapping in try except pass try: for joint in occ.joints: if joint and joint.occurrenceOne and joint.occurrenceTwo: @@ -391,7 +403,7 @@ def _populateNode( else: continue except: - pass # This is to temporarily bypass the bug + pass if prev is not None: edge = DynamicEdge(relationship, node) @@ -403,7 +415,7 @@ def _populateNode( def searchForGrounded( occ: adsk.fusion.Occurrence, -) -> Union[adsk.fusion.Occurrence, None]: +) -> adsk.fusion.Occurrence | None: """Search for a grounded component or occurrence in the assembly Args: @@ -442,7 +454,7 @@ def BuildJointPartHierarchy( joints: joint_pb2.Joints, options: ExporterOptions, progressDialog: PDMessage, -): +) -> None: try: progressDialog.currentMessage = f"Constructing Simulation Hierarchy" progressDialog.update() @@ -466,10 +478,10 @@ def BuildJointPartHierarchy( raise RuntimeError("User canceled export") except Warning: - return False + pass -def populateJoint(simNode: SimulationNode, joints: joint_pb2.Joints, progressDialog): +def populateJoint(simNode: SimulationNode, joints: joint_pb2.Joints, progressDialog: PDMessage) -> None: if progressDialog.wasCancelled(): raise RuntimeError("User canceled export") @@ -494,15 +506,15 @@ def populateJoint(simNode: SimulationNode, joints: joint_pb2.Joints, progressDia # next in line to be populated for edge in simNode.edges: - populateJoint(edge.node, joints, progressDialog) + populateJoint(cast(SimulationNode, edge.node), joints, progressDialog) def createTreeParts( dynNode: DynamicOccurrenceNode, - relationship: OccurrenceRelationship, + relationship: RelationshipBase | None, node: types_pb2.Node, - progressDialog, -): + progressDialog: PDMessage, +) -> None: if progressDialog.wasCancelled(): raise RuntimeError("User canceled export") @@ -531,5 +543,5 @@ def createTreeParts( # recurse and add all children connections for edge in dynNode.edges: child_node = types_pb2.Node() - createTreeParts(edge.node, edge.relationship, child_node, progressDialog) + createTreeParts(cast(DynamicOccurrenceNode, edge.node), edge.relationship, child_node, progressDialog) node.children.append(child_node) diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py index 7a3e0da3b9..71c01326dd 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py @@ -24,7 +24,7 @@ import traceback import uuid -from typing import Union +from typing import Any, Callable, Union import adsk.core import adsk.fusion @@ -38,7 +38,7 @@ guid_occurrence, ) from src.Proto import assembly_pb2, joint_pb2, signal_pb2, types_pb2 -from src.Types import JointParentType, SignalType +from src.Types import Joint, JointParentType, SignalType, Wheel logger = getLogger() @@ -71,7 +71,7 @@ def populateJoints( progressDialog: PDMessage, options: ExporterOptions, assembly: assembly_pb2.Assembly, -): +) -> None: fill_info(joints, None) # This is for creating all of the Joint Definition objects @@ -152,7 +152,7 @@ def populateJoints( continue -def _addJoint(joint: adsk.fusion.Joint, joint_definition: joint_pb2.Joint): +def _addJoint(joint: adsk.fusion.Joint, joint_definition: joint_pb2.Joint) -> None: fill_info(joint_definition, joint) jointPivotTranslation = _jointOrigin(joint) @@ -177,7 +177,7 @@ def _addJointInstance( joint_definition: joint_pb2.Joint, signals: signal_pb2.Signals, options: ExporterOptions, -): +) -> None: fill_info(joint_instance, joint) # because there is only one and we are using the token - should be the same joint_instance.joint_reference = joint_instance.info.GUID @@ -232,7 +232,7 @@ def _addJointInstance( joint_instance.signal_reference = "" -def _addRigidGroup(joint: adsk.fusion.Joint, assembly: assembly_pb2.Assembly): +def _addRigidGroup(joint: adsk.fusion.Joint, assembly: assembly_pb2.Assembly) -> None: if joint.jointMotion.jointType != 0 or not ( joint.occurrenceOne.isLightBulbOn and joint.occurrenceTwo.isLightBulbOn ): @@ -249,22 +249,22 @@ def _motionFromJoint(fusionMotionDefinition: adsk.fusion.JointMotion, proto_join # if fusionJoint.geometryOrOriginOne.objectType == "adsk::fusion::JointGeometry" # create the DOF depending on what kind of information the joint has - fillJointMotionFuncSwitcher = { - 0: noop, # this should be ignored + fillJointMotionFuncSwitcher: dict[int, Callable[..., None]] = { + 0: notImplementedPlaceholder, # this should be ignored 1: fillRevoluteJointMotion, 2: fillSliderJointMotion, - 3: noop, # TODO: Implement - Ball Joint at least - 4: noop, # TODO: Implement - 5: noop, # TODO: Implement - 6: noop, # TODO: Implement + 3: notImplementedPlaceholder, # TODO: Implement - Ball Joint at least + 4: notImplementedPlaceholder, # TODO: Implement + 5: notImplementedPlaceholder, # TODO: Implement + 6: notImplementedPlaceholder, # TODO: Implement } - fillJointMotionFunc = fillJointMotionFuncSwitcher.get(fusionMotionDefinition.jointType, lambda: None) + fillJointMotionFunc = fillJointMotionFuncSwitcher.get(fusionMotionDefinition.jointType, notImplementedPlaceholder) fillJointMotionFunc(fusionMotionDefinition, proto_joint) -def fillRevoluteJointMotion(revoluteMotion: adsk.fusion.RevoluteJointMotion, proto_joint: joint_pb2.Joint): +def fillRevoluteJointMotion(revoluteMotion: adsk.fusion.RevoluteJointMotion, proto_joint: joint_pb2.Joint) -> None: """#### Fill Protobuf revolute joint motion data Args: @@ -338,9 +338,7 @@ def fillSliderJointMotion(sliderMotion: adsk.fusion.SliderJointMotion, proto_joi dof.value = sliderMotion.slideValue -def noop(*argv): - """Easy way to keep track of no-op code that required function pointers""" - pass +def notImplementedPlaceholder(*argv: Any) -> None: ... def _searchForGrounded( @@ -431,9 +429,9 @@ def _jointOrigin(fusionJoint: Union[adsk.fusion.Joint, adsk.fusion.AsBuiltJoint] def createJointGraph( - supplied_joints: list, - wheels: list, - joint_tree: types_pb2.GraphContainer, + suppliedJoints: list[Joint], + _wheels: list[Wheel], + jointTree: types_pb2.GraphContainer, progressDialog: PDMessage, ) -> None: # progressDialog.message = f"Building Joint Graph Map from given joints" @@ -442,44 +440,43 @@ def createJointGraph( progressDialog.update() # keep track of current nodes to link them - node_map = dict({}) + nodeMap = dict() # contains all of the static ground objects groundNode = types_pb2.Node() groundNode.value = "ground" - node_map[groundNode.value] = groundNode + nodeMap[groundNode.value] = groundNode # addWheelsToGraph(wheels, groundNode, joint_tree) # first iterate through to create the nodes - for supplied_joint in supplied_joints: + for suppliedJoint in suppliedJoints: newNode = types_pb2.Node() - newNode.value = supplied_joint.jointToken - node_map[newNode.value] = newNode + newNode.value = suppliedJoint.jointToken + nodeMap[newNode.value] = newNode # second sort them - for supplied_joint in supplied_joints: - current_node = node_map[supplied_joint.jointToken] - if supplied_joint.parent == JointParentType.ROOT: - node_map["ground"].children.append(node_map[supplied_joint.jointToken]) - elif node_map[supplied_joint.parent.value] is not None and node_map[supplied_joint.jointToken] is not None: - node_map[supplied_joint.parent].children.append(node_map[supplied_joint.jointToken]) + for suppliedJoint in suppliedJoints: + if suppliedJoint.parent == JointParentType.ROOT: + nodeMap["ground"].children.append(nodeMap[suppliedJoint.jointToken]) + elif nodeMap[suppliedJoint.parent.value] is not None and nodeMap[suppliedJoint.jointToken] is not None: + nodeMap[str(suppliedJoint.parent)].children.append(nodeMap[suppliedJoint.jointToken]) else: - logger.error(f"Cannot construct hierarhcy because of detached tree at : {supplied_joint.jointToken}") + logger.error(f"Cannot construct hierarhcy because of detached tree at : {suppliedJoint.jointToken}") - for node in node_map.values(): + for node in nodeMap.values(): # append everything at top level to isolate kinematics - joint_tree.nodes.append(node) + jointTree.nodes.append(node) -def addWheelsToGraph(wheels: list, rootNode: types_pb2.Node, joint_tree: types_pb2.GraphContainer): +def addWheelsToGraph(wheels: list[Wheel], rootNode: types_pb2.Node, jointTree: types_pb2.GraphContainer) -> None: for wheel in wheels: # wheel name # wheel signal # wheel occ id # these don't have children wheelNode = types_pb2.Node() - wheelNode.value = wheel.occurrenceToken + wheelNode.value = wheel.jointToken rootNode.children.append(wheelNode) - joint_tree.nodes.append(wheelNode) + jointTree.nodes.append(wheelNode) diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py index 22b42180a2..1ff5d3fe6c 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py @@ -1,4 +1,4 @@ -import adsk +import adsk.core from src.Logging import logFailure from src.Parser.ExporterOptions import ExporterOptions @@ -27,7 +27,7 @@ def _MapAllPhysicalMaterials( - physicalMaterials: list, + physicalMaterials: list[material_pb2.PhysicalMaterial], materials: material_pb2.Materials, options: ExporterOptions, progressDialog: PDMessage, @@ -44,24 +44,26 @@ def _MapAllPhysicalMaterials( getPhysicalMaterialData(material, newmaterial, options) -def setDefaultMaterial(physical_material: material_pb2.PhysicalMaterial, options: ExporterOptions): - construct_info("default", physical_material) +def setDefaultMaterial(physicalMaterial: material_pb2.PhysicalMaterial, options: ExporterOptions) -> None: + construct_info("default", physicalMaterial) - physical_material.description = "A default physical material" + physicalMaterial.description = "A default physical material" if options.frictionOverride: - physical_material.dynamic_friction = options.frictionOverrideCoeff - physical_material.static_friction = options.frictionOverrideCoeff + physicalMaterial.dynamic_friction = options.frictionOverrideCoeff + physicalMaterial.static_friction = options.frictionOverrideCoeff else: - physical_material.dynamic_friction = 0.5 - physical_material.static_friction = 0.5 + physicalMaterial.dynamic_friction = 0.5 + physicalMaterial.static_friction = 0.5 - physical_material.restitution = 0.5 - physical_material.deformable = False - physical_material.matType = 0 + physicalMaterial.restitution = 0.5 + physicalMaterial.deformable = False + physicalMaterial.matType = 0 # type: ignore[assignment] @logFailure -def getPhysicalMaterialData(fusion_material, proto_material, options): +def getPhysicalMaterialData( + fusionMaterial: adsk.core.Material, physicalMaterial: material_pb2.PhysicalMaterial, options: ExporterOptions +) -> None: """Gets the material data and adds it to protobuf Args: @@ -69,26 +71,26 @@ def getPhysicalMaterialData(fusion_material, proto_material, options): proto_material (protomaterial): proto material mirabuf options (parseoptions): parse options """ - construct_info("", proto_material, fus_object=fusion_material) + construct_info("", physicalMaterial, fus_object=fusionMaterial) - proto_material.deformable = False - proto_material.matType = 0 + physicalMaterial.deformable = False + physicalMaterial.matType = 0 # type: ignore[assignment] - materialProperties = fusion_material.materialProperties + materialProperties = fusionMaterial.materialProperties - thermalProperties = proto_material.thermal - mechanicalProperties = proto_material.mechanical - strengthProperties = proto_material.strength + thermalProperties = physicalMaterial.thermal + mechanicalProperties = physicalMaterial.mechanical + strengthProperties = physicalMaterial.strength if options.frictionOverride: - proto_material.dynamic_friction = options.frictionOverrideCoeff - proto_material.static_friction = options.frictionOverrideCoeff + physicalMaterial.dynamic_friction = options.frictionOverrideCoeff + physicalMaterial.static_friction = options.frictionOverrideCoeff else: - proto_material.dynamic_friction = DYNAMIC_FRICTION_COEFFS.get(fusion_material.name, 0.5) - proto_material.static_friction = STATIC_FRICTION_COEFFS.get(fusion_material.name, 0.5) + physicalMaterial.dynamic_friction = DYNAMIC_FRICTION_COEFFS.get(fusionMaterial.name, 0.5) + physicalMaterial.static_friction = STATIC_FRICTION_COEFFS.get(fusionMaterial.name, 0.5) - proto_material.restitution = 0.5 - proto_material.description = f"{fusion_material.name} exported from FUSION" + physicalMaterial.restitution = 0.5 + physicalMaterial.description = f"{fusionMaterial.name} exported from FUSION" """ Thermal Properties @@ -138,7 +140,7 @@ def getPhysicalMaterialData(fusion_material, proto_material, options): def _MapAllAppearances( - appearances: list, + appearances: list[material_pb2.Appearance], materials: material_pb2.Materials, options: ExporterOptions, progressDialog: PDMessage, diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PDMessage.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PDMessage.py index ad441fc901..c157495a0f 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PDMessage.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PDMessage.py @@ -33,7 +33,7 @@ def __init__( self.progressDialog = progressDialog - def _format(self): + def _format(self) -> str: # USE FORMATTING TO CENTER THESE BAD BOIS # TABS DO NOTHING HALP out = f"{self.assemblyName} parsing:\n" @@ -45,44 +45,44 @@ def _format(self): return out - def addComponent(self, name=None): + def addComponent(self, name: str | None = None) -> None: self.currentValue += 1 self.currentCompCount += 1 self.currentMessage = f"Exporting Component {name}" self.update() - def addOccurrence(self, name=None): + def addOccurrence(self, name: str | None = None) -> None: self.currentValue += 1 self.currentOccCount += 1 self.currentMessage = f"Exporting Occurrence {name}" self.update() - def addMaterial(self, name=None): + def addMaterial(self, name: str | None = None) -> None: self.currentValue += 1 self.currentMatCount += 1 self.currentMessage = f"Exporting Physical Material {name}" self.update() - def addAppearance(self, name=None): + def addAppearance(self, name: str | None = None) -> None: self.currentValue += 1 self.currentAppCount += 1 self.currentMessage = f"Exporting Appearance Material {name}" self.update() - def addJoint(self, name=None): + def addJoint(self, name: str | None = None) -> None: self.currentMessage = f"Connecting Joints {name}" self.update() - def update(self): + def update(self) -> None: self.progressDialog.message = self._format() self.progressDialog.progressValue = self.currentValue self.value = self.currentValue def wasCancelled(self) -> bool: - return self.progressDialog.wasCancelled + return self.progressDialog.wasCancelled # type: ignore[no-any-return] - def __str__(self): + def __str__(self) -> str: return self._format() - def __repr__(self): + def __repr__(self) -> str: return self._format() diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py index c44fd50fdb..b085b48d32 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py @@ -179,8 +179,7 @@ def export(self) -> None: logger.debug("Uploading file to APS") project = app.data.activeProject if not project.isValid: - gm.ui.messageBox("Project is invalid", "") - return False # add throw later + raise RuntimeError("Project is invalid") project_id = project.id folder_id = project.rootFolder.id file_name = f"{self.exporterOptions.fileLocation}.mira" @@ -189,18 +188,17 @@ def export(self) -> None: else: assert self.exporterOptions.exportLocation == ExportLocation.DOWNLOAD # check if entire path exists and create if not since gzip doesn't do that. - path = pathlib.Path(self.exporterOptions.fileLocation).parent + path = pathlib.Path(str(self.exporterOptions.fileLocation)).parent path.mkdir(parents=True, exist_ok=True) + self.pdMessage.currentMessage = "Saving File..." + self.pdMessage.update() if self.exporterOptions.compressOutput: logger.debug("Compressing file") - with gzip.open(self.exporterOptions.fileLocation, "wb", 9) as f: - self.pdMessage.currentMessage = "Saving File..." - self.pdMessage.update() + with gzip.open(str(self.exporterOptions.fileLocation), "wb", 9) as f: f.write(assembly_out.SerializeToString()) else: - f = open(self.exporterOptions.fileLocation, "wb") - f.write(assembly_out.SerializeToString()) - f.close() + with open(str(self.exporterOptions.fileLocation), "wb") as f: + f.write(assembly_out.SerializeToString()) _ = progressDialog.hide() diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py index c19b0bd6b3..53af57dfe5 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py @@ -28,8 +28,8 @@ def GetPhysicalProperties( fusionObject: Union[adsk.fusion.BRepBody, adsk.fusion.Occurrence, adsk.fusion.Component], physicalProperties: types_pb2.PhysicalProperties, - level=1, -): + level: int = 1, +) -> None: """Will populate a physical properties section of an exported file Args: diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py index 78da93ffec..a5e389beda 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py @@ -21,10 +21,13 @@ from src.Proto import assembly_pb2 +# Transition: AARD-1765 +# According to the type errors I'm getting here this code would have never compiled. +# Should be removed later @logFailure def ExportRigidGroups( fus_occ: Union[adsk.fusion.Occurrence, adsk.fusion.Component], - hel_occ: assembly_pb2.Occurrence, + hel_occ: assembly_pb2.Occurrence, # type: ignore[name-defined] ) -> None: """Takes a Fusion and Protobuf Occurrence and will assign Rigidbody data per the occurrence if any exist and are not surpressed. diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Utilities.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Utilities.py index 0a5d277766..d8f38d921f 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Utilities.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Utilities.py @@ -1,15 +1,17 @@ import math import uuid -from adsk.core import Vector3D -from adsk.fusion import Component, Occurrence +import adsk.core +import adsk.fusion +from src.Proto import assembly_pb2 -def guid_component(comp: Component) -> str: + +def guid_component(comp: adsk.fusion.Component) -> str: return f"{comp.entityToken}_{comp.id}" -def guid_occurrence(occ: Occurrence) -> str: +def guid_occurrence(occ: adsk.fusion.Occurrence) -> str: return f"{occ.entityToken}_{guid_component(occ.component)}" @@ -17,11 +19,17 @@ def guid_none(_: None) -> str: return str(uuid.uuid4()) -def fill_info(proto_obj, fus_object, override_guid=None) -> None: +def fill_info(proto_obj: assembly_pb2.Assembly, fus_object: adsk.core.Base, override_guid: str | None = None) -> None: construct_info("", proto_obj, fus_object=fus_object, GUID=override_guid) -def construct_info(name: str, proto_obj, version=5, fus_object=None, GUID=None) -> None: +def construct_info( + name: str, + proto_obj: assembly_pb2.Assembly, + version: int = 5, + fus_object: adsk.core.Base | None = None, + GUID: str | None = None, +) -> None: """Constructs a info object from either a name or a fus_object Args: @@ -41,24 +49,21 @@ def construct_info(name: str, proto_obj, version=5, fus_object=None, GUID=None) if fus_object is not None: proto_obj.info.name = fus_object.name - elif name is not None: - proto_obj.info.name = name else: - raise ValueError("Cannot construct info from no name or fus_object") + proto_obj.info.name = name if GUID is not None: proto_obj.info.GUID = str(GUID) + elif fus_object is not None and hasattr(fus_object, "entityToken"): + proto_obj.info.GUID = fus_object.entityToken else: - try: - # attempt to get entity token - proto_obj.info.GUID = fus_object.entityToken - except AttributeError: - # fails and gets new uuid - proto_obj.info.GUID = str(uuid.uuid4()) + proto_obj.info.GUID = str(uuid.uuid4()) +# Transition: AARD-1765 +# Will likely be removed later as this is no longer used. Avoiding adding typing for now. # My previous function was alot more optimized however now I realize the bug was this doesn't work well with degrees -def euler_to_quaternion(r): +def euler_to_quaternion(r): # type: ignore (yaw, pitch, roll) = (r[0], r[1], r[2]) qx = math.sin(roll / 2) * math.cos(pitch / 2) * math.cos(yaw / 2) - math.cos(roll / 2) * math.sin( pitch / 2 @@ -75,7 +80,7 @@ def euler_to_quaternion(r): return [qx, qy, qz, qw] -def rad_to_deg(rad): +def rad_to_deg(rad): # type: ignore """Very simple method to convert Radians to degrees Args: @@ -87,7 +92,7 @@ def rad_to_deg(rad): return (rad * 180) / math.pi -def quaternion_to_euler(qx, qy, qz, qw): +def quaternion_to_euler(qx, qy, qz, qw): # type: ignore """Takes in quat values and converts to degrees - roll is x axis - atan2(2(qwqy + qzqw), 1-2(qy^2 + qz^2)) @@ -127,7 +132,7 @@ def quaternion_to_euler(qx, qy, qz, qw): return round(roll, 4), round(pitch, 4), round(yaw, 4) -def throwZero(): +def throwZero(): # type: ignore """Simple function to report incorrect quat values Raises: @@ -136,7 +141,7 @@ def throwZero(): raise RuntimeError("While computing the quaternion the trace was reported as 0 which is invalid") -def spatial_to_quaternion(mat): +def spatial_to_quaternion(mat): # type: ignore """Takes a 1D Spatial Transform Matrix and derives rotational quaternion I wrote this however it is difficult to extensibly test so use with caution @@ -194,13 +199,13 @@ def spatial_to_quaternion(mat): raise RuntimeError("Supplied matrix to spatial_to_quaternion is not a 1D spatial matrix in size.") -def normalize_quaternion(x, y, z, w): +def normalize_quaternion(x, y, z, w): # type: ignore f = 1.0 / math.sqrt((x * x) + (y * y) + (z * z) + (w * w)) return x * f, y * f, z * f, w * f -def _getAngleTo(vec_origin: list, vec_current: Vector3D) -> int: - origin = Vector3D.create(vec_origin[0], vec_origin[1], vec_origin[2]) +def _getAngleTo(vec_origin: list, vec_current: adsk.core.Vector3D) -> int: # type: ignore + origin = adsk.core.Vector3D.create(vec_origin[0], vec_origin[1], vec_origin[2]) val = origin.angleTo(vec_current) deg = val * (180 / math.pi) - return val + return val # type: ignore diff --git a/exporter/SynthesisFusionAddin/src/Proto/assembly_pb2.pyi b/exporter/SynthesisFusionAddin/src/Proto/assembly_pb2.pyi new file mode 100644 index 0000000000..84548670b8 --- /dev/null +++ b/exporter/SynthesisFusionAddin/src/Proto/assembly_pb2.pyi @@ -0,0 +1,449 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import collections.abc +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.message +import joint_pb2 +import material_pb2 +import signal_pb2 +import types_pb2 +import typing + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class Assembly(google.protobuf.message.Message): + """* + Assembly + Base Design to be interacted with + THIS IS THE CURRENT FILE EXPORTED + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INFO_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + DYNAMIC_FIELD_NUMBER: builtins.int + PHYSICAL_DATA_FIELD_NUMBER: builtins.int + DESIGN_HIERARCHY_FIELD_NUMBER: builtins.int + JOINT_HIERARCHY_FIELD_NUMBER: builtins.int + TRANSFORM_FIELD_NUMBER: builtins.int + THUMBNAIL_FIELD_NUMBER: builtins.int + dynamic: builtins.bool + """/ Can it be effected by the simulation dynamically""" + @property + def info(self) -> types_pb2.Info: + """/ Basic information (name, Author, etc)""" + + @property + def data(self) -> global___AssemblyData: + """/ All of the data in the assembly""" + + @property + def physical_data(self) -> types_pb2.PhysicalProperties: + """/ Overall physical data of the assembly""" + + @property + def design_hierarchy(self) -> types_pb2.GraphContainer: + """/ The Design hierarchy represented by Part Refs - The first object is a root container for all top level items""" + + @property + def joint_hierarchy(self) -> types_pb2.GraphContainer: + """/ The Joint hierarchy for compound shapes""" + + @property + def transform(self) -> types_pb2.Transform: + """/ The Transform in space currently""" + + @property + def thumbnail(self) -> types_pb2.Thumbnail: + """/ Optional thumbnail saved from Fusion 360 or scraped from previous configuration""" + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + data: global___AssemblyData | None = ..., + dynamic: builtins.bool = ..., + physical_data: types_pb2.PhysicalProperties | None = ..., + design_hierarchy: types_pb2.GraphContainer | None = ..., + joint_hierarchy: types_pb2.GraphContainer | None = ..., + transform: types_pb2.Transform | None = ..., + thumbnail: types_pb2.Thumbnail | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["data", b"data", "design_hierarchy", b"design_hierarchy", "info", b"info", "joint_hierarchy", b"joint_hierarchy", "physical_data", b"physical_data", "thumbnail", b"thumbnail", "transform", b"transform"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["data", b"data", "design_hierarchy", b"design_hierarchy", "dynamic", b"dynamic", "info", b"info", "joint_hierarchy", b"joint_hierarchy", "physical_data", b"physical_data", "thumbnail", b"thumbnail", "transform", b"transform"]) -> None: ... + +global___Assembly = Assembly + +@typing.final +class AssemblyData(google.protobuf.message.Message): + """* + Data used to construct the assembly + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PARTS_FIELD_NUMBER: builtins.int + JOINTS_FIELD_NUMBER: builtins.int + MATERIALS_FIELD_NUMBER: builtins.int + SIGNALS_FIELD_NUMBER: builtins.int + @property + def parts(self) -> global___Parts: + """/ Meshes and Design Objects""" + + @property + def joints(self) -> joint_pb2.Joints: + """/ Joint Definition Set""" + + @property + def materials(self) -> material_pb2.Materials: + """/ Appearance and Physical Material Set""" + + @property + def signals(self) -> signal_pb2.Signals: + """Contains table of all signals with ID reference""" + + def __init__( + self, + *, + parts: global___Parts | None = ..., + joints: joint_pb2.Joints | None = ..., + materials: material_pb2.Materials | None = ..., + signals: signal_pb2.Signals | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["joints", b"joints", "materials", b"materials", "parts", b"parts", "signals", b"signals"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["joints", b"joints", "materials", b"materials", "parts", b"parts", "signals", b"signals"]) -> None: ... + +global___AssemblyData = AssemblyData + +@typing.final +class Parts(google.protobuf.message.Message): + """Part file can be exported seperately in the future""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing.final + class PartDefinitionsEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> global___PartDefinition: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: global___PartDefinition | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["key", b"key", "value", b"value"]) -> None: ... + + @typing.final + class PartInstancesEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> global___PartInstance: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: global___PartInstance | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["key", b"key", "value", b"value"]) -> None: ... + + INFO_FIELD_NUMBER: builtins.int + PART_DEFINITIONS_FIELD_NUMBER: builtins.int + PART_INSTANCES_FIELD_NUMBER: builtins.int + USER_DATA_FIELD_NUMBER: builtins.int + @property + def info(self) -> types_pb2.Info: + """/ Part name, version, GUID""" + + @property + def part_definitions(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, global___PartDefinition]: + """/ Map of the Exported Part Definitions""" + + @property + def part_instances(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, global___PartInstance]: + """/ Map of the Exported Parts that make up the object""" + + @property + def user_data(self) -> types_pb2.UserData: + """/ other associated data that can be used + end effector, wheel, etc + """ + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + part_definitions: collections.abc.Mapping[builtins.str, global___PartDefinition] | None = ..., + part_instances: collections.abc.Mapping[builtins.str, global___PartInstance] | None = ..., + user_data: types_pb2.UserData | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["info", b"info", "user_data", b"user_data"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["info", b"info", "part_definitions", b"part_definitions", "part_instances", b"part_instances", "user_data", b"user_data"]) -> None: ... + +global___Parts = Parts + +@typing.final +class PartDefinition(google.protobuf.message.Message): + """* + Part Definition + Unique Definition of a part that can be replicated. + Useful for keeping the object counter down in the scene. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INFO_FIELD_NUMBER: builtins.int + PHYSICAL_DATA_FIELD_NUMBER: builtins.int + BASE_TRANSFORM_FIELD_NUMBER: builtins.int + BODIES_FIELD_NUMBER: builtins.int + DYNAMIC_FIELD_NUMBER: builtins.int + FRICTION_OVERRIDE_FIELD_NUMBER: builtins.int + MASS_OVERRIDE_FIELD_NUMBER: builtins.int + dynamic: builtins.bool + """/ Optional value to state whether an object is a dynamic object in a static assembly - all children are also considered overriden""" + friction_override: builtins.float + """/ Optional value for overriding the friction value 0-1""" + mass_override: builtins.float + """/ Optional value for overriding an indiviaul object's mass""" + @property + def info(self) -> types_pb2.Info: + """/ Information about version - id - name""" + + @property + def physical_data(self) -> types_pb2.PhysicalProperties: + """/ Physical data associated with Part""" + + @property + def base_transform(self) -> types_pb2.Transform: + """/ Base Transform applied - Most Likely Identity Matrix""" + + @property + def bodies(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Body]: + """/ Mesh Bodies to populate part""" + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + physical_data: types_pb2.PhysicalProperties | None = ..., + base_transform: types_pb2.Transform | None = ..., + bodies: collections.abc.Iterable[global___Body] | None = ..., + dynamic: builtins.bool = ..., + friction_override: builtins.float = ..., + mass_override: builtins.float = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["base_transform", b"base_transform", "info", b"info", "physical_data", b"physical_data"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["base_transform", b"base_transform", "bodies", b"bodies", "dynamic", b"dynamic", "friction_override", b"friction_override", "info", b"info", "mass_override", b"mass_override", "physical_data", b"physical_data"]) -> None: ... + +global___PartDefinition = PartDefinition + +@typing.final +class PartInstance(google.protobuf.message.Message): + """ + Part + Represents a object that does not have to be unique + Can be an override for an existing definition + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INFO_FIELD_NUMBER: builtins.int + PART_DEFINITION_REFERENCE_FIELD_NUMBER: builtins.int + TRANSFORM_FIELD_NUMBER: builtins.int + GLOBAL_TRANSFORM_FIELD_NUMBER: builtins.int + JOINTS_FIELD_NUMBER: builtins.int + APPEARANCE_FIELD_NUMBER: builtins.int + PHYSICAL_MATERIAL_FIELD_NUMBER: builtins.int + SKIP_COLLIDER_FIELD_NUMBER: builtins.int + part_definition_reference: builtins.str + """/ Reference to the Part Definition defined in Assembly Data""" + appearance: builtins.str + """Appearance Reference to link to `Materials->Appearance->Info->id`""" + physical_material: builtins.str + """/ Physical Material Reference to link to `Materials->PhysicalMaterial->Info->id`""" + skip_collider: builtins.bool + """/ Flag that if enabled indicates we should skip generating a collider, defaults to FALSE or undefined""" + @property + def info(self) -> types_pb2.Info: ... + @property + def transform(self) -> types_pb2.Transform: + """/ Overriding the object transform (moves the part from the def) - in design hierarchy context""" + + @property + def global_transform(self) -> types_pb2.Transform: + """/ Position transform from a global scope""" + + @property + def joints(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: + """/ Joints that interact with this element""" + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + part_definition_reference: builtins.str = ..., + transform: types_pb2.Transform | None = ..., + global_transform: types_pb2.Transform | None = ..., + joints: collections.abc.Iterable[builtins.str] | None = ..., + appearance: builtins.str = ..., + physical_material: builtins.str = ..., + skip_collider: builtins.bool = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["global_transform", b"global_transform", "info", b"info", "transform", b"transform"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["appearance", b"appearance", "global_transform", b"global_transform", "info", b"info", "joints", b"joints", "part_definition_reference", b"part_definition_reference", "physical_material", b"physical_material", "skip_collider", b"skip_collider", "transform", b"transform"]) -> None: ... + +global___PartInstance = PartInstance + +@typing.final +class Body(google.protobuf.message.Message): + """ + Body object + Can contain a TriangleMesh or Collection of Faces. + Must be unique in the context of the Assembly. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INFO_FIELD_NUMBER: builtins.int + PART_FIELD_NUMBER: builtins.int + TRIANGLE_MESH_FIELD_NUMBER: builtins.int + APPEARANCE_OVERRIDE_FIELD_NUMBER: builtins.int + part: builtins.str + """/ Reference to Part Definition""" + appearance_override: builtins.str + """/ Override Visual Appearance for the body""" + @property + def info(self) -> types_pb2.Info: ... + @property + def triangle_mesh(self) -> global___TriangleMesh: + """/ Triangle Mesh for rendering""" + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + part: builtins.str = ..., + triangle_mesh: global___TriangleMesh | None = ..., + appearance_override: builtins.str = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["info", b"info", "triangle_mesh", b"triangle_mesh"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["appearance_override", b"appearance_override", "info", b"info", "part", b"part", "triangle_mesh", b"triangle_mesh"]) -> None: ... + +global___Body = Body + +@typing.final +class TriangleMesh(google.protobuf.message.Message): + """* + Traingle Mesh for Storing Display Mesh data + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INFO_FIELD_NUMBER: builtins.int + HAS_VOLUME_FIELD_NUMBER: builtins.int + MATERIAL_REFERENCE_FIELD_NUMBER: builtins.int + MESH_FIELD_NUMBER: builtins.int + BMESH_FIELD_NUMBER: builtins.int + has_volume: builtins.bool + """/ Is this object a Plane ? (Does it have volume)""" + material_reference: builtins.str + """/ Rendered Appearance properties referenced from Assembly Data""" + @property + def info(self) -> types_pb2.Info: ... + @property + def mesh(self) -> global___Mesh: + """/ Stored as true types, inidicies, verts, uv""" + + @property + def bmesh(self) -> global___BinaryMesh: + """/ Stored as binary data in bytes""" + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + has_volume: builtins.bool = ..., + material_reference: builtins.str = ..., + mesh: global___Mesh | None = ..., + bmesh: global___BinaryMesh | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["bmesh", b"bmesh", "info", b"info", "mesh", b"mesh", "mesh_type", b"mesh_type"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["bmesh", b"bmesh", "has_volume", b"has_volume", "info", b"info", "material_reference", b"material_reference", "mesh", b"mesh", "mesh_type", b"mesh_type"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["mesh_type", b"mesh_type"]) -> typing.Literal["mesh", "bmesh"] | None: ... + +global___TriangleMesh = TriangleMesh + +@typing.final +class Mesh(google.protobuf.message.Message): + """* + Mesh Data stored as generic Data Structure + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + VERTS_FIELD_NUMBER: builtins.int + NORMALS_FIELD_NUMBER: builtins.int + UV_FIELD_NUMBER: builtins.int + INDICES_FIELD_NUMBER: builtins.int + @property + def verts(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.float]: + """/ Tri Mesh Verts vec3""" + + @property + def normals(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.float]: + """/ Tri Mesh Normals vec3""" + + @property + def uv(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.float]: + """/ Tri Mesh uv Mapping vec2""" + + @property + def indices(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: + """/ Tri Mesh indicies (Vert Map)""" + + def __init__( + self, + *, + verts: collections.abc.Iterable[builtins.float] | None = ..., + normals: collections.abc.Iterable[builtins.float] | None = ..., + uv: collections.abc.Iterable[builtins.float] | None = ..., + indices: collections.abc.Iterable[builtins.int] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["indices", b"indices", "normals", b"normals", "uv", b"uv", "verts", b"verts"]) -> None: ... + +global___Mesh = Mesh + +@typing.final +class BinaryMesh(google.protobuf.message.Message): + """/ Mesh used for more effective file transfers""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + DATA_FIELD_NUMBER: builtins.int + data: builtins.bytes + """/ BEWARE of ENDIANESS""" + def __init__( + self, + *, + data: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["data", b"data"]) -> None: ... + +global___BinaryMesh = BinaryMesh diff --git a/exporter/SynthesisFusionAddin/src/Proto/joint_pb2.pyi b/exporter/SynthesisFusionAddin/src/Proto/joint_pb2.pyi new file mode 100644 index 0000000000..f716bc2705 --- /dev/null +++ b/exporter/SynthesisFusionAddin/src/Proto/joint_pb2.pyi @@ -0,0 +1,570 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import collections.abc +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import motor_pb2 +import sys +import types_pb2 +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +class _JointMotion: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _JointMotionEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_JointMotion.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + RIGID: _JointMotion.ValueType # 0 + REVOLUTE: _JointMotion.ValueType # 1 + SLIDER: _JointMotion.ValueType # 2 + CYLINDRICAL: _JointMotion.ValueType # 3 + PINSLOT: _JointMotion.ValueType # 4 + PLANAR: _JointMotion.ValueType # 5 + BALL: _JointMotion.ValueType # 6 + CUSTOM: _JointMotion.ValueType # 7 + +class JointMotion(_JointMotion, metaclass=_JointMotionEnumTypeWrapper): + """Describes the joint - Not really sure what to do with this for now - TBD""" + +RIGID: JointMotion.ValueType # 0 +REVOLUTE: JointMotion.ValueType # 1 +SLIDER: JointMotion.ValueType # 2 +CYLINDRICAL: JointMotion.ValueType # 3 +PINSLOT: JointMotion.ValueType # 4 +PLANAR: JointMotion.ValueType # 5 +BALL: JointMotion.ValueType # 6 +CUSTOM: JointMotion.ValueType # 7 +global___JointMotion = JointMotion + +@typing.final +class Joints(google.protobuf.message.Message): + """You can have an Open-Chain robot meaning a single path + You can have a closed chain mechanism or Four-bar (closed loop) + Or multiple paths with closed loop like a stewart platform + + * + Joints + A way to define the motion between various group connections + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing.final + class JointDefinitionsEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> global___Joint: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: global___Joint | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["key", b"key", "value", b"value"]) -> None: ... + + @typing.final + class JointInstancesEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> global___JointInstance: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: global___JointInstance | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["key", b"key", "value", b"value"]) -> None: ... + + @typing.final + class MotorDefinitionsEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> motor_pb2.Motor: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: motor_pb2.Motor | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["key", b"key", "value", b"value"]) -> None: ... + + INFO_FIELD_NUMBER: builtins.int + JOINT_DEFINITIONS_FIELD_NUMBER: builtins.int + JOINT_INSTANCES_FIELD_NUMBER: builtins.int + RIGID_GROUPS_FIELD_NUMBER: builtins.int + MOTOR_DEFINITIONS_FIELD_NUMBER: builtins.int + @property + def info(self) -> types_pb2.Info: + """/ name, version, uid""" + + @property + def joint_definitions(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, global___Joint]: + """/ Unique Joint Implementations""" + + @property + def joint_instances(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, global___JointInstance]: + """/ Instances of the Joint Implementations""" + + @property + def rigid_groups(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___RigidGroup]: + """/ Rigidgroups ?""" + + @property + def motor_definitions(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, motor_pb2.Motor]: + """/ Collection of all Motors exported""" + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + joint_definitions: collections.abc.Mapping[builtins.str, global___Joint] | None = ..., + joint_instances: collections.abc.Mapping[builtins.str, global___JointInstance] | None = ..., + rigid_groups: collections.abc.Iterable[global___RigidGroup] | None = ..., + motor_definitions: collections.abc.Mapping[builtins.str, motor_pb2.Motor] | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["info", b"info"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["info", b"info", "joint_definitions", b"joint_definitions", "joint_instances", b"joint_instances", "motor_definitions", b"motor_definitions", "rigid_groups", b"rigid_groups"]) -> None: ... + +global___Joints = Joints + +@typing.final +class JointInstance(google.protobuf.message.Message): + """* + Instance of a Joint that has a defined motion and limits. + Instancing helps with identifiy closed loop systems. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INFO_FIELD_NUMBER: builtins.int + ISENDEFFECTOR_FIELD_NUMBER: builtins.int + PARENT_PART_FIELD_NUMBER: builtins.int + CHILD_PART_FIELD_NUMBER: builtins.int + JOINT_REFERENCE_FIELD_NUMBER: builtins.int + OFFSET_FIELD_NUMBER: builtins.int + PARTS_FIELD_NUMBER: builtins.int + SIGNAL_REFERENCE_FIELD_NUMBER: builtins.int + MOTION_LINK_FIELD_NUMBER: builtins.int + isEndEffector: builtins.bool + """Is this joint the end effector in the tree ? - might remove this""" + parent_part: builtins.str + """Object that contains the joint - the ID - Part usually""" + child_part: builtins.str + """Object that is affected by the joint - the ID - Part usually""" + joint_reference: builtins.str + """Reference to the Joint Definition""" + signal_reference: builtins.str + """Reference to the Signals as Drivers - use for signal_map in Assembly Data""" + @property + def info(self) -> types_pb2.Info: + """Joint name, ID, version, etc""" + + @property + def offset(self) -> types_pb2.Vector3: + """Offset from Joint Definition Origin""" + + @property + def parts(self) -> types_pb2.GraphContainer: + """Part Instances all contained and affected by this joint directly - tree""" + + @property + def motion_link(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___MotionLink]: + """Motion Links to other joints - ways to preserve motion between dynamic objects""" + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + isEndEffector: builtins.bool = ..., + parent_part: builtins.str = ..., + child_part: builtins.str = ..., + joint_reference: builtins.str = ..., + offset: types_pb2.Vector3 | None = ..., + parts: types_pb2.GraphContainer | None = ..., + signal_reference: builtins.str = ..., + motion_link: collections.abc.Iterable[global___MotionLink] | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["info", b"info", "offset", b"offset", "parts", b"parts"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["child_part", b"child_part", "info", b"info", "isEndEffector", b"isEndEffector", "joint_reference", b"joint_reference", "motion_link", b"motion_link", "offset", b"offset", "parent_part", b"parent_part", "parts", b"parts", "signal_reference", b"signal_reference"]) -> None: ... + +global___JointInstance = JointInstance + +@typing.final +class MotionLink(google.protobuf.message.Message): + """* + Motion Link Feature + Enables the restriction on a joint to a certain range of motion as it is relative to another joint + This is useful for moving parts restricted by belts and gears + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + JOINT_INSTANCE_FIELD_NUMBER: builtins.int + RATIO_FIELD_NUMBER: builtins.int + REVERSED_FIELD_NUMBER: builtins.int + joint_instance: builtins.str + """The Joint that this is linked to""" + ratio: builtins.float + """Ratio of motion between joint 1 and joint 2, we assume this is in mm for linear and deg for rotational""" + reversed: builtins.bool + """Reverse the relationship - turn in the same or opposite directions - useful when moving axis arent both the same way.""" + def __init__( + self, + *, + joint_instance: builtins.str = ..., + ratio: builtins.float = ..., + reversed: builtins.bool = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["joint_instance", b"joint_instance", "ratio", b"ratio", "reversed", b"reversed"]) -> None: ... + +global___MotionLink = MotionLink + +@typing.final +class Joint(google.protobuf.message.Message): + """* + A unqiue implementation of a joint motion + Contains information about motion but not assembly relation + NOTE: A spring motion is a joint with no driver + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INFO_FIELD_NUMBER: builtins.int + ORIGIN_FIELD_NUMBER: builtins.int + JOINT_MOTION_TYPE_FIELD_NUMBER: builtins.int + BREAK_MAGNITUDE_FIELD_NUMBER: builtins.int + ROTATIONAL_FIELD_NUMBER: builtins.int + PRISMATIC_FIELD_NUMBER: builtins.int + CUSTOM_FIELD_NUMBER: builtins.int + USER_DATA_FIELD_NUMBER: builtins.int + MOTOR_REFERENCE_FIELD_NUMBER: builtins.int + joint_motion_type: global___JointMotion.ValueType + """type of motion described by the joint""" + break_magnitude: builtins.float + """At what effort does it come apart at. - leave blank if it doesn't""" + motor_reference: builtins.str + """/ Motor definition reference to lookup in joints collection""" + @property + def info(self) -> types_pb2.Info: + """/ Joint name, ID, version, etc""" + + @property + def origin(self) -> types_pb2.Vector3: + """Transform relative to the parent""" + + @property + def rotational(self) -> global___RotationalJoint: + """/ ONEOF rotational joint""" + + @property + def prismatic(self) -> global___PrismaticJoint: + """/ ONEOF prismatic joint""" + + @property + def custom(self) -> global___CustomJoint: + """/ ONEOF custom joint""" + + @property + def user_data(self) -> types_pb2.UserData: + """/ Additional information someone can query or store relative to your joint.""" + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + origin: types_pb2.Vector3 | None = ..., + joint_motion_type: global___JointMotion.ValueType = ..., + break_magnitude: builtins.float = ..., + rotational: global___RotationalJoint | None = ..., + prismatic: global___PrismaticJoint | None = ..., + custom: global___CustomJoint | None = ..., + user_data: types_pb2.UserData | None = ..., + motor_reference: builtins.str = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["JointMotion", b"JointMotion", "custom", b"custom", "info", b"info", "origin", b"origin", "prismatic", b"prismatic", "rotational", b"rotational", "user_data", b"user_data"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["JointMotion", b"JointMotion", "break_magnitude", b"break_magnitude", "custom", b"custom", "info", b"info", "joint_motion_type", b"joint_motion_type", "motor_reference", b"motor_reference", "origin", b"origin", "prismatic", b"prismatic", "rotational", b"rotational", "user_data", b"user_data"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["JointMotion", b"JointMotion"]) -> typing.Literal["rotational", "prismatic", "custom"] | None: ... + +global___Joint = Joint + +@typing.final +class Dynamics(google.protobuf.message.Message): + """* + Dynamics specify the mechanical effects on the motion. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + DAMPING_FIELD_NUMBER: builtins.int + FRICTION_FIELD_NUMBER: builtins.int + damping: builtins.float + """/ Damping effect on a given joint motion""" + friction: builtins.float + """/ Friction effect on a given joint motion""" + def __init__( + self, + *, + damping: builtins.float = ..., + friction: builtins.float = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["damping", b"damping", "friction", b"friction"]) -> None: ... + +global___Dynamics = Dynamics + +@typing.final +class Limits(google.protobuf.message.Message): + """* + Limits specify the mechanical range of a given joint. + + TODO: Add units + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + LOWER_FIELD_NUMBER: builtins.int + UPPER_FIELD_NUMBER: builtins.int + VELOCITY_FIELD_NUMBER: builtins.int + EFFORT_FIELD_NUMBER: builtins.int + lower: builtins.float + """/ Lower Limit corresponds to default displacement""" + upper: builtins.float + """/ Upper Limit is the joint extent""" + velocity: builtins.float + """/ Velocity Max in m/s^2 (angular for rotational)""" + effort: builtins.float + """/ Effort is the absolute force a joint can apply for a given instant - ROS has a great article on it http://wiki.ros.org/pr2_controller_manager/safety_limits""" + def __init__( + self, + *, + lower: builtins.float = ..., + upper: builtins.float = ..., + velocity: builtins.float = ..., + effort: builtins.float = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["effort", b"effort", "lower", b"lower", "upper", b"upper", "velocity", b"velocity"]) -> None: ... + +global___Limits = Limits + +@typing.final +class Safety(google.protobuf.message.Message): + """* + Safety switch configuration for a given joint. + Can usefully indicate a bounds issue. + Inspired by the URDF implementation. + + This should really just be created by the controller. + http://wiki.ros.org/pr2_controller_manager/safety_limits + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + LOWER_LIMIT_FIELD_NUMBER: builtins.int + UPPER_LIMIT_FIELD_NUMBER: builtins.int + K_POSITION_FIELD_NUMBER: builtins.int + K_VELOCITY_FIELD_NUMBER: builtins.int + lower_limit: builtins.float + """/ Lower software limit""" + upper_limit: builtins.float + """/ Upper Software limit""" + k_position: builtins.float + """/ Relation between position and velocity limit""" + k_velocity: builtins.float + """/ Relation between effort and velocity limit""" + def __init__( + self, + *, + lower_limit: builtins.float = ..., + upper_limit: builtins.float = ..., + k_position: builtins.float = ..., + k_velocity: builtins.float = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["k_position", b"k_position", "k_velocity", b"k_velocity", "lower_limit", b"lower_limit", "upper_limit", b"upper_limit"]) -> None: ... + +global___Safety = Safety + +@typing.final +class DOF(google.protobuf.message.Message): + """* + DOF - representing the construction of a joint motion + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + NAME_FIELD_NUMBER: builtins.int + AXIS_FIELD_NUMBER: builtins.int + PIVOTDIRECTION_FIELD_NUMBER: builtins.int + DYNAMICS_FIELD_NUMBER: builtins.int + LIMITS_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + name: builtins.str + """/ In case you want to name this degree of freedom""" + pivotDirection: types_pb2.Axis.ValueType + """/ Direction the axis vector is offset from - this has an incorrect naming scheme""" + value: builtins.float + """/ Current value of the DOF""" + @property + def axis(self) -> types_pb2.Vector3: + """/ Axis the degree of freedom is pivoting by""" + + @property + def dynamics(self) -> global___Dynamics: + """/ Dynamic properties of this joint pivot""" + + @property + def limits(self) -> global___Limits: + """/ Limits of this freedom""" + + def __init__( + self, + *, + name: builtins.str = ..., + axis: types_pb2.Vector3 | None = ..., + pivotDirection: types_pb2.Axis.ValueType = ..., + dynamics: global___Dynamics | None = ..., + limits: global___Limits | None = ..., + value: builtins.float = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["axis", b"axis", "dynamics", b"dynamics", "limits", b"limits"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["axis", b"axis", "dynamics", b"dynamics", "limits", b"limits", "name", b"name", "pivotDirection", b"pivotDirection", "value", b"value"]) -> None: ... + +global___DOF = DOF + +@typing.final +class CustomJoint(google.protobuf.message.Message): + """* + CustomJoint is a joint with N degrees of freedom specified. + There should be input validation to handle max freedom case. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + DOFS_FIELD_NUMBER: builtins.int + @property + def dofs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___DOF]: + """/ A list of degrees of freedom that the joint can contain""" + + def __init__( + self, + *, + dofs: collections.abc.Iterable[global___DOF] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["dofs", b"dofs"]) -> None: ... + +global___CustomJoint = CustomJoint + +@typing.final +class RotationalJoint(google.protobuf.message.Message): + """* + RotationalJoint describes a joint with rotational translation. + This is the exact same as prismatic for now. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ROTATIONAL_FREEDOM_FIELD_NUMBER: builtins.int + @property + def rotational_freedom(self) -> global___DOF: ... + def __init__( + self, + *, + rotational_freedom: global___DOF | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["rotational_freedom", b"rotational_freedom"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["rotational_freedom", b"rotational_freedom"]) -> None: ... + +global___RotationalJoint = RotationalJoint + +@typing.final +class BallJoint(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + YAW_FIELD_NUMBER: builtins.int + PITCH_FIELD_NUMBER: builtins.int + ROTATION_FIELD_NUMBER: builtins.int + @property + def yaw(self) -> global___DOF: ... + @property + def pitch(self) -> global___DOF: ... + @property + def rotation(self) -> global___DOF: ... + def __init__( + self, + *, + yaw: global___DOF | None = ..., + pitch: global___DOF | None = ..., + rotation: global___DOF | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["pitch", b"pitch", "rotation", b"rotation", "yaw", b"yaw"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["pitch", b"pitch", "rotation", b"rotation", "yaw", b"yaw"]) -> None: ... + +global___BallJoint = BallJoint + +@typing.final +class PrismaticJoint(google.protobuf.message.Message): + """* + Prismatic Joint describes a motion that translates the position in a single axis + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PRISMATIC_FREEDOM_FIELD_NUMBER: builtins.int + @property + def prismatic_freedom(self) -> global___DOF: ... + def __init__( + self, + *, + prismatic_freedom: global___DOF | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["prismatic_freedom", b"prismatic_freedom"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["prismatic_freedom", b"prismatic_freedom"]) -> None: ... + +global___PrismaticJoint = PrismaticJoint + +@typing.final +class RigidGroup(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + NAME_FIELD_NUMBER: builtins.int + OCCURRENCES_FIELD_NUMBER: builtins.int + name: builtins.str + @property + def occurrences(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: + """this could be the full path of the occurrence in order to make it easier to assembly them possibly - just parse on the unity side""" + + def __init__( + self, + *, + name: builtins.str = ..., + occurrences: collections.abc.Iterable[builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["name", b"name", "occurrences", b"occurrences"]) -> None: ... + +global___RigidGroup = RigidGroup diff --git a/exporter/SynthesisFusionAddin/src/Proto/material_pb2.pyi b/exporter/SynthesisFusionAddin/src/Proto/material_pb2.pyi new file mode 100644 index 0000000000..8d81262376 --- /dev/null +++ b/exporter/SynthesisFusionAddin/src/Proto/material_pb2.pyi @@ -0,0 +1,302 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import collections.abc +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import sys +import types_pb2 +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class Materials(google.protobuf.message.Message): + """* + Represents a File or Set of Materials with Appearances and Physical Data + + Can be Stored in AssemblyData + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing.final + class PhysicalMaterialsEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> global___PhysicalMaterial: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: global___PhysicalMaterial | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["key", b"key", "value", b"value"]) -> None: ... + + @typing.final + class AppearancesEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> global___Appearance: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: global___Appearance | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["key", b"key", "value", b"value"]) -> None: ... + + INFO_FIELD_NUMBER: builtins.int + PHYSICALMATERIALS_FIELD_NUMBER: builtins.int + APPEARANCES_FIELD_NUMBER: builtins.int + @property + def info(self) -> types_pb2.Info: + """/ Identifiable information (id, name, version)""" + + @property + def physicalMaterials(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, global___PhysicalMaterial]: + """/ Map of Physical Materials""" + + @property + def appearances(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, global___Appearance]: + """/ Map of Appearances that are purely visual""" + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + physicalMaterials: collections.abc.Mapping[builtins.str, global___PhysicalMaterial] | None = ..., + appearances: collections.abc.Mapping[builtins.str, global___Appearance] | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["info", b"info"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["appearances", b"appearances", "info", b"info", "physicalMaterials", b"physicalMaterials"]) -> None: ... + +global___Materials = Materials + +@typing.final +class Appearance(google.protobuf.message.Message): + """* + Contains information on how a object looks + Limited to just color for now + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INFO_FIELD_NUMBER: builtins.int + ALBEDO_FIELD_NUMBER: builtins.int + ROUGHNESS_FIELD_NUMBER: builtins.int + METALLIC_FIELD_NUMBER: builtins.int + SPECULAR_FIELD_NUMBER: builtins.int + roughness: builtins.float + """/ roughness value 0-1""" + metallic: builtins.float + """/ metallic value 0-1""" + specular: builtins.float + """/ specular value 0-1""" + @property + def info(self) -> types_pb2.Info: + """/ Identfiable information (id, name, version)""" + + @property + def albedo(self) -> types_pb2.Color: + """/ albedo map RGBA 0-255""" + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + albedo: types_pb2.Color | None = ..., + roughness: builtins.float = ..., + metallic: builtins.float = ..., + specular: builtins.float = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["albedo", b"albedo", "info", b"info"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["albedo", b"albedo", "info", b"info", "metallic", b"metallic", "roughness", b"roughness", "specular", b"specular"]) -> None: ... + +global___Appearance = Appearance + +@typing.final +class PhysicalMaterial(google.protobuf.message.Message): + """* + Data to represent any given Physical Material + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _MaterialType: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _MaterialTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[PhysicalMaterial._MaterialType.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + METAL: PhysicalMaterial._MaterialType.ValueType # 0 + PLASTIC: PhysicalMaterial._MaterialType.ValueType # 1 + + class MaterialType(_MaterialType, metaclass=_MaterialTypeEnumTypeWrapper): ... + METAL: PhysicalMaterial.MaterialType.ValueType # 0 + PLASTIC: PhysicalMaterial.MaterialType.ValueType # 1 + + @typing.final + class Thermal(google.protobuf.message.Message): + """* + Thermal Properties Set Definition for Simulation. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + THERMAL_CONDUCTIVITY_FIELD_NUMBER: builtins.int + SPECIFIC_HEAT_FIELD_NUMBER: builtins.int + THERMAL_EXPANSION_COEFFICIENT_FIELD_NUMBER: builtins.int + thermal_conductivity: builtins.float + """/ W/(m*K)""" + specific_heat: builtins.float + """/ J/(g*C)""" + thermal_expansion_coefficient: builtins.float + """/ um/(m*C)""" + def __init__( + self, + *, + thermal_conductivity: builtins.float = ..., + specific_heat: builtins.float = ..., + thermal_expansion_coefficient: builtins.float = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["specific_heat", b"specific_heat", "thermal_conductivity", b"thermal_conductivity", "thermal_expansion_coefficient", b"thermal_expansion_coefficient"]) -> None: ... + + @typing.final + class Mechanical(google.protobuf.message.Message): + """* + Mechanical Properties Set Definition for Simulation. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + YOUNG_MOD_FIELD_NUMBER: builtins.int + POISSON_RATIO_FIELD_NUMBER: builtins.int + SHEAR_MOD_FIELD_NUMBER: builtins.int + DENSITY_FIELD_NUMBER: builtins.int + DAMPING_COEFFICIENT_FIELD_NUMBER: builtins.int + young_mod: builtins.float + """naming scheme changes here + / GPa + """ + poisson_ratio: builtins.float + """/ ?""" + shear_mod: builtins.float + """/ MPa""" + density: builtins.float + """/ g/cm^3""" + damping_coefficient: builtins.float + """/ ?""" + def __init__( + self, + *, + young_mod: builtins.float = ..., + poisson_ratio: builtins.float = ..., + shear_mod: builtins.float = ..., + density: builtins.float = ..., + damping_coefficient: builtins.float = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["damping_coefficient", b"damping_coefficient", "density", b"density", "poisson_ratio", b"poisson_ratio", "shear_mod", b"shear_mod", "young_mod", b"young_mod"]) -> None: ... + + @typing.final + class Strength(google.protobuf.message.Message): + """* + Strength Properties Set Definition for Simulation. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + YIELD_STRENGTH_FIELD_NUMBER: builtins.int + TENSILE_STRENGTH_FIELD_NUMBER: builtins.int + THERMAL_TREATMENT_FIELD_NUMBER: builtins.int + yield_strength: builtins.float + """/ MPa""" + tensile_strength: builtins.float + """/ MPa""" + thermal_treatment: builtins.bool + """/ yes / no""" + def __init__( + self, + *, + yield_strength: builtins.float = ..., + tensile_strength: builtins.float = ..., + thermal_treatment: builtins.bool = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["tensile_strength", b"tensile_strength", "thermal_treatment", b"thermal_treatment", "yield_strength", b"yield_strength"]) -> None: ... + + INFO_FIELD_NUMBER: builtins.int + DESCRIPTION_FIELD_NUMBER: builtins.int + THERMAL_FIELD_NUMBER: builtins.int + MECHANICAL_FIELD_NUMBER: builtins.int + STRENGTH_FIELD_NUMBER: builtins.int + DYNAMIC_FRICTION_FIELD_NUMBER: builtins.int + STATIC_FRICTION_FIELD_NUMBER: builtins.int + RESTITUTION_FIELD_NUMBER: builtins.int + DEFORMABLE_FIELD_NUMBER: builtins.int + MATTYPE_FIELD_NUMBER: builtins.int + description: builtins.str + """/ short description of physical material""" + dynamic_friction: builtins.float + """/ Frictional force for dampening - Interpolate (0-1)""" + static_friction: builtins.float + """/ Frictional force override at stop - Interpolate (0-1)""" + restitution: builtins.float + """/ Restitution of the object - Interpolate (0-1)""" + deformable: builtins.bool + """/ should this object deform when encountering large forces - TODO: This needs a proper message and equation field""" + matType: global___PhysicalMaterial.MaterialType.ValueType + """/ generic type to assign some default params""" + @property + def info(self) -> types_pb2.Info: + """/ Identifiable information (id, name, version, etc)""" + + @property + def thermal(self) -> global___PhysicalMaterial.Thermal: + """/ Thermal Physical properties of the model OPTIONAL""" + + @property + def mechanical(self) -> global___PhysicalMaterial.Mechanical: + """/ Mechanical properties of the model OPTIONAL""" + + @property + def strength(self) -> global___PhysicalMaterial.Strength: + """/ Physical Strength properties of the model OPTIONAL""" + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + description: builtins.str = ..., + thermal: global___PhysicalMaterial.Thermal | None = ..., + mechanical: global___PhysicalMaterial.Mechanical | None = ..., + strength: global___PhysicalMaterial.Strength | None = ..., + dynamic_friction: builtins.float = ..., + static_friction: builtins.float = ..., + restitution: builtins.float = ..., + deformable: builtins.bool = ..., + matType: global___PhysicalMaterial.MaterialType.ValueType = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["info", b"info", "mechanical", b"mechanical", "strength", b"strength", "thermal", b"thermal"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["deformable", b"deformable", "description", b"description", "dynamic_friction", b"dynamic_friction", "info", b"info", "matType", b"matType", "mechanical", b"mechanical", "restitution", b"restitution", "static_friction", b"static_friction", "strength", b"strength", "thermal", b"thermal"]) -> None: ... + +global___PhysicalMaterial = PhysicalMaterial diff --git a/exporter/SynthesisFusionAddin/src/Proto/motor_pb2.pyi b/exporter/SynthesisFusionAddin/src/Proto/motor_pb2.pyi new file mode 100644 index 0000000000..6131aeb69f --- /dev/null +++ b/exporter/SynthesisFusionAddin/src/Proto/motor_pb2.pyi @@ -0,0 +1,203 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import google.protobuf.descriptor +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import sys +import types_pb2 +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +class _DutyCycles: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _DutyCyclesEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_DutyCycles.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + CONTINUOUS_RUNNING: _DutyCycles.ValueType # 0 + """/ S1""" + SHORT_TIME: _DutyCycles.ValueType # 1 + """/ S2""" + INTERMITTENT_PERIODIC: _DutyCycles.ValueType # 2 + """/ S3""" + CONTINUOUS_PERIODIC: _DutyCycles.ValueType # 3 + """/ S6 Continuous Operation with Periodic Duty""" + +class DutyCycles(_DutyCycles, metaclass=_DutyCyclesEnumTypeWrapper): + """* + Duty Cycles for electric motors + Affects the dynamic output of the motor + https://www.news.benevelli-group.com/index.php/en/88-what-motor-duty-cycle.html + These each have associated data we are not going to use right now + """ + +CONTINUOUS_RUNNING: DutyCycles.ValueType # 0 +"""/ S1""" +SHORT_TIME: DutyCycles.ValueType # 1 +"""/ S2""" +INTERMITTENT_PERIODIC: DutyCycles.ValueType # 2 +"""/ S3""" +CONTINUOUS_PERIODIC: DutyCycles.ValueType # 3 +"""/ S6 Continuous Operation with Periodic Duty""" +global___DutyCycles = DutyCycles + +@typing.final +class Motor(google.protobuf.message.Message): + """* + A Motor should determine the relationship between an input and joint motion + Could represent something like a DC Motor relationship + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INFO_FIELD_NUMBER: builtins.int + DC_MOTOR_FIELD_NUMBER: builtins.int + SIMPLE_MOTOR_FIELD_NUMBER: builtins.int + @property + def info(self) -> types_pb2.Info: ... + @property + def dc_motor(self) -> global___DCMotor: ... + @property + def simple_motor(self) -> global___SimpleMotor: ... + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + dc_motor: global___DCMotor | None = ..., + simple_motor: global___SimpleMotor | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["dc_motor", b"dc_motor", "info", b"info", "motor_type", b"motor_type", "simple_motor", b"simple_motor"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["dc_motor", b"dc_motor", "info", b"info", "motor_type", b"motor_type", "simple_motor", b"simple_motor"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["motor_type", b"motor_type"]) -> typing.Literal["dc_motor", "simple_motor"] | None: ... + +global___Motor = Motor + +@typing.final +class SimpleMotor(google.protobuf.message.Message): + """* + SimpleMotor Configuration + Very easy motor used to simulate joints without specifying a real motor + Can set braking_constant - stall_torque - and max_velocity + Assumes you are solving using a velocity constraint for a joint and not a acceleration constraint + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + STALL_TORQUE_FIELD_NUMBER: builtins.int + MAX_VELOCITY_FIELD_NUMBER: builtins.int + BRAKING_CONSTANT_FIELD_NUMBER: builtins.int + stall_torque: builtins.float + """/ Torque at 0 rpm with a inverse linear relationship to max_velocity""" + max_velocity: builtins.float + """/ The target velocity in RPM, will use stall_torque relationship to reach each step""" + braking_constant: builtins.float + """/ (Optional) 0 - 1, the relationship of stall_torque used to perserve the position of this motor""" + def __init__( + self, + *, + stall_torque: builtins.float = ..., + max_velocity: builtins.float = ..., + braking_constant: builtins.float = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["braking_constant", b"braking_constant", "max_velocity", b"max_velocity", "stall_torque", b"stall_torque"]) -> None: ... + +global___SimpleMotor = SimpleMotor + +@typing.final +class DCMotor(google.protobuf.message.Message): + """* + DCMotor Configuration + Parameters to simulate a DC Electric Motor + Still needs some more but overall they are most of the parameters we can use + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing.final + class Advanced(google.protobuf.message.Message): + """/ Information usually found on datasheet""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + FREE_CURRENT_FIELD_NUMBER: builtins.int + FREE_SPEED_FIELD_NUMBER: builtins.int + STALL_CURRENT_FIELD_NUMBER: builtins.int + STALL_TORQUE_FIELD_NUMBER: builtins.int + INPUT_VOLTAGE_FIELD_NUMBER: builtins.int + RESISTANCE_VARIATION_FIELD_NUMBER: builtins.int + free_current: builtins.float + """/ measured in AMPs""" + free_speed: builtins.int + """/ measured in RPM""" + stall_current: builtins.float + """/ measure in AMPs""" + stall_torque: builtins.float + """/ measured in Nm""" + input_voltage: builtins.int + """/ measured in Volts DC""" + resistance_variation: builtins.float + """/ between (K * (N / 4)) and (K * ((N-2) / 4)) where N is number of poles - leave at 0 if unknown""" + def __init__( + self, + *, + free_current: builtins.float = ..., + free_speed: builtins.int = ..., + stall_current: builtins.float = ..., + stall_torque: builtins.float = ..., + input_voltage: builtins.int = ..., + resistance_variation: builtins.float = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["free_current", b"free_current", "free_speed", b"free_speed", "input_voltage", b"input_voltage", "resistance_variation", b"resistance_variation", "stall_current", b"stall_current", "stall_torque", b"stall_torque"]) -> None: ... + + REFERENCE_URL_FIELD_NUMBER: builtins.int + TORQUE_CONSTANT_FIELD_NUMBER: builtins.int + EMF_CONSTANT_FIELD_NUMBER: builtins.int + RESISTANCE_FIELD_NUMBER: builtins.int + MAXIMUM_EFFECIENCY_FIELD_NUMBER: builtins.int + MAXIMUM_POWER_FIELD_NUMBER: builtins.int + DUTY_CYCLE_FIELD_NUMBER: builtins.int + ADVANCED_FIELD_NUMBER: builtins.int + reference_url: builtins.str + """/ Reference for purchase page or spec sheet""" + torque_constant: builtins.float + """/ m-Nm/Amp""" + emf_constant: builtins.float + """/ mV/rad/sec""" + resistance: builtins.float + """/ Resistance of Motor - Optional if other values are known""" + maximum_effeciency: builtins.int + """/ measure in percentage of 100 - generally around 60 - measured under optimal load""" + maximum_power: builtins.int + """/ measured in Watts""" + duty_cycle: global___DutyCycles.ValueType + """/ Stated Duty Cycle of motor""" + @property + def advanced(self) -> global___DCMotor.Advanced: + """/ Optional data that can give a better relationship to the simulation""" + + def __init__( + self, + *, + reference_url: builtins.str = ..., + torque_constant: builtins.float = ..., + emf_constant: builtins.float = ..., + resistance: builtins.float = ..., + maximum_effeciency: builtins.int = ..., + maximum_power: builtins.int = ..., + duty_cycle: global___DutyCycles.ValueType = ..., + advanced: global___DCMotor.Advanced | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["advanced", b"advanced"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["advanced", b"advanced", "duty_cycle", b"duty_cycle", "emf_constant", b"emf_constant", "maximum_effeciency", b"maximum_effeciency", "maximum_power", b"maximum_power", "reference_url", b"reference_url", "resistance", b"resistance", "torque_constant", b"torque_constant"]) -> None: ... + +global___DCMotor = DCMotor diff --git a/exporter/SynthesisFusionAddin/src/Proto/signal_pb2.pyi b/exporter/SynthesisFusionAddin/src/Proto/signal_pb2.pyi new file mode 100644 index 0000000000..0befac8035 --- /dev/null +++ b/exporter/SynthesisFusionAddin/src/Proto/signal_pb2.pyi @@ -0,0 +1,159 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import collections.abc +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import sys +import types_pb2 +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +class _IOType: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _IOTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_IOType.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + INPUT: _IOType.ValueType # 0 + """/ Input Signal""" + OUTPUT: _IOType.ValueType # 1 + """/ Output Signal""" + +class IOType(_IOType, metaclass=_IOTypeEnumTypeWrapper): + """* + IOType is a way to specify Input or Output. + """ + +INPUT: IOType.ValueType # 0 +"""/ Input Signal""" +OUTPUT: IOType.ValueType # 1 +"""/ Output Signal""" +global___IOType = IOType + +class _DeviceType: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _DeviceTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_DeviceType.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + PWM: _DeviceType.ValueType # 0 + Digital: _DeviceType.ValueType # 1 + Analog: _DeviceType.ValueType # 2 + I2C: _DeviceType.ValueType # 3 + CANBUS: _DeviceType.ValueType # 4 + CUSTOM: _DeviceType.ValueType # 5 + +class DeviceType(_DeviceType, metaclass=_DeviceTypeEnumTypeWrapper): + """* + DeviceType needs to be a type of device that has a supported connection + As well as a signal frmae but that can come later + """ + +PWM: DeviceType.ValueType # 0 +Digital: DeviceType.ValueType # 1 +Analog: DeviceType.ValueType # 2 +I2C: DeviceType.ValueType # 3 +CANBUS: DeviceType.ValueType # 4 +CUSTOM: DeviceType.ValueType # 5 +global___DeviceType = DeviceType + +@typing.final +class Signals(google.protobuf.message.Message): + """* + Signals is a container for all of the potential signals. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing.final + class SignalMapEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> global___Signal: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: global___Signal | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["key", b"key", "value", b"value"]) -> None: ... + + INFO_FIELD_NUMBER: builtins.int + SIGNAL_MAP_FIELD_NUMBER: builtins.int + @property + def info(self) -> types_pb2.Info: + """/ Has identifiable data (id, name, version)""" + + @property + def signal_map(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, global___Signal]: + """/ Contains a full collection of symbols""" + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + signal_map: collections.abc.Mapping[builtins.str, global___Signal] | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["info", b"info"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["info", b"info", "signal_map", b"signal_map"]) -> None: ... + +global___Signals = Signals + +@typing.final +class Signal(google.protobuf.message.Message): + """* + Signal is a way to define a controlling signal. + + TODO: Add Origin + TODO: Decide how this is linked to a exported object + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INFO_FIELD_NUMBER: builtins.int + IO_FIELD_NUMBER: builtins.int + CUSTOM_TYPE_FIELD_NUMBER: builtins.int + SIGNAL_ID_FIELD_NUMBER: builtins.int + DEVICE_TYPE_FIELD_NUMBER: builtins.int + io: global___IOType.ValueType + """/ Is this a Input or Output""" + custom_type: builtins.str + """/ The name of a custom input type that is not listed as a device type""" + signal_id: builtins.int + """/ ID for a given signal that exists... PWM 2, CANBUS 4""" + device_type: global___DeviceType.ValueType + """/ Enum for device type that should always be set""" + @property + def info(self) -> types_pb2.Info: + """/ Has identifiable data (id, name, version)""" + + def __init__( + self, + *, + info: types_pb2.Info | None = ..., + io: global___IOType.ValueType = ..., + custom_type: builtins.str = ..., + signal_id: builtins.int = ..., + device_type: global___DeviceType.ValueType = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["info", b"info"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["custom_type", b"custom_type", "device_type", b"device_type", "info", b"info", "io", b"io", "signal_id", b"signal_id"]) -> None: ... + +global___Signal = Signal diff --git a/exporter/SynthesisFusionAddin/src/Proto/types_pb2.pyi b/exporter/SynthesisFusionAddin/src/Proto/types_pb2.pyi new file mode 100644 index 0000000000..4463567347 --- /dev/null +++ b/exporter/SynthesisFusionAddin/src/Proto/types_pb2.pyi @@ -0,0 +1,315 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +Common data type implementations +Intended to be re-used +""" + +import builtins +import collections.abc +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import sys +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +class _Axis: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _AxisEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_Axis.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + X: _Axis.ValueType # 0 + Y: _Axis.ValueType # 1 + Z: _Axis.ValueType # 2 + +class Axis(_Axis, metaclass=_AxisEnumTypeWrapper): + """Axis Enum""" + +X: Axis.ValueType # 0 +Y: Axis.ValueType # 1 +Z: Axis.ValueType # 2 +global___Axis = Axis + +@typing.final +class Node(google.protobuf.message.Message): + """Each proper object within the Graph - First one is Root""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + VALUE_FIELD_NUMBER: builtins.int + CHILDREN_FIELD_NUMBER: builtins.int + USER_DATA_FIELD_NUMBER: builtins.int + value: builtins.str + """/ the reference ID for whatever kind of graph this is""" + @property + def children(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Node]: + """/ the children for the given leaf""" + + @property + def user_data(self) -> global___UserData: + """/ other associated data that can be used""" + + def __init__( + self, + *, + value: builtins.str = ..., + children: collections.abc.Iterable[global___Node] | None = ..., + user_data: global___UserData | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["user_data", b"user_data"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["children", b"children", "user_data", b"user_data", "value", b"value"]) -> None: ... + +global___Node = Node + +@typing.final +class GraphContainer(google.protobuf.message.Message): + """Top level GraphContainer + Contains all Graph element roots within + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + NODES_FIELD_NUMBER: builtins.int + @property + def nodes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Node]: + """represents the root of each seperate assembly - most of the time 1 node""" + + def __init__( + self, + *, + nodes: collections.abc.Iterable[global___Node] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["nodes", b"nodes"]) -> None: ... + +global___GraphContainer = GraphContainer + +@typing.final +class UserData(google.protobuf.message.Message): + """* + UserData + + Arbitrary data to append to a given message in map form + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing.final + class DataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["key", b"key", "value", b"value"]) -> None: ... + + DATA_FIELD_NUMBER: builtins.int + @property + def data(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """/ e.g. data["wheel"] = "yes" """ + + def __init__( + self, + *, + data: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["data", b"data"]) -> None: ... + +global___UserData = UserData + +@typing.final +class Vector3(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + X_FIELD_NUMBER: builtins.int + Y_FIELD_NUMBER: builtins.int + Z_FIELD_NUMBER: builtins.int + x: builtins.float + y: builtins.float + z: builtins.float + def __init__( + self, + *, + x: builtins.float = ..., + y: builtins.float = ..., + z: builtins.float = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["x", b"x", "y", b"y", "z", b"z"]) -> None: ... + +global___Vector3 = Vector3 + +@typing.final +class PhysicalProperties(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + DENSITY_FIELD_NUMBER: builtins.int + MASS_FIELD_NUMBER: builtins.int + VOLUME_FIELD_NUMBER: builtins.int + AREA_FIELD_NUMBER: builtins.int + COM_FIELD_NUMBER: builtins.int + density: builtins.float + """/ kg per cubic cm kg/(cm^3)""" + mass: builtins.float + """/ kg""" + volume: builtins.float + """/ cm^3""" + area: builtins.float + """/ cm^2""" + @property + def com(self) -> global___Vector3: + """/ non-negative? Vec3""" + + def __init__( + self, + *, + density: builtins.float = ..., + mass: builtins.float = ..., + volume: builtins.float = ..., + area: builtins.float = ..., + com: global___Vector3 | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["com", b"com"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["area", b"area", "com", b"com", "density", b"density", "mass", b"mass", "volume", b"volume"]) -> None: ... + +global___PhysicalProperties = PhysicalProperties + +@typing.final +class Transform(google.protobuf.message.Message): + """* + Transform + + Data needed to apply scale, position, and rotational changes + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + SPATIAL_MATRIX_FIELD_NUMBER: builtins.int + @property + def spatial_matrix(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.float]: + """ + flat map of 4x4 transform matrix + [00][01][02][03][10][11][12][13][20][21][22][23] + """ + + def __init__( + self, + *, + spatial_matrix: collections.abc.Iterable[builtins.float] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["spatial_matrix", b"spatial_matrix"]) -> None: ... + +global___Transform = Transform + +@typing.final +class Color(google.protobuf.message.Message): + """RGBA in expanded form 0-255""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + R_FIELD_NUMBER: builtins.int + G_FIELD_NUMBER: builtins.int + B_FIELD_NUMBER: builtins.int + A_FIELD_NUMBER: builtins.int + R: builtins.int + """red""" + G: builtins.int + """green""" + B: builtins.int + """blue""" + A: builtins.int + """alpha""" + def __init__( + self, + *, + R: builtins.int = ..., + G: builtins.int = ..., + B: builtins.int = ..., + A: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["A", b"A", "B", b"B", "G", b"G", "R", b"R"]) -> None: ... + +global___Color = Color + +@typing.final +class Info(google.protobuf.message.Message): + """* + Defines basic fields for almost all objects + The location where you can access the GUID for a reference + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + GUID_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + VERSION_FIELD_NUMBER: builtins.int + GUID: builtins.str + """GUID unique value - must always be defined + since guid's have exactly 128bits could be represented with bytes[] + however endian becomes an issue + """ + name: builtins.str + """Generic readable name""" + version: builtins.int + """Version of object iteration""" + def __init__( + self, + *, + GUID: builtins.str = ..., + name: builtins.str = ..., + version: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["GUID", b"GUID", "name", b"name", "version", b"version"]) -> None: ... + +global___Info = Info + +@typing.final +class Thumbnail(google.protobuf.message.Message): + """* + A basic Thumbnail to be encoded in the file + Most of the Time Fusion can encode the file with transparency as PNG not bitmap + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + WIDTH_FIELD_NUMBER: builtins.int + HEIGHT_FIELD_NUMBER: builtins.int + EXTENSION_FIELD_NUMBER: builtins.int + TRANSPARENT_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + width: builtins.int + """/ Image Width""" + height: builtins.int + """/ Image Height""" + extension: builtins.str + """/ Image Extension - ex. (.png, .bitmap, .jpeg)""" + transparent: builtins.bool + """/ Transparency - true from fusion when correctly configured""" + data: builtins.bytes + """/ Data as read from the file in bytes[] form""" + def __init__( + self, + *, + width: builtins.int = ..., + height: builtins.int = ..., + extension: builtins.str = ..., + transparent: builtins.bool = ..., + data: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["data", b"data", "extension", b"extension", "height", b"height", "transparent", b"transparent", "width", b"width"]) -> None: ... + +global___Thumbnail = Thumbnail diff --git a/exporter/SynthesisFusionAddin/src/Types.py b/exporter/SynthesisFusionAddin/src/Types.py index d8215d33b2..5e2d7c967f 100644 --- a/exporter/SynthesisFusionAddin/src/Types.py +++ b/exporter/SynthesisFusionAddin/src/Types.py @@ -1,11 +1,9 @@ import os import pathlib import platform -from dataclasses import dataclass, field, fields, is_dataclass +from dataclasses import MISSING, dataclass, field, fields, is_dataclass from enum import Enum, EnumType -from typing import Literal, TypeAlias, Union, get_origin - -OperatingSystemString: TypeAlias = Literal["Windows", "Darwin", "Linux"] +from typing import Any, TypeAlias, get_args, get_origin # Not 100% sure what this is for - Brandon JointParentType = Enum("JointParentType", ["ROOT", "END"]) @@ -19,18 +17,18 @@ @dataclass class Wheel: - jointToken: str = field(default=None) - wheelType: WheelType = field(default=None) - signalType: SignalType = field(default=None) + jointToken: str = field(default="") + wheelType: WheelType = field(default=WheelType.STANDARD) + signalType: SignalType = field(default=SignalType.PWM) @dataclass class Joint: - jointToken: str = field(default=None) - parent: JointParentType = field(default=None) - signalType: SignalType = field(default=None) - speed: float = field(default=None) - force: float = field(default=None) + jointToken: str = field(default="") + parent: JointParentType = field(default=JointParentType.ROOT) + signalType: SignalType = field(default=SignalType.PWM) + speed: float = field(default=float("-inf")) + force: float = field(default=float("-inf")) # Transition: AARD-1865 # Should consider changing how the parser handles wheels and joints as there is overlap between @@ -41,9 +39,9 @@ class Joint: @dataclass class Gamepiece: - occurrenceToken: str = field(default=None) - weight: float = field(default=None) - friction: float = field(default=None) + occurrenceToken: str = field(default="") + weight: float = field(default=float("-inf")) + friction: float = field(default=float("-inf")) class PhysicalDepth(Enum): @@ -74,26 +72,22 @@ class ModelHierarchy(Enum): SingleMesh = 3 -class LBS(float): - """Mass Unit in Pounds.""" - - -class KG(float): - """Mass Unit in Kilograms.""" +LBS: TypeAlias = float +KG: TypeAlias = float -def toLbs(kgs: float) -> LBS: +def toLbs(kgs: KG) -> LBS: return LBS(round(kgs * 2.2062, 2)) -def toKg(pounds: float) -> KG: +def toKg(pounds: LBS) -> KG: return KG(round(pounds / 2.2062, 2)) PRIMITIVES = (bool, str, int, float, type(None)) -def encodeNestedObjects(obj: any) -> any: +def encodeNestedObjects(obj: Any) -> Any: if isinstance(obj, Enum): return obj.value elif hasattr(obj, "__dict__"): @@ -103,13 +97,13 @@ def encodeNestedObjects(obj: any) -> any: return obj -def makeObjectFromJson(objType: type, data: any) -> any: +def makeObjectFromJson(objType: type, data: Any) -> Any: if isinstance(objType, EnumType): return objType(data) elif isinstance(objType, PRIMITIVES) or isinstance(data, PRIMITIVES): return data elif get_origin(objType) is list: - return [makeObjectFromJson(objType.__args__[0], item) for item in data] + return [makeObjectFromJson(get_args(objType)[0], item) for item in data] obj = objType() assert is_dataclass(obj) and isinstance(data, dict), "Found unsupported type to decode." @@ -117,13 +111,13 @@ def makeObjectFromJson(objType: type, data: any) -> any: if field.name in data: setattr(obj, field.name, makeObjectFromJson(field.type, data[field.name])) else: - setattr(obj, field.name, field.default) + setattr(obj, field.name, field.default_factory if field.default_factory is not MISSING else field.default) return obj class OString: - def __init__(self, path: object, fileName: str): + def __init__(self, path: str | os.PathLike[str] | list[str], fileName: str): """Generate a string for the operating system that matches fusion requirements Args: @@ -144,7 +138,7 @@ def __repr__(self) -> str: str: OString [ - ['test', 'test2] - 'test.hell' ] """ # return f"OString [\n-\t[{self.literals!r} \n-\t{self.fileName}\n]" - return f"{os.path.join(self.path, self.fileName)}" + return f"{os.path.join(str(self.path), self.fileName)}" def __eq__(self, value: object) -> bool: """Equals operator for this class @@ -181,7 +175,7 @@ def _os() -> str: else: raise OSError(2, "No Operating System Recognized", f"{osName}") - def AssertEquals(self, comparing: object): + def AssertEquals(self, comparing: object) -> bool: """Compares the two OString objects Args: @@ -192,21 +186,21 @@ def AssertEquals(self, comparing: object): """ return comparing == self - def getPath(self) -> Union[str, object]: + def getPath(self) -> str | os.PathLike[str]: """Returns a OSPath from literals and filename Returns: Path | str: OsPath that is cross platform """ - return os.path.join(self.path, self.fileName) + return os.path.join(str(self.path), self.fileName) - def getDirectory(self) -> Union[str, object]: + def getDirectory(self) -> str | os.PathLike[str]: """Returns a OSPath from literals and filename Returns: Path | str: OsPath that is cross platform """ - return self.path + return self.path if not isinstance(self.path, list) else "".join(self.path) def exists(self) -> bool: """Check to see if Directory and File exist in the current system @@ -218,7 +212,7 @@ def exists(self) -> bool: return True return False - def serialize(self) -> str: + def serialize(self) -> str | os.PathLike[str]: """Serialize the OString to be storred in a temp doc Returns: @@ -227,7 +221,7 @@ def serialize(self) -> str: return self.getPath() @classmethod - def deserialize(cls, serialized) -> object: + def deserialize(cls, serialized: str | os.PathLike[str]) -> object: path, file = os.path.split(serialized) if path is None or file is None: raise RuntimeError(f"Can not parse OString Path supplied \n {serialized}") @@ -275,7 +269,7 @@ def AppDataPath(cls, fileName: str) -> object: """ if cls._os() == "Windows": if os.getenv("APPDATA") is not None: - path = os.path.join(os.getenv("APPDATA"), "..", "Local", "Temp") + path = os.path.join(os.getenv("APPDATA") or "", "..", "Local", "Temp") return cls(path, fileName) return None diff --git a/exporter/SynthesisFusionAddin/src/UI/Camera.py b/exporter/SynthesisFusionAddin/src/UI/Camera.py index d59b1c6e28..53168fd24d 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Camera.py +++ b/exporter/SynthesisFusionAddin/src/UI/Camera.py @@ -9,7 +9,7 @@ @logFailure -def captureThumbnail(size=250): +def captureThumbnail(size: int = 250) -> str | os.PathLike[str]: """ ## Captures Thumbnail and saves it to a temporary path - needs to be cleared after or on startup - Size: int (Default: 200) : (width & height) @@ -47,7 +47,7 @@ def clearIconCache() -> None: This is useful for now but should be cached in the event the app is closed and re-opened. """ - path = OString.ThumbnailPath("Whatever.png").getDirectory() + path = OString.ThumbnailPath("Whatever.png").getDirectory() # type: ignore[attr-defined] for _r, _d, f in os.walk(path): for file in f: diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index fc582e6bac..cbdd5f1881 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -5,6 +5,7 @@ import os import pathlib from enum import Enum +from typing import Any import adsk.core import adsk.fusion @@ -36,7 +37,9 @@ INPUTS_ROOT = None -def GUID(arg): +# Transition: AARD-1765 +# This should be removed in the config command refactor. Seemingly impossible to type. +def GUID(arg: str | adsk.core.Base) -> str | adsk.core.Base: """### Will return command object when given a string GUID, or the string GUID of an object (depending on arg value) Args: @@ -49,7 +52,7 @@ def GUID(arg): object = gm.app.activeDocument.design.findEntityByToken(arg)[0] return object else: # type(obj) - return arg.entityToken + return arg.entityToken # type: ignore[union-attr] class JointMotions(Enum): @@ -76,14 +79,13 @@ class ConfigureCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): - will be called from (@ref Events.py) """ - def __init__(self, configure): + def __init__(self, configure: Any) -> None: super().__init__() @logFailure(messageBox=True) - def notify(self, args): - exporterOptions = ExporterOptions().readFromDesign() - eventArgs = adsk.core.CommandCreatedEventArgs.cast(args) - cmd = eventArgs.command # adsk.core.Command + def notify(self, args: adsk.core.CommandCreatedEventArgs) -> None: + exporterOptions = ExporterOptions().readFromDesign() or ExporterOptions() + cmd = args.command # Set to false so won't automatically export on switch context cmd.isAutoExecute = False @@ -284,16 +286,15 @@ class ConfigureCommandExecuteHandler(adsk.core.CommandEventHandler): """ - def __init__(self): + def __init__(self) -> None: super().__init__() self.current = SerialCommand() @logFailure(messageBox=True) - def notify(self, args): - eventArgs = adsk.core.CommandEventArgs.cast(args) + def notify(self, args: adsk.core.CommandEventArgs) -> None: exporterOptions = ExporterOptions().readFromDesign() - if eventArgs.executeFailed: + if args.executeFailed: logger.error("Could not execute configuration due to failure") return @@ -330,7 +331,7 @@ def notify(self, args): units = gamepieceConfigTab.selectedUnits exporterOptions = ExporterOptions( - savepath, + str(savepath), name, version, materials=0, @@ -366,12 +367,12 @@ class CommandExecutePreviewHandler(adsk.core.CommandEventHandler): adsk (CommandEventHandler): Command event handler that a client derives from to handle events triggered by a CommandEvent. """ - def __init__(self, cmd) -> None: + def __init__(self, cmd: adsk.core.Command) -> None: super().__init__() self.cmd = cmd @logFailure(messageBox=True) - def notify(self, args): + def notify(self, args: adsk.core.CommandEventArgs) -> None: """Notify member called when a command event is triggered Args: @@ -390,22 +391,26 @@ class MySelectHandler(adsk.core.SelectionEventHandler): lastInputCmd = None - def __init__(self, cmd): + def __init__(self, cmd: adsk.core.Command) -> None: super().__init__() self.cmd = cmd - self.allWheelPreselections = [] # all child occurrences of selections - self.allGamepiecePreselections = [] # all child gamepiece occurrences of selections + # Transition: AARD-1765 + # self.allWheelPreselections = [] # all child occurrences of selections + # self.allGamepiecePreselections = [] # all child gamepiece occurrences of selections self.selectedOcc = None # selected occurrence (if there is one) self.selectedJoint = None # selected joint (if there is one) - self.wheelJointList = [] + # Transition: AARD-1765 + # self.wheelJointList = [] self.algorithmicSelection = True @logFailure(messageBox=True) def traverseAssembly( - self, child_occurrences: adsk.fusion.OccurrenceList, jointedOcc: dict + self, child_occurrences: adsk.fusion.OccurrenceList, jointedOcc: dict[adsk.fusion.Joint, adsk.fusion.Occurrence] + ) -> ( + list[adsk.fusion.Joint | adsk.fusion.Occurrence] | None ): # recursive traversal to check if children are jointed """### Traverses the entire occurrence hierarchy to find a match (jointed occurrence) in self.occurrence @@ -426,7 +431,7 @@ def traverseAssembly( return None # no jointed occurrence found @logFailure(messageBox=True) - def wheelParent(self, occ: adsk.fusion.Occurrence): + def wheelParent(self, occ: adsk.fusion.Occurrence) -> list[str | adsk.fusion.Occurrence | None]: """### Identify an occurrence that encompasses the entire wheel component. Process: @@ -494,7 +499,7 @@ def wheelParent(self, occ: adsk.fusion.Occurrence): return [None, occ] # no jointed occurrence found, return what is selected @logFailure(messageBox=True) - def notify(self, args: adsk.core.SelectionEventArgs): + def notify(self, args: adsk.core.SelectionEventArgs) -> None: """### Notify member is called when a selection event is triggered. Args: @@ -516,12 +521,12 @@ class MyPreSelectHandler(adsk.core.SelectionEventHandler): Args: SelectionEventHandler """ - def __init__(self, cmd): + def __init__(self, cmd: adsk.core.Command) -> None: super().__init__() self.cmd = cmd @logFailure(messageBox=True) - def notify(self, args): + def notify(self, args: adsk.core.SelectionEventArgs) -> None: design = adsk.fusion.Design.cast(gm.app.activeProduct) preSelectedOcc = adsk.fusion.Occurrence.cast(args.selection.entity) preSelectedJoint = adsk.fusion.Joint.cast(args.selection.entity) @@ -543,12 +548,12 @@ class MyPreselectEndHandler(adsk.core.SelectionEventHandler): Args: SelectionEventArgs """ - def __init__(self, cmd): + def __init__(self, cmd: adsk.core.Command) -> None: super().__init__() self.cmd = cmd @logFailure(messageBox=True) - def notify(self, args): + def notify(self, args: adsk.core.SelectionEventArgs) -> None: design = adsk.fusion.Design.cast(gm.app.activeProduct) preSelectedOcc = adsk.fusion.Occurrence.cast(args.selection.entity) preSelectedJoint = adsk.fusion.Joint.cast(args.selection.entity) @@ -564,7 +569,7 @@ class ConfigureCommandInputChanged(adsk.core.InputChangedEventHandler): Args: InputChangedEventHandler """ - def __init__(self, cmd): + def __init__(self, cmd: adsk.core.Command) -> None: super().__init__() self.cmd = cmd self.allWeights = [None, None] # [lbs, kg] @@ -572,7 +577,7 @@ def __init__(self, cmd): self.isLbs_f = True @logFailure - def reset(self): + def reset(self) -> None: """### Process: - Reset the mouse icon to default - Clear active selections @@ -580,13 +585,14 @@ def reset(self): self.cmd.setCursor("", 0, 0) gm.ui.activeSelections.clear() - def notify(self, args): - generalConfigTab.handleInputChanged(args) + def notify(self, args: adsk.core.InputChangedEventArgs) -> None: + if generalConfigTab.isActive: + generalConfigTab.handleInputChanged(args) - if jointConfigTab.isVisible: + if jointConfigTab.isVisible and jointConfigTab.isActive: jointConfigTab.handleInputChanged(args, INPUTS_ROOT) - if gamepieceConfigTab.isVisible: + if gamepieceConfigTab.isVisible and gamepieceConfigTab.isActive: gamepieceConfigTab.handleInputChanged(args, INPUTS_ROOT) @@ -597,11 +603,8 @@ class MyCommandDestroyHandler(adsk.core.CommandEventHandler): Args: CommandEventHandler """ - def __init__(self): - super().__init__() - @logFailure(messageBox=True) - def notify(self, args): + def notify(self, args: adsk.core.CommandEventArgs) -> None: jointConfigTab.reset() gamepieceConfigTab.reset() diff --git a/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py b/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py index 3163dc03b2..61c1e30108 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py @@ -10,6 +10,8 @@ from src.Types import OString +# Transition: AARD-1765 +# Will likely be removed later as this is no longer used. Avoiding adding typing for now. def generateFilePath() -> str: """Generates a temporary file path that can be used to save the file for exporting @@ -19,21 +21,21 @@ def generateFilePath() -> str: Returns: str: file path """ - tempPath = OString.TempPath("").getPath() + tempPath = OString.TempPath("").getPath() # type: ignore return str(tempPath) class Struct: """For decoding the dict values into named values""" - def __init__(self, **entries): + def __init__(self, **entries): # type: ignore self.__dict__.update(entries) class SerialCommand: """All of the command inputs combined""" - def __init__(self): + def __init__(self): # type: ignore self.general = General() self.advanced = Advanced() @@ -55,7 +57,7 @@ def toJSON(self) -> str: class General: """General Options""" - def __init__(self): + def __init__(self): # type: ignore # This is the overall export decision point self.exportMode = ExportMode.standard self.RenderType = RenderType.basic3D @@ -69,7 +71,7 @@ def __init__(self): class Advanced: """Advanced settings in the command input""" - def __init__(self): + def __init__(self): # type: ignore self.friction = BooleanInput("friction", True) self.density = BooleanInput("density", True) self.mass = BooleanInput("mass", True) diff --git a/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py b/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py index 3b6bd8e2c2..58a331a198 100644 --- a/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py +++ b/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py @@ -6,7 +6,7 @@ @logFailure -def createTextGraphics(wheel: adsk.fusion.Occurrence, _wheels) -> None: +def createTextGraphics(wheel: adsk.fusion.Occurrence, _wheels: list[adsk.fusion.Occurrence]) -> None: design = gm.app.activeDocument.design boundingBox = wheel.boundingBox # occurrence bounding box diff --git a/exporter/SynthesisFusionAddin/src/UI/Events.py b/exporter/SynthesisFusionAddin/src/UI/Events.py index 64d0f1ea29..c8da4590a6 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Events.py +++ b/exporter/SynthesisFusionAddin/src/UI/Events.py @@ -14,16 +14,12 @@ logger = getLogger() -def updateDocument(*argv: Sequence[str]): - pass +def updateDocument(*argv: Sequence[str]) -> None: ... -def updateConnection(_) -> str: +def updateConnection() -> str: """Updates the JS side connection with the Network Manager connected() - Args: - _ (Any): Any - Returns: str: Json formatted connected: true | false """ @@ -61,6 +57,8 @@ def openDocument(json_data: str) -> str: return "" -def example(palette): +def example(palette: adsk.core.Palette) -> None: app = adsk.core.Application.get() - app.userInterface(f"{Helper.getDocName()}") + # Transition: AARD-1765 + # Many many things in this file can be removed, this is just the part that typing can not be added to + # app.userInterface(f"{Helper.getDocName()}") diff --git a/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py b/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py index c7ea1b0e2a..d7708bfec6 100644 --- a/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py +++ b/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py @@ -21,7 +21,7 @@ def saveFileDialog(defaultPath: str | None = None, defaultName: str | None = Non str: full file path """ - fileDialog: adsk.core.FileDialog = gm.ui.createFileDialog() + fileDialog = gm.ui.createFileDialog() fileDialog.isMultiSelectEnabled = False fileDialog.title = "Save Export Result" @@ -48,7 +48,7 @@ def saveFileDialog(defaultPath: str | None = None, defaultName: str | None = Non gm.ui.messageBox("Synthesis does not have the required permissions to write to this directory.") return saveFileDialog(defaultPath, defaultName) - return fileDialog.filename + return fileDialog.filename or "" def isWriteableDirectory(path: str | os.PathLike[str]) -> bool: @@ -73,7 +73,9 @@ def generateFilePath() -> str: Returns: str: file path """ - tempPath = OString.TempPath("").getPath() + # Transition: AARD-1765 + # Ignoring the type for now, will revisit in the OString refactor + tempPath = OString.TempPath("").getPath() # type: ignore return str(tempPath) @@ -96,5 +98,4 @@ def generateFileName() -> str: return "{0}_{1}.mira".format(name, version) -def OpenFileDialog(): - pass +def OpenFileDialog() -> None: ... diff --git a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py index 8eb4f80044..d3e26f1f99 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py @@ -102,12 +102,16 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp @property def isVisible(self) -> bool: - return self.gamepieceConfigTab.isVisible + return self.gamepieceConfigTab.isVisible or False @isVisible.setter def isVisible(self, value: bool) -> None: self.gamepieceConfigTab.isVisible = value + @property + def isActive(self) -> bool: + return self.gamepieceConfigTab.isActive or False + @property def selectedUnits(self) -> PreferredUnits: return self.currentUnits @@ -117,7 +121,7 @@ def autoCalculateWeight(self) -> bool: autoCalcWeightButton: adsk.core.BoolValueCommandInput = self.gamepieceConfigTab.children.itemById( "autoCalcGamepieceWeight" ) - return autoCalcWeightButton.value + return autoCalcWeightButton.value or False @logFailure def weightInputs(self) -> list[adsk.core.ValueCommandInput]: @@ -252,6 +256,17 @@ def calcGamepieceWeights(self) -> None: def handleInputChanged( self, args: adsk.core.InputChangedEventArgs, globalCommandInputs: adsk.core.CommandInputs ) -> None: + gamepieceAddButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("gamepieceAddButton") + gamepieceTable: adsk.core.TableCommandInput = args.inputs.itemById("gamepieceTable") + gamepieceRemoveButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("gamepieceRemoveButton") + gamepieceSelectCancelButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById( + "gamepieceSelectCancelButton" + ) + gamepieceSelection: adsk.core.SelectionCommandInput = self.gamepieceConfigTab.children.itemById( + "gamepieceSelect" + ) + spacer: adsk.core.SelectionCommandInput = self.gamepieceConfigTab.children.itemById("gamepieceTabSpacer") + commandInput = args.input if commandInput.id == "autoCalcGamepieceWeight": autoCalcWeightButton = adsk.core.BoolValueCommandInput.cast(commandInput) @@ -283,18 +298,6 @@ def handleInputChanged( self.previousSelectedUnitDropdownIndex = weightUnitDropdown.selectedItem.index elif commandInput.id == "gamepieceAddButton": - gamepieceAddButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("gamepieceAddButton") - gamepieceRemoveButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById( - "gamepieceRemoveButton" - ) - gamepieceSelectCancelButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById( - "gamepieceSelectCancelButton" - ) - gamepieceSelection: adsk.core.SelectionCommandInput = self.gamepieceConfigTab.children.itemById( - "gamepieceSelect" - ) - spacer: adsk.core.SelectionCommandInput = self.gamepieceConfigTab.children.itemById("gamepieceTabSpacer") - gamepieceSelection.isVisible = gamepieceSelection.isEnabled = True gamepieceSelection.clearSelection() gamepieceAddButton.isEnabled = gamepieceRemoveButton.isEnabled = False @@ -302,9 +305,6 @@ def handleInputChanged( spacer.isVisible = False elif commandInput.id == "gamepieceRemoveButton": - gamepieceAddButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("gamepieceAddButton") - gamepieceTable: adsk.core.TableCommandInput = args.inputs.itemById("gamepieceTable") - gamepieceAddButton.isEnabled = True if gamepieceTable.selectedRow == -1 or gamepieceTable.selectedRow == 0: ui = adsk.core.Application.get().userInterface @@ -313,18 +313,6 @@ def handleInputChanged( self.removeIndexedGamepiece(gamepieceTable.selectedRow - 1) # selectedRow is 1 indexed elif commandInput.id == "gamepieceSelectCancelButton": - gamepieceAddButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("gamepieceAddButton") - gamepieceRemoveButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById( - "gamepieceRemoveButton" - ) - gamepieceSelectCancelButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById( - "gamepieceSelectCancelButton" - ) - gamepieceSelection: adsk.core.SelectionCommandInput = self.gamepieceConfigTab.children.itemById( - "gamepieceSelect" - ) - spacer: adsk.core.SelectionCommandInput = self.gamepieceConfigTab.children.itemById("gamepieceTabSpacer") - gamepieceSelection.isEnabled = gamepieceSelection.isVisible = False gamepieceSelectCancelButton.isEnabled = gamepieceSelectCancelButton.isVisible = False gamepieceAddButton.isEnabled = gamepieceRemoveButton.isEnabled = True diff --git a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py index 40a602a406..673e56054b 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py @@ -2,13 +2,8 @@ import adsk.fusion from src.Logging import logFailure -from src.Parser.ExporterOptions import ( - ExporterOptions, - ExportLocation, - ExportMode, - PreferredUnits, -) -from src.Types import KG, toKg, toLbs +from src.Parser.ExporterOptions import ExporterOptions +from src.Types import KG, ExportLocation, ExportMode, PreferredUnits, toKg, toLbs from src.UI import IconPaths from src.UI.CreateCommandInputsHelper import createBooleanInput, createTableInput from src.UI.GamepieceConfigTab import GamepieceConfigTab @@ -49,7 +44,7 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp "exportLocation", "Export Location", dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle ) - upload: bool = exporterOptions.exportLocation == ExportLocation.UPLOAD + upload = exporterOptions.exportLocation == ExportLocation.UPLOAD dropdownExportLocation.listItems.add("Upload", upload) dropdownExportLocation.listItems.add("Download", not upload) dropdownExportLocation.tooltip = "Export Location" @@ -160,6 +155,10 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp weightInput.isVisible = weightTableInput.isVisible = False frictionOverrideButton.isVisible = frictionCoefficient.isVisible = False + @property + def isActive(self) -> bool: + return self.generalOptionsTab.isActive or False + @property def exportMode(self) -> ExportMode: exportModeDropdown: adsk.core.DropDownCommandInput = self.generalOptionsTab.children.itemById( @@ -176,14 +175,14 @@ def compress(self) -> bool: compressButton: adsk.core.BoolValueCommandInput = self.generalOptionsTab.children.itemById( "compressOutputButton" ) - return compressButton.value + return compressButton.value or False @property def exportAsPart(self) -> bool: exportAsPartButton: adsk.core.BoolValueCommandInput = self.generalOptionsTab.children.itemById( "exportAsPartButton" ) - return exportAsPartButton.value + return exportAsPartButton.value or False @property def selectedUnits(self) -> PreferredUnits: @@ -205,7 +204,7 @@ def autoCalculateWeight(self) -> bool: autoCalcWeightButton: adsk.core.BoolValueCommandInput = self.generalOptionsTab.children.itemById( "autoCalcWeightButton" ) - return autoCalcWeightButton.value + return autoCalcWeightButton.value or False @property def exportLocation(self) -> ExportLocation: @@ -223,25 +222,27 @@ def overrideFriction(self) -> bool: overrideFrictionButton: adsk.core.BoolValueCommandInput = self.generalOptionsTab.children.itemById( "frictionOverride" ) - return overrideFrictionButton.value + return overrideFrictionButton.value or False @property def frictionOverrideCoeff(self) -> float: frictionSlider: adsk.core.FloatSliderCommandInput = self.generalOptionsTab.children.itemById( "frictionCoefficient" ) - return frictionSlider.valueOne + return frictionSlider.valueOne or -1.0 @logFailure def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: + autoCalcWeightButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("autoCalcWeightButton") + weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") + weightInput: adsk.core.ValueCommandInput = weightTable.getInputAtPosition(0, 1) + exportAsPartButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("exportAsPartButton") + overrideFrictionButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("frictionOverride") + frictionSlider: adsk.core.FloatSliderCommandInput = args.inputs.itemById("frictionCoefficient") commandInput = args.input if commandInput.id == "exportModeDropdown": modeDropdown = adsk.core.DropDownCommandInput.cast(commandInput) - autoCalcWeightButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("autoCalcWeightButton") - weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") - exportAsPartButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("exportAsPartButton") - overrideFrictionButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("frictionOverride") - frictionSlider: adsk.core.FloatSliderCommandInput = args.inputs.itemById("frictionCoefficient") + if modeDropdown.selectedItem.index == self.previousSelectedModeDropdownIndex: return @@ -268,8 +269,6 @@ def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: elif commandInput.id == "weightUnitDropdown": weightUnitDropdown = adsk.core.DropDownCommandInput.cast(commandInput) - weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") - weightInput: adsk.core.ValueCommandInput = weightTable.getInputAtPosition(0, 1) if weightUnitDropdown.selectedItem.index == self.previousSelectedUnitDropdownIndex: return @@ -294,9 +293,6 @@ def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: if autoCalcWeightButton.value == self.previousAutoCalcWeightCheckboxState: return - weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") - weightInput: adsk.core.ValueCommandInput = weightTable.getInputAtPosition(0, 1) - if autoCalcWeightButton.value: robotMass = designMassCalculation() weightInput.value = robotMass if self.currentUnits is PreferredUnits.METRIC else toLbs(robotMass) @@ -308,7 +304,6 @@ def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: elif commandInput.id == "frictionOverride": frictionOverrideButton = adsk.core.BoolValueCommandInput.cast(commandInput) - frictionSlider: adsk.core.FloatSliderCommandInput = args.inputs.itemById("frictionCoefficient") if frictionOverrideButton.value == self.previousFrictionOverrideCheckboxState: return @@ -331,4 +326,4 @@ def designMassCalculation() -> KG: physical = body.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy) mass += physical.mass - return KG(round(mass, 2)) + return round(mass, 2) diff --git a/exporter/SynthesisFusionAddin/src/UI/HUI.py b/exporter/SynthesisFusionAddin/src/UI/HUI.py index d1a968d642..d5be4473f7 100644 --- a/exporter/SynthesisFusionAddin/src/UI/HUI.py +++ b/exporter/SynthesisFusionAddin/src/UI/HUI.py @@ -1,3 +1,5 @@ +from typing import Any, Callable + import adsk.core from src import INTERNAL_ID, gm @@ -7,8 +9,8 @@ # no longer used class HPalette: - handlers = [] - events = [] + handlers: list[Any] = [] + events: list[adsk.core.Event] = [] def __init__( self, @@ -19,8 +21,8 @@ def __init__( resizeable: bool, width: int, height: int, - *argv, - ): + *argv: Any, + ) -> None: """#### Creates a HPalette Object with a number of function pointers that correspond to a action on the js side. Arguments: @@ -69,9 +71,12 @@ def __init__( self.palette.dockingState = adsk.core.PaletteDockingStates.PaletteDockStateLeft - onHTML = Handlers.HPaletteHTMLEventHandler(self) - self.palette.incomingFromHTML.add(onHTML) - self.handlers.append(onHTML) + # Transition: AARD-1765 + # Should be removed later as this is no longer used, would have been + # impossible to add typing for this block. + # onHTML = Handlers.HPaletteHTMLEventHandler(self) + # self.palette.incomingFromHTML.add(onHTML) + # self.handlers.append(onHTML) self.palette.isVisible = True @@ -86,7 +91,7 @@ def deleteMe(self) -> None: class HButton: - handlers = [] + handlers: list[Any] = [] """ Keeps all handler classes alive which is essential apparently. - used in command events """ @logFailure @@ -94,11 +99,11 @@ def __init__( self, name: str, location: str, - check_func: object, - exec_func: object, + check_func: Callable[..., bool], + exec_func: Callable[..., Any], description: str = "No Description", command: bool = False, - ): + ) -> None: """# Creates a new HButton Class. Arguments: @@ -170,7 +175,7 @@ def promote(self, flag: bool) -> None: self.buttonControl.isPromotedByDefault = flag self.buttonControl.isPromoted = flag - def deleteMe(self): + def deleteMe(self) -> None: """## Custom deleteMe method to easily deconstruct button data. This somehow doesn't work if I keep local references to all of these definitions. @@ -186,7 +191,7 @@ def deleteMe(self): if ctrl: ctrl.deleteMe() - def scrub(self): + def scrub(self) -> None: """### In-case I make a mistake or a crash happens early it can scrub the command. It can only be called if the ID is not currently in the buttons list. @@ -195,7 +200,7 @@ def scrub(self): """ self.deleteMe() - def __str__(self): + def __str__(self) -> str: """### Retrieves the button unique ID and treats it as a string. Returns: *str* -- button unique ID. diff --git a/exporter/SynthesisFusionAddin/src/UI/Handlers.py b/exporter/SynthesisFusionAddin/src/UI/Handlers.py index 4529b861db..0e60ba36f7 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Handlers.py +++ b/exporter/SynthesisFusionAddin/src/UI/Handlers.py @@ -1,3 +1,5 @@ +from typing import Any + import adsk.core @@ -8,17 +10,17 @@ class HButtonCommandCreatedEvent(adsk.core.CommandCreatedEventHandler): **adsk.core.CommandCreatedEventHandler** -- Parent abstract created event class """ - def __init__(self, button): + def __init__(self, button: Any) -> None: super().__init__() self.button = button - def notify(self, args): + def notify(self, args: adsk.core.CommandCreatedEventArgs) -> None: """## Called when parent button object is created and links the execute function pointer. Arguments: **args** *args* -- List of arbitrary info given to fusion event handlers. """ - cmd = adsk.core.CommandCreatedEventArgs.cast(args).command + cmd = args.command if self.button.check_func(): onExecute = HButtonCommandExecuteHandler(self.button) @@ -33,11 +35,11 @@ class HButtonCommandExecuteHandler(adsk.core.CommandEventHandler): **adsk.core.CommandEventHandler** -- Fusion CommandEventHandler Abstract parent to link notify to ui. """ - def __init__(self, button): + def __init__(self, button: Any) -> None: super().__init__() self.button = button - def notify(self, _): + def notify(self, _: adsk.core.CommandEventArgs) -> None: self.button.exec_func() diff --git a/exporter/SynthesisFusionAddin/src/UI/Helper.py b/exporter/SynthesisFusionAddin/src/UI/Helper.py index ba8bf9b0e9..9253af0600 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Helper.py +++ b/exporter/SynthesisFusionAddin/src/UI/Helper.py @@ -3,52 +3,52 @@ import adsk.core from src import APP_NAME, APP_TITLE, INTERNAL_ID, gm +from src.Logging import logFailure from src.UI import HUI, Events -def getDocName() -> str or None: +def check_solid_open() -> bool: + return True + + +def getDocName() -> str | None: """### Gets the active Document Name - If it can't find one then it will return None """ app = adsk.core.Application.get() if check_solid_open(): - return app.activeDocument.design.rootComponent.name.rsplit(" ", 1)[0] + return app.activeDocument.design.rootComponent.name.rsplit(" ", 1)[0] or "" else: return None +@logFailure(messageBox=True) def checkAttribute() -> bool: """### Will process the file and look for a flag that unity is already using it.""" app = adsk.core.Application.get() - try: - connected = app.activeDocument.attributes.itemByName("UnityFile", "Connected") - if connected is not None: - return connected.value - return False - except: - app.userInterface.messageBox(f"Could not access the attributes of the file \n -- {traceback.format_exc()}.") - return False + connected = app.activeDocument.attributes.itemByName("UnityFile", "Connected") + if connected is not None: + return connected.value or False + + return False -def addUnityAttribute() -> bool or None: +@logFailure +def addUnityAttribute() -> bool | None: """#### Adds an attribute to the Fusion File - Initially intended to be used to add a marker for in use untiy files - No longer necessary """ app = adsk.core.Application.get() - try: - current = app.activeDocument.attributes.itemByName("UnityFile", "Connected") - - if check_solid_open and (current is None): - val = app.activeDocument.attributes.add("UnityFile", "Connected", "True") - return val - elif current is not None: - return current - return None + current = app.activeDocument.attributes.itemByName("UnityFile", "Connected") + + if check_solid_open() and (current is None): + val = app.activeDocument.attributes.add("UnityFile", "Connected", "True") + return val or False + elif current is not None: + return current or False - except: - app.userInterface.messageBox(f"Could not access the attributes of the file \n -- {traceback.format_exc()}.") - return False + return None def openPanel() -> None: @@ -70,5 +70,3 @@ def openPanel() -> None: func_list = [o for o in getmembers(Events, isfunction)] palette_new = HUI.HPalette(name, APP_TITLE, True, True, False, 400, 500, func_list) gm.elements.append(palette_new) - - return diff --git a/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py index b06ae6f2af..217d409329 100644 --- a/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py @@ -104,12 +104,16 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs) -> None: @property def isVisible(self) -> bool: - return self.jointConfigTab.isVisible + return self.jointConfigTab.isVisible or False @isVisible.setter def isVisible(self, value: bool) -> None: self.jointConfigTab.isVisible = value + @property + def isActive(self) -> bool: + return self.jointConfigTab.isActive or False + @logFailure def addJoint(self, fusionJoint: adsk.fusion.Joint, synJoint: Joint | None = None) -> bool: if fusionJoint in self.selectedJointList: @@ -264,6 +268,7 @@ def addJoint(self, fusionJoint: adsk.fusion.Joint, synJoint: Joint | None = None ) self.previousWheelCheckboxState.append(isWheel) + return True @logFailure def addWheel(self, joint: adsk.fusion.Joint, wheel: Wheel | None = None) -> None: @@ -403,6 +408,14 @@ def handleInputChanged( self, args: adsk.core.InputChangedEventArgs, globalCommandInputs: adsk.core.CommandInputs ) -> None: commandInput = args.input + jointAddButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("jointAddButton") + jointTable: adsk.core.TableCommandInput = args.inputs.itemById("jointTable") + jointRemoveButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("jointRemoveButton") + jointSelectCancelButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById( + "jointSelectCancelButton" + ) + jointSelection: adsk.core.SelectionCommandInput = globalCommandInputs.itemById("jointSelection") + if commandInput.id == "wheelType": wheelTypeDropdown = adsk.core.DropDownCommandInput.cast(commandInput) position = self.wheelConfigTable.getPosition(wheelTypeDropdown)[1] @@ -443,22 +456,12 @@ def handleInputChanged( wheelSignalItems.listItems.item(signalTypeDropdown.selectedItem.index).isSelected = True elif commandInput.id == "jointAddButton": - jointAddButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("jointAddButton") - jointRemoveButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("jointRemoveButton") - jointSelectCancelButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById( - "jointSelectCancelButton" - ) - jointSelection: adsk.core.SelectionCommandInput = globalCommandInputs.itemById("jointSelection") - jointSelection.isVisible = jointSelection.isEnabled = True jointSelection.clearSelection() jointAddButton.isEnabled = jointRemoveButton.isEnabled = False jointSelectCancelButton.isVisible = jointSelectCancelButton.isEnabled = True elif commandInput.id == "jointRemoveButton": - jointAddButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("jointAddButton") - jointTable: adsk.core.TableCommandInput = args.inputs.itemById("jointTable") - jointAddButton.isEnabled = True if jointTable.selectedRow == -1 or jointTable.selectedRow == 0: @@ -468,12 +471,6 @@ def handleInputChanged( self.removeIndexedJoint(jointTable.selectedRow - 1) # selectedRow is 1 indexed elif commandInput.id == "jointSelectCancelButton": - jointAddButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("jointAddButton") - jointRemoveButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("jointRemoveButton") - jointSelectCancelButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById( - "jointSelectCancelButton" - ) - jointSelection: adsk.core.SelectionCommandInput = globalCommandInputs.itemById("jointSelection") jointSelection.isEnabled = jointSelection.isVisible = False jointSelectCancelButton.isEnabled = jointSelectCancelButton.isVisible = False jointAddButton.isEnabled = jointRemoveButton.isEnabled = True diff --git a/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py b/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py index 5b90c1b671..30cb8831d7 100644 --- a/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py +++ b/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py @@ -1,3 +1,5 @@ +from typing import Callable + import adsk.core import adsk.fusion @@ -7,23 +9,20 @@ # global mapping list of event handlers to keep them referenced for the duration of the command # handlers = {} -handlers = [] -cmdDefs = [] -entities = [] -occurrencesOfComponents = {} +handlers: list[adsk.core.CommandEventHandler] = [] +cmdDefs: list[adsk.core.CommandDefinition] = [] +entities: list[adsk.fusion.Occurrence] = [] logger = getLogger() @logFailure(messageBox=True) -def setupMarkingMenu(ui: adsk.core.UserInterface): +def setupMarkingMenu(ui: adsk.core.UserInterface) -> None: handlers.clear() @logFailure(messageBox=True) - def setLinearMarkingMenu(args): - menuArgs = adsk.core.MarkingMenuEventArgs.cast(args) - - linearMenu = menuArgs.linearMarkingMenu + def setLinearMarkingMenu(args: adsk.core.MarkingMenuEventArgs) -> None: + linearMenu = args.linearMarkingMenu linearMenu.controls.addSeparator("LinearSeparator") synthDropDown = linearMenu.controls.addDropDown("Synthesis", "", "synthesis") @@ -49,14 +48,16 @@ def setLinearMarkingMenu(args): cmdEnableCollision = ui.commandDefinitions.itemById("EnableCollision") synthDropDown.controls.addCommand(cmdEnableCollision) - def setCollisionAttribute(occ: adsk.fusion.Occurrence, isEnabled: bool = True): + def setCollisionAttribute(occ: adsk.fusion.Occurrence, isEnabled: bool = True) -> None: attr = occ.attributes.itemByName("synthesis", "collision_off") if attr == None and not isEnabled: occ.attributes.add("synthesis", "collision_off", "true") elif attr != None and isEnabled: attr.deleteMe() - def applyToSelfAndAllChildren(occ: adsk.fusion.Occurrence, modFunc): + def applyToSelfAndAllChildren( + occ: adsk.fusion.Occurrence, modFunc: Callable[[adsk.fusion.Occurrence], None] + ) -> None: modFunc(occ) childLists = [] childLists.append(occ.childOccurrences) @@ -70,22 +71,16 @@ def applyToSelfAndAllChildren(occ: adsk.fusion.Occurrence, modFunc): childLists.append(o.childOccurrences) class MyCommandCreatedEventHandler(adsk.core.CommandCreatedEventHandler): - def __init__(self): - super().__init__() - @logFailure(messageBox=True) - def notify(self, args): + def notify(self, args: adsk.core.CommandCreatedEventArgs) -> None: command = args.command onCommandExcute = MyCommandExecuteHandler() handlers.append(onCommandExcute) command.execute.add(onCommandExcute) class MyCommandExecuteHandler(adsk.core.CommandEventHandler): - def __init__(self): - super().__init__() - @logFailure(messageBox=True) - def notify(self, args): + def notify(self, args: adsk.core.CommandEventArgs) -> None: command = args.firingEvent.sender cmdDef = command.parentCommandDefinition if cmdDef: @@ -128,15 +123,10 @@ def notify(self, args): ui.messageBox("No CommandDefinition") class MyMarkingMenuHandler(adsk.core.MarkingMenuEventHandler): - def __init__(self): - super().__init__() - @logFailure(messageBox=True) - def notify(self, args): + def notify(self, args: adsk.core.CommandEventArgs) -> None: setLinearMarkingMenu(args) - global occurrencesOfComponents - # selected entities global entities entities.clear() @@ -201,7 +191,7 @@ def notify(self, args): @logFailure(messageBox=True) -def stopMarkingMenu(ui: adsk.core.UserInterface): +def stopMarkingMenu(ui: adsk.core.UserInterface) -> None: for obj in cmdDefs: if obj.isValid: obj.deleteMe() diff --git a/exporter/SynthesisFusionAddin/src/UI/OsHelper.py b/exporter/SynthesisFusionAddin/src/UI/OsHelper.py index e338be5815..3b97a4b891 100644 --- a/exporter/SynthesisFusionAddin/src/UI/OsHelper.py +++ b/exporter/SynthesisFusionAddin/src/UI/OsHelper.py @@ -2,7 +2,7 @@ import platform -def getOSPath(*argv) -> str: +def getOSPath(*argv: str) -> str: """Takes n strings and constructs a OS specific path Returns: @@ -17,7 +17,7 @@ def getOSPath(*argv) -> str: return path -def getOSPathPalette(*argv) -> str: +def getOSPathPalette(*argv: str) -> str: """## This is a different delimeter than the resources path.""" path = "" for arg in argv: @@ -25,7 +25,7 @@ def getOSPathPalette(*argv) -> str: return path -def getDesktop(): +def getDesktop() -> str: """Gets the Desktop Path. Returns: @@ -37,7 +37,7 @@ def getDesktop(): return os.path.join(os.path.join(os.environ["USERPROFILE"]), "Desktop/") -def getOS(): +def getOS() -> str: """## Returns platform as a string - Darwin diff --git a/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py b/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py index 999abd1176..abf6f7df32 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py @@ -3,6 +3,7 @@ import traceback import urllib.parse import urllib.request +from typing import Any import adsk.core @@ -15,10 +16,7 @@ class ShowAPSAuthCommandExecuteHandler(adsk.core.CommandEventHandler): - def __init__(self): - super().__init__() - - def notify(self, args): + def notify(self, args: adsk.core.CommandEventArgs) -> None: try: global palette palette = gm.ui.palettes.itemById("authPalette") @@ -60,10 +58,10 @@ def notify(self, args): class ShowAPSAuthCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): - def __init__(self, configure): + def __init__(self, configure: Any) -> None: super().__init__() - def notify(self, args): + def notify(self, args: adsk.core.CommandCreatedEventArgs) -> None: try: command = args.command onExecute = ShowAPSAuthCommandExecuteHandler() @@ -77,18 +75,11 @@ def notify(self, args): class SendInfoCommandExecuteHandler(adsk.core.CommandEventHandler): - def __init__(self): - super().__init__() - - def notify(self, args): - pass + def notify(self, args: adsk.core.CommandEventArgs) -> None: ... class SendInfoCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): - def __init__(self): - super().__init__() - - def notify(self, args): + def notify(self, args: adsk.core.CommandCreatedEventArgs) -> None: try: command = args.command onExecute = SendInfoCommandExecuteHandler() @@ -102,10 +93,7 @@ def notify(self, args): class MyCloseEventHandler(adsk.core.UserInterfaceGeneralEventHandler): - def __init__(self): - super().__init__() - - def notify(self, args): + def notify(self, args: adsk.core.EventArgs) -> None: try: if palette: palette.deleteMe() @@ -118,13 +106,9 @@ def notify(self, args): class MyHTMLEventHandler(adsk.core.HTMLEventHandler): - def __init__(self): - super().__init__() - - def notify(self, args): + def notify(self, args: adsk.core.HTMLEventArgs) -> None: try: - htmlArgs = adsk.core.HTMLEventArgs.cast(args) - data = json.loads(htmlArgs.data) + data = json.loads(args.data) # gm.ui.messageBox(msg) convertAuthToken(data["code"]) diff --git a/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py b/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py index cc99cffb07..7441a071b4 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py @@ -1,34 +1,31 @@ -import traceback import webbrowser +from typing import Any import adsk.core from src import gm +from src.Logging import logFailure class ShowWebsiteCommandExecuteHandler(adsk.core.CommandEventHandler): def __init__(self) -> None: super().__init__() - def notify(self, args): - try: - url = "https://synthesis.autodesk.com/tutorials.html" - res = webbrowser.open(url, new=2) - if not res: - gm.ui.messageBox("Failed\n{}".format(traceback.format_exc())) - except: - gm.ui.messageBox("Failed\n{}".format(traceback.format_exc())) + @logFailure + def notify(self, args: adsk.core.CommandEventArgs) -> None: + url = "https://synthesis.autodesk.com/tutorials.html" + res = webbrowser.open(url, new=2) + if not res: + raise BaseException("Could not open webbrowser") class ShowWebsiteCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): - def __init__(self, configure) -> None: + def __init__(self, configure: Any) -> None: super().__init__() - def notify(self, args): - try: - command = args.command - onExecute = ShowWebsiteCommandExecuteHandler() - command.execute.add(onExecute) - gm.handlers.append(onExecute) - except: - gm.ui.messageBox("Failed\n{}".format(traceback.format_exc())) + @logFailure + def notify(self, args: adsk.core.CommandCreatedEventArgs) -> None: + command = args.command + onExecute = ShowWebsiteCommandExecuteHandler() + command.execute.add(onExecute) + gm.handlers.append(onExecute) diff --git a/exporter/SynthesisFusionAddin/src/UI/Toolbar.py b/exporter/SynthesisFusionAddin/src/UI/Toolbar.py index f9d150cc3c..93e6173502 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Toolbar.py +++ b/exporter/SynthesisFusionAddin/src/UI/Toolbar.py @@ -1,3 +1,5 @@ +import adsk.core + from src import INTERNAL_ID, gm from src.Logging import logFailure @@ -8,27 +10,23 @@ class Toolbar: - holds handlers """ - uid = None - tab = None - panels = [] - controls = [] + uid: str + tab: adsk.core.ToolbarTab + panels: list[str] = [] + controls: list[str] = [] @logFailure def __init__(self, name: str): self.uid = f"{name}_{INTERNAL_ID}_toolbar" self.name = name - designWorkspace = gm.ui.workspaces.itemById("FusionSolidEnvironment") - - if designWorkspace: - allDesignTabs = designWorkspace.toolbarTabs - - self.tab = allDesignTabs.itemById(self.uid) - - if self.tab is None: - self.tab = allDesignTabs.add(self.uid, name) + designWorkspace = gm.ui.workspaces.itemById("FusionSolidEnvironment") or adsk.core.Workspace() + allDesignTabs = designWorkspace.toolbarTabs + self.tab = allDesignTabs.itemById(self.uid) + if self.tab is None: + self.tab = allDesignTabs.add(self.uid, name) - self.tab.activate() + self.tab.activate() def getPanel(self, name: str, visibility: bool = True) -> str | None: """# Gets a control for a panel to the tabbed toolbar diff --git a/exporter/SynthesisFusionAddin/src/Util.py b/exporter/SynthesisFusionAddin/src/Util.py index 3980e85975..9916cbb853 100644 --- a/exporter/SynthesisFusionAddin/src/Util.py +++ b/exporter/SynthesisFusionAddin/src/Util.py @@ -1,7 +1,7 @@ import os -def makeDirectories(directory: str | os.PathLike[str]) -> str | os.PathLike[str]: +def makeDirectories(directory: str) -> str: """Ensures than an input directory exists and attempts to create it if it doesn't.""" os.makedirs(directory, exist_ok=True) return directory diff --git a/exporter/SynthesisFusionAddin/src/__init__.py b/exporter/SynthesisFusionAddin/src/__init__.py index ac2bea477d..b53fb6b045 100644 --- a/exporter/SynthesisFusionAddin/src/__init__.py +++ b/exporter/SynthesisFusionAddin/src/__init__.py @@ -3,7 +3,6 @@ from pathlib import Path from src.GlobalManager import GlobalManager -from src.Types import OperatingSystemString from src.Util import makeDirectories APP_NAME = "Synthesis" @@ -13,9 +12,7 @@ ADDIN_PATH = os.path.dirname(os.path.realpath(__file__)) IS_RELEASE = str(Path(os.path.abspath(__file__)).parent.parent.parent.parent).split(os.sep)[-1] == "ApplicationPlugins" -SYSTEM: OperatingSystemString = platform.system() -assert SYSTEM != "Linux" - +SYSTEM = platform.system() if SYSTEM == "Windows": SUPPORT_PATH = makeDirectories(os.path.expandvars(r"%appdata%\Autodesk\Synthesis")) else: