diff --git a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py
index 92e739da77..1eb34e9e52 100644
--- a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py
+++ b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py
@@ -21,7 +21,6 @@
Joint,
ModelHierarchy,
PhysicalDepth,
- PreferredUnits,
Wheel,
encodeNestedObjects,
makeObjectFromJson,
@@ -43,10 +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)
- preferredUnits: PreferredUnits = field(default=PreferredUnits.IMPERIAL)
-
- # Always stored in kg regardless of 'preferredUnits'
- robotWeight: KG = field(default=0.0)
+ robotWeight: KG = field(default=KG(0.0))
autoCalcRobotWeight: bool = field(default=False)
autoCalcGamepieceWeight: bool = field(default=False)
diff --git a/exporter/SynthesisFusionAddin/src/Resources/kg_icon/16x16-normal.png b/exporter/SynthesisFusionAddin/src/Resources/kg_icon/16x16-normal.png
deleted file mode 100644
index 86e8fd3cf8..0000000000
Binary files a/exporter/SynthesisFusionAddin/src/Resources/kg_icon/16x16-normal.png and /dev/null differ
diff --git a/exporter/SynthesisFusionAddin/src/Resources/lbs_icon/16x16-normal.png b/exporter/SynthesisFusionAddin/src/Resources/lbs_icon/16x16-normal.png
deleted file mode 100644
index ced649bb03..0000000000
Binary files a/exporter/SynthesisFusionAddin/src/Resources/lbs_icon/16x16-normal.png and /dev/null differ
diff --git a/exporter/SynthesisFusionAddin/src/Types.py b/exporter/SynthesisFusionAddin/src/Types.py
index 5e2d7c967f..166bc43976 100644
--- a/exporter/SynthesisFusionAddin/src/Types.py
+++ b/exporter/SynthesisFusionAddin/src/Types.py
@@ -5,14 +5,24 @@
from enum import Enum, EnumType
from typing import Any, TypeAlias, get_args, get_origin
+import adsk.fusion
+
# Not 100% sure what this is for - Brandon
JointParentType = Enum("JointParentType", ["ROOT", "END"])
WheelType = Enum("WheelType", ["STANDARD", "OMNI", "MECANUM"])
SignalType = Enum("SignalType", ["PWM", "CAN", "PASSIVE"])
ExportMode = Enum("ExportMode", ["ROBOT", "FIELD"]) # Dynamic / Static export
-PreferredUnits = Enum("PreferredUnits", ["METRIC", "IMPERIAL"])
ExportLocation = Enum("ExportLocation", ["UPLOAD", "DOWNLOAD"])
+UnitSystem = Enum("UnitSystem", ["METRIC", "IMPERIAL"])
+
+FUSION_UNIT_SYSTEM: dict[int, UnitSystem] = {
+ adsk.fusion.DistanceUnits.MillimeterDistanceUnits: UnitSystem.METRIC,
+ adsk.fusion.DistanceUnits.CentimeterDistanceUnits: UnitSystem.METRIC,
+ adsk.fusion.DistanceUnits.MeterDistanceUnits: UnitSystem.METRIC,
+ adsk.fusion.DistanceUnits.InchDistanceUnits: UnitSystem.IMPERIAL,
+ adsk.fusion.DistanceUnits.FootDistanceUnits: UnitSystem.IMPERIAL,
+}
@dataclass
@@ -72,18 +82,8 @@ class ModelHierarchy(Enum):
SingleMesh = 3
-LBS: TypeAlias = float
KG: TypeAlias = float
-
-
-def toLbs(kgs: KG) -> LBS:
- return LBS(round(kgs * 2.2062, 2))
-
-
-def toKg(pounds: LBS) -> KG:
- return KG(round(pounds / 2.2062, 2))
-
-
+LBS: TypeAlias = float
PRIMITIVES = (bool, str, int, float, type(None))
diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py
index b13fb7ccba..b463302c5b 100644
--- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py
+++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py
@@ -331,12 +331,6 @@ def notify(self, args: adsk.core.CommandEventArgs) -> None:
selectedJoints, selectedWheels = jointConfigTab.getSelectedJointsAndWheels()
selectedGamepieces = gamepieceConfigTab.getGamepieces()
- if generalConfigTab.exportMode == ExportMode.ROBOT:
- units = generalConfigTab.selectedUnits
- else:
- assert generalConfigTab.exportMode == ExportMode.FIELD
- units = gamepieceConfigTab.selectedUnits
-
exporterOptions = ExporterOptions(
str(savepath),
name,
@@ -345,7 +339,6 @@ def notify(self, args: adsk.core.CommandEventArgs) -> None:
joints=selectedJoints,
wheels=selectedWheels,
gamepieces=selectedGamepieces,
- preferredUnits=units,
robotWeight=generalConfigTab.robotWeight,
autoCalcRobotWeight=generalConfigTab.autoCalculateWeight,
autoCalcGamepieceWeight=gamepieceConfigTab.autoCalculateWeight,
diff --git a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py
index d3e26f1f99..c28f6c60f1 100644
--- a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py
+++ b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py
@@ -3,13 +3,13 @@
from src.Logging import logFailure
from src.Parser.ExporterOptions import ExporterOptions
-from src.Types import Gamepiece, PreferredUnits, toKg, toLbs
-from src.UI import IconPaths
+from src.Types import Gamepiece, UnitSystem
from src.UI.CreateCommandInputsHelper import (
createBooleanInput,
createTableInput,
createTextBoxInput,
)
+from src.Util import convertMassUnitsFrom, convertMassUnitsTo, getFusionUnitSystem
class GamepieceConfigTab:
@@ -19,7 +19,6 @@ class GamepieceConfigTab:
gamepieceTable: adsk.core.TableCommandInput
previousAutoCalcWeightCheckboxState: bool
previousSelectedUnitDropdownIndex: int
- currentUnits: PreferredUnits
@logFailure
def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: ExporterOptions) -> None:
@@ -37,20 +36,6 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp
)
self.previousAutoCalcWeightCheckboxState = exporterOptions.autoCalcGamepieceWeight
- self.currentUnits = exporterOptions.preferredUnits
- imperialUnits = self.currentUnits == PreferredUnits.IMPERIAL
- weightUnitTable = gamepieceTabInputs.addDropDownCommandInput(
- "gamepieceWeightUnit", "Unit of Mass", adsk.core.DropDownStyles.LabeledIconDropDownStyle
- )
-
- # Invisible white space characters are required in the list item name field to make this work.
- # I have no idea why, Fusion API needs some special education help - Brandon
- weightUnitTable.listItems.add("", imperialUnits, IconPaths.massIcons["LBS"])
- weightUnitTable.listItems.add("", not imperialUnits, IconPaths.massIcons["KG"])
- weightUnitTable.tooltip = "Unit of mass"
- weightUnitTable.tooltipDescription = "
Configure the unit of mass for for the weight calculation."
- self.previousSelectedUnitDropdownIndex = int(not imperialUnits)
-
self.gamepieceTable = createTableInput(
"gamepieceTable",
"Gamepiece",
@@ -62,8 +47,17 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp
self.gamepieceTable.addCommandInput(
createTextBoxInput("gamepieceNameHeader", "Name", gamepieceTabInputs, "Name", bold=False), 0, 0
)
+ fusUnitSystem = getFusionUnitSystem()
self.gamepieceTable.addCommandInput(
- createTextBoxInput("gamepieceWeightHeader", "Weight", gamepieceTabInputs, "Weight", bold=False), 0, 1
+ createTextBoxInput(
+ "gamepieceWeightHeader",
+ "Weight",
+ gamepieceTabInputs,
+ f"Weight {'(lbs)' if fusUnitSystem is UnitSystem.IMPERIAL else '(kg)'}",
+ bold=False,
+ ),
+ 0,
+ 1,
)
self.gamepieceTable.addCommandInput(
createTextBoxInput(
@@ -112,10 +106,6 @@ def isVisible(self, value: bool) -> None:
def isActive(self) -> bool:
return self.gamepieceConfigTab.isActive or False
- @property
- def selectedUnits(self) -> PreferredUnits:
- return self.currentUnits
-
@property
def autoCalculateWeight(self) -> bool:
autoCalcWeightButton: adsk.core.BoolValueCommandInput = self.gamepieceConfigTab.children.itemById(
@@ -168,26 +158,13 @@ def addChildOccurrences(childOccurrences: adsk.fusion.OccurrenceList) -> None:
frictionCoefficient.valueOne = 0.5
physical = gamepiece.component.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy)
- if self.currentUnits == PreferredUnits.IMPERIAL:
- gamepieceMass = toLbs(physical.mass)
- else:
- gamepieceMass = round(physical.mass, 2)
-
+ gamepieceMass = round(convertMassUnitsFrom(physical.mass), 2)
weight = commandInputs.addValueInput(
"gamepieceWeight", "Weight Input", "", adsk.core.ValueInput.createByString(str(gamepieceMass))
)
weight.tooltip = "Weight of field element"
weight.isEnabled = not self.previousAutoCalcWeightCheckboxState
- weightUnitDropdown: adsk.core.DropDownCommandInput = self.gamepieceConfigTab.children.itemById(
- "gamepieceWeightUnit"
- )
- if weightUnitDropdown.selectedItem.index == 0:
- weight.tooltipDescription = "(in pounds)"
- else:
- assert weightUnitDropdown.selectedItem.index == 1
- weight.tooltipDescription = "(in kilograms)"
-
row = self.gamepieceTable.rowCount
self.gamepieceTable.addCommandInput(gamepieceName, row, 0)
self.gamepieceTable.addCommandInput(weight, row, 1)
@@ -222,7 +199,7 @@ def getGamepieces(self) -> list[Gamepiece]:
gamepieces: list[Gamepiece] = []
for row in range(1, self.gamepieceTable.rowCount): # Row is 1 indexed
gamepieceEntityToken = self.selectedGamepieceList[row - 1].entityToken
- gamepieceWeight = self.gamepieceTable.getInputAtPosition(row, 1).value
+ gamepieceWeight = convertMassUnitsTo(self.gamepieceTable.getInputAtPosition(row, 1).value)
gamepieceFrictionCoefficient = self.gamepieceTable.getInputAtPosition(row, 2).valueOne
gamepieces.append(Gamepiece(gamepieceEntityToken, gamepieceWeight, gamepieceFrictionCoefficient))
@@ -232,14 +209,6 @@ def reset(self) -> None:
self.selectedGamepieceEntityIDs.clear()
self.selectedGamepieceList.clear()
- @logFailure
- def updateWeightTableToUnits(self, units: PreferredUnits) -> None:
- assert units in {PreferredUnits.METRIC, PreferredUnits.IMPERIAL}
- conversionFunc = toKg if units == PreferredUnits.METRIC else toLbs
- for row in range(1, self.gamepieceTable.rowCount): # Row is 1 indexed
- weightInput: adsk.core.ValueCommandInput = self.gamepieceTable.getInputAtPosition(row, 1)
- weightInput.value = conversionFunc(weightInput.value)
-
@logFailure
def calcGamepieceWeights(self) -> None:
for row in range(1, self.gamepieceTable.rowCount): # Row is 1 indexed
@@ -247,10 +216,7 @@ def calcGamepieceWeights(self) -> None:
physical = self.selectedGamepieceList[row - 1].component.getPhysicalProperties(
adsk.fusion.CalculationAccuracy.LowCalculationAccuracy
)
- if self.currentUnits == PreferredUnits.IMPERIAL:
- weightInput.value = toLbs(physical.mass)
- else:
- weightInput.value = round(physical.mass, 2)
+ weightInput.value = round(convertMassUnitsFrom(physical.mass), 2)
@logFailure
def handleInputChanged(
@@ -283,20 +249,6 @@ def handleInputChanged(
self.previousAutoCalcWeightCheckboxState = autoCalcWeightButton.value
- elif commandInput.id == "gamepieceWeightUnit":
- weightUnitDropdown = adsk.core.DropDownCommandInput.cast(commandInput)
- if weightUnitDropdown.selectedItem.index == self.previousSelectedUnitDropdownIndex:
- return
-
- if weightUnitDropdown.selectedItem.index == 0:
- self.currentUnits = PreferredUnits.IMPERIAL
- else:
- assert weightUnitDropdown.selectedItem.index == 1
- self.currentUnits = PreferredUnits.METRIC
-
- self.updateWeightTableToUnits(self.currentUnits)
- self.previousSelectedUnitDropdownIndex = weightUnitDropdown.selectedItem.index
-
elif commandInput.id == "gamepieceAddButton":
gamepieceSelection.isVisible = gamepieceSelection.isEnabled = True
gamepieceSelection.clearSelection()
diff --git a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py
index 673e56054b..977f5d9298 100644
--- a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py
+++ b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py
@@ -3,11 +3,16 @@
from src.Logging import logFailure
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.Types import KG, ExportLocation, ExportMode, UnitSystem
+from src.UI.CreateCommandInputsHelper import createBooleanInput
from src.UI.GamepieceConfigTab import GamepieceConfigTab
from src.UI.JointConfigTab import JointConfigTab
+from src.Util import (
+ convertMassUnitsFrom,
+ convertMassUnitsTo,
+ designMassCalculation,
+ getFusionUnitSystem,
+)
class GeneralConfigTab:
@@ -16,7 +21,6 @@ class GeneralConfigTab:
previousFrictionOverrideCheckboxState: bool
previousSelectedUnitDropdownIndex: int
previousSelectedModeDropdownIndex: int
- currentUnits: PreferredUnits
jointConfigTab: JointConfigTab
gamepieceConfigTab: GamepieceConfigTab
@@ -52,20 +56,6 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp
"
Do you want to upload this mirabuf file to APS, or download it to your local machine?"
)
- weightTableInput = createTableInput(
- "weightTable",
- "Weight Table",
- generalTabInputs,
- 4,
- "2:1:1",
- 1,
- )
- weightTableInput.tablePresentationStyle = 2 # Transparent background
-
- weightName = generalTabInputs.addStringValueInput("weightName", "Weight")
- weightName.value = "Weight"
- weightName.isReadOnly = True
-
autoCalcWeightButton = createBooleanInput(
"autoCalcWeightButton",
"Auto Calculate Robot Weight",
@@ -75,44 +65,15 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp
)
self.previousAutoCalcWeightCheckboxState = exporterOptions.autoCalcRobotWeight
- self.currentUnits = exporterOptions.preferredUnits
- imperialUnits = self.currentUnits == PreferredUnits.IMPERIAL
- if imperialUnits:
- # ExporterOptions always contains the metric value
- displayWeight = toLbs(exporterOptions.robotWeight)
- else:
- displayWeight = exporterOptions.robotWeight
+ displayWeight = convertMassUnitsFrom(exporterOptions.robotWeight)
+ fusUnitSystem = getFusionUnitSystem()
weightInput = generalTabInputs.addValueInput(
"weightInput",
- "Weight Input",
+ f"Weight {'(lbs)' if fusUnitSystem is UnitSystem.IMPERIAL else '(kg)'}",
"",
adsk.core.ValueInput.createByReal(displayWeight),
)
- weightInput.tooltip = "Robot weight"
- weightInput.tooltipDescription = (
- f"(in {'pounds' if self.currentUnits == PreferredUnits.IMPERIAL else 'kilograms'})"
- "
This is the weight of the entire robot assembly."
- )
- weightInput.isEnabled = not exporterOptions.autoCalcRobotWeight
-
- weightUnitDropdown = generalTabInputs.addDropDownCommandInput(
- "weightUnitDropdown",
- "Weight Unit",
- adsk.core.DropDownStyles.LabeledIconDropDownStyle,
- )
-
- # Invisible white space characters are required in the list item name field to make this work.
- # I have no idea why, Fusion API needs some special education help - Brandon
- weightUnitDropdown.listItems.add("", imperialUnits, IconPaths.massIcons["LBS"])
- weightUnitDropdown.listItems.add("", not imperialUnits, IconPaths.massIcons["KG"])
- weightUnitDropdown.tooltip = "Unit of Mass"
- weightUnitDropdown.tooltipDescription = "
Configure the unit of mass for the weight calculation."
- self.previousSelectedUnitDropdownIndex = int(not imperialUnits)
-
- weightTableInput.addCommandInput(weightName, 0, 0)
- weightTableInput.addCommandInput(weightInput, 0, 1)
- weightTableInput.addCommandInput(weightUnitDropdown, 0, 2)
createBooleanInput(
"compressOutputButton",
@@ -152,7 +113,7 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp
if exporterOptions.exportMode == ExportMode.FIELD:
autoCalcWeightButton.isVisible = False
exportAsPartButton.isVisible = False
- weightInput.isVisible = weightTableInput.isVisible = False
+ weightInput.isVisible = False
frictionOverrideButton.isVisible = frictionCoefficient.isVisible = False
@property
@@ -184,20 +145,10 @@ def exportAsPart(self) -> bool:
)
return exportAsPartButton.value or False
- @property
- def selectedUnits(self) -> PreferredUnits:
- return self.currentUnits
-
@property
def robotWeight(self) -> KG:
- weightInput: adsk.core.ValueCommandInput = self.generalOptionsTab.children.itemById(
- "weightTable"
- ).getInputAtPosition(0, 1)
- if self.currentUnits == PreferredUnits.METRIC:
- return KG(weightInput.value)
- else:
- assert self.currentUnits == PreferredUnits.IMPERIAL
- return toKg(weightInput.value)
+ weightInput: adsk.core.ValueCommandInput = self.generalOptionsTab.children.itemById("weightInput")
+ return convertMassUnitsTo(weightInput.value)
@property
def autoCalculateWeight(self) -> bool:
@@ -234,8 +185,7 @@ def frictionOverrideCoeff(self) -> float:
@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)
+ weightInput: adsk.core.ValueCommandInput = args.inputs.itemById("weightInput")
exportAsPartButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("exportAsPartButton")
overrideFrictionButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("frictionOverride")
frictionSlider: adsk.core.FloatSliderCommandInput = args.inputs.itemById("frictionCoefficient")
@@ -251,7 +201,7 @@ def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None:
self.gamepieceConfigTab.isVisible = False
autoCalcWeightButton.isVisible = True
- weightTable.isVisible = True
+ weightInput.isVisible = True
exportAsPartButton.isVisible = True
overrideFrictionButton.isVisible = True
frictionSlider.isVisible = overrideFrictionButton.value
@@ -261,41 +211,19 @@ def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None:
self.gamepieceConfigTab.isVisible = True
autoCalcWeightButton.isVisible = False
- weightTable.isVisible = False
+ weightInput.isVisible = False
exportAsPartButton.isVisible = False
overrideFrictionButton.isVisible = frictionSlider.isVisible = False
self.previousSelectedModeDropdownIndex = modeDropdown.selectedItem.index
- elif commandInput.id == "weightUnitDropdown":
- weightUnitDropdown = adsk.core.DropDownCommandInput.cast(commandInput)
- if weightUnitDropdown.selectedItem.index == self.previousSelectedUnitDropdownIndex:
- return
-
- if weightUnitDropdown.selectedItem.index == 0:
- self.currentUnits = PreferredUnits.IMPERIAL
- weightInput.value = toLbs(weightInput.value)
- weightInput.tooltipDescription = (
- "(in pounds)
This is the weight of the entire robot assembly."
- )
- else:
- assert weightUnitDropdown.selectedItem.index == 1
- self.currentUnits = PreferredUnits.METRIC
- weightInput.value = toKg(weightInput.value)
- weightInput.tooltipDescription = (
- "(in kilograms)
This is the weight of the entire robot assembly."
- )
-
- self.previousSelectedUnitDropdownIndex = weightUnitDropdown.selectedItem.index
-
elif commandInput.id == "autoCalcWeightButton":
autoCalcWeightButton = adsk.core.BoolValueCommandInput.cast(commandInput)
if autoCalcWeightButton.value == self.previousAutoCalcWeightCheckboxState:
return
if autoCalcWeightButton.value:
- robotMass = designMassCalculation()
- weightInput.value = robotMass if self.currentUnits is PreferredUnits.METRIC else toLbs(robotMass)
+ weightInput.value = designMassCalculation()
weightInput.isEnabled = False
else:
weightInput.isEnabled = True
@@ -308,22 +236,4 @@ def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None:
return
frictionSlider.isVisible = frictionOverrideButton.value
-
self.previousFrictionOverrideCheckboxState = frictionOverrideButton.value
-
-
-# TODO: Perhaps move this into a different module
-@logFailure
-def designMassCalculation() -> KG:
- app = adsk.core.Application.get()
- mass = 0.0
- for body in [x for x in app.activeDocument.design.rootComponent.bRepBodies if x.isLightBulbOn]:
- physical = body.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy)
- mass += physical.mass
-
- for occ in [x for x in app.activeDocument.design.rootComponent.allOccurrences if x.isLightBulbOn]:
- for body in [x for x in occ.component.bRepBodies if x.isLightBulbOn]:
- physical = body.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy)
- mass += physical.mass
-
- return round(mass, 2)
diff --git a/exporter/SynthesisFusionAddin/src/UI/IconPaths.py b/exporter/SynthesisFusionAddin/src/UI/IconPaths.py
index 2804af221a..8b377eb659 100644
--- a/exporter/SynthesisFusionAddin/src/UI/IconPaths.py
+++ b/exporter/SynthesisFusionAddin/src/UI/IconPaths.py
@@ -32,11 +32,6 @@
"remove": resources + os.path.join("MousePreselectIcons", "mouse-remove-icon.png"),
}
-massIcons = {
- "KG": resources + os.path.join("kg_icon"), # resource folder
- "LBS": resources + os.path.join("lbs_icon"), # resource folder
-}
-
signalIcons = {
"PWM": resources + os.path.join("PWM_icon"), # resource folder
"CAN": resources + os.path.join("CAN_icon"), # resource folder
diff --git a/exporter/SynthesisFusionAddin/src/Util.py b/exporter/SynthesisFusionAddin/src/Util.py
index 9916cbb853..c5f91ef635 100644
--- a/exporter/SynthesisFusionAddin/src/Util.py
+++ b/exporter/SynthesisFusionAddin/src/Util.py
@@ -1,5 +1,46 @@
import os
+import adsk.core
+import adsk.fusion
+
+from src.Types import FUSION_UNIT_SYSTEM, KG, LBS, UnitSystem
+
+
+def getFusionUnitSystem() -> UnitSystem:
+ fusDesign = adsk.fusion.Design.cast(adsk.core.Application.get().activeProduct)
+ return FUSION_UNIT_SYSTEM.get(fusDesign.fusionUnitsManager.distanceDisplayUnits, UnitSystem.METRIC)
+
+
+def convertMassUnitsFrom(input: KG | LBS) -> KG | LBS:
+ """Converts stored Synthesis mass units into user selected Fusion units."""
+ unitManager = adsk.fusion.Design.cast(adsk.core.Application.get().activeProduct).fusionUnitsManager
+ toString = "kg" if getFusionUnitSystem() is UnitSystem.METRIC else "lbmass"
+ return unitManager.convert(input, "kg", toString) or 0.0
+
+
+def convertMassUnitsTo(input: KG | LBS) -> KG | LBS:
+ """Converts user selected Fusion mass units into Synthesis units."""
+ unitManager = adsk.fusion.Design.cast(adsk.core.Application.get().activeProduct).fusionUnitsManager
+ fromString = "kg" if getFusionUnitSystem() is UnitSystem.METRIC else "lbmass"
+ return unitManager.convert(input, fromString, "kg") or 0.0
+
+
+def designMassCalculation() -> KG | LBS:
+ """Calculates and returns the total mass of the active design in Fusion units."""
+ app = adsk.core.Application.get()
+ mass = 0.0
+ for body in [x for x in app.activeDocument.design.rootComponent.bRepBodies if x.isLightBulbOn]:
+ physical = body.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy)
+ mass += physical.mass
+
+ for occ in [x for x in app.activeDocument.design.rootComponent.allOccurrences if x.isLightBulbOn]:
+ for body in [x for x in occ.component.bRepBodies if x.isLightBulbOn]:
+ physical = body.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy)
+ mass += physical.mass
+
+ # Internally, Fusion always uses metric units, same as Synthesis
+ return round(convertMassUnitsFrom(mass), 2)
+
def makeDirectories(directory: str) -> str:
"""Ensures than an input directory exists and attempts to create it if it doesn't."""
diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx
index 2a289c6d12..52475c3f80 100644
--- a/fission/src/Synthesis.tsx
+++ b/fission/src/Synthesis.tsx
@@ -36,7 +36,6 @@ import ThemeEditorModal from "@/modals/configuring/theme-editor/ThemeEditorModal
import MatchModeModal from "@/modals/spawning/MatchModeModal"
import RobotSwitchPanel from "@/panels/RobotSwitchPanel"
import SpawnLocationsPanel from "@/panels/SpawnLocationPanel"
-import ConfigureSubsystemsPanel from "@/ui/panels/configuring/ConfigureSubsystemsPanel.tsx"
import ScoreboardPanel from "@/panels/information/ScoreboardPanel"
import DriverStationPanel from "@/panels/simulation/DriverStationPanel"
import PokerPanel from "@/panels/PokerPanel.tsx"
@@ -46,7 +45,6 @@ import ImportMirabufPanel from "@/ui/panels/mirabuf/ImportMirabufPanel.tsx"
import Skybox from "./ui/components/Skybox.tsx"
import ChooseInputSchemePanel from "./ui/panels/configuring/ChooseInputSchemePanel.tsx"
import ProgressNotifications from "./ui/components/ProgressNotification.tsx"
-import ConfigureRobotBrainPanel from "./ui/panels/configuring/ConfigureRobotBrainPanel.tsx"
import SceneOverlay from "./ui/components/SceneOverlay.tsx"
import WPILibWSWorker from "@/systems/simulation/wpilib_brain/WPILibWSWorker.ts?worker"
@@ -228,17 +226,10 @@ const initialPanels: ReactElement[] = [
,
,
,
- ,
,
,
,
,
- ,
]
export default Synthesis
diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts
index 1355d9e38c..89b61c82e6 100644
--- a/fission/src/mirabuf/MirabufLoader.ts
+++ b/fission/src/mirabuf/MirabufLoader.ts
@@ -10,8 +10,9 @@ export type MirabufCacheID = string
export interface MirabufCacheInfo {
id: MirabufCacheID
- cacheKey: string
miraType: MiraType
+ cacheKey: string
+ buffer?: ArrayBuffer
name?: string
thumbnailStorageID?: string
}
@@ -29,10 +30,10 @@ const root = await navigator.storage.getDirectory()
const robotFolderHandle = await root.getDirectoryHandle(robotsDirName, { create: true })
const fieldFolderHandle = await root.getDirectoryHandle(fieldsDirName, { create: true })
-export const backUpRobots: Map = new Map()
-export const backUpFields: Map = new Map()
+export let backUpRobots: MapCache = {}
+export let backUpFields: MapCache = {}
-const canOPFS = await (async () => {
+export const canOPFS = await (async () => {
try {
if (robotFolderHandle.name == robotsDirName) {
robotFolderHandle.entries
@@ -52,6 +53,21 @@ const canOPFS = await (async () => {
}
} catch (e) {
console.log(`No access to OPFS`)
+
+ // Copy-pasted from RemoveAll()
+ for await (const key of robotFolderHandle.keys()) {
+ robotFolderHandle.removeEntry(key)
+ }
+ for await (const key of fieldFolderHandle.keys()) {
+ fieldFolderHandle.removeEntry(key)
+ }
+
+ window.localStorage.setItem(robotsDirName, "{}")
+ window.localStorage.setItem(fieldsDirName, "{}")
+
+ backUpRobots = {}
+ backUpFields = {}
+
return false
}
})()
@@ -75,11 +91,10 @@ class MirabufCachingService {
*/
public static GetCacheMap(miraType: MiraType): MapCache {
if (
- (window.localStorage.getItem(MIRABUF_LOCALSTORAGE_GENERATION_KEY) ?? "") == MIRABUF_LOCALSTORAGE_GENERATION
+ (window.localStorage.getItem(MIRABUF_LOCALSTORAGE_GENERATION_KEY) ?? "") != MIRABUF_LOCALSTORAGE_GENERATION
) {
window.localStorage.setItem(MIRABUF_LOCALSTORAGE_GENERATION_KEY, MIRABUF_LOCALSTORAGE_GENERATION)
- window.localStorage.setItem(robotsDirName, "{}")
- window.localStorage.setItem(fieldsDirName, "{}")
+ this.RemoveAll()
return {}
}
@@ -188,16 +203,19 @@ class MirabufCachingService {
try {
const map: MapCache = this.GetCacheMap(miraType)
const id = map[key].id
+ const _buffer = miraType == MiraType.ROBOT ? backUpRobots[id].buffer : backUpFields[id].buffer
const _name = map[key].name
const _thumbnailStorageID = map[key].thumbnailStorageID
const info: MirabufCacheInfo = {
id: id,
cacheKey: key,
miraType: miraType,
+ buffer: _buffer,
name: name ?? _name,
thumbnailStorageID: thumbnailStorageID ?? _thumbnailStorageID,
}
map[key] = info
+ miraType == MiraType.ROBOT ? (backUpRobots[id] = info) : (backUpFields[id] = info)
window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map))
return true
} catch (e) {
@@ -243,7 +261,7 @@ class MirabufCachingService {
// Get buffer from hashMap. If not in hashMap, check OPFS. Otherwise, buff is undefined
const cache = miraType == MiraType.ROBOT ? backUpRobots : backUpFields
const buff =
- cache.get(id) ??
+ cache[id]?.buffer ??
(await (async () => {
const fileHandle = canOPFS
? await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(id, {
@@ -299,7 +317,7 @@ class MirabufCachingService {
const backUpCache = miraType == MiraType.ROBOT ? backUpRobots : backUpFields
if (backUpCache) {
- backUpCache.delete(id)
+ delete backUpCache[id]
}
World.AnalyticsSystem?.Event("Cache Remove", {
@@ -318,18 +336,20 @@ class MirabufCachingService {
* Removes all Mirabuf files from the caching services. Mostly for debugging purposes.
*/
public static async RemoveAll() {
- for await (const key of robotFolderHandle.keys()) {
- robotFolderHandle.removeEntry(key)
- }
- for await (const key of fieldFolderHandle.keys()) {
- fieldFolderHandle.removeEntry(key)
+ if (canOPFS) {
+ for await (const key of robotFolderHandle.keys()) {
+ robotFolderHandle.removeEntry(key)
+ }
+ for await (const key of fieldFolderHandle.keys()) {
+ fieldFolderHandle.removeEntry(key)
+ }
}
- window.localStorage.removeItem(robotsDirName)
- window.localStorage.removeItem(fieldsDirName)
+ window.localStorage.setItem(robotsDirName, "{}")
+ window.localStorage.setItem(fieldsDirName, "{}")
- backUpRobots.clear()
- backUpFields.clear()
+ backUpRobots = {}
+ backUpFields = {}
}
// Optional name for when assembly is being decoded anyway like in CacheAndGetLocal()
@@ -339,10 +359,10 @@ class MirabufCachingService {
miraType?: MiraType,
name?: string
): Promise {
- const backupID = Date.now().toString()
try {
+ const backupID = Date.now().toString()
if (!miraType) {
- console.log("Double loading")
+ console.debug("Double loading")
miraType = this.AssemblyFromBuffer(miraBuff).dynamic ? MiraType.ROBOT : MiraType.FIELD
}
@@ -350,8 +370,8 @@ class MirabufCachingService {
const map: MapCache = this.GetCacheMap(miraType)
const info: MirabufCacheInfo = {
id: backupID,
- cacheKey: key,
miraType: miraType,
+ cacheKey: key,
name: name,
}
map[key] = info
@@ -377,7 +397,14 @@ class MirabufCachingService {
// Store in hash
const cache = miraType == MiraType.ROBOT ? backUpRobots : backUpFields
- cache.set(backupID, miraBuff)
+ const mapInfo: MirabufCacheInfo = {
+ id: backupID,
+ miraType: miraType,
+ cacheKey: key,
+ buffer: miraBuff,
+ name: name,
+ }
+ cache[backupID] = mapInfo
return info
} catch (e) {
diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx
index b37283c705..c834e30cec 100644
--- a/fission/src/ui/components/MainHUD.tsx
+++ b/fission/src/ui/components/MainHUD.tsx
@@ -147,11 +147,6 @@ const MainHUD: React.FC = () => {
icon={SynthesisIcons.MagnifyingGlass}
onClick={() => openModal("view")}
/> */}
- openPanel("subsystem-config")}
- />
= ({ value, index, onSelected, onDel
{/* Indentation before the name */}
{/* Label for joint index and type (grey if child) */}
+
= ({ value, index, onSelected, onDel
sx={{ borderColor: "#888888" }}
id={`select-button-${value.name}`}
/>
+ {value.tooltipText && CustomTooltip(value.tooltipText)}
{/** Delete button only if onDelete is defined */}
{onDelete && includeDelete && (
<>
diff --git a/fission/src/ui/components/StyledComponents.tsx b/fission/src/ui/components/StyledComponents.tsx
index e5de2cfba3..66e404d71f 100644
--- a/fission/src/ui/components/StyledComponents.tsx
+++ b/fission/src/ui/components/StyledComponents.tsx
@@ -64,6 +64,7 @@ export class SynthesisIcons {
public static EditLarge = ()
public static LeftArrowLarge = ()
public static BugLarge = ()
+ public static XmarkLarge = ()
public static OpenHudIcon = (
= ({ modalId }) => {
}}
/>
("SubsystemGravity")}
onClick={checked => {
setSubsystemGravity(checked)
}}
+ tooltipText="Allows you to set a target torque or force for subsystems and joints. If not properly configured, joints may not be able to resist gravity or may not behave as intended."
/>
{
- /* Adds the joints that the wheels are associated with */
- if (behavior instanceof ArcadeDriveBehavior) {
- behavior.wheels.forEach(wheel => {
- const assoc = World.PhysicsSystem.GetBodyAssociation(
- wheel.constraint.GetVehicleBody().GetID()
- ) as RigidNodeAssociate
-
- if (!assoc || assoc.sceneObject !== robot) {
- return
- }
-
- output.push(
-
-
- Wheel Node {elementKey}
-
-
- {assoc.rigidNodeId}
-
-
- )
- elementKey++
- })
- output.push()
- } else if (behavior instanceof GenericArmBehavior) {
- /* Adds the joints that the arm is associated with */
- // Get the rigid node associates for the two bodies
- const assoc1 = World.PhysicsSystem.GetBodyAssociation(
- behavior.hingeDriver.constraint.GetBody1().GetID()
- ) as RigidNodeAssociate
- const assoc2 = World.PhysicsSystem.GetBodyAssociation(
- behavior.hingeDriver.constraint.GetBody2().GetID()
- ) as RigidNodeAssociate
-
- if (!assoc1 || assoc1.sceneObject !== robot || !assoc2 || assoc2.sceneObject !== robot) {
- return
- }
-
- output.push(
-
-
- Arm Nodes
-
-
- {assoc1.rigidNodeId + " " + assoc2.rigidNodeId}
-
-
- )
- elementKey++
- } else if (behavior instanceof GenericElevatorBehavior) {
- /* Adds the joints that the elevator is associated with */
- // Get the rigid node associates for the two bodies
- const assoc1 = World.PhysicsSystem.GetBodyAssociation(
- behavior.sliderDriver.constraint.GetBody1().GetID()
- ) as RigidNodeAssociate
- const assoc2 = World.PhysicsSystem.GetBodyAssociation(
- behavior.sliderDriver.constraint.GetBody2().GetID()
- ) as RigidNodeAssociate
-
- if (!assoc1 || assoc1.sceneObject !== robot || !assoc2 || assoc2.sceneObject !== robot) {
- return
- }
-
- output.push(
-
-
- Elevator Nodes
-
-
- {assoc1.rigidNodeId + " " + assoc2.rigidNodeId}
-
-
- )
- elementKey++
- }
- })
-
- return output
-}
-
-const ConfigureRobotBrainPanel: React.FC = ({ panelId, openLocation, sidePadding }) => {
- const [selectedRobot, setSelectedRobot] = useState(undefined)
- const [viewType, setViewType] = useState(ConfigureRobotBrainTypes.SYNTHESIS)
- const robots = useMemo(() => {
- const assemblies = [...World.SceneRenderer.sceneObjects.values()].filter(x => {
- if (x instanceof MirabufSceneObject) {
- return x.miraType === MiraType.ROBOT
- }
- return false
- }) as MirabufSceneObject[]
- return assemblies
- }, [])
-
- return (
- }
- panelId={panelId}
- openLocation={openLocation}
- sidePadding={sidePadding}
- onAccept={() => {}}
- onCancel={() => {}}
- >
- {selectedRobot?.ejectorPreferences == undefined ? (
- <>
-
- {/** Scroll view for selecting a robot to configure */}
-
- {robots.map(mirabufSceneObject => {
- return (
-
- )
- })}
-
- {/* TODO: remove the accept button on this version */}
- >
- ) : (
- <>
-
-
v != null && setViewType(v)}
- sx={{
- alignSelf: "center",
- }}
- >
- SynthesisBrain
- WIPLIBBrain
-
- {viewType === ConfigureRobotBrainTypes.SYNTHESIS ? (
- <>
-
- Behaviors
-
-
- {GetJoints(selectedRobot)}
- >
- ) : (
- <>
-
- Example WIPLIB Brain
-
-
-
-
- Example 2
-
-
-
-
- Example 3
-
-
-
-
- Example 4
-
-
- >
- )}
-
- >
- )}
-
- )
-}
-
-export default ConfigureRobotBrainPanel
diff --git a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx
deleted file mode 100644
index cf343f012f..0000000000
--- a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx
+++ /dev/null
@@ -1,279 +0,0 @@
-import { MiraType } from "@/mirabuf/MirabufLoader"
-import MirabufSceneObject from "@/mirabuf/MirabufSceneObject"
-import PreferencesSystem from "@/systems/preferences/PreferencesSystem"
-import { RobotPreferences } from "@/systems/preferences/PreferenceTypes"
-import Driver from "@/systems/simulation/driver/Driver"
-import HingeDriver from "@/systems/simulation/driver/HingeDriver"
-import SliderDriver from "@/systems/simulation/driver/SliderDriver"
-import WheelDriver from "@/systems/simulation/driver/WheelDriver"
-import World from "@/systems/World"
-import Button from "@/ui/components/Button"
-import Label, { LabelSize } from "@/ui/components/Label"
-import Panel, { PanelPropsImpl } from "@/ui/components/Panel"
-import ScrollView from "@/ui/components/ScrollView"
-import Slider from "@/ui/components/Slider"
-import Stack, { StackDirection } from "@/ui/components/Stack"
-import { SectionDivider } from "@/ui/components/StyledComponents"
-import { Box } from "@mui/material"
-import { useCallback, useMemo, useState } from "react"
-import { FaGear } from "react-icons/fa6"
-
-type SubsystemRowProps = {
- robot: MirabufSceneObject
- driver: Driver
-}
-
-const SubsystemRow: React.FC = ({ robot, driver }) => {
- const driverSwitch = (driver: Driver, slider: unknown, hinge: unknown, drivetrain: unknown) => {
- switch (driver.constructor) {
- case SliderDriver:
- return slider
- case HingeDriver:
- return hinge
- case WheelDriver:
- return drivetrain
- default:
- return drivetrain
- }
- }
-
- const [velocity, setVelocity] = useState(
- ((driver as SliderDriver) || (driver as HingeDriver) || (driver as WheelDriver)).maxVelocity
- )
- const [force, setForce] = useState(
- ((driver as SliderDriver) || (driver as HingeDriver) || (driver as WheelDriver)).maxForce
- )
-
- const onChange = useCallback(
- (vel: number, force: number) => {
- if (driver instanceof WheelDriver) {
- const wheelDrivers = robot?.mechanism
- ? World.SimulationSystem.GetSimulationLayer(robot.mechanism)?.drivers.filter(
- x => x instanceof WheelDriver
- )
- : undefined
- wheelDrivers?.forEach(x => {
- x.maxVelocity = vel
- x.maxForce = force
- })
-
- // Preferences
- PreferencesSystem.getRobotPreferences(robot.assemblyName).driveVelocity = vel
- PreferencesSystem.getRobotPreferences(robot.assemblyName).driveAcceleration = force
- } else {
- // Preferences
- if (driver.info && driver.info.name) {
- const removedMotor = PreferencesSystem.getRobotPreferences(robot.assemblyName).motors
- ? PreferencesSystem.getRobotPreferences(robot.assemblyName).motors.filter(x => {
- if (x.name) return x.name != driver.info?.name
- return false
- })
- : []
-
- removedMotor.push({
- name: driver.info?.name ?? "",
- maxVelocity: vel,
- maxForce: force,
- })
-
- PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor
- }
-
- // eslint-disable-next-line no-extra-semi
- ;((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel
- ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force
- }
-
- PreferencesSystem.savePreferences()
- },
- [driver, robot.mechanism, robot.assemblyName]
- )
-
- return (
- <>
-
-
-
- {
- setVelocity(_velocity as number)
- onChange(_velocity as number, force)
- }}
- step={0.01}
- />
- {PreferencesSystem.getGlobalPreference("SubsystemGravity") || driver instanceof WheelDriver ? (
- {
- setForce(_force as number)
- onChange(velocity, _force as number)
- }}
- step={0.01}
- />
- ) : driver instanceof HingeDriver ? (
-
- ) : (
-
- )}
-
-
-
- >
- )
-}
-
-const ConfigureSubsystemsPanel: React.FC = ({ panelId, openLocation, sidePadding }) => {
- const [selectedRobot, setSelectedRobot] = useState(undefined)
- const [origPref, setOrigPref] = useState(undefined)
-
- const robots = useMemo(() => {
- const assemblies = [...World.SceneRenderer.sceneObjects.values()].filter(x => {
- if (x instanceof MirabufSceneObject) {
- return x.miraType === MiraType.ROBOT
- }
- return false
- }) as MirabufSceneObject[]
- return assemblies
- }, [])
-
- const drivers = useMemo(() => {
- return selectedRobot?.mechanism
- ? World.SimulationSystem.GetSimulationLayer(selectedRobot.mechanism)?.drivers
- : undefined
- }, [selectedRobot])
-
- // Gets motors in preferences for ease of saving into origPrefs which can be used to revert on Cancel()
- function saveOrigMotors(robot: MirabufSceneObject) {
- drivers?.forEach(driver => {
- if (driver.info && driver.info.name && !(driver instanceof WheelDriver)) {
- const motors = PreferencesSystem.getRobotPreferences(robot.assemblyName).motors
- const removedMotor = motors.filter(x => {
- if (x.name) return x.name != driver.info?.name
- return false
- })
-
- if (removedMotor.length == drivers.length) {
- removedMotor.push({
- name: driver.info?.name ?? "",
- maxVelocity: ((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity,
- maxForce: ((driver as SliderDriver) || (driver as HingeDriver)).maxForce,
- })
- PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor
- }
- }
- })
- PreferencesSystem.savePreferences()
- setOrigPref({ ...PreferencesSystem.getRobotPreferences(robot.assemblyName) }) // clone
- }
-
- function Cancel() {
- if (selectedRobot && origPref) {
- drivers?.forEach(driver => {
- if (driver instanceof WheelDriver) {
- driver.maxVelocity = origPref.driveVelocity
- driver.maxForce = origPref.driveAcceleration
- } else {
- if (driver.info && driver.info.name) {
- const motor = origPref.motors.filter(x => {
- if (x.name) return x.name == driver.info?.name
- return false
- })[0]
- if (motor) {
- // This line is a separate variable to get ES Lint and Prettier to agree on formatting the semicolon below
- const forcePref = PreferencesSystem.getGlobalPreference("SubsystemGravity")
- ? motor.maxForce
- : driver instanceof SliderDriver
- ? 500
- : 100
- ;((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = motor.maxVelocity
- ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = forcePref
- }
- }
- }
- })
- PreferencesSystem.setRobotPreferences(selectedRobot.assemblyName, origPref)
- }
- PreferencesSystem.savePreferences()
- }
-
- return (
- }
- panelId={panelId}
- openLocation={openLocation}
- sidePadding={sidePadding}
- onAccept={() => {
- PreferencesSystem.savePreferences()
- }}
- onCancel={Cancel}
- acceptEnabled={true}
- >
- {selectedRobot?.ejectorPreferences == undefined ? (
- <>
-
- {/** Scroll view for selecting a robot to configure */}
-
- {robots.map(mirabufSceneObject => {
- return (
-
- )
- })}
-
- >
- ) : (
- <>
- {drivers ? (
-
- {/** Drivetrain row. Then other SliderDrivers and HingeDrivers */}
- {
- return selectedRobot
- })()}
- driver={(() => {
- return drivers.filter(x => x instanceof WheelDriver)[0]
- })()}
- />
- {drivers
- .filter(x => x instanceof SliderDriver || x instanceof HingeDriver)
- .map((driver: Driver, i: number) => (
- {
- return selectedRobot
- })()}
- driver={(() => {
- return driver
- })()}
- />
- ))}
-
- ) : (
-
- )}
- >
- )}
-
- )
-}
-
-export default ConfigureSubsystemsPanel
diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx
index aa5d5b7f2a..9dbdb7e076 100644
--- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx
+++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx
@@ -6,10 +6,7 @@ import Panel, { PanelPropsImpl } from "@/ui/components/Panel"
import SelectMenu, { SelectMenuOption } from "@/ui/components/SelectMenu"
import { ToggleButton, ToggleButtonGroup } from "@/ui/components/ToggleButtonGroup"
import { useEffect, useMemo, useReducer, useState } from "react"
-import ConfigureGamepiecePickupInterface from "./interfaces/ConfigureGamepiecePickupInterface"
-import ConfigureShotTrajectoryInterface from "./interfaces/ConfigureShotTrajectoryInterface"
import ConfigureScoringZonesInterface from "./interfaces/scoring/ConfigureScoringZonesInterface"
-import SequentialBehaviorsInterface from "./interfaces/SequentialBehaviorsInterface"
import ChangeInputsInterface from "./interfaces/inputs/ConfigureInputsInterface"
import InputSystem from "@/systems/input/InputSystem"
import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain"
@@ -18,12 +15,17 @@ import Button from "@/ui/components/Button"
import { setSelectedBrainIndexGlobal } from "../ChooseInputSchemePanel"
import ConfigureSchemeInterface from "./interfaces/inputs/ConfigureSchemeInterface"
import { SynthesisIcons } from "@/ui/components/StyledComponents"
+import ConfigureSubsystemsInterface from "./interfaces/ConfigureSubsystemsInterface"
+import SequentialBehaviorsInterface from "./interfaces/SequentialBehaviorsInterface"
+import ConfigureShotTrajectoryInterface from "./interfaces/ConfigureShotTrajectoryInterface"
+import ConfigureGamepiecePickupInterface from "./interfaces/ConfigureGamepiecePickupInterface"
enum ConfigMode {
- INTAKE,
+ SUBSYSTEMS,
EJECTOR,
- MOTORS,
+ INTAKE,
CONTROLS,
+ SEQUENTIAL,
SCORING_ZONES,
}
@@ -117,8 +119,8 @@ const AssemblySelection: React.FC = ({ configuratio
class ConfigModeSelectionOption extends SelectMenuOption {
configMode: ConfigMode
- constructor(name: string, configMode: ConfigMode) {
- super(name)
+ constructor(name: string, configMode: ConfigMode, tooltip?: string) {
+ super(name, tooltip)
this.configMode = configMode
}
}
@@ -126,7 +128,16 @@ class ConfigModeSelectionOption extends SelectMenuOption {
const robotModes = [
new ConfigModeSelectionOption("Intake", ConfigMode.INTAKE),
new ConfigModeSelectionOption("Ejector", ConfigMode.EJECTOR),
- new ConfigModeSelectionOption("Sequential Joints", ConfigMode.MOTORS),
+ new ConfigModeSelectionOption(
+ "Configure Joints",
+ ConfigMode.SUBSYSTEMS,
+ "Set the velocities, torques, and accelerations of your robot's motors."
+ ),
+ new ConfigModeSelectionOption(
+ "Sequence Joints",
+ ConfigMode.SEQUENTIAL,
+ "Set which joints follow each other. For example, the second stage of an elevator could follow the first, moving in unison with it."
+ ),
new ConfigModeSelectionOption("Controls", ConfigMode.CONTROLS),
]
const fieldModes = [new ConfigModeSelectionOption("Scoring Zones", ConfigMode.SCORING_ZONES)]
@@ -162,8 +173,8 @@ const ConfigInterface: React.FC = ({ configMode, assembly,
return
case ConfigMode.EJECTOR:
return
- case ConfigMode.MOTORS:
- return
+ case ConfigMode.SUBSYSTEMS:
+ return
case ConfigMode.CONTROLS: {
const brainIndex = (assembly.brain as SynthesisBrain).brainIndex
const scheme = InputSystem.brainIndexSchemeMap.get(brainIndex)
@@ -181,6 +192,8 @@ const ConfigInterface: React.FC = ({ configMode, assembly,
>
)
}
+ case ConfigMode.SEQUENTIAL:
+ return
case ConfigMode.SCORING_ZONES: {
const zones = assembly.fieldPreferences?.scoringZones
if (zones == undefined) {
diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureSubsystemsInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureSubsystemsInterface.tsx
new file mode 100644
index 0000000000..d2e98e94d2
--- /dev/null
+++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureSubsystemsInterface.tsx
@@ -0,0 +1,113 @@
+import MirabufSceneObject from "@/mirabuf/MirabufSceneObject"
+import SelectMenu, { SelectMenuOption } from "@/ui/components/SelectMenu"
+import React, { useMemo, useState } from "react"
+import { ConfigurationSavedEvent } from "../ConfigurePanel"
+import World from "@/systems/World"
+import SliderDriver from "@/systems/simulation/driver/SliderDriver"
+import HingeDriver from "@/systems/simulation/driver/HingeDriver"
+import Driver from "@/systems/simulation/driver/Driver"
+import WheelDriver from "@/systems/simulation/driver/WheelDriver"
+import SubsystemRowInterface from "./SubsystemRowInterface"
+import PreferencesSystem from "@/systems/preferences/PreferencesSystem"
+import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain"
+import SequenceableBehavior from "@/systems/simulation/behavior/synthesis/SequenceableBehavior"
+import { DefaultSequentialConfig, SequentialBehaviorPreferences } from "@/systems/preferences/PreferenceTypes"
+import GenericArmBehavior from "@/systems/simulation/behavior/synthesis/GenericArmBehavior"
+
+class ConfigModeSelectionOption extends SelectMenuOption {
+ driver: Driver
+ sequential?: SequentialBehaviorPreferences
+
+ constructor(name: string, driver: Driver, sequential?: SequentialBehaviorPreferences) {
+ super(name)
+ this.driver = driver
+ this.sequential = sequential
+ }
+}
+
+interface ConfigSubsystemProps {
+ selectedRobot: MirabufSceneObject
+}
+
+interface ConfigInterfaceProps {
+ configModeOption: ConfigModeSelectionOption
+ selectedRobot: MirabufSceneObject
+ saveBehaviors: () => void
+}
+
+const ConfigInterface: React.FC = ({ configModeOption, selectedRobot, saveBehaviors }) => {
+ return (
+
+ )
+}
+
+const ConfigureSubsystemsInterface: React.FC = ({ selectedRobot }) => {
+ const [selectedConfigMode, setSelectedConfigMode] = useState(undefined)
+
+ const behaviors = useMemo(
+ () =>
+ PreferencesSystem.getRobotPreferences(selectedRobot.assemblyName)?.sequentialConfig ??
+ (selectedRobot.brain as SynthesisBrain).behaviors
+ .filter(b => b instanceof SequenceableBehavior)
+ .map(b => DefaultSequentialConfig(b.jointIndex, b instanceof GenericArmBehavior ? "Arm" : "Elevator")),
+ []
+ )
+
+ const drivers = useMemo(() => {
+ return World.SimulationSystem.GetSimulationLayer(selectedRobot.mechanism)?.drivers
+ }, [selectedRobot])
+
+ const getSubsystemOptions = () => {
+ if (drivers == undefined) return []
+ const options = [new ConfigModeSelectionOption("Drivetrain", drivers.filter(x => x instanceof WheelDriver)[0])]
+
+ let jointIndex = 0
+
+ drivers
+ .filter(x => x instanceof HingeDriver)
+ .forEach(d => {
+ options.push(new ConfigModeSelectionOption(d.info?.name ?? "UnnamedMotor", d, behaviors[jointIndex]))
+ jointIndex++
+ })
+
+ drivers
+ .filter(x => x instanceof SliderDriver)
+ .forEach(d => {
+ options.push(new ConfigModeSelectionOption(d.info?.name ?? "UnnamedMotor", d, behaviors[jointIndex]))
+ jointIndex++
+ })
+
+ return options
+ }
+
+ return (
+ <>
+ {
+ if (val != undefined) new ConfigurationSavedEvent()
+ setSelectedConfigMode(val as ConfigModeSelectionOption)
+ }}
+ defaultHeaderText="Select a Subsystem"
+ indentation={2}
+ />
+ {selectedConfigMode != undefined && (
+ {
+ PreferencesSystem.getRobotPreferences(selectedRobot.assemblyName).sequentialConfig = behaviors
+ PreferencesSystem.savePreferences()
+ }}
+ />
+ )}
+ >
+ )
+}
+
+export default ConfigureSubsystemsInterface
diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx
index 6a8523d465..bec98015dd 100644
--- a/fission/src/ui/panels/configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx
+++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx
@@ -1,20 +1,15 @@
import React, { useCallback, useEffect, useReducer, useState } from "react"
import MirabufSceneObject from "@/mirabuf/MirabufSceneObject"
import Label, { LabelSize } from "@/ui/components/Label"
-import { FaArrowRightArrowLeft, FaXmark } from "react-icons/fa6"
-import { Box, Button as MUIButton, styled, alpha, Icon } from "@mui/material"
+import { Box, Button as MUIButton, styled, alpha } from "@mui/material"
import Button, { ButtonSize } from "@/ui/components/Button"
import { DefaultSequentialConfig, SequentialBehaviorPreferences } from "@/systems/preferences/PreferenceTypes"
import PreferencesSystem from "@/systems/preferences/PreferencesSystem"
import SequenceableBehavior from "@/systems/simulation/behavior/synthesis/SequenceableBehavior"
-import Checkbox from "@/ui/components/Checkbox"
import GenericArmBehavior from "@/systems/simulation/behavior/synthesis/GenericArmBehavior"
import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain"
import { ConfigurationSavedEvent } from "../ConfigurePanel"
-import { SectionLabel, Spacer } from "@/ui/components/StyledComponents"
-
-const UnselectParentIcon =
-const InvertIcon =
+import { SectionLabel, Spacer, SynthesisIcons } from "@/ui/components/StyledComponents"
/** Grey label for a child behavior name */
const ChildLabelStyled = styled(Label)({
@@ -113,19 +108,8 @@ const BehaviorCard: React.FC = ({
/>
{/* Spacer between the CustomButton and invert button */}
-
-
-
- {/* Invert joint icon & checkbox */}
- {InvertIcon}
- (behavior.inverted = val)}
- hideLabel={true}
- />
-
-
+ {/*
+ */}
{/* Button to set the parent of this behavior */}