Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cae307e
feat: Added Tagging Tab with body selection, dropdown, & table
Dhruv-0-Arora Jun 25, 2025
9531a07
Merge branch 'dev' into dhruv/1880/material-tagging
Dhruv-0-Arora Jun 25, 2025
be9fddd
feat: added add + remove functionality with table
Dhruv-0-Arora Jun 25, 2025
a6324da
feat: added remove button functionality
Dhruv-0-Arora Jun 25, 2025
56739aa
chore: cleanup of code and formatting
Dhruv-0-Arora Jun 25, 2025
8fc46fe
fix: corrected which row is removed
Dhruv-0-Arora Jun 25, 2025
99378e6
feat: adding function to export tags
Dhruv-0-Arora Jun 26, 2025
a54ce77
fix: export button is unselectable due to tagging selection
Dhruv-0-Arora Jun 27, 2025
48018ea
fix: full UI functionality
Dhruv-0-Arora Jun 28, 2025
5f485df
Merging Dev
Dhruv-0-Arora Jun 30, 2025
d7b6ffd
chore: formatting
Dhruv-0-Arora Jun 30, 2025
a68333b
Merging Dev
Dhruv-0-Arora Jul 2, 2025
66da85e
fix: merging reload PR
Dhruv-0-Arora Jul 3, 2025
7d818e7
feat: stores bodies and tags for exporting
Dhruv-0-Arora Jul 3, 2025
6f56faf
Merging Dev
Dhruv-0-Arora Jul 3, 2025
159eb3f
feat: adding exporting
Dhruv-0-Arora Jul 7, 2025
cc2fdbb
Merging Dev
Dhruv-0-Arora Jul 8, 2025
7989f03
formatting
Dhruv-0-Arora Jul 8, 2025
c1651a0
chore: code cleanup
Dhruv-0-Arora Jul 8, 2025
648f41e
feat: adding component column and changing UI remove row message
Dhruv-0-Arora Jul 10, 2025
67bfd07
fix: cleanup of unused code
Dhruv-0-Arora Jul 10, 2025
facc569
Merging Dev
Dhruv-0-Arora Jul 10, 2025
b0b150a
fix: cleaning dead code and fixing spelling error
Dhruv-0-Arora Jul 10, 2025
6cd3d14
feat: added blank icon to allow users to select rows
Dhruv-0-Arora Jul 10, 2025
4f7ff17
formatting
Dhruv-0-Arora Jul 10, 2025
c40a341
isort formatting
Dhruv-0-Arora Jul 11, 2025
8c319e5
feat: adding export of tags into assembly data
Dhruv-0-Arora Jul 11, 2025
0e7987e
code cleanup and formatting
Dhruv-0-Arora Jul 11, 2025
688753e
Merge `dev`
BrandonPacewic Jul 14, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class ExporterOptions:
wheels: list[Wheel] = field(default_factory=list)
joints: list[Joint] = field(default_factory=list)
gamepieces: list[Gamepiece] = field(default_factory=list)
tags: dict[str, str] = field(default_factory=dict)
robotWeight: KG = field(default=KG(0.0))
autoCalcRobotWeight: bool = field(default=False)
autoCalcGamepieceWeight: bool = field(default=False)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ def processBody(body: adsk.fusion.BRepBody | adsk.fusion.MeshBody) -> None:
fill_info(part_body, body)
part_body.part = comp_ref

if body.entityToken in options.tags:
partsData.user_data.data[f"tag_{body.entityToken}"] = options.tags[body.entityToken]

if isinstance(body, adsk.fusion.BRepBody):
_ParseBRep(body, options, part_body.triangle_mesh)
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@
logger = getLogger()


def reload() -> None:
"""Reloads the Parser module"""
import importlib

importlib.reload(Components)
importlib.reload(Joints)
importlib.reload(Materials)
importlib.reload(PDMessage)


class Parser:
def __init__(self, options: ExporterOptions):
"""Creates a new parser with the supplied options
Expand Down
24 changes: 20 additions & 4 deletions exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
import src.UI.GamepieceConfigTab as GamepieceConfigTab
import src.UI.GeneralConfigTab as GeneralConfigTab
import src.UI.JointConfigTab as JointConfigTab
import src.UI.TaggingConfigTab as TaggingConfigTab
from src import APP_WEBSITE_URL, gm
from src.APS.APS import getAuth, getUserInfo
from src.Logging import getLogger, logFailure
from src.Logging import logFailure
from src.Parser.ExporterOptions import ExporterOptions
from src.Types import SELECTABLE_JOINT_TYPES, ExportLocation, ExportMode
from src.UI import FileDialogConfig
Expand All @@ -26,8 +27,7 @@
generalConfigTab: GeneralConfigTab.GeneralConfigTab
jointConfigTab: JointConfigTab.JointConfigTab
gamepieceConfigTab: GamepieceConfigTab.GamepieceConfigTab

logger = getLogger()
taggingConfigTab: TaggingConfigTab.TaggingConfigTab

INPUTS_ROOT: adsk.core.CommandInputs

Expand All @@ -37,9 +37,10 @@ def reload() -> None:
importlib.reload(GeneralConfigTab)
importlib.reload(GamepieceConfigTab)
importlib.reload(JointConfigTab)
importlib.reload(TaggingConfigTab)

importlib.reload(Parser)
logger.info("UI modules reloaded successfully.")
Parser.reload()


class ConfigureCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
Expand Down Expand Up @@ -92,6 +93,10 @@ def notify(self, args: adsk.core.CommandCreatedEventArgs) -> None:
jointConfigTab = JointConfigTab.JointConfigTab(args)
generalConfigTab.jointConfigTab = jointConfigTab

global taggingConfigTab
taggingConfigTab = TaggingConfigTab.TaggingConfigTab(args)
generalConfigTab.taggingConfigTab = taggingConfigTab

if not exporterOptions.exportMode == ExportMode.FIELD:
gamepieceConfigTab.isVisible = False

Expand Down Expand Up @@ -119,6 +124,12 @@ def notify(self, args: adsk.core.CommandCreatedEventArgs) -> None:
if len(fusionJoints):
jointConfigTab.addWheel(fusionJoints[0], wheel)

if len(exporterOptions.tags):
for token, tag in exporterOptions.tags.items():
fusionBody = design.findEntityByToken(token)
if len(fusionBody):
taggingConfigTab.addTag(fusionBody[0], tag)

getAuth()
user_info = getUserInfo()
apsSettings = INPUTS_ROOT.addTabCommandInput(
Expand Down Expand Up @@ -154,6 +165,7 @@ def notify(self, _: adsk.core.CommandEventArgs) -> None:

selectedJoints, selectedWheels = jointConfigTab.getSelectedJointsAndWheels()
selectedGamepieces = gamepieceConfigTab.getGamepieces()
selectedTags = taggingConfigTab.getTags()

exporterOptions = ExporterOptions(
savepath,
Expand All @@ -163,6 +175,7 @@ def notify(self, _: adsk.core.CommandEventArgs) -> None:
joints=selectedJoints,
wheels=selectedWheels,
gamepieces=selectedGamepieces,
tags=selectedTags,
robotWeight=generalConfigTab.robotWeight,
autoCalcRobotWeight=generalConfigTab.autoCalculateWeight,
autoCalcGamepieceWeight=gamepieceConfigTab.autoCalculateWeight,
Expand Down Expand Up @@ -233,6 +246,9 @@ def notify(self, args: adsk.core.InputChangedEventArgs) -> None:
if gamepieceConfigTab.isVisible and gamepieceConfigTab.isActive:
gamepieceConfigTab.handleInputChanged(args, INPUTS_ROOT)

if taggingConfigTab.isVisible and taggingConfigTab.isActive:
taggingConfigTab.handleInputChanged(args, INPUTS_ROOT)


class MyCommandDestroyHandler(PersistentEventHandler, adsk.core.CommandEventHandler):
"""Called when the configuration panel is destroyed."""
Expand Down
2 changes: 2 additions & 0 deletions exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from src.UI.CreateCommandInputsHelper import createBooleanInput
from src.UI.GamepieceConfigTab import GamepieceConfigTab
from src.UI.JointConfigTab import JointConfigTab
from src.UI.TaggingConfigTab import TaggingConfigTab
from src.Util import (
convertMassUnitsFrom,
convertMassUnitsTo,
Expand All @@ -23,6 +24,7 @@ class GeneralConfigTab:
previousSelectedModeDropdownIndex: int
jointConfigTab: JointConfigTab
gamepieceConfigTab: GamepieceConfigTab
taggingConfigTab: TaggingConfigTab

@logFailure
def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: ExporterOptions) -> None:
Expand Down
4 changes: 4 additions & 0 deletions exporter/SynthesisFusionAddin/src/UI/IconPaths.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@
"calculate-enabled": resources + os.path.join("AutoCalcWeight_icon"), # resource folder
"friction_override-enabled": resources + os.path.join("FrictionOverride_icon"), # resource folder
}

tagIcons = {
"blank": resources + "blank-preview16x16.png",
}
168 changes: 168 additions & 0 deletions exporter/SynthesisFusionAddin/src/UI/TaggingConfigTab.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import adsk.core
import adsk.fusion

from src.Logging import getLogger, logFailure
from src.UI import IconPaths
from src.UI.CreateCommandInputsHelper import createTableInput, createTextBoxInput

logger = getLogger()


class TaggingConfigTab:
# stores the types of tags available for selection
tagTypes = ["Softbody", "Rigid", "Chain", "Spring", "Rope"]

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to finalize which tags we would like

tagMap: dict[str, str] = {}
tagList: list[str] = []

taggingConfigTab: adsk.core.TabCommandInput
taggingListTable: adsk.core.TableCommandInput
tagTypeDropdown: adsk.core.DropDownCommandInput

@logFailure
def __init__(self, args: adsk.core.CommandCreatedEventArgs) -> None:
self.taggingConfigTab = args.command.commandInputs.addTabCommandInput("taggingOptionsTab", "Tagging Options")
self.taggingConfigTab.tooltip = "Configure tagging options for materials"
taggingConfigTabInputs = self.taggingConfigTab.children

self.tagTypeDropdown = taggingConfigTabInputs.addDropDownCommandInput(
"tageTypeDropdown", "Tag Type", dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle
)
self.tagTypeDropdown.isFullWidth = False
for tag in self.tagTypes:
self.tagTypeDropdown.listItems.add(tag, False)
self.tagTypeDropdown.isEnabled = self.tagTypeDropdown.isVisible = False

bodySelection = taggingConfigTabInputs.addSelectionInput(
"tagBodySelect", "Select Body", "Select a single body."
)
bodySelection.addSelectionFilter("SolidBodies")
bodySelection.addSelectionFilter("SurfaceBodies")

self.taggingListTable = createTableInput("tagListTable", "Tag List", taggingConfigTabInputs, 6, "1:4:4:4")
self.taggingListTable.addCommandInput(
createTextBoxInput("headerIcon", "", taggingConfigTabInputs, "", bold=False), 0, 0
)
self.taggingListTable.addCommandInput(
createTextBoxInput("headerBodyName", "Body", taggingConfigTabInputs, "Body Name", background="#d9d9d9"),
0,
1,
)
self.taggingListTable.addCommandInput(
createTextBoxInput(
"headerComponentName", "Component", taggingConfigTabInputs, "Component Name", background="#d9d9d9"
),
0,
2,
)
self.taggingListTable.addCommandInput(
createTextBoxInput("headerTagType", "Type", taggingConfigTabInputs, "Tag Type", background="#d9d9d9"), 0, 3
)

addTagInputButton = taggingConfigTabInputs.addBoolValueInput("tagAddButton", "Add", False)
removeTagInputButton = taggingConfigTabInputs.addBoolValueInput("tagRemoveButton", "Remove", False)
cancelInputButton = taggingConfigTabInputs.addBoolValueInput("tagCancelButton", "Cancel", False)

self.taggingListTable.addToolbarCommandInput(addTagInputButton)
self.taggingListTable.addToolbarCommandInput(removeTagInputButton)
self.taggingListTable.addToolbarCommandInput(cancelInputButton)

self.toggleSelecting(False)

@property
def isVisible(self) -> bool:
return self.taggingConfigTab.isVisible or False
Comment thread
Dhruv-0-Arora marked this conversation as resolved.

@isVisible.setter
def isVisible(self, value: bool) -> None:
self.taggingConfigTab.isVisible = value

@property
def isActive(self) -> bool:
return self.taggingConfigTab.isActive or False

def toggleSelecting(self, value: bool) -> None:
tagAddButton: adsk.core.BoolValueCommandInput = self.taggingConfigTab.commandInputs.itemById("tagAddButton")
tagRemoveButton: adsk.core.BoolValueCommandInput = self.taggingConfigTab.commandInputs.itemById(
"tagRemoveButton"
)
tagBodySelection: adsk.core.SelectionCommandInput = self.taggingConfigTab.commandInputs.itemById(
"tagBodySelect"
)
tagCancelButton: adsk.core.BoolValueCommandInput = self.taggingConfigTab.commandInputs.itemById(
"tagCancelButton"
)

tagAddButton.isEnabled = tagRemoveButton.isEnabled = not value
tagCancelButton.isVisible = value
self.tagTypeDropdown.isEnabled = self.tagTypeDropdown.isVisible = value

if not value:
tagBodySelection.clearSelection()
tagBodySelection.setSelectionLimits(0)
tagBodySelection.isEnabled = tagBodySelection.isVisible = False

else:
tagBodySelection.isVisible = tagBodySelection.isEnabled = True
tagBodySelection.setSelectionLimits(0, 1)

def addTag(self, body: adsk.fusion.BRepBody, tag: str) -> None:
commandInputs = self.taggingListTable.commandInputs
row = self.taggingListTable.rowCount

icon = commandInputs.addImageCommandInput(f"tag_icon_{row}", "Ball", IconPaths.tagIcons["blank"])
icon.tooltip = "Tag"

bodyName = commandInputs.addTextBoxCommandInput(f"bodyName_{row}", "Body Name", "", 1, True)
bodyName.formattedText = f"<p style='font-size:11px'>{body.name}</p>"

componentName = commandInputs.addTextBoxCommandInput(f"componentName_{row}", "Component Name", "", 1, True)
componentName.formattedText = f"<p style='font-size:11px'>{body.parentComponent.name}</p>"

tagType = commandInputs.addTextBoxCommandInput(f"tagType_{row}", "Tag Type", "", 1, True)
tagType.formattedText = f"<p style='font-size:11px'>{tag}</p>"

self.taggingListTable.addCommandInput(icon, row, 0)
self.taggingListTable.addCommandInput(bodyName, row, 1)
self.taggingListTable.addCommandInput(componentName, row, 2)
self.taggingListTable.addCommandInput(tagType, row, 3)

key_body = body.nativeObject or body
self.tagMap[key_body.entityToken] = tag
self.tagList.append(key_body.entityToken)

@logFailure
def handleInputChanged(
self, args: adsk.core.InputChangedEventArgs, globalCommandInputs: adsk.core.CommandInputs
) -> None:
commandInput = args.input
tagBodySelection: adsk.core.SelectionCommandInput = globalCommandInputs.itemById("tagBodySelect")

if commandInput.id == "tagAddButton":
self.toggleSelecting(True)

elif commandInput.id == "tagRemoveButton":
if self.taggingListTable.selectedRow == -1:
app = adsk.core.Application.get()
ui = app.userInterface
ui.messageBox("Please first select a tag to remove.")
return

selectedRow = self.taggingListTable.selectedRow
if selectedRow == 0:
return

token_to_remove = self.tagList[selectedRow - 1]
self.taggingListTable.deleteRow(selectedRow)
self.tagMap.pop(token_to_remove)
self.tagList.pop(selectedRow - 1)

elif commandInput.id == "tagCancelButton":
self.toggleSelecting(False)

elif tagBodySelection.selectionCount == 1 and self.tagTypeDropdown.selectedItem is not None:
self.addTag(tagBodySelection.selection(0).entity, self.tagTypeDropdown.selectedItem.name)
self.toggleSelecting(False)

@logFailure
def getTags(self) -> dict[str, str]:
return self.tagMap
Loading