Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

AARD-1687: Save exporter options with active design #981

Merged
merged 85 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
ce40abc
Exporter Updates (#963)
HunterBarclay Mar 14, 2024
541df47
Pre-Summer Update (#974)
HunterBarclay May 29, 2024
b86ba24
Testing save attrs
BrandonPacewic Jun 17, 2024
a4c5040
More explicit `None` check
BrandonPacewic Jun 17, 2024
f3fcf78
Debug messages
BrandonPacewic Jun 17, 2024
845af5a
More debug logs
BrandonPacewic Jun 17, 2024
b1ecb3f
More debug logs
BrandonPacewic Jun 18, 2024
523f973
More testing
BrandonPacewic Jun 18, 2024
c1a6fe8
Lets see if this works
BrandonPacewic Jun 18, 2024
173296e
Testing even more things
BrandonPacewic Jun 18, 2024
6b08763
Working compress option save
BrandonPacewic Jun 18, 2024
3e9d1f7
Various cleanups
BrandonPacewic Jun 18, 2024
d7586f9
Remove unused saving code
BrandonPacewic Jun 18, 2024
c7a0f00
Small bug (#978)
BrandonPacewic Jun 18, 2024
4b60281
Working on being pythonic
BrandonPacewic Jun 19, 2024
3e943c1
Working generalized exporter options save / load
BrandonPacewic Jun 19, 2024
18033c3
Small refactor to rely on enums
BrandonPacewic Jun 19, 2024
2f66cda
Can now save all options
BrandonPacewic Jun 20, 2024
0cd2e39
Refactored for new exporter options
BrandonPacewic Jun 20, 2024
93baef6
Working wheel and weight design save
BrandonPacewic Jun 20, 2024
90bc976
Formatting
BrandonPacewic Jun 20, 2024
8907702
Final todos
BrandonPacewic Jun 20, 2024
4b5f889
Final cleanups
BrandonPacewic Jun 20, 2024
dda2853
Final formatting
BrandonPacewic Jun 20, 2024
e9373be
Fix export as part save
BrandonPacewic Jun 20, 2024
0a292c6
Bug fix when loading in with no wheels
BrandonPacewic Jun 20, 2024
5fcd7c1
Bug fix for joint type of 0
BrandonPacewic Jun 20, 2024
ed9abe4
Wheel joint type of 0 fix
BrandonPacewic Jun 21, 2024
1e01e9b
Wheel type fix
BrandonPacewic Jun 21, 2024
e99bfc1
Formatting
BrandonPacewic Jun 21, 2024
7c0a0c8
Testing save attrs
BrandonPacewic Jun 17, 2024
d18a890
More explicit `None` check
BrandonPacewic Jun 17, 2024
b07ec61
Debug messages
BrandonPacewic Jun 17, 2024
099f8af
More debug logs
BrandonPacewic Jun 17, 2024
0888764
More debug logs
BrandonPacewic Jun 18, 2024
d3dc497
More testing
BrandonPacewic Jun 18, 2024
cc1dafd
Lets see if this works
BrandonPacewic Jun 18, 2024
06a8e7c
Testing even more things
BrandonPacewic Jun 18, 2024
8bf8057
Working compress option save
BrandonPacewic Jun 18, 2024
9dbea98
Various cleanups
BrandonPacewic Jun 18, 2024
36da53a
Remove unused saving code
BrandonPacewic Jun 18, 2024
150b6b0
Small bug (#978)
BrandonPacewic Jun 18, 2024
f574df0
Working on being pythonic
BrandonPacewic Jun 19, 2024
149d163
Working generalized exporter options save / load
BrandonPacewic Jun 19, 2024
8768d61
Small refactor to rely on enums
BrandonPacewic Jun 19, 2024
51a3786
Can now save all options
BrandonPacewic Jun 20, 2024
04f2a64
Refactored for new exporter options
BrandonPacewic Jun 20, 2024
ae5fccd
Working wheel and weight design save
BrandonPacewic Jun 20, 2024
50a46ee
Formatting
BrandonPacewic Jun 20, 2024
f3688bc
Final todos
BrandonPacewic Jun 20, 2024
14d81ec
Final cleanups
BrandonPacewic Jun 20, 2024
1d08a6e
Final formatting
BrandonPacewic Jun 20, 2024
52272fc
Fix export as part save
BrandonPacewic Jun 20, 2024
c32684f
Bug fix when loading in with no wheels
BrandonPacewic Jun 20, 2024
390e521
Bug fix for joint type of 0
BrandonPacewic Jun 20, 2024
4801963
Wheel joint type of 0 fix
BrandonPacewic Jun 21, 2024
7fee08a
Wheel type fix
BrandonPacewic Jun 21, 2024
7926003
Formatting
BrandonPacewic Jun 21, 2024
4026467
Merge branch 'branp/1687/parsing-options-refactor' of github.com:Auto…
BrandonPacewic Jun 21, 2024
0e82d2c
Merge dev into options refactor
BrandonPacewic Jun 21, 2024
5a2603e
fixed checkbox style
PepperLola Jun 18, 2024
59688f2
fixed panel background
PepperLola Jun 18, 2024
5e1026b
modal now blocks background interactions and is dismissed on click-away
PepperLola Jun 18, 2024
4f338c4
format with prettier:fix
PepperLola Jun 18, 2024
8d39ae7
added option for default panel open location
PepperLola Jun 18, 2024
3f42ab4
format with prettier:fix
PepperLola Jun 18, 2024
f28fafb
added configurable side padding to panels
PepperLola Jun 18, 2024
499467d
configured formatter and formatted all files
PepperLola Jun 18, 2024
8be409a
satisfied linter and fix focus outline on buttons
PepperLola Jun 19, 2024
e7618b6
Cleaned up some linter errors
HunterBarclay Jun 18, 2024
d75c5f6
fix new linter issue with unused eslint-disable directive
PepperLola Jun 19, 2024
0eed6cb
fixed build issue with colorOverrideClass prop
PepperLola Jun 19, 2024
0a376aa
Lock file changed
HunterBarclay Jun 19, 2024
1eaa4a2
Updated README
HunterBarclay Jun 19, 2024
5814e58
Revert all this madness
BrandonPacewic Jun 21, 2024
a9aa61f
Added the ability to choose where the file will be exported
BrandonPacewic Jun 21, 2024
d998bfe
Default path to user home
BrandonPacewic Jun 21, 2024
e721e0e
Updated type and added switch for windows export
HunterBarclay Jun 22, 2024
ff6659e
Inline ternary expression syntax fix
BrandonPacewic Jun 24, 2024
d2488a4
Removed SubProcess, using direct os command
HunterBarclay Jun 24, 2024
0c61a7b
Small error fixes
BrandonPacewic Jun 24, 2024
2f53732
Updated print to show joined command instead of tuple
HunterBarclay Jun 24, 2024
83af8c0
Perhaps a fix
BrandonPacewic Jun 25, 2024
67eae7b
Merge branch 'branp/1687/parsing-options-refactor' of github.com:Auto…
BrandonPacewic Jun 25, 2024
de77f7e
Another fix?
BrandonPacewic Jun 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 61 additions & 26 deletions exporter/SynthesisFusionAddin/proto/deps.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import os
import platform, subprocess
import platform
import logging
from pathlib import Path

from src.general_imports import INTERNAL_ID;
from src.general_imports import INTERNAL_ID

import adsk.core, adsk.fusion

system = platform.system()


def getPythonFolder() -> str:
"""Retreives the folder that contains the Autodesk python executable

Expand All @@ -21,19 +23,23 @@
# Thank you Kris Kaplan
import sys
import importlib.machinery
osPath = importlib.machinery.PathFinder.find_spec('os', sys.path).origin

osPath = importlib.machinery.PathFinder.find_spec("os", sys.path).origin

# The location of the python executable is found relative to the location of the os module in each operating system
if system == "Windows":
pythonFolder = Path(osPath).parents[1]
elif system == "Darwin":
pythonFolder = f'{Path(osPath).parents[2]}/bin'
pythonFolder = f"{Path(osPath).parents[2]}/bin"
else:
raise ImportError('Unsupported platform! This add-in only supports windows and macos')

logging.getLogger(f'{INTERNAL_ID}').debug(f'Python Folder -> {pythonFolder}')
raise ImportError(
"Unsupported platform! This add-in only supports windows and macos"
)

logging.getLogger(f"{INTERNAL_ID}").debug(f"Python Folder -> {pythonFolder}")
return pythonFolder


def executeCommand(command: tuple) -> int:
"""Abstracts the execution of commands to account for platform differences

Expand All @@ -43,20 +49,14 @@
Returns:
int: Exit code of the process
"""
if system == "Windows":
executionResult = subprocess.call(
command,
bufsize=1,
creationflags=subprocess.CREATE_NO_WINDOW,
shell=False
)
else:
# Uses os.system because I was unable to get subprocess.call to work on MacOS
installComm = str.join(' ', command)
executionResult = os.system(installComm)

joinedCommand = str.join(" ", command)
logging.getLogger(f"{INTERNAL_ID}").debug(f"Command -> {joinedCommand}")
executionResult = os.system(joinedCommand)

Check failure on line 55 in exporter/SynthesisFusionAddin/proto/deps.py

View check run for this annotation

Autodesk Chorus / security/bandit

B605: start_process_with_a_shell

Starting a process with a shell, possible injection detected, security issue. secure coding id: PYTH-INJC-30.

return executionResult


def installCross(pipDeps: list) -> bool:
"""Attempts to fetch pip script and resolve dependencies with less user interaction

Expand Down Expand Up @@ -89,25 +89,48 @@
try:
pythonFolder = getPythonFolder()
except ImportError as e:
logging.getLogger(f'{INTERNAL_ID}').error(f'Failed to download dependencies: {e.msg}')
logging.getLogger(f"{INTERNAL_ID}").error(
f"Failed to download dependencies: {e.msg}"
)
return False

if system == "Darwin": # macos

# if nothing has previously fetched it
if (not os.path.exists(f'{pythonFolder}/get-pip.py')) :
executeCommand(['curl', 'https://bootstrap.pypa.io/get-pip.py', '-o', f'"{pythonFolder}/get-pip.py"'])
if not os.path.exists(f"{pythonFolder}/get-pip.py"):
executeCommand(
[
"curl",
"https://bootstrap.pypa.io/get-pip.py",
"-o",
f'"{pythonFolder}/get-pip.py"',
]
)

executeCommand([f'"{pythonFolder}/python"', f'"{pythonFolder}/get-pip.py"'])

pythonExecutable = 'python'
if system == "Windows":
pythonExecutable = 'python.exe'

for depName in pipDeps:
progressBar.progressValue += 1
progressBar.message = f"Installing {depName}..."
adsk.doEvents()

# os.path.join needed for varying system path separators
installResult = executeCommand([f"\"{os.path.join(pythonFolder, 'python')}\"", '-m', 'pip', 'install', depName])
installResult = executeCommand(
[
f"\"{os.path.join(pythonFolder, pythonExecutable)}\"",
"-m",
"pip",
"install",
depName,
]
)
if installResult != 0:
logging.getLogger(f'{INTERNAL_ID}').warn(f'Dep installation "{depName}" exited with code "{installResult}"')
logging.getLogger(f"{INTERNAL_ID}").warn(
f'Dep installation "{depName}" exited with code "{installResult}"'
)

if system == "Darwin":
pipAntiDeps = ["dataclasses", "typing"]
Expand All @@ -117,9 +140,20 @@
progressBar.message = f"Uninstalling {depName}..."
progressBar.progressValue += 1
adsk.doEvents()
uninstallResult = executeCommand([f"\"{os.path.join(pythonFolder, 'python')}\"", '-m', 'pip', 'uninstall', f'{depName}', '-y'])
uninstallResult = executeCommand(
[
f"\"{os.path.join(pythonFolder, pythonExecutable)}\"",
"-m",
"pip",
"uninstall",
f"{depName}",
"-y",
]
)
if uninstallResult != 0:
logging.getLogger(f'{INTERNAL_ID}').warn(f'AntiDep uninstallation "{depName}" exited with code "{uninstallResult}"')
logging.getLogger(f"{INTERNAL_ID}").warn(
f'AntiDep uninstallation "{depName}" exited with code "{uninstallResult}"'
)

progressBar.hide()

Expand All @@ -132,6 +166,7 @@
def _checkDeps() -> bool:
try:
from .proto_out import joint_pb2, assembly_pb2, types_pb2, material_pb2

return True
except ImportError:
return False
Expand Down
179 changes: 179 additions & 0 deletions exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
"""
Container for all options pertaining to the Fusion Exporter.
These options are saved per-design and are passed to the parser upon design export.
"""

import os
import json
import platform
from typing import get_origin
from enum import Enum, EnumType
from dataclasses import dataclass, fields, field
import adsk.core

from adsk.fusion import CalculationAccuracy, TriangleMeshQualityOptions

from ..strings import INTERNAL_ID

# Not 100% sure what this is for - Brandon
JointParentType = Enum("JointParentType", ["ROOT", "END"])

WheelType = Enum("WheelType", ["STANDARD", "OMNI"])
SignalType = Enum("SignalType", ["PWM", "CAN", "PASSIVE"])
ExportMode = Enum("ExportMode", ["ROBOT", "FIELD"]) # Dynamic / Static export
PreferredUnits = Enum("PreferredUnits", ["METRIC", "IMPERIAL"])


@dataclass
class Wheel:
jointToken: str = field(default=None)
wheelType: WheelType = field(default=None)
signalType: SignalType = field(default=None)


@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)


@dataclass
class Gamepiece:
occurrenceToken: str = field(default=None)
weight: float = field(default=None)
friction: float = field(default=None)


class PhysicalDepth(Enum):
# No Physical Properties are generated
NoPhysical = 0

# Only Body Physical Objects are generated
Body = 1

# Only Occurrence that contain Bodies and Bodies have Physical Properties
SurfaceOccurrence = 2

# Every Single Occurrence has Physical Properties even if empty
AllOccurrence = 3


class ModelHierarchy(Enum):
# Model exactly as it is shown in Fusion 360 in the model view tree
FusionAssembly = 0

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

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

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


@dataclass
class ExporterOptions:
fileLocation: str = field(default=(os.getenv("HOME") if platform.system() == "Windows" else os.path.expanduser("~")))
name: str = field(default=None)
version: str = 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)
preferredUnits: PreferredUnits = field(default=PreferredUnits.IMPERIAL)

# Always stored in kg regardless of 'preferredUnits'
robotWeight: float = field(default=0.0)

compressOutput: bool = field(default=True)
exportAsPart: bool = field(default=False)

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

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

return self

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

# There should be a way to clean this up - Brandon
def _makeObjectFromJson(self, objectType: type, data: any) -> any:
primitives = (bool, str, int, float, type(None))
if isinstance(objectType, EnumType):
return objectType(data)
elif (
objectType in primitives or type(data) in primitives
): # Required to catch `fusion.TriangleMeshQualityOptions`
return data
elif get_origin(objectType) is list:
return [
self._makeObjectFromJson(objectType.__args__[0], item) for item in data
]

newObject = objectType()
attrs = [
x
for x in dir(newObject)
if not x.startswith("__") and not callable(getattr(newObject, x))
]
for attr in attrs:
currType = objectType.__annotations__.get(attr, None)
if get_origin(currType) is list:
setattr(
newObject,
attr,
[
self._makeObjectFromJson(currType.__args__[0], item)
for item in data[attr]
],
)
elif currType in primitives:
setattr(newObject, attr, data[attr])
elif isinstance(currType, object):
setattr(newObject, attr, self._makeObjectFromJson(currType, data[attr]))

return newObject
Loading
Loading