diff --git a/exporter/SynthesisFusionAddin/src/Resources/DesignCheckIcons/invalid-preview112x18.png b/exporter/SynthesisFusionAddin/src/Resources/DesignCheckIcons/invalid-preview112x18.png
new file mode 100644
index 0000000000..22ff6ad2e2
Binary files /dev/null and b/exporter/SynthesisFusionAddin/src/Resources/DesignCheckIcons/invalid-preview112x18.png differ
diff --git a/exporter/SynthesisFusionAddin/src/Resources/DesignCheckIcons/valid-preview93x18.png b/exporter/SynthesisFusionAddin/src/Resources/DesignCheckIcons/valid-preview93x18.png
new file mode 100644
index 0000000000..aa887d1dc5
Binary files /dev/null and b/exporter/SynthesisFusionAddin/src/Resources/DesignCheckIcons/valid-preview93x18.png differ
diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py
index 13431f7475..954fbfc512 100644
--- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py
+++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py
@@ -15,12 +15,14 @@
import src.Parser.ExporterOptions as moduleExporterOptions
import src.Parser.SynthesisParser.Parser as Parser
+import src.UI.DesignCheckTab as DesignCheckTab
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, Logging, gm
from src.APS.APS import getAuth, getUserInfo
+from src.lib.DesignRuleChecks import DesignRuleChecks
from src.lib.Handlers import PersistentEventHandler
from src.lib.Util import convertMassUnitsTo, designMassCalculation
from src.Logging import getLogger, logFailure
@@ -32,6 +34,7 @@
generalConfigTab: GeneralConfigTab.GeneralConfigTab
jointConfigTab: JointConfigTab.JointConfigTab
gamepieceConfigTab: GamepieceConfigTab.GamepieceConfigTab
+designCheckTab: DesignCheckTab.DesignCheckTab
taggingConfigTab: TaggingConfigTab.TaggingConfigTab
exporterPalette: Palette
@@ -50,6 +53,7 @@ def reload() -> None:
importlib.reload(GeneralConfigTab)
importlib.reload(GamepieceConfigTab)
importlib.reload(JointConfigTab)
+ importlib.reload(DesignCheckTab)
importlib.reload(TaggingConfigTab)
importlib.reload(moduleExporterOptions)
@@ -113,6 +117,10 @@ def notify(self, args: adsk.core.CommandCreatedEventArgs) -> None:
taggingConfigTab = TaggingConfigTab.TaggingConfigTab(args)
generalConfigTab.taggingConfigTab = taggingConfigTab
+ global designCheckTab
+ designCheckTab = DesignCheckTab.DesignCheckTab(args)
+ generalConfigTab.designCheckTab = designCheckTab
+
if not exporterOptions.exportMode == ExportMode.FIELD:
gamepieceConfigTab.isVisible = False
@@ -292,6 +300,9 @@ def notify(self, html_args: adsk.core.HTMLEventArgs) -> None:
elif html_args.action == "cancelSelection":
gm.ui.terminateActiveCommand()
html_args.returnData = "{}"
+
+ elif html_args.action == "designRules":
+ html_args.returnData = json.dumps(DesignRuleChecks().getDesignRules())
else:
gm.ui.messageBox(f"Event {html_args.action} arrived{json.dumps(data, indent=2)}")
diff --git a/exporter/SynthesisFusionAddin/src/UI/DesignCheckTab.py b/exporter/SynthesisFusionAddin/src/UI/DesignCheckTab.py
new file mode 100644
index 0000000000..3b513b91e1
--- /dev/null
+++ b/exporter/SynthesisFusionAddin/src/UI/DesignCheckTab.py
@@ -0,0 +1,62 @@
+from typing import Any, Callable, Dict, List, TypedDict, cast
+
+import adsk.core
+import adsk.fusion
+
+from src import Logging
+from src.lib import IconPaths
+from src.lib.DesignRuleChecks import DesignRuleChecks
+
+logger = Logging.getLogger()
+
+
+class DesignCheckTab:
+ designCheckTab: adsk.core.TabCommandInput
+ designCheckTable: adsk.core.TableCommandInput
+
+ @Logging.logFailure
+ def __init__(self, args: adsk.core.CommandCreatedEventArgs) -> None:
+ self.designCheckTab = args.command.commandInputs.addTabCommandInput("designCheckTab", "Design Rule Check")
+ designCheckTabInputs = self.designCheckTab.children
+
+ # Create the table for design checks
+ self.designCheckTable = designCheckTabInputs.addTableCommandInput(
+ "designCheckTable", "Design Checks", 3, "3:2:2"
+ )
+ self.designCheckTable.tablePresentationStyle = (
+ adsk.core.TablePresentationStyles.itemBorderTablePresentationStyle
+ )
+
+ for i, rule in enumerate(DesignRuleChecks().getDesignRules()):
+ calculation = rule["calculation"]
+ max_value: float = rule["max_value"]
+ value: float = calculation()
+ is_valid: bool = value <= max_value
+ rule_name: str = str(rule["name"])
+ rule_id: str = rule_name.replace(" ", "")
+
+ name_input = designCheckTabInputs.addTextBoxCommandInput(f"{rule_id}Name", rule_name, rule_name, 1, True)
+ value_input = designCheckTabInputs.addTextBoxCommandInput(
+ f"{rule_id}Value", "Value", f"{value:.2f} cm", 1, True
+ )
+ icon_input = designCheckTabInputs.addImageCommandInput(
+ f"{rule_id}StatusIcon",
+ "",
+ IconPaths.designCheckIcons["valid"] if is_valid else IconPaths.designCheckIcons["invalid"],
+ )
+
+ self.designCheckTable.addCommandInput(name_input, i, 0)
+ self.designCheckTable.addCommandInput(value_input, i, 1)
+ self.designCheckTable.addCommandInput(icon_input, i, 2)
+
+ @property
+ def isVisible(self) -> bool:
+ return self.designCheckTab.isVisible or False
+
+ @isVisible.setter
+ def isVisible(self, value: bool) -> None:
+ self.designCheckTab.isVisible = value
+
+ @property
+ def isActive(self) -> bool:
+ return self.designCheckTab.isActive or False
diff --git a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py
index 1027cfe5bb..fe45056065 100644
--- a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py
+++ b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py
@@ -11,6 +11,7 @@
from src.Parser.ExporterOptions import ExporterOptions
from src.Types import KG, ExportLocation, ExportMode, UnitSystem
from src.UI.CreateCommandInputsHelper import createBooleanInput
+from src.UI.DesignCheckTab import DesignCheckTab
from src.UI.GamepieceConfigTab import GamepieceConfigTab
from src.UI.JointConfigTab import JointConfigTab
from src.UI.TaggingConfigTab import TaggingConfigTab
@@ -24,6 +25,7 @@ class GeneralConfigTab:
previousSelectedModeDropdownIndex: int
jointConfigTab: JointConfigTab
gamepieceConfigTab: GamepieceConfigTab
+ designCheckTab: DesignCheckTab
taggingConfigTab: TaggingConfigTab
@logFailure
diff --git a/exporter/SynthesisFusionAddin/src/lib/DesignRuleChecks.py b/exporter/SynthesisFusionAddin/src/lib/DesignRuleChecks.py
new file mode 100644
index 0000000000..048437bdf5
--- /dev/null
+++ b/exporter/SynthesisFusionAddin/src/lib/DesignRuleChecks.py
@@ -0,0 +1,50 @@
+from typing import Callable, List, TypedDict
+
+import adsk.core
+import adsk.fusion
+
+from src import Logging, gm
+
+
+class DesignRule(TypedDict):
+ name: str
+ calculation: Callable[[], float]
+ max_value: float
+
+
+class DesignRuleChecks:
+ designRules: List[DesignRule]
+
+ @Logging.logFailure
+ def __init__(self) -> None:
+ self.designRules = [
+ {
+ "name": "Design Height",
+ "calculation": self.fusion_design_height(),
+ "max_value": 106.0, # cm
+ },
+ {
+ "name": "Design Perimeter",
+ "calculation": self.fusion_design_perimeter(),
+ "max_value": 304.0, # cm
+ },
+ ]
+
+ def getDesignRules(self) -> List[DesignRule]:
+ return self.designRules
+
+ @Logging.logFailure
+ def fusion_design_height(self) -> float:
+ design = adsk.fusion.Design.cast(gm.app.activeProduct)
+ if design:
+ overall_bounding_box = design.rootComponent.orientedMinimumBoundingBox
+ return float(overall_bounding_box.width)
+ return 0.0
+
+ @Logging.logFailure
+ def fusion_design_perimeter(self) -> float:
+ design = adsk.fusion.Design.cast(gm.app.activeProduct)
+ if design:
+ overall_bounding_box = design.rootComponent.orientedMinimumBoundingBox
+ return float(2 * (overall_bounding_box.height + overall_bounding_box.length))
+ return 0.0
diff --git a/exporter/SynthesisFusionAddin/src/lib/IconPaths.py b/exporter/SynthesisFusionAddin/src/lib/IconPaths.py
index a7116bbad3..9ed89e7d7b 100644
--- a/exporter/SynthesisFusionAddin/src/lib/IconPaths.py
+++ b/exporter/SynthesisFusionAddin/src/lib/IconPaths.py
@@ -43,6 +43,11 @@
"friction_override-enabled": resources + os.path.join("FrictionOverride_icon"), # resource folder
}
+designCheckIcons = {
+ "valid": resources + os.path.join("DesignCheckIcons", "valid-preview93x18.png"),
+ "invalid": resources + os.path.join("DesignCheckIcons", "invalid-preview112x18.png"),
+}
+
tagIcons = {
"blank": resources + "blank-preview16x16.png",
}
diff --git a/exporter/SynthesisFusionAddin/web/src/App.tsx b/exporter/SynthesisFusionAddin/web/src/App.tsx
index c77a825651..6ea703b08c 100644
--- a/exporter/SynthesisFusionAddin/web/src/App.tsx
+++ b/exporter/SynthesisFusionAddin/web/src/App.tsx
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from "react"
import "./App.css"
-import { RestartAlt, Settings, SportsFootball, Texture } from "@mui/icons-material"
+import { CheckBox, RestartAlt, Settings, SportsFootball, Texture } from "@mui/icons-material"
import DownloadIcon from "@mui/icons-material/Download"
import PrecisionManufacturingIcon from "@mui/icons-material/PrecisionManufacturing"
import SaveIcon from "@mui/icons-material/Save"
@@ -32,6 +32,7 @@ import {
type Joint,
WheelType,
} from "./lib/types.ts"
+import DesignCheckTab from "./ui/DesignCheckTab.tsx"
import GamepiecesConfigTab from "./ui/GamepiecesConfigTab.tsx"
import GeneralConfigTab from "./ui/GeneralConfigTab.tsx"
import GlobalAlert from "./ui/GlobalAlert.tsx"
@@ -228,6 +229,7 @@ function App() {
/>
} iconPosition={"start"} label="Materials" />
+ } iconPosition={"start"} label="Design Check" />
{/**/}
@@ -259,6 +261,9 @@ function App() {
selection={{ isSelecting, setIsSelecting }}
/>
+
+
+
([])
+
+ useEffect(() => {
+ const fetchRules = async () => {
+ const data = await sendData("designRules", {})
+ if (data) {
+ setRules(data)
+ console.log(data[0].calculation)
+ }
+ }
+
+ if (typeof window.adsk === "undefined") {
+ requestAnimationFrame(fetchRules)
+ return
+ }
+ }, [])
+
+ function isDesignValid(): string {
+ rules.forEach(rule => {
+ if (rule.calculation > rule.max_value) {
+ return "Invalid"
+ }
+ })
+
+ return "Valid"
+ }
+
+ return (
+ <>
+ Checks Passing: {isDesignValid()}
+
+
+
+
+
+ Component
+
+
+ Calculation
+
+
+ Is Valid
+
+
+
+
+
+ {rules.map(rule => (
+
+ {rule.name}
+ {Math.round(rule.calculation * 100) / 100} cm
+
+ {rule.calculation <= rule.max_value ? "Valid" : "Invalid"}
+
+
+
+ ))}
+
+
+
+ >
+ )
+}
+
+export default DesignCheckTab