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/.gitignore b/.gitignore
index a7ea447430..a57a98b5ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,8 @@
.vs/
.vscode/
-/build/
+build/
+dist/
*.log
.DS_Store
+*.pkg
+*.exe
diff --git a/exporter/SynthesisFusionAddin/Synthesis.manifest b/exporter/SynthesisFusionAddin/Synthesis.manifest
index f8e96ae366..47434ab6a2 100644
--- a/exporter/SynthesisFusionAddin/Synthesis.manifest
+++ b/exporter/SynthesisFusionAddin/Synthesis.manifest
@@ -6,7 +6,7 @@
"description": {
"": "Synthesis Exporter"
},
- "version": "1.0.0",
+ "version": "2.0.0",
"runOnStartup": true,
"supportedOS": "windows|mac",
"editEnabled": true
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 e5f352f480..3b2bedb68c 100644
--- a/exporter/SynthesisFusionAddin/src/Logging.py
+++ b/exporter/SynthesisFusionAddin/src/Logging.py
@@ -7,19 +7,19 @@
import time
import traceback
from datetime import date, datetime
-from typing import cast
+from typing import Any, Callable, cast
import adsk.core
-from src import INTERNAL_ID
-from src.UI.OsHelper import getOSPath
+from src import INTERNAL_ID, IS_RELEASE, SUPPORT_PATH
+from src.Util import makeDirectories
MAX_LOG_FILES_TO_KEEP = 10
TIMING_LEVEL = 25
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:
@@ -30,14 +30,17 @@ def cleanupHandlers(self) -> None:
def setupLogger() -> SynthesisLogger:
now = datetime.now().strftime("%H-%M-%S")
today = date.today()
- logFileFolder = getOSPath(f"{pathlib.Path(__file__).parent.parent}", "logs")
- logFiles = [os.path.join(logFileFolder, file) for file in os.listdir(logFileFolder) if file.endswith(".log")]
- logFiles.sort()
- if len(logFiles) >= MAX_LOG_FILES_TO_KEEP:
- for file in logFiles[: len(logFiles) - MAX_LOG_FILES_TO_KEEP]:
- os.remove(file)
-
- logFileName = f"{logFileFolder}{getOSPath(f'{INTERNAL_ID}-{today}-{now}.log')}"
+ if IS_RELEASE:
+ logFileFolder = makeDirectories(os.path.join(SUPPORT_PATH, "Logs"))
+ else:
+ logFileFolder = makeDirectories(os.path.join(f"{pathlib.Path(__file__).parent.parent}", "logs"))
+ logFiles = [os.path.join(logFileFolder, file) for file in os.listdir(logFileFolder) if file.endswith(".log")]
+ logFiles.sort()
+ if len(logFiles) >= MAX_LOG_FILES_TO_KEEP:
+ for file in logFiles[: len(logFiles) - MAX_LOG_FILES_TO_KEEP]:
+ os.remove(file)
+
+ logFileName = os.path.join(logFileFolder, f"{today}-{now}.log")
logHandler = logging.handlers.WatchedFileHandler(logFileName, mode="w")
logHandler.setFormatter(logging.Formatter("%(name)s - %(levelname)s - %(message)s"))
@@ -46,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__"):
@@ -88,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 1aca3a5162..5e2d7c967f 100644
--- a/exporter/SynthesisFusionAddin/src/Types.py
+++ b/exporter/SynthesisFusionAddin/src/Types.py
@@ -1,9 +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 Union, get_origin
+from typing import Any, TypeAlias, get_args, get_origin
# Not 100% sure what this is for - Brandon
JointParentType = Enum("JointParentType", ["ROOT", "END"])
@@ -17,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
@@ -39,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):
@@ -72,26 +72,22 @@ class ModelHierarchy(Enum):
SingleMesh = 3
-class LBS(float):
- """Mass Unit in Pounds."""
+LBS: TypeAlias = float
+KG: TypeAlias = float
-class KG(float):
- """Mass Unit in Kilograms."""
-
-
-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__"):
@@ -101,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."
@@ -115,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:
@@ -142,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
@@ -179,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:
@@ -190,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
@@ -216,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:
@@ -225,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}")
@@ -273,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 02de04083a..53168fd24d 100644
--- a/exporter/SynthesisFusionAddin/src/UI/Camera.py
+++ b/exporter/SynthesisFusionAddin/src/UI/Camera.py
@@ -2,12 +2,14 @@
import adsk.core
+from src import SUPPORT_PATH
from src.Logging import logFailure
from src.Types import OString
+from src.Util import makeDirectories
@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)
@@ -21,9 +23,10 @@ def captureThumbnail(size=250):
) # remove whitespace from just the filename
)
- path = OString.ThumbnailPath(name)
+ path = makeDirectories(f"{SUPPORT_PATH}/Resources/Icons/")
+ path += name
- saveOptions = adsk.core.SaveImageFileOptions.create(str(path.getPath()))
+ saveOptions = adsk.core.SaveImageFileOptions.create(path)
saveOptions.height = size
saveOptions.width = size
saveOptions.isAntiAliased = True
@@ -36,7 +39,7 @@ def captureThumbnail(size=250):
app.activeViewport.saveAsImageFileWithOptions(saveOptions)
app.activeViewport.camera = originalCamera
- return str(path.getPath())
+ return path
def clearIconCache() -> None:
@@ -44,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 c50c7feed2..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
@@ -301,13 +302,17 @@ def notify(self, args):
if generalConfigTab.exportLocation == ExportLocation.DOWNLOAD:
savepath = FileDialogConfig.saveFileDialog(defaultPath=exporterOptions.fileLocation)
- if savepath == False:
+ if not savepath:
# save was canceled
return
- updatedPath = pathlib.Path(savepath).parent
- if updatedPath != self.current.filePath:
- self.current.filePath = str(updatedPath)
+ # Transition: AARD-1742
+ # With the addition of a 'release' build the fusion exporter will not have permissions within the sourced
+ # folder. Because of this we cannot use this kind of tmp path anymore. This code was already unused and
+ # should be removed.
+ # updatedPath = pathlib.Path(savepath).parent
+ # if updatedPath != self.current.filePath:
+ # self.current.filePath = str(updatedPath)
else:
savepath = processedFileName
@@ -326,7 +331,7 @@ def notify(self, args):
units = gamepieceConfigTab.selectedUnits
exporterOptions = ExporterOptions(
- savepath,
+ str(savepath),
name,
version,
materials=0,
@@ -362,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:
@@ -386,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
@@ -422,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:
@@ -490,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:
@@ -512,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)
@@ -539,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)
@@ -560,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]
@@ -568,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
@@ -576,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)
@@ -593,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 663afe9337..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,24 +21,29 @@ 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()
- self.filePath = generateFilePath()
+
+ # Transition: AARD-1742
+ # With the addition of a 'release' build the fusion exporter will not have permissions within the sourced
+ # folder. Because of this we cannot use this kind of tmp path anymore. This code was already unused and
+ # should be removed.
+ # self.filePath = generateFilePath()
def toJSON(self) -> str:
"""Converts this class into a json object that can be written to the object data
@@ -50,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
@@ -64,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 d2465ae29b..d7708bfec6 100644
--- a/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py
+++ b/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py
@@ -1,3 +1,7 @@
+import os
+import tempfile
+from pathlib import Path
+
import adsk.core
import adsk.fusion
@@ -5,7 +9,7 @@
from src.Types import OString
-def saveFileDialog(defaultPath: str | None = None, defaultName: str | None = None) -> str | bool:
+def saveFileDialog(defaultPath: str | None = None, defaultName: str | None = None) -> str | os.PathLike[str] | None:
"""Function to generate the Save File Dialog for the Hellion Data files
Args:
@@ -13,11 +17,11 @@ def saveFileDialog(defaultPath: str | None = None, defaultName: str | None = Non
defaultName (str): default name for the saving file
Returns:
- bool: False if canceled
+ None: if canceled
str: full file path
"""
- fileDialog: adsk.core.FileDialog = gm.ui.createFileDialog()
+ fileDialog = gm.ui.createFileDialog()
fileDialog.isMultiSelectEnabled = False
fileDialog.title = "Save Export Result"
@@ -36,11 +40,29 @@ def saveFileDialog(defaultPath: str | None = None, defaultName: str | None = Non
fileDialog.filterIndex = 0
dialogResult = fileDialog.showSave()
- if dialogResult == adsk.core.DialogResults.DialogOK:
- return fileDialog.filename
- else:
+ if dialogResult != adsk.core.DialogResults.DialogOK:
+ return None
+
+ canWrite = isWriteableDirectory(Path(fileDialog.filename).parent)
+ if not canWrite:
+ gm.ui.messageBox("Synthesis does not have the required permissions to write to this directory.")
+ return saveFileDialog(defaultPath, defaultName)
+
+ return fileDialog.filename or ""
+
+
+def isWriteableDirectory(path: str | os.PathLike[str]) -> bool:
+ if not os.access(path, os.W_OK):
+ return False
+
+ try:
+ with tempfile.NamedTemporaryFile(dir=path, delete=True) as f:
+ f.write(b"test")
+ except OSError:
return False
+ return True
+
def generateFilePath() -> str:
"""Generates a temporary file path that can be used to save the file for exporting
@@ -51,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)
@@ -74,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
new file mode 100644
index 0000000000..9916cbb853
--- /dev/null
+++ b/exporter/SynthesisFusionAddin/src/Util.py
@@ -0,0 +1,7 @@
+import os
+
+
+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 1e426279bb..b53fb6b045 100644
--- a/exporter/SynthesisFusionAddin/src/__init__.py
+++ b/exporter/SynthesisFusionAddin/src/__init__.py
@@ -1,17 +1,34 @@
import os
import platform
+from pathlib import Path
from src.GlobalManager import GlobalManager
+from src.Util import makeDirectories
APP_NAME = "Synthesis"
APP_TITLE = "Synthesis Robot Exporter"
DESCRIPTION = "Exports files from Fusion into the Synthesis Format"
INTERNAL_ID = "Synthesis"
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 = platform.system()
-assert SYSTEM != "Linux"
+if SYSTEM == "Windows":
+ SUPPORT_PATH = makeDirectories(os.path.expandvars(r"%appdata%\Autodesk\Synthesis"))
+else:
+ assert SYSTEM == "Darwin"
+ SUPPORT_PATH = makeDirectories(f"{os.path.expanduser('~')}/.config/Autodesk/Synthesis")
gm = GlobalManager()
-__all__ = ["APP_NAME", "APP_TITLE", "DESCRIPTION", "INTERNAL_ID", "ADDIN_PATH", "SYSTEM", "gm"]
+__all__ = [
+ "APP_NAME",
+ "APP_TITLE",
+ "DESCRIPTION",
+ "INTERNAL_ID",
+ "ADDIN_PATH",
+ "IS_RELEASE",
+ "SYSTEM",
+ "SUPPORT_PATH",
+ "gm",
+]
diff --git a/fission/README.md b/fission/README.md
index fc4098ecce..6d8a493e87 100644
--- a/fission/README.md
+++ b/fission/README.md
@@ -1,14 +1,31 @@
-# Fission: Synthesis' web-based robot simulator
+# Fission
-## Gettings Started
+Fission is Synthesis' web-based robotics simulator. This app is hosted [on our website](https://synthesis.github.com/fission/), in addition to a closed, in-development version [here](https://synthesis.autodesk.com/beta/).
+## Setup & Building
### Requirements
1. NPM (v10.2.4 recommended)
+ - Yarn, Bun, or any other package managers work just as well.
2. NodeJS (v20.10.0 recommended)
-3. TypeScript (v4.8.4 recommended) _Unknown if this is actually required_
+ - Needed for running the development server.
-### Assets
+### Setup
+
+You can either run the `init` command or run the following commands details in "Specific Steps":
+
+```bash
+npm i && npm init
+```
+
+Specific Steps
+
+To install all dependencies:
+
+```bash
+npm i
+```
For the asset pack that will be available in production, download the asset pack [here](https://synthesis.autodesk.com/Downloadables/assetpack.zip) and unzip it.
Make sure that the Downloadables directory is placed inside of the public directory like so:
@@ -19,50 +36,63 @@ Make sure that the Downloadables directory is placed inside of the public direct
This can be accomplished with the `assetpack` npm script:
-```
+```bash
npm run assetpack
```
-### Building
-
-To build, install all dependencies:
+We use [Playwright](https://playwright.dev/) for testing consistency. The package is installed with the rest of the dependencies; however, be sure to install the playwright browsers with the following command:
```bash
-npm i
+npx playwright install
+```
+or
+```bash
+npm run playwright:install
```
-### NPM Scripts
-
-| Script | Description |
-| -------------- | ----------------------------------------------------------------------------------------------------------------------- |
-| `dev` | Starts the development server used for testing. Supports hotloading (though finicky with WASM module loading). |
-| `test` | Runs the unit tests via vitest. |
-| `build` | Builds the project into it's packaged form. Uses root base path. |
-| `build:prod` | Builds the project into it's packaged form. Uses the `/fission/` base path. |
-| `preview` | Runs the built project for preview locally before deploying. |
-| `lint` | Runs eslint on the project. |
-| `lint:fix` | Attempts to fix issues found with eslint. |
-| `prettier` | Runs prettier on the project as a check. |
-| `prettier:fix` | Runs prettier on the project to fix any issues with formating. **DO NOT USE**, I don't like the current format it uses. |
-| `format` | Runs `prettier:fix` and `lint:fix`. **Do not use** for the same reasons as `prettier:fix`. |
-| `assetpack` | Downloads the assetpack and unzips/installs it in the correct location. |
-| `playwright:install` | Downloads the playwright browsers. |
+