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