From ec2eda0684fccd37439676bf69681bcd3f17e333 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Wed, 3 Jul 2024 15:50:06 -0700 Subject: [PATCH 001/344] Updates easy absolute imports --- exporter/SynthesisFusionAddin/Synthesis.py | 16 +++++++++------- exporter/SynthesisFusionAddin/proto/deps.py | 2 +- .../SynthesisFusionAddin/src/GlobalManager.py | 7 +++---- .../src/Parser/ExporterOptions.py | 2 +- .../src/Parser/SynthesisParser/Components.py | 17 +++++++++-------- .../Parser/SynthesisParser/JointHierarchy.py | 7 ++++--- .../src/Parser/SynthesisParser/Joints.py | 15 ++++++++++----- .../src/Parser/SynthesisParser/Materials.py | 7 ++++--- .../src/Parser/SynthesisParser/Parser.py | 17 ++++++++++++----- .../SynthesisParser/PhysicalProperties.py | 3 ++- .../src/Parser/SynthesisParser/RigidGroup.py | 2 +- .../src/Parser/SynthesisParser/Utilities.py | 4 +--- .../SynthesisFusionAddin/src/UI/Camera.py | 6 ++++-- .../src/UI/ConfigCommand.py | 19 ++++++++++--------- .../src/UI/Configuration/SerialCommand.py | 2 +- .../src/UI/CustomGraphics.py | 1 + .../SynthesisFusionAddin/src/UI/Events.py | 3 ++- .../src/UI/FileDialogConfig.py | 6 +++--- exporter/SynthesisFusionAddin/src/UI/HUI.py | 4 +++- .../SynthesisFusionAddin/src/UI/Handlers.py | 1 + .../SynthesisFusionAddin/src/UI/Helper.py | 5 +++-- .../SynthesisFusionAddin/src/UI/IconPaths.py | 2 +- .../SynthesisFusionAddin/src/UI/Toolbar.py | 4 +++- exporter/SynthesisFusionAddin/src/__init__.py | 6 ++++++ .../SynthesisFusionAddin/src/configure.py | 4 ++-- exporter/SynthesisFusionAddin/src/logging.py | 4 ++-- 26 files changed, 99 insertions(+), 67 deletions(-) create mode 100644 exporter/SynthesisFusionAddin/src/__init__.py diff --git a/exporter/SynthesisFusionAddin/Synthesis.py b/exporter/SynthesisFusionAddin/Synthesis.py index 335a2416ce..aeb2f6d06a 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.py +++ b/exporter/SynthesisFusionAddin/Synthesis.py @@ -1,16 +1,18 @@ -import importlib.util import logging.handlers import os +import sys import traceback -from shutil import rmtree import adsk.core -from .src.configure import setAnalytics, unload_config -from .src.general_imports import APP_NAME, DESCRIPTION, INTERNAL_ID, gm, root_logger -from .src.Types.OString import OString -from .src.UI import HUI, Camera, ConfigCommand, Handlers, Helper, MarkingMenu -from .src.UI.Toolbar import Toolbar +# Required for absolute imports +fileDir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(os.path.abspath(os.path.join(fileDir))) + +from src.configure import unload_config +from src.general_imports import APP_NAME, DESCRIPTION, INTERNAL_ID, gm, root_logger +from src.UI import HUI, Camera, ConfigCommand, Helper, MarkingMenu +from src.UI.Toolbar import Toolbar def run(_): diff --git a/exporter/SynthesisFusionAddin/proto/deps.py b/exporter/SynthesisFusionAddin/proto/deps.py index ea6da5a406..02fae64b36 100644 --- a/exporter/SynthesisFusionAddin/proto/deps.py +++ b/exporter/SynthesisFusionAddin/proto/deps.py @@ -6,7 +6,7 @@ import adsk.core import adsk.fusion -from src.general_imports import INTERNAL_ID +from src import INTERNAL_ID system = platform.system() diff --git a/exporter/SynthesisFusionAddin/src/GlobalManager.py b/exporter/SynthesisFusionAddin/src/GlobalManager.py index b438a2eb23..9fb6d44b12 100644 --- a/exporter/SynthesisFusionAddin/src/GlobalManager.py +++ b/exporter/SynthesisFusionAddin/src/GlobalManager.py @@ -1,13 +1,12 @@ """ Initializes the global variables that are set in the run method to reduce hanging commands. """ -import inspect -import traceback - import adsk.core import adsk.fusion +from src import INTERNAL_ID + +# Transition: AARD-1737 from .general_imports import * -from .strings import * class GlobalManager(object): diff --git a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py index 31ed4cd0c4..0cf0ce59af 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py +++ b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py @@ -13,7 +13,7 @@ import adsk.core from adsk.fusion import CalculationAccuracy, TriangleMeshQualityOptions -from ..strings import INTERNAL_ID +from src import INTERNAL_ID # Not 100% sure what this is for - Brandon JointParentType = Enum("JointParentType", ["ROOT", "END"]) diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py index c6c741b0b2..efc8bf5298 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py @@ -1,19 +1,20 @@ # Contains all of the logic for mapping the Components / Occurrences import logging import traceback -import uuid -from typing import * import adsk.core import adsk.fusion from proto.proto_out import assembly_pb2, joint_pb2, material_pb2, types_pb2 - -from ...Analyzer.timer import TimeThis -from ..ExporterOptions import ExporterOptions, ExportMode -from . import PhysicalProperties -from .PDMessage import PDMessage -from .Utilities import * +from src.Analyzer.timer import TimeThis +from src.Parser.ExporterOptions import ExporterOptions, ExportMode +from src.Parser.SynthesisParser import PhysicalProperties +from src.Parser.SynthesisParser.PDMessage import PDMessage +from src.Parser.SynthesisParser.Utilities import ( + fill_info, + guid_component, + guid_occurrence, +) # TODO: Impelement Material overrides diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py index 93d320b987..d42c20b69e 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py @@ -7,11 +7,12 @@ import adsk.fusion from proto.proto_out import joint_pb2, types_pb2 +from src.Parser.ExporterOptions import ExporterOptions +from src.Parser.SynthesisParser.PDMessage import PDMessage +from src.Parser.SynthesisParser.Utilities import guid_component, guid_occurrence +# Transition: AARD-1737 from ...general_imports import * -from ..ExporterOptions import ExporterOptions -from .PDMessage import PDMessage -from .Utilities import guid_component, guid_occurrence # ____________________________ DATA TYPES __________________ diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py index 7ba62c1f5e..8921f7c9ee 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py @@ -29,12 +29,17 @@ import adsk.core import adsk.fusion -from proto.proto_out import assembly_pb2, joint_pb2, motor_pb2, signal_pb2, types_pb2 - +from proto.proto_out import assembly_pb2, joint_pb2, signal_pb2, types_pb2 +from src.Parser.ExporterOptions import ExporterOptions, JointParentType, SignalType +from src.Parser.SynthesisParser.PDMessage import PDMessage +from src.Parser.SynthesisParser.Utilities import ( + construct_info, + fill_info, + guid_occurrence, +) + +# Transition: AARD-1737 from ...general_imports import * -from ..ExporterOptions import ExporterOptions, JointParentType, SignalType -from .PDMessage import PDMessage -from .Utilities import construct_info, fill_info, guid_occurrence # Need to take in a graphcontainer # Need to create a new base node for each Joint Instance diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py index b3199a1f9d..77faff62d4 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py @@ -6,11 +6,12 @@ import adsk from proto.proto_out import material_pb2 +from src.Parser.ExporterOptions import ExporterOptions +from src.Parser.SynthesisParser.PDMessage import PDMessage +from src.Parser.SynthesisParser.Utilities import construct_info, fill_info +# Transition: AARD-1737 from ...general_imports import INTERNAL_ID -from .. import ExporterOptions -from .PDMessage import PDMessage -from .Utilities import * OPACITY_RAMPING_CONSTANT = 14.0 diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py index a925f5b119..5dfca273ad 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py @@ -6,12 +6,19 @@ from google.protobuf.json_format import MessageToJson from proto.proto_out import assembly_pb2, types_pb2 - +from src.Parser.ExporterOptions import ExporterOptions, ExportMode +from src.Parser.SynthesisParser import ( + Components, + JointHierarchy, + Joints, + Materials, + PDMessage, +) +from src.Parser.SynthesisParser.Utilities import fill_info +from src.UI.Camera import captureThumbnail, clearIconCache + +# Transition: AARD-1737 from ...general_imports import * -from ...UI.Camera import captureThumbnail, clearIconCache -from ..ExporterOptions import ExporterOptions, ExportMode -from . import Components, JointHierarchy, Joints, Materials, PDMessage -from .Utilities import * class Parser: diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py index afd9489909..3d8967268c 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py @@ -24,7 +24,8 @@ from proto.proto_out import types_pb2 -from ...general_imports import INTERNAL_ID +# Transition: AARD-1737 +from src import INTERNAL_ID def GetPhysicalProperties( diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py index 7e8f592466..6ca28d999a 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py @@ -13,7 +13,7 @@ """ import logging -from typing import * +from typing import Union import adsk.core import adsk.fusion diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Utilities.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Utilities.py index 7169cd4085..418ee152d8 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Utilities.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Utilities.py @@ -1,11 +1,9 @@ import math import uuid -from adsk.core import Base, Vector3D +from adsk.core import Vector3D from adsk.fusion import Component, Occurrence -# from proto.proto_out import types_pb2 - def guid_component(comp: Component) -> str: return f"{comp.entityToken}_{comp.id}" diff --git a/exporter/SynthesisFusionAddin/src/UI/Camera.py b/exporter/SynthesisFusionAddin/src/UI/Camera.py index a7076fdfd2..618971bbc7 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Camera.py +++ b/exporter/SynthesisFusionAddin/src/UI/Camera.py @@ -2,9 +2,11 @@ from adsk.core import SaveImageFileOptions +from src.Types.OString import OString +from src.UI import Helper + +# Transition: AARD-1737 from ..general_imports import * -from ..Types.OString import OString -from . import Helper def captureThumbnail(size=250): diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index 6ea4e0b702..f27132616a 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -4,17 +4,15 @@ import logging import os -import platform import traceback from enum import Enum import adsk.core import adsk.fusion -from ..Analytics.alert import showAnalyticsAlert -from ..configure import NOTIFIED, write_configuration -from ..general_imports import * -from ..Parser.ExporterOptions import ( +from src.Analytics.alert import showAnalyticsAlert +from src.configure import NOTIFIED, write_configuration +from src.Parser.ExporterOptions import ( ExporterOptions, ExportMode, Gamepiece, @@ -25,10 +23,13 @@ Wheel, WheelType, ) -from ..Parser.SynthesisParser.Parser import Parser -from ..Parser.SynthesisParser.Utilities import guid_occurrence -from . import CustomGraphics, FileDialogConfig, Helper, IconPaths, OsHelper -from .Configuration.SerialCommand import SerialCommand +from src.Parser.SynthesisParser.Parser import Parser +from src.Parser.SynthesisParser.Utilities import guid_occurrence +from src.UI import CustomGraphics, FileDialogConfig, Helper, IconPaths, OsHelper +from src.UI.Configuration.SerialCommand import SerialCommand + +# Transition: AARD-1737 +from ..general_imports import * # ====================================== CONFIG COMMAND ====================================== diff --git a/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py b/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py index 7f03642fec..d2d5e3ac6f 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py @@ -7,7 +7,7 @@ import json -from ...Types.OString import OString +from src.Types.OString import OString def generateFilePath() -> str: diff --git a/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py b/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py index 17b63c8222..1f0a06fc0f 100644 --- a/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py +++ b/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py @@ -4,6 +4,7 @@ import adsk.core import adsk.fusion +# Transition: AARD-1737 from ..general_imports import * diff --git a/exporter/SynthesisFusionAddin/src/UI/Events.py b/exporter/SynthesisFusionAddin/src/UI/Events.py index 60e2af032f..5e817d5746 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Events.py +++ b/exporter/SynthesisFusionAddin/src/UI/Events.py @@ -1,6 +1,7 @@ import logging.handlers -from typing import Sequence, Tuple +from typing import Sequence +# Transition: AARD-1737 from ..general_imports import * """ # This file is Special diff --git a/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py b/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py index e49cf240b7..b738eb463d 100644 --- a/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py +++ b/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py @@ -3,10 +3,10 @@ import adsk.core import adsk.fusion -from ..general_imports import * +from src.Types.OString import OString -# from ..proto_out import Configuration_pb2 -from ..Types.OString import OString +# Transition: AARD-1737 +from ..general_imports import * def SaveFileDialog(defaultPath="", defaultName="", ext="MiraBuf Package (*.mira)") -> Union[str, bool]: diff --git a/exporter/SynthesisFusionAddin/src/UI/HUI.py b/exporter/SynthesisFusionAddin/src/UI/HUI.py index b17ba6ea96..0be5a0bddd 100644 --- a/exporter/SynthesisFusionAddin/src/UI/HUI.py +++ b/exporter/SynthesisFusionAddin/src/UI/HUI.py @@ -1,5 +1,7 @@ +# Transition: AARD-1737 +from src.UI import Handlers, OsHelper + from ..general_imports import * -from . import Handlers, OsHelper # no longer used diff --git a/exporter/SynthesisFusionAddin/src/UI/Handlers.py b/exporter/SynthesisFusionAddin/src/UI/Handlers.py index 710f61e8c8..5b1e6aa97d 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Handlers.py +++ b/exporter/SynthesisFusionAddin/src/UI/Handlers.py @@ -1,3 +1,4 @@ +# Transition: AARD-1737 from ..general_imports import * diff --git a/exporter/SynthesisFusionAddin/src/UI/Helper.py b/exporter/SynthesisFusionAddin/src/UI/Helper.py index 8f88240247..5f796d087f 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Helper.py +++ b/exporter/SynthesisFusionAddin/src/UI/Helper.py @@ -1,8 +1,9 @@ from inspect import getmembers, isfunction -from typing import Union +from src.UI import HUI, Events + +# Transition: AARD-1737 from ..general_imports import * -from . import HUI, Events def check_solid_open() -> bool: diff --git a/exporter/SynthesisFusionAddin/src/UI/IconPaths.py b/exporter/SynthesisFusionAddin/src/UI/IconPaths.py index 261720494c..2804af221a 100644 --- a/exporter/SynthesisFusionAddin/src/UI/IconPaths.py +++ b/exporter/SynthesisFusionAddin/src/UI/IconPaths.py @@ -1,6 +1,6 @@ import os -from . import OsHelper +from src.UI import OsHelper """ Dictionaries that store all the icon paths in ConfigCommand. All path strings are OS-independent diff --git a/exporter/SynthesisFusionAddin/src/UI/Toolbar.py b/exporter/SynthesisFusionAddin/src/UI/Toolbar.py index e0b8f26f19..f5ae186d0c 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Toolbar.py +++ b/exporter/SynthesisFusionAddin/src/UI/Toolbar.py @@ -1,5 +1,7 @@ +# Transition: AARD-1737 +from src import INTERNAL_ID + from ..general_imports import * -from ..strings import INTERNAL_ID class Toolbar: diff --git a/exporter/SynthesisFusionAddin/src/__init__.py b/exporter/SynthesisFusionAddin/src/__init__.py new file mode 100644 index 0000000000..cb8ec64c64 --- /dev/null +++ b/exporter/SynthesisFusionAddin/src/__init__.py @@ -0,0 +1,6 @@ +APP_NAME = "Synthesis" +APP_TITLE = "Synthesis Robot Exporter" +DESCRIPTION = "Exports files from Fusion into the Synthesis Format" +INTERNAL_ID = "synthesis" + +__all__ = ["APP_NAME", "APP_TITLE", "DESCRIPTION", "INTERNAL_ID"] diff --git a/exporter/SynthesisFusionAddin/src/configure.py b/exporter/SynthesisFusionAddin/src/configure.py index e121e7ae6e..f986963465 100644 --- a/exporter/SynthesisFusionAddin/src/configure.py +++ b/exporter/SynthesisFusionAddin/src/configure.py @@ -5,8 +5,8 @@ import uuid from configparser import ConfigParser -from .strings import INTERNAL_ID -from .Types.OString import OString +from src import INTERNAL_ID +from src.Types.OString import OString try: config = ConfigParser() diff --git a/exporter/SynthesisFusionAddin/src/logging.py b/exporter/SynthesisFusionAddin/src/logging.py index 2a0233ae5a..455f0b9157 100644 --- a/exporter/SynthesisFusionAddin/src/logging.py +++ b/exporter/SynthesisFusionAddin/src/logging.py @@ -6,8 +6,8 @@ import pathlib from datetime import datetime -from .strings import * -from .UI.OsHelper import getOSPath +from src import INTERNAL_ID +from src.UI.OsHelper import getOSPath def setupLogger(): From 324c598b06db669061dbdbecc78ecb1a51ff15a3 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Tue, 9 Jul 2024 09:02:34 -0700 Subject: [PATCH 002/344] Fixed all the harder imports --- exporter/SynthesisFusionAddin/Synthesis.py | 33 +++-------- .../src/Analytics/alert.py | 4 +- .../src/Analyzer/timer.py | 3 +- .../SynthesisFusionAddin/src/GlobalManager.py | 16 ++---- .../Parser/SynthesisParser/JointHierarchy.py | 4 +- .../src/Parser/SynthesisParser/Joints.py | 5 +- .../src/Parser/SynthesisParser/Materials.py | 4 +- .../src/Parser/SynthesisParser/Parser.py | 6 +- .../SynthesisParser/PhysicalProperties.py | 2 - .../SynthesisFusionAddin/src/UI/Camera.py | 7 ++- .../src/UI/ConfigCommand.py | 5 +- .../src/UI/CustomGraphics.py | 3 +- .../SynthesisFusionAddin/src/UI/Events.py | 7 ++- .../src/UI/FileDialogConfig.py | 4 +- exporter/SynthesisFusionAddin/src/UI/HUI.py | 8 ++- .../SynthesisFusionAddin/src/UI/Handlers.py | 3 +- .../SynthesisFusionAddin/src/UI/Helper.py | 11 ++-- .../src/UI/MarkingMenu.py | 3 + .../SynthesisFusionAddin/src/UI/Toolbar.py | 10 ++-- exporter/SynthesisFusionAddin/src/__init__.py | 17 +++++- .../src/general_imports.py | 55 ------------------- exporter/SynthesisFusionAddin/src/strings.py | 4 -- 22 files changed, 73 insertions(+), 141 deletions(-) delete mode 100644 exporter/SynthesisFusionAddin/src/general_imports.py delete mode 100644 exporter/SynthesisFusionAddin/src/strings.py diff --git a/exporter/SynthesisFusionAddin/Synthesis.py b/exporter/SynthesisFusionAddin/Synthesis.py index aeb2f6d06a..9389070088 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.py +++ b/exporter/SynthesisFusionAddin/Synthesis.py @@ -6,14 +6,16 @@ import adsk.core # Required for absolute imports -fileDir = os.path.dirname(os.path.abspath(__file__)) -sys.path.append(os.path.abspath(os.path.join(fileDir))) +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from src import APP_NAME, DESCRIPTION, INTERNAL_ID, gm from src.configure import unload_config -from src.general_imports import APP_NAME, DESCRIPTION, INTERNAL_ID, gm, root_logger +from src.logging import setupLogger from src.UI import HUI, Camera, ConfigCommand, Helper, MarkingMenu from src.UI.Toolbar import Toolbar +root_logger: logging.Logger # TODO: Will need to be updated after GH-1010 + def run(_): """## Entry point to application from Fusion. @@ -23,8 +25,8 @@ def run(_): """ try: - # Remove all items prior to start just to make sure - unregister_all() + global root_logger + root_logger, _ = setupLogger() # TODO: Will need to be updated after GH-1010 # creates the UI elements register_ui() @@ -63,26 +65,7 @@ def stop(_): unload_config() - for file in gm.files: - try: - os.remove(file) - except OSError: - pass - - # removes path so that proto files don't get confused - - import sys - - path = os.path.abspath(os.path.dirname(__file__)) - - path_proto_files = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "proto", "proto_out")) - - if path in sys.path: - sys.path.remove(path) - - if path_proto_files in sys.path: - sys.path.remove(path_proto_files) - + gm.clear() except: logging.getLogger(f"{INTERNAL_ID}").error("Failed:\n{}".format(traceback.format_exc())) diff --git a/exporter/SynthesisFusionAddin/src/Analytics/alert.py b/exporter/SynthesisFusionAddin/src/Analytics/alert.py index c134f1906b..3633b03c86 100644 --- a/exporter/SynthesisFusionAddin/src/Analytics/alert.py +++ b/exporter/SynthesisFusionAddin/src/Analytics/alert.py @@ -1,5 +1,5 @@ -from ..configure import setAnalytics -from ..general_imports import gm +from src import gm +from src.configure import setAnalytics def showAnalyticsAlert(): diff --git a/exporter/SynthesisFusionAddin/src/Analyzer/timer.py b/exporter/SynthesisFusionAddin/src/Analyzer/timer.py index 41bb86135f..961af83fd7 100644 --- a/exporter/SynthesisFusionAddin/src/Analyzer/timer.py +++ b/exporter/SynthesisFusionAddin/src/Analyzer/timer.py @@ -2,10 +2,11 @@ """ import inspect +import logging import os from time import time -from ..general_imports import * +from src import DEBUG, INTERNAL_ID class Timer: diff --git a/exporter/SynthesisFusionAddin/src/GlobalManager.py b/exporter/SynthesisFusionAddin/src/GlobalManager.py index 9fb6d44b12..287052fc90 100644 --- a/exporter/SynthesisFusionAddin/src/GlobalManager.py +++ b/exporter/SynthesisFusionAddin/src/GlobalManager.py @@ -3,11 +3,6 @@ import adsk.core import adsk.fusion -from src import INTERNAL_ID - -# Transition: AARD-1737 -from .general_imports import * - class GlobalManager(object): """Global Manager instance""" @@ -15,14 +10,10 @@ class GlobalManager(object): class __GlobalManager: def __init__(self): self.app = adsk.core.Application.get() - self.logger = logging.getLogger(f"{INTERNAL_ID}.{self.__class__.__name__}") if self.app: self.ui = self.app.userInterface - self.connected = False - """ Is unity currently connected """ - self.uniqueIds = [] """ Collection of unique ID values to not overlap """ @@ -43,11 +34,14 @@ def __init__(self): - this is the list of objects being sent """ - self.files = [] - def __str__(self): return "GlobalManager" + def clear(self): + for attr, value in self.__dict__.items(): + if isinstance(value, list): + setattr(self, attr, []) + instance = None def __new__(cls): diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py index d42c20b69e..55302f9aec 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py @@ -7,13 +7,11 @@ import adsk.fusion from proto.proto_out import joint_pb2, types_pb2 +from src import INTERNAL_ID, gm from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser.PDMessage import PDMessage from src.Parser.SynthesisParser.Utilities import guid_component, guid_occurrence -# Transition: AARD-1737 -from ...general_imports import * - # ____________________________ DATA TYPES __________________ diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py index 8921f7c9ee..d5566f329f 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py @@ -22,6 +22,7 @@ """ +import logging import traceback import uuid from typing import Union @@ -30,6 +31,7 @@ import adsk.fusion from proto.proto_out import assembly_pb2, joint_pb2, signal_pb2, types_pb2 +from src import DEBUG, INTERNAL_ID, gm from src.Parser.ExporterOptions import ExporterOptions, JointParentType, SignalType from src.Parser.SynthesisParser.PDMessage import PDMessage from src.Parser.SynthesisParser.Utilities import ( @@ -38,9 +40,6 @@ guid_occurrence, ) -# Transition: AARD-1737 -from ...general_imports import * - # Need to take in a graphcontainer # Need to create a new base node for each Joint Instance # Need to create at least one grounded Node diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py index 77faff62d4..e2f8c42283 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py @@ -6,13 +6,11 @@ import adsk from proto.proto_out import material_pb2 +from src import INTERNAL_ID from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser.PDMessage import PDMessage from src.Parser.SynthesisParser.Utilities import construct_info, fill_info -# Transition: AARD-1737 -from ...general_imports import INTERNAL_ID - OPACITY_RAMPING_CONSTANT = 14.0 diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py index 5dfca273ad..9866c18d4d 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py @@ -1,4 +1,6 @@ import gzip +import logging +import pathlib import traceback import adsk.core @@ -6,6 +8,7 @@ from google.protobuf.json_format import MessageToJson from proto.proto_out import assembly_pb2, types_pb2 +from src import DEBUG, INTERNAL_ID, gm from src.Parser.ExporterOptions import ExporterOptions, ExportMode from src.Parser.SynthesisParser import ( Components, @@ -17,9 +20,6 @@ from src.Parser.SynthesisParser.Utilities import fill_info from src.UI.Camera import captureThumbnail, clearIconCache -# Transition: AARD-1737 -from ...general_imports import * - class Parser: def __init__(self, options: ExporterOptions): diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py index 3d8967268c..7fbe822bac 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py @@ -23,8 +23,6 @@ import adsk from proto.proto_out import types_pb2 - -# Transition: AARD-1737 from src import INTERNAL_ID diff --git a/exporter/SynthesisFusionAddin/src/UI/Camera.py b/exporter/SynthesisFusionAddin/src/UI/Camera.py index 618971bbc7..5142762e57 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Camera.py +++ b/exporter/SynthesisFusionAddin/src/UI/Camera.py @@ -1,13 +1,14 @@ +import logging import os +import traceback +import adsk.core from adsk.core import SaveImageFileOptions +from src import A_EP from src.Types.OString import OString from src.UI import Helper -# Transition: AARD-1737 -from ..general_imports import * - def captureThumbnail(size=250): """ diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index f27132616a..01917aec6a 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -4,12 +4,14 @@ import logging import os +import pathlib import traceback from enum import Enum import adsk.core import adsk.fusion +from src import A_EP, INTERNAL_ID, gm from src.Analytics.alert import showAnalyticsAlert from src.configure import NOTIFIED, write_configuration from src.Parser.ExporterOptions import ( @@ -28,9 +30,6 @@ from src.UI import CustomGraphics, FileDialogConfig, Helper, IconPaths, OsHelper from src.UI.Configuration.SerialCommand import SerialCommand -# Transition: AARD-1737 -from ..general_imports import * - # ====================================== CONFIG COMMAND ====================================== """ diff --git a/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py b/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py index 1f0a06fc0f..97935b6827 100644 --- a/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py +++ b/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py @@ -4,8 +4,7 @@ import adsk.core import adsk.fusion -# Transition: AARD-1737 -from ..general_imports import * +from src import gm def createTextGraphics(wheel: adsk.fusion.Occurrence, _wheels) -> None: diff --git a/exporter/SynthesisFusionAddin/src/UI/Events.py b/exporter/SynthesisFusionAddin/src/UI/Events.py index 5e817d5746..4189d6b174 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Events.py +++ b/exporter/SynthesisFusionAddin/src/UI/Events.py @@ -1,8 +1,11 @@ +import json import logging.handlers from typing import Sequence -# Transition: AARD-1737 -from ..general_imports import * +import adsk.core + +from src import INTERNAL_ID, gm +from src.UI import Helper """ # This file is Special It links all function names to command requests that palletes can make automatically diff --git a/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py b/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py index b738eb463d..a68809d676 100644 --- a/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py +++ b/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py @@ -3,11 +3,9 @@ import adsk.core import adsk.fusion +from src import gm from src.Types.OString import OString -# Transition: AARD-1737 -from ..general_imports import * - def SaveFileDialog(defaultPath="", defaultName="", ext="MiraBuf Package (*.mira)") -> Union[str, bool]: """Function to generate the Save File Dialog for the Hellion Data files diff --git a/exporter/SynthesisFusionAddin/src/UI/HUI.py b/exporter/SynthesisFusionAddin/src/UI/HUI.py index 0be5a0bddd..f044fa7d9b 100644 --- a/exporter/SynthesisFusionAddin/src/UI/HUI.py +++ b/exporter/SynthesisFusionAddin/src/UI/HUI.py @@ -1,7 +1,9 @@ -# Transition: AARD-1737 -from src.UI import Handlers, OsHelper +import logging + +import adsk.core -from ..general_imports import * +from src import INTERNAL_ID, gm +from src.UI import Handlers, OsHelper # no longer used diff --git a/exporter/SynthesisFusionAddin/src/UI/Handlers.py b/exporter/SynthesisFusionAddin/src/UI/Handlers.py index 5b1e6aa97d..4529b861db 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Handlers.py +++ b/exporter/SynthesisFusionAddin/src/UI/Handlers.py @@ -1,5 +1,4 @@ -# Transition: AARD-1737 -from ..general_imports import * +import adsk.core class HButtonCommandCreatedEvent(adsk.core.CommandCreatedEventHandler): diff --git a/exporter/SynthesisFusionAddin/src/UI/Helper.py b/exporter/SynthesisFusionAddin/src/UI/Helper.py index 5f796d087f..ad7346fd46 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Helper.py +++ b/exporter/SynthesisFusionAddin/src/UI/Helper.py @@ -1,9 +1,10 @@ +import traceback from inspect import getmembers, isfunction -from src.UI import HUI, Events +import adsk.core -# Transition: AARD-1737 -from ..general_imports import * +from src import APP_NAME, APP_TITLE, INTERNAL_ID, gm +from src.UI import HUI, Events def check_solid_open() -> bool: @@ -14,7 +15,7 @@ def check_solid_open() -> bool: return True -def getDocName() -> str or None: +def getDocName() -> str | None: """### Gets the active Document Name - If it can't find one then it will return None """ @@ -38,7 +39,7 @@ def checkAttribute() -> bool: return False -def addUnityAttribute() -> bool or None: +def addUnityAttribute() -> bool | None: """#### Adds an attribute to the Fusion File - Initially intended to be used to add a marker for in use untiy files - No longer necessary diff --git a/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py b/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py index 5c68d2c79a..9b894127d9 100644 --- a/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py +++ b/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py @@ -4,6 +4,8 @@ import adsk.core import adsk.fusion +from src import INTERNAL_ID + # Ripped all the boiler plate from the example code: https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-c90ce6a2-c282-11e6-a365-3417ebc87622 # global mapping list of event handlers to keep them referenced for the duration of the command @@ -221,6 +223,7 @@ def stopMarkingMenu(ui: adsk.core.UserInterface): else: ui.messageBox(str(obj) + " is not a valid object") + cmdDefs.clear() handlers.clear() except: ui.messageBox("Failed:\n{}".format(traceback.format_exc())) diff --git a/exporter/SynthesisFusionAddin/src/UI/Toolbar.py b/exporter/SynthesisFusionAddin/src/UI/Toolbar.py index f5ae186d0c..388abbc4b9 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Toolbar.py +++ b/exporter/SynthesisFusionAddin/src/UI/Toolbar.py @@ -1,7 +1,7 @@ -# Transition: AARD-1737 -from src import INTERNAL_ID +import logging +import traceback -from ..general_imports import * +from src import INTERNAL_ID, gm class Toolbar: @@ -40,7 +40,7 @@ def __init__(self, name: str): error = traceback.format_exc() self.logger.error(f"Failed at creating toolbar with {self.uid} due to {error}") - def getPanel(self, name: str, visibility: bool = True) -> str or None: + def getPanel(self, name: str, visibility: bool = True) -> str | None: """# Gets a control for a panel to the tabbed toolbar - optional param for visibility """ @@ -61,7 +61,7 @@ def getPanel(self, name: str, visibility: bool = True) -> str or None: return None @staticmethod - def getNewPanel(name: str, tab_id: str, toolbar_id: str, visibility: bool = True) -> str or None: + def getNewPanel(name: str, tab_id: str, toolbar_id: str, visibility: bool = True) -> str | None: """# Gets a control for a panel to the tabbed toolbar visibility""" logger = logging.getLogger(f"{INTERNAL_ID}.Toolbar.getNewPanel") diff --git a/exporter/SynthesisFusionAddin/src/__init__.py b/exporter/SynthesisFusionAddin/src/__init__.py index cb8ec64c64..f041f71631 100644 --- a/exporter/SynthesisFusionAddin/src/__init__.py +++ b/exporter/SynthesisFusionAddin/src/__init__.py @@ -1,6 +1,21 @@ +import os +import sys + +from src.GlobalManager import GlobalManager + APP_NAME = "Synthesis" APP_TITLE = "Synthesis Robot Exporter" DESCRIPTION = "Exports files from Fusion into the Synthesis Format" INTERNAL_ID = "synthesis" -__all__ = ["APP_NAME", "APP_TITLE", "DESCRIPTION", "INTERNAL_ID"] +A_EP = None # TODO: Will be removed by GH-1010 +DEBUG = True # TODO: Will be removed by GH-1010 + +gm = GlobalManager() + +__all__ = ["APP_NAME", "APP_TITLE", "DESCRIPTION", "INTERNAL_ID", "gm"] + +# Transition: AARD-1737 +# This method of running the resolve dependencies module will be revisited in AARD-1734 +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "proto", "proto_out"))) +from proto import deps diff --git a/exporter/SynthesisFusionAddin/src/general_imports.py b/exporter/SynthesisFusionAddin/src/general_imports.py deleted file mode 100644 index 042e4618ea..0000000000 --- a/exporter/SynthesisFusionAddin/src/general_imports.py +++ /dev/null @@ -1,55 +0,0 @@ -import json -import logging.handlers -import os -import pathlib -import sys -import traceback -import uuid -from datetime import datetime -from time import time -from types import FunctionType - -import adsk.core -import adsk.fusion - -# hard coded to bypass errors for now -PROTOBUF = True -DEBUG = True - -try: - from .GlobalManager import * - from .logging import setupLogger - from .strings import * - - (root_logger, log_handler) = setupLogger() -except ImportError as e: - # nothing to really do here - print(e) - -try: - path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) - - path_proto_files = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "proto", "proto_out")) - - if not path in sys.path: - sys.path.insert(1, path) - - if not path_proto_files in sys.path: - sys.path.insert(2, path_proto_files) - - from proto import deps - -except: - logging.getLogger(f"{INTERNAL_ID}.import_manager").error("Failed\n{}".format(traceback.format_exc())) - -try: - # simple analytics endpoint - # A_EP = AnalyticsEndpoint("UA-188467590-1", 1) - A_EP = None - - # Setup the global state - gm: GlobalManager = GlobalManager() - my_addin_path = os.path.dirname(os.path.realpath(__file__)) -except: - # should also log this - logging.getLogger(f"{INTERNAL_ID}.import_manager").error("Failed\n{}".format(traceback.format_exc())) diff --git a/exporter/SynthesisFusionAddin/src/strings.py b/exporter/SynthesisFusionAddin/src/strings.py deleted file mode 100644 index 6e3aa109e2..0000000000 --- a/exporter/SynthesisFusionAddin/src/strings.py +++ /dev/null @@ -1,4 +0,0 @@ -APP_NAME = "Synthesis" -APP_TITLE = "Synthesis Robot Exporter" -DESCRIPTION = "Exports files from Fusion into the Synthesis Format" -INTERNAL_ID = "synthesis" From 2245c6f79131213fa524ceb5c7efe402d52a1679 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Wed, 10 Jul 2024 15:09:37 -0700 Subject: [PATCH 003/344] Dependency module update --- exporter/SynthesisFusionAddin/Synthesis.py | 17 ++- .../SynthesisFusionAddin/src/Dependencies.py | 143 ++++++++++++++++++ 2 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 exporter/SynthesisFusionAddin/src/Dependencies.py diff --git a/exporter/SynthesisFusionAddin/Synthesis.py b/exporter/SynthesisFusionAddin/Synthesis.py index 335a2416ce..75a8242da0 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.py +++ b/exporter/SynthesisFusionAddin/Synthesis.py @@ -6,11 +6,18 @@ import adsk.core -from .src.configure import setAnalytics, unload_config -from .src.general_imports import APP_NAME, DESCRIPTION, INTERNAL_ID, gm, root_logger -from .src.Types.OString import OString -from .src.UI import HUI, Camera, ConfigCommand, Handlers, Helper, MarkingMenu -from .src.UI.Toolbar import Toolbar +from .src.Dependencies import resolveDependencies + +try: + from .src.configure import setAnalytics, unload_config + from .src.general_imports import APP_NAME, DESCRIPTION, INTERNAL_ID, gm, root_logger + from .src.Types.OString import OString + from .src.UI import HUI, Camera, ConfigCommand, Handlers, Helper, MarkingMenu + from .src.UI.Toolbar import Toolbar +except (ImportError, ModuleNotFoundError): + # Dependency likely has not been installed OR protobuf files were not compiled and could not be found. + resolveDependencies() + adsk.core.Application.get().userInterface.messageBox("Installed required dependencies, please restart Fusion.") def run(_): diff --git a/exporter/SynthesisFusionAddin/src/Dependencies.py b/exporter/SynthesisFusionAddin/src/Dependencies.py new file mode 100644 index 0000000000..73e03343c3 --- /dev/null +++ b/exporter/SynthesisFusionAddin/src/Dependencies.py @@ -0,0 +1,143 @@ +import importlib.machinery +import logging +import os +import platform +import subprocess +import sys +from pathlib import Path + +import adsk.core +import adsk.fusion + +from .general_imports import INTERNAL_ID + +logger = logging.getLogger(INTERNAL_ID) +system = platform.system() + +# Since the Fusion python runtime is separate from the system python runtime we need to do some funky things +# in order to download and install python packages separate from the standard library. +PIP_DEPENDENCY_VERSION_MAP: dict[str, str] = { + "protobuf": "5.27.2", +} + + +# TODO: Waiting on GH-1010 for failure logging. +def getInternalFusionPythonInstillationFolder() -> str: + # Thank you Kris Kaplan + # Find the folder location where the Autodesk python instillation keeps the 'os' standard library module. + pythonStandardLibraryModulePath = importlib.machinery.PathFinder.find_spec("os", sys.path).origin + + # Depending on platform, adjust to folder to where the python executable binaries are stored. + if system == "Windows": + folder = f"{Path(pythonStandardLibraryModulePath).parents[1]}" + elif system == "Darwin": + folder = f"{Path(pythonStandardLibraryModulePath).parents[2]}/bin" + else: + # TODO: System string should be moved to __init__ after GH-1013 + raise RuntimeError("Unsupported platform.") + + return folder + + +def executeCommand(*args: str) -> subprocess.CompletedProcess: + logger.debug(f"Running Command -> {' '.join(args)}") + try: + result: subprocess.CompletedProcess = subprocess.run( + args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True + ) + logger.debug(f"Command Output:\n{result.stdout}") + return result + + except subprocess.CalledProcessError as error: + logger.error(f"Exit code: {error.returncode}") + logger.error(f"Output:\n{error.stderr}") + raise error + + +def verifyCompiledProtoImports() -> bool: + try: + from ..proto.proto_out import assembly_pb2, joint_pb2, material_pb2, types_pb2 + + return True + except ImportError: + # TODO: Add logging from GH-1010 + return False + + +def getInstalledPipPackages(pythonExecutablePath: str) -> dict[str, str]: + result: str = executeCommand(pythonExecutablePath, "-m", "pip", "freeze").stdout + # We don't need to check against packages with a specific hash as those are not required by Synthesis. + return {x.split("==")[0]: x.split("==")[1] for x in result.splitlines() if "==" in x} + + +def packagesOutOfDate(installedPackages: dict[str, str]) -> bool: + for package, installedVersion in installedPackages.items(): + expectedVersion = PIP_DEPENDENCY_VERSION_MAP.get(package) + if expectedVersion and expectedVersion != installedVersion: + return True + + return False + + +# TODO: GH-1010 for log failure +def resolveDependencies() -> None: + app = adsk.core.Application.get() + ui = app.userInterface + if app.isOffLine: + # If we have gotten this far that means that an import error was thrown for possible missing + # dependencies... And we can't try to download them because we have no internet... ¯\_(ツ)_/¯ + ui.messageBox("Unable to resolve dependencies while not connected to the internet.") + raise RuntimeError("Internet required to check dependencies") + + # This is important to reduce the chance of hang on startup. + adsk.doEvents() + + pythonFolder = getInternalFusionPythonInstillationFolder() + pythonExecutableFile = "python.exe" if system == "Windows" else "python" # Confirming 110% everything is fine. + pythonExecutablePath = os.path.join(pythonFolder, pythonExecutableFile) + + progressBar = ui.createProgressDialog() + progressBar.isCancelButtonShown = False + progressBar.reset() + progressBar.show("Synthesis", f"Installing dependencies...", 0, len(PIP_DEPENDENCY_VERSION_MAP), 0) + + # TODO: Is this really true? Do we need this? waheusnta eho? - Brandon + # Install pip manually on macos as it is not included by default? Really? + if system == "Darwin" and not os.path.exists(os.path.join(pythonFolder, "pip")): + pipInstallScriptPath = os.path.join(pythonFolder, "get-pip.py") + if not os.path.exists(pipInstallScriptPath): + executeCommand("curl", "https://bootstrap.pypa.io/get-pip.py", "-o", pipInstallScriptPath) + progressBar.maximumValue += 2 + progressBar.progressValue += 1 + progressBar.message = "Downloading PIP Installer..." + + progressBar.progressValue += 1 + progressBar.message = "Installing PIP..." + executeCommand(pythonExecutablePath, pipInstallScriptPath) + + installedPackages = getInstalledPipPackages(pythonExecutablePath) + if packagesOutOfDate(installedPackages): + # Uninstall and reinstall everything to confirm updated versions. + progressBar.message = "Uninstalling out-of-date Dependencies..." + progressBar.maximumValue += len(PIP_DEPENDENCY_VERSION_MAP) + + for dep in PIP_DEPENDENCY_VERSION_MAP.keys(): + progressBar.progressValue += 1 + executeCommand(pythonExecutablePath, "-m", "pip", "uninstall", "-y", dep) + + progressBar.message = "Installing Dependencies..." + for dep, version in PIP_DEPENDENCY_VERSION_MAP.items(): + progressBar.progressValue += 1 + progressBar.message = f"Installing {dep}..." + adsk.doEvents() + + # TODO: Will need to update logging after GH-1010 + result = executeCommand(pythonExecutablePath, "-m", "pip", "install", f"{dep}=={version}").returncode + if result: + logging.getLogger(f"{INTERNAL_ID}").warn(f'Dep installation "{dep}" exited with code "{result}"') + + progressBar.hide() + + # TODO: Will need to update logging after GH-1010 + if not verifyCompiledProtoImports(): + ui.messageBox("Missing required compiled protobuf files.") From 783260a14bb663696e6bb26dcc530bcf8f0e4d92 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Fri, 12 Jul 2024 09:32:05 -0700 Subject: [PATCH 004/344] add execute command --- exporter/SynthesisFusionAddin/Synthesis.py | 11 +++++++++ .../src/UI/ShowAPSAuthCommand.py | 2 -- .../src/UI/ShowWebsiteCommand.py | 24 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py diff --git a/exporter/SynthesisFusionAddin/Synthesis.py b/exporter/SynthesisFusionAddin/Synthesis.py index 37403d9f82..c3461d575e 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.py +++ b/exporter/SynthesisFusionAddin/Synthesis.py @@ -17,6 +17,7 @@ Helper, MarkingMenu, ShowAPSAuthCommand, + ShowWebsiteCommand ) from .src.UI.Toolbar import Toolbar @@ -141,3 +142,13 @@ def register_ui() -> None: ) gm.elements.append(apsButton) + + websiteButton = HUI.HButton( + "Synthesis Website", + work_panel, + Helper.check_solid_open, + ShowWebsiteCommand.ShowWebsiteCommandCreatedHandler, + description=f"WEBSITE TEST", + command=True + ) + gm.elements.append(websiteButton) diff --git a/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py b/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py index bacc6e094b..89f76190dd 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py @@ -1,11 +1,9 @@ import json import logging -import os import time import traceback import urllib.parse import urllib.request -import webbrowser import adsk.core diff --git a/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py b/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py new file mode 100644 index 0000000000..1a225a0845 --- /dev/null +++ b/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py @@ -0,0 +1,24 @@ +import webbrowser +import adsk.core +class ShowWebsiteCommandExecuteHandler(adsk.core.CommandEventHandler): + def __init__(self) -> None: + super().__init__() + def notify(self, args): + try: + url = "https://synthesis.autodesk.com/" + res = webbrowser.open(url, new=2) + if not res: + gm.ui.messageBox("Failed\n{}".format(traceback.format_exc())) + except: + gm.ui.messageBox("Failed\n{}".format(traceback.format_exc())) +class ShowWebsiteCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): + def __init__(self, configure) -> None: + super().__init__() + def notify(self, args): + try: + command = args.command + onExecute = ShowWebsiteCommandExecuteHandler() + command.execute.add(onExecute) + gm.handlers.append(onExecute) + except: + gm.ui.messageBox("Failed\n{}".format(traceback.format_exc())) From 6855427b23bd34055d15f48beb049767c87877d4 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Wed, 17 Jul 2024 10:04:02 -0700 Subject: [PATCH 005/344] icons shows up now --- exporter/SynthesisFusionAddin/Synthesis.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exporter/SynthesisFusionAddin/Synthesis.py b/exporter/SynthesisFusionAddin/Synthesis.py index c3461d575e..e0060d2bca 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.py +++ b/exporter/SynthesisFusionAddin/Synthesis.py @@ -143,12 +143,13 @@ def register_ui() -> None: gm.elements.append(apsButton) + websiteButton = HUI.HButton( "Synthesis Website", work_panel, Helper.check_solid_open, ShowWebsiteCommand.ShowWebsiteCommandCreatedHandler, - description=f"WEBSITE TEST", + description=f"Website Test", command=True ) gm.elements.append(websiteButton) From 84ab146bb9db23b25f57aa1fa4b2b6554e3946e8 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Wed, 17 Jul 2024 10:05:05 -0700 Subject: [PATCH 006/344] added needed resources --- .../FrictionOverride_icon/32x32-normal.png | Bin 0 -> 586 bytes .../SynthesisWebsite/16x16-disabled.png | Bin 0 -> 1469 bytes .../Resources/SynthesisWebsite/16x16-normal.png | Bin 0 -> 1535 bytes .../SynthesisWebsite/32x32-disabled.png | Bin 0 -> 2002 bytes .../Resources/SynthesisWebsite/32x32-normal.png | Bin 0 -> 2176 bytes .../Resources/SynthesisWebsite/64x64-normal.png | Bin 0 -> 3335 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 exporter/SynthesisFusionAddin/src/Resources/FrictionOverride_icon/32x32-normal.png create mode 100644 exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/16x16-disabled.png create mode 100644 exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/16x16-normal.png create mode 100644 exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/32x32-disabled.png create mode 100644 exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/32x32-normal.png create mode 100644 exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/64x64-normal.png diff --git a/exporter/SynthesisFusionAddin/src/Resources/FrictionOverride_icon/32x32-normal.png b/exporter/SynthesisFusionAddin/src/Resources/FrictionOverride_icon/32x32-normal.png new file mode 100644 index 0000000000000000000000000000000000000000..34f6b927a4769ba9eb525c709ed7a8aa7cb0ab95 GIT binary patch literal 586 zcmV-Q0=4~#P)i}@hQI;js>6EjxGeQUec<))S*R*ZR<>e*bd!C-2$n%`5tE*uGNs@4W zeva0f^?Hqn&@>HNYpk`TX-ZKP7-RapqA0M|(liab-HzF8#&|p)CZLo;DTVi*x3@P$ zgu1Rt(-eTZu6cQR!5D+JmMqKATBDRgYt48(##+l}vl%Av!DKSw=H`YF0?*IS{VC*m z&h_;*Ns@4KazarQeIID8`(7x^@@oRlIX0UOB0^PFD5Ve)x~{`}k5cbrCdNpN5qNLR z7$aJ1(li||AR=tHTkh}g`bgcmaL%z>t=Mk2`~QOZd@e&IKg*yi_}>D*4k@tr3IOKw`A^{YAw`ZHIr4q^ Y1IMisNbTmNO8@`>07*qoM6N<$f)uL|FaQ7m literal 0 HcmV?d00001 diff --git a/exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/16x16-disabled.png b/exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/16x16-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..f4ba1b8f8393d4c4dbf6f24a1034a85484b017c6 GIT binary patch literal 1469 zcmaJ>eNYr-7(XI1V89QQ2vN2b$Pf1R^KL)lWbSScxa5#dDr^$nyWNF7xZ9iCMNXnA z%@K()(itJfGG`i)!NG>eaReM8KWNHqCUeY!nub5huxuo2m>lh0;LJZ9clO~e@(J+Fp1vo5El%?mJ50wfWnBk{$E)ogyUEnw zmV8G@0O!PT$!5)DN51@rKgnht3k6Msy@ja4TgcX$y87n4GaES zNr!w=wGy(yH;2x^kRsaPmn|O56Lv~9N=aix+SFK9;cu+^gKKm>%%NyQLxZ`2G^>$n6lYl$#R!xj z5TJo*J3_h;MMB!DIgoi6Uef##C9ErI2r^&=pIWcmU=Z|NHo>sRb00mV%@+-n4~+_8 z6gOjNFleMWtF7r?>Hk}>6dA*Kn61OYVl2s!1Y@`3 zxQl05JMQEyEQ!%p7ful|TX#s)g^*v0`Bi}5d0XOlEnKTq=l^bNg-U%C_w9#uL(0`YQ8(^B%pt zBX4)cft*vei|;DqpX}r(4_{bz=nmg5?RGqqvm}v+7e`N5?VpjUOj~+V!okElZYHaU z$~-R**s%=W+!5dB6?a|<96b8r@JDZsO^(hqUGVj9vpLTEwBYsL&#S^qF;Cjm?aXU+ z>Ak(5-Tdb*>nkG4B)wnuER~s3v3A)-nLOvis}8Ox-We@;r!?>E!WC`BceC;WV=o-J zIwcHEz@4SHZvF7P_3wXA8)(~ZetdOmb;p-IFCDvavgM+hl`!t;N!zM4Y#p31Kb_j_ zn|b5C!tFy_S`rJc1S}GkG2EKf0BPA%cbnG#V*7KZX*}&OxWDzsvJ&_6UEb_HLetxp zRP9x0(TGUIomkg>dCfQ1si#g%S1(-Z`Z;%H>H_MJC%GG$`8;`l0 guh#vM?kKsQJh`PS^W>?4Hsc@ac9rqR9ou&O2Pq5pmjD0& literal 0 HcmV?d00001 diff --git a/exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/16x16-normal.png b/exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/16x16-normal.png new file mode 100644 index 0000000000000000000000000000000000000000..34948454a985666777913757ecc4a1fa6102867e GIT binary patch literal 1535 zcmaJ>eNYr-7+;VJgU}<tz!T z%C$<=iCjBy2!R#JiB#wLaG&Ou>y(maU9M>MSBlN`A}1jQ`59@B7!MF4vMIo^NH}Wn zF()zx%!Bv17(?JO6|>%nxZ)n+fUgX8tGWy`HUbrK9ETasCQAYtq(XwY5GHYwzzA?N zD8cX~#SvO+@qg?x9DGiK2n>VKG6&2pf(? zBJmW*v<`}#45jkpl zJx&CCu}O-=<2Z{G1ViHtiQ8F%U>Tyw#j+%c({7rhsRYJ@Yzy-^oTFHZWL++TSnT0A z7vc8gaTLzj7ZWs@u=Pd_Q;3Rk!mk4SPS}zUwdLKqESRcZsjA@w8OrKZQ#I;T4R*Uh z=LrU$?-N8N8W%1|bPDupJavz(D-E(#q^l8ltk}HrfM6(+39$l2qIQW2p)|`%sKD59 zloDA9ltt!3d3nf$y>wp@$9W4RAR#Rg7mwRO~2f`{LDbgO>xG+&?o;+I(^*Qu|2enM%P)LrE9gUIpNVcJCa*pD7hsa%RD=JWb1*tG=0PCzx8;%-*xP-wjaNC745&h=I(&}?a93r zndY6HHTHdKO8?)F$yI&Vhnp55wR0P;_Ixe5Ts=>m8k}o)oQq9wS=8fS5$Rsp)Vem( z-;f~}bnLyA>7R1v;+58o*A;6<-^e?kcUJfQa<(mX0C{R!=I)9=0>+vmM{e-$NY>*+ wcwgE6{1Z8?ySKetQZQ>q?u})0*YkZ)-=$sRbA!Vz@xQQlvEQ?=@RfD{0oLsno&W#< literal 0 HcmV?d00001 diff --git a/exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/32x32-disabled.png b/exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/32x32-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..770208ec0e4889c412d946e67f8dcd6bc23b24ec GIT binary patch literal 2002 zcmaJ?dsGu=77tR;66t{z&;=^fAQmV|W+v}hB8EgDDbYwH(g+8J$wUGq6DI=+gk_a= zU7$z>!=c+0 zpjaMG0onLYqgl$D?zzMQjCv_+yFd-9Epj}^m{d&S?-Xy=V#RrwM9+%b;Oi4>M`#2S zPN9I^ly9~ob}4Hf7@^2oj5+1esV2!ysSIm+*N@ z7%#Jp7f3(}Pt1eGG8qJIQb;5+NUjh_c%VqQ3F5;`wo0>=Ld_Vy{LCu^oEo-Isc%aO$;zC=0+1@Ov zFo^Xh{q50D4aW^TZ`A2i&+{s7tzp*q<(HRVSF8xq4f_oPWyRVX-IGtwZ%v;2?arZEd{nS-=mD;p_Lt zddf8A<>k9?h1mH?%m+j)YN~?&PFCa^S6E(9v*I6_vX5F-?nY=(49Jc+Mk~7n7G*3 zZzd+Z`~Ldw>5-8XXMO$PosRfJiG>^0ExJ87Z)(@AUw@{gsHoz~6`8rErKPK9eE%D9 zabYzzH8IQOoy*R>%{>QXtlm{xTHV?jalE4g%gbXH_a5s1eUom_!-siM9!jM$CpS0V zZ01aV5Omn(8ec!9`pl;)*Q>g^+G4jyI(A#Io40Q_cXkH)`uZkq*-}q9n{*!V`L?#G zPlt!;>LphfR~-y`Gvb=u*{t&@i}_taK_G@QA7N9z0f;@7P7zQd$JVIHrh zzP^9|_oMeMr@lV8+WttcNPR6?r8%*&uuyocy}eu7OZddj%*>pb?2eB3OKO(Gu|u_S z+qS^u^{Xl?D>a#!)At1369WUU4qnlmxct>uYx9L6(+5g=8yguoj+Y2tX(+pu`_!v7 zYp;{JLbZ{4ops2XLLv=fsq`N}K%}?kZ+_jo@yGX$9|&AgH|ewY+kMY#pZi?Q^>n#h z>1lgC&u}Z7KW2spY#oZI)JpNr3*WT)BY();QS5MB^47%)g+T&=U~0x$qEe~+R;{{v zqO0qJhw|X?3r8ZACxcfu+{r&$`_Jte8KZ>1R;37i9{F`|BTa-|yLN>>8yOi9|7xV! zxjOo-w@)8UUlS9eV9YxtbJaw_NFYag>2KQaF3I-KwN S%~7rU$Ew`4S#dHxtL%T(C<5XD literal 0 HcmV?d00001 diff --git a/exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/32x32-normal.png b/exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/32x32-normal.png new file mode 100644 index 0000000000000000000000000000000000000000..77458fce7d136cceab5bce620c5501e226f82445 GIT binary patch literal 2176 zcmaJ@dsGu=77w^a2#O*PmHHSG-3pRqW|EKxL=6cEL4q`*Qbl2sOdvuYCL;t8X|+&_ zqJS%u9_kCJwX(PZt!S%gK|w);V^=I%c~b=x1X@^L-3fy2AD+&cZ@&5N+{f?U`@84N zXTd>|1teDzfk0Rw^%u$U9k47LBK|ZbBn;!5l}Rq~B^1{$e}NzV5h;)f2!zYW>?V-8 z_}SLr|DlOMu&=Z%t7`q%Dgt57X-!Bd7AgzmDfL@YKqrkh?%N0QQkP2aU()aN~6YrY4ke4;ucZp_hEc84*E?tT7yjX*1OI$ zQ#8DMbTeY0Lo|@C)ml=V);3{s^iM4B)Ha308&J9&HR<;ml_;)GnKj1C`1gS{2vMJz4@WPABROnBB-sxx7TPKnOO)!=bwY~jDPh%(DcBq)1T)#F8YBbsX=dU;=@u6Kg;j4DJ1xa2Ynd4O z+mW%H-i{rr!)MKi4^_r;>Kc5WHBylvBvkS;H!*PM*5%a|BhS{G%Tfl{me_}Q#eAf5 ze5_i`=tvXi4}8iHGa|9%s2}V0SEbc5H;FRJtNc3ld#1`97kBJ@zpgDUf^6sOBF22Y zBB-TBH+P(FXl}Yb^{OkzVHwr$arlAk@Uij9dQJVv$szAwpVqd8bDHCvD4jfJM~nw@ zp-Z*H>GhGWTZR>fZ*i#fL5r>R0qh?0NpIh1L~*s3M28~X4ZhZ8(q4yLIVq#wck_7#HR~=U z%#{7k-Tt6BE6UF}|1 zV#<3h;57q8Rd{4gnCV-aC3e-(5jP5K$BI_mYt4Qxc}|~KWBJxb~jwp{=hEJ^U5=De6v2$It&^w*)ZQXF87?zqxtwxbp^A$Zk>DvbqBI zh1X7WW!swT+Xq*TgIsLsmhUL50qI={MQ!DFDkFYl{$9szdQFPkION*ke<=Br%f%kI zzb+2FR8!@7)BBNi|8uEJR@zVU&(nJiLrL~U9+OtK%esoty;YuskY6b0J)lREo)eC{ zJl(e+|AJVND_Snx(Mir1_F3C}7Tef)GjZuuJS9fvvSa*uNakdbvwwY({hnR4!(_+O zkE-fOOLfhKpNGA;m1osFGB0Y0lRRmD>F(vsF`sV9Kh;))Q*7sz+dQ+rnjj4prMb}$ z+dwt>7GR992R^{{fjQ^&=6T}U-74Kw>dEG_owqaRt#5L_eN0?utrj0e+Mhn@l(ue7 z;9;>8Rz+$~_kqG)q{Ozq{p~$xoHEupw~hAG+ZDYof%Z2RwwTvVyb3aZ`F?B9g^ypJ zJ0Utyn;?#q<~AmOOrtcQ=SHz*C{;tuIsV7ouu>wC_-TZ9)19+jYc^I_zPR!82zZs) zZ8H!!eo%IQMbC|)*uu(yp$FvQP1(bDs!uV0Sfm*k{bY4fOWBH?eIpweF>9!g)630s z@>WI<^&h-dPP*dsFF~*-^MH_cM8hsgrX2~M9R6}3an~vI*|tC8v)05EHgyL)@;b&J zPxjldfJx~dnK_qT9<>K7&1PhsD0TQ>%9H0Ddu7v5cqLyad6sHioS9+gkRvbn@2bu% zgL|yI@BfzMxx_&bOyQ!e=AAta#tFc+!aTPS-Q4#3zc;s6(?`}Ni6vtnZ2NG*?$D?2 z4W5m&l5815wA}i#hQGJG-hVWXWkKj?1aSNW=f^yHN8{+4rjaOqmuw7xQ+D6G+!1*E$TV=Qu_%r;CZ z9DVV9lf(ScMI>VD#86STW2Ia9t|g66lB^jg_s;+A3UW_&sm~5*x|x5egTK(^9l!Ea qiQ#r$+>(aZK+DGSkp;b01MEuDv9?<8gF7t00aCvpQL%5t!T$q3?qw4I literal 0 HcmV?d00001 diff --git a/exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/64x64-normal.png b/exporter/SynthesisFusionAddin/src/Resources/SynthesisWebsite/64x64-normal.png new file mode 100644 index 0000000000000000000000000000000000000000..3076b744ca20f1537e6a0475c423a89d5e862b07 GIT binary patch literal 3335 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 S-@N`eVr%Jye_(#()c*iIn!beq literal 0 HcmV?d00001 From 314547fbbe1bd8280f6e519edffda71678ee4dbe Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Wed, 17 Jul 2024 13:53:43 -0700 Subject: [PATCH 007/344] open website with button on toolbar --- exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py | 1 + 1 file changed, 1 insertion(+) diff --git a/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py b/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py index 1a225a0845..77ebe018ab 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py @@ -1,5 +1,6 @@ import webbrowser import adsk.core +from ..general_imports import * class ShowWebsiteCommandExecuteHandler(adsk.core.CommandEventHandler): def __init__(self) -> None: super().__init__() From f4cfaaa591ab9b7bc1ba997f46b583cf2f4d17c9 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Wed, 17 Jul 2024 15:28:00 -0700 Subject: [PATCH 008/344] change url to learn page --- exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py b/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py index 77ebe018ab..14cb233c0c 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py @@ -6,7 +6,7 @@ def __init__(self) -> None: super().__init__() def notify(self, args): try: - url = "https://synthesis.autodesk.com/" + url = "https://synthesis.autodesk.com/tutorials.html" res = webbrowser.open(url, new=2) if not res: gm.ui.messageBox("Failed\n{}".format(traceback.format_exc())) From 0c2004257fe2a945c0bd78f6b4a0e3473b3dc2c9 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Wed, 17 Jul 2024 17:13:00 -0700 Subject: [PATCH 009/344] Initial take on a general options tab --- .../src/Parser/ExporterOptions.py | 4 +- exporter/SynthesisFusionAddin/src/TypesTmp.py | 15 + .../src/UI/ConfigCommand.py | 454 +++++++++--------- .../src/UI/GeneralConfigTab.py | 216 +++++++++ 4 files changed, 472 insertions(+), 217 deletions(-) create mode 100644 exporter/SynthesisFusionAddin/src/TypesTmp.py create mode 100644 exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py diff --git a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py index 569d29e158..603e71d382 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py +++ b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py @@ -14,6 +14,7 @@ from adsk.fusion import CalculationAccuracy, TriangleMeshQualityOptions from ..strings import INTERNAL_ID +from ..TypesTmp import KG # Not 100% sure what this is for - Brandon JointParentType = Enum("JointParentType", ["ROOT", "END"]) @@ -96,7 +97,8 @@ class ExporterOptions: preferredUnits: PreferredUnits = field(default=PreferredUnits.IMPERIAL) # Always stored in kg regardless of 'preferredUnits' - robotWeight: float = field(default=0.0) + robotWeight: KG = field(default=0.0) + autoCalcWeight: bool = field(default=False) compressOutput: bool = field(default=True) exportAsPart: bool = field(default=False) diff --git a/exporter/SynthesisFusionAddin/src/TypesTmp.py b/exporter/SynthesisFusionAddin/src/TypesTmp.py new file mode 100644 index 0000000000..7926bcd360 --- /dev/null +++ b/exporter/SynthesisFusionAddin/src/TypesTmp.py @@ -0,0 +1,15 @@ +# Pure typing hints. +class LBS(float): + """Mass Unit in Pounds.""" + + +class KG(float): + """Mass Unit in Kilograms.""" + + +def toLbs(kgs: float) -> LBS: + return LBS(kgs * 2.2062) + + +def toKg(pounds: float) -> KG: + return KG(pounds / 2.2062) diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index d8bd844f34..1fdb9f5b5b 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -24,6 +24,7 @@ from ..Parser.SynthesisParser.Utilities import guid_occurrence from . import CustomGraphics, FileDialogConfig, Helper, IconPaths from .Configuration.SerialCommand import SerialCommand +from .GeneralConfigTab import GeneralConfigTab # Transition: AARD-1685 # In the future all components should be handled in this way. @@ -32,6 +33,7 @@ # ====================================== CONFIG COMMAND ====================================== +generalConfigTab: GeneralConfigTab jointConfigTab: JointConfigTab """ @@ -91,41 +93,42 @@ class JointMotions(Enum): BALL = 6 -class FullMassCalculation: - def __init__(self): - self.totalMass = 0.0 - self.bRepMassInRoot() - self.traverseOccurrenceHierarchy() - - def bRepMassInRoot(self): - try: - for body in gm.app.activeDocument.design.rootComponent.bRepBodies: - if not body.isLightBulbOn: - continue - physical = body.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy) - self.totalMass += physical.mass - except: - if gm.ui: - gm.ui.messageBox("Failed:\n{}".format(traceback.format_exc())) - - def traverseOccurrenceHierarchy(self): - try: - for occ in gm.app.activeDocument.design.rootComponent.allOccurrences: - if not occ.isLightBulbOn: - continue - - for body in occ.component.bRepBodies: - if not body.isLightBulbOn: - continue - physical = body.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy) - self.totalMass += physical.mass - except: - pass - if gm.ui: - gm.ui.messageBox("Failed:\n{}".format(traceback.format_exc())) - - def getTotalMass(self): - return self.totalMass +# Transition: AARD-1683 +# class FullMassCalculation: +# def __init__(self): +# self.totalMass = 0.0 +# self.bRepMassInRoot() +# self.traverseOccurrenceHierarchy() + +# def bRepMassInRoot(self): +# try: +# for body in gm.app.activeDocument.design.rootComponent.bRepBodies: +# if not body.isLightBulbOn: +# continue +# physical = body.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy) +# self.totalMass += physical.mass +# except: +# if gm.ui: +# gm.ui.messageBox("Failed:\n{}".format(traceback.format_exc())) + +# def traverseOccurrenceHierarchy(self): +# try: +# for occ in gm.app.activeDocument.design.rootComponent.allOccurrences: +# if not occ.isLightBulbOn: +# continue + +# for body in occ.component.bRepBodies: +# if not body.isLightBulbOn: +# continue +# physical = body.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy) +# self.totalMass += physical.mass +# except: +# pass +# if gm.ui: +# gm.ui.messageBox("Failed:\n{}".format(traceback.format_exc())) + +# def getTotalMass(self): +# return self.totalMass class ConfigureCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): @@ -170,6 +173,11 @@ def notify(self, args): global INPUTS_ROOT # Global CommandInputs arg INPUTS_ROOT = cmd.commandInputs + # Transition: AARD-1683 + # Working on replacing all of the general tab stuff + global generalConfigTab + generalConfigTab = GeneralConfigTab(args, exporterOptions) + # ====================================== GENERAL TAB ====================================== """ Creates the general tab. @@ -184,22 +192,23 @@ def notify(self, args): """ cmd.helpFile = os.path.join(".", "src", "Resources", "HTML", "info.html") + # Transition: AARD-1683 # ~~~~~~~~~~~~~~~~ EXPORT MODE ~~~~~~~~~~~~~~~~ """ Dropdown to choose whether to export robot or field element """ - dropdownExportMode = inputs.addDropDownCommandInput( - "mode", - "Export Mode", - dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle, - ) + # dropdownExportMode = inputs.addDropDownCommandInput( + # "mode", + # "Export Mode", + # dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle, + # ) - dynamic = exporterOptions.exportMode == ExportMode.ROBOT - dropdownExportMode.listItems.add("Dynamic", dynamic) - dropdownExportMode.listItems.add("Static", not dynamic) + # dynamic = exporterOptions.exportMode == ExportMode.ROBOT + # dropdownExportMode.listItems.add("Dynamic", dynamic) + # dropdownExportMode.listItems.add("Static", not dynamic) - dropdownExportMode.tooltip = "Export Mode" - dropdownExportMode.tooltipDescription = "
Does this object move dynamically?" + # dropdownExportMode.tooltip = "Export Mode" + # dropdownExportMode.tooltipDescription = "
Does this object move dynamically?" # ~~~~~~~~~~~~~~~~ WEIGHT CONFIGURATION ~~~~~~~~~~~~~~~~ """ @@ -423,40 +432,42 @@ def notify(self, args): """ Creates the advanced tab, which is the parent container for internal command inputs """ + # Transition: AARD-1683 advancedSettings = INPUTS_ROOT.addTabCommandInput("advanced_settings", "Advanced") advancedSettings.tooltip = ( "Additional Advanced Settings to change how your model will be translated into Unity." ) a_input = advancedSettings.children + # Transition: AARD-1683 # ~~~~~~~~~~~~~~~~ EXPORTER SETTINGS ~~~~~~~~~~~~~~~~ """ Exporter settings group command """ - exporterSettings = a_input.addGroupCommandInput("exporter_settings", "Exporter Settings") - exporterSettings.isExpanded = True - exporterSettings.isEnabled = True - exporterSettings.tooltip = "tooltip" # TODO: update tooltip - exporter_settings = exporterSettings.children - - self.createBooleanInput( - "compress", - "Compress Output", - exporter_settings, - checked=exporterOptions.compressOutput, - tooltip="Compress the output file for a smaller file size.", - tooltipadvanced="
Use the GZIP compression system to compress the resulting file which will be opened in the simulator, perfect if you want to share the file.
", - enabled=True, - ) + # exporterSettings = a_input.addGroupCommandInput("exporter_settings", "Exporter Settings") + # exporterSettings.isExpanded = True + # exporterSettings.isEnabled = True + # exporterSettings.tooltip = "tooltip" # TODO: update tooltip + # exporter_settings = exporterSettings.children - self.createBooleanInput( - "export_as_part", - "Export As Part", - exporter_settings, - checked=exporterOptions.exportAsPart, - tooltip="Use to export as a part for Mix And Match", - enabled=True, - ) + # self.createBooleanInput( + # "compress", + # "Compress Output", + # exporter_settings, + # checked=exporterOptions.compressOutput, + # tooltip="Compress the output file for a smaller file size.", + # tooltipadvanced="
Use the GZIP compression system to compress the resulting file which will be opened in the simulator, perfect if you want to share the file.
", + # enabled=True, + # ) + + # self.createBooleanInput( + # "export_as_part", + # "Export As Part", + # exporter_settings, + # checked=exporterOptions.exportAsPart, + # tooltip="Use to export as a part for Mix And Match", + # enabled=True, + # ) # ~~~~~~~~~~~~~~~~ PHYSICS SETTINGS ~~~~~~~~~~~~~~~~ """ @@ -581,13 +592,16 @@ def notify(self, args): # enabled=True, # ) - getAuth() - user_info = getUserInfo() - apsSettings = INPUTS_ROOT.addTabCommandInput( - "aps_settings", f"APS Settings ({user_info.given_name if user_info else 'Not Signed In'})" - ) - apsSettings.tooltip = "Configuration settings for Autodesk Platform Services." - aps_input = apsSettings.children + # Transition: AARD-1683 + # Needed to comment this out because it throws if you don't login preventing anything from working + + # getAuth() + # user_info = getUserInfo() + # apsSettings = INPUTS_ROOT.addTabCommandInput( + # "aps_settings", f"APS Settings ({user_info.given_name if user_info else 'Not Signed In'})" + # ) + # apsSettings.tooltip = "Configuration settings for Autodesk Platform Services." + # aps_input = apsSettings.children # clear all selections before instantiating handlers. gm.ui.activeSelections.clear() @@ -814,26 +828,27 @@ def notify(self, args): self.log.error("Could not execute configuration due to failure") return - export_as_part_boolean = ( - eventArgs.command.commandInputs.itemById("advanced_settings") - .children.itemById("exporter_settings") - .children.itemById("export_as_part") - ).value - - processedFileName = gm.app.activeDocument.name.replace(" ", "_") - dropdownExportMode = INPUTS_ROOT.itemById("mode") - if dropdownExportMode.selectedItem.index == 0: - isRobot = True - elif dropdownExportMode.selectedItem.index == 1: - isRobot = False - - if isRobot: - savepath = FileDialogConfig.SaveFileDialog( - defaultPath=exporterOptions.fileLocation, - ext="Synthesis File (*.synth)", - ) - else: - savepath = FileDialogConfig.SaveFileDialog(defaultPath=exporterOptions.fileLocation) + # Transition: AARD-1683 + # export_as_part_boolean = ( + # eventArgs.command.commandInputs.itemById("advanced_settings") + # .children.itemById("exporter_settings") + # .children.itemById("export_as_part") + # ).value + + # processedFileName = gm.app.activeDocument.name.replace(" ", "_") + # dropdownExportMode = INPUTS_ROOT.itemById("mode") + # if dropdownExportMode.selectedItem.index == 0: + # isRobot = True + # elif dropdownExportMode.selectedItem.index == 1: + # isRobot = False + + # if isRobot: + # savepath = FileDialogConfig.SaveFileDialog( + # defaultPath=exporterOptions.fileLocation, + # ext="Synthesis File (*.synth)", + # ) + # else: + savepath = FileDialogConfig.SaveFileDialog(defaultPath=exporterOptions.fileLocation) if not savepath: # save was canceled @@ -850,61 +865,63 @@ def notify(self, args): version = design.rootComponent.name.rsplit(" ", 1)[1] _exportGamepieces = [] # TODO work on the code to populate Gamepiece - _robotWeight = float + _robotWeight = 0.0 _mode = ExportMode.ROBOT + # Transition: AARD-1683 + # This was never being used it appears, should be looped back to. """ Loops through all rows in the gamepiece table to extract the input values """ - gamepieceTableInput = gamepieceTable() - weight_unit_f = INPUTS_ROOT.itemById("weight_unit_f") - for row in range(gamepieceTableInput.rowCount): - if row == 0: - continue + # gamepieceTableInput = gamepieceTable() + # weight_unit_f = INPUTS_ROOT.itemById("weight_unit_f") + # for row in range(gamepieceTableInput.rowCount): + # if row == 0: + # continue - weightValue = gamepieceTableInput.getInputAtPosition(row, 2).value # weight/mass input, float + # weightValue = gamepieceTableInput.getInputAtPosition(row, 2).value # weight/mass input, float - if weight_unit_f.selectedItem.index == 0: - weightValue /= 2.2046226218 + # if weight_unit_f.selectedItem.index == 0: + # weightValue /= 2.2046226218 - frictionValue = gamepieceTableInput.getInputAtPosition(row, 3).valueOne # friction value, float + # frictionValue = gamepieceTableInput.getInputAtPosition(row, 3).valueOne # friction value, float - _exportGamepieces.append( - Gamepiece( - guid_occurrence(GamepieceListGlobal[row - 1]), - weightValue, - frictionValue, - ) - ) + # _exportGamepieces.append( + # Gamepiece( + # guid_occurrence(GamepieceListGlobal[row - 1]), + # weightValue, + # frictionValue, + # ) + # ) """ Robot Weight """ - weight_input = INPUTS_ROOT.itemById("weight_input") - weight_unit = INPUTS_ROOT.itemById("weight_unit") + # weight_input = INPUTS_ROOT.itemById("weight_input") + # weight_unit = INPUTS_ROOT.itemById("weight_unit") - if weight_unit.selectedItem.index == 0: - selectedUnits = PreferredUnits.IMPERIAL - _robotWeight = float(weight_input.value) / 2.2046226218 - else: - selectedUnits = PreferredUnits.METRIC - _robotWeight = float(weight_input.value) + # if weight_unit.selectedItem.index == 0: + # selectedUnits = PreferredUnits.IMPERIAL + # _robotWeight = float(weight_input.value) / 2.2046226218 + # else: + # selectedUnits = PreferredUnits.METRIC + # _robotWeight = float(weight_input.value) """ Export Mode """ - dropdownExportMode = INPUTS_ROOT.itemById("mode") - if dropdownExportMode.selectedItem.index == 0: - _mode = ExportMode.ROBOT - elif dropdownExportMode.selectedItem.index == 1: - _mode = ExportMode.FIELD - - global compress - compress = ( - eventArgs.command.commandInputs.itemById("advanced_settings") - .children.itemById("exporter_settings") - .children.itemById("compress") - ).value + # dropdownExportMode = INPUTS_ROOT.itemById("mode") + # if dropdownExportMode.selectedItem.index == 0: + # _mode = ExportMode.ROBOT + # elif dropdownExportMode.selectedItem.index == 1: + # _mode = ExportMode.FIELD + + # global compress + # compress = ( + # eventArgs.command.commandInputs.itemById("advanced_settings") + # .children.itemById("exporter_settings") + # .children.itemById("compress") + # ).value selectedJoints, selectedWheels = jointConfigTab.getSelectedJointsAndWheels() @@ -915,16 +932,17 @@ def notify(self, args): materials=0, joints=selectedJoints, wheels=selectedWheels, - gamepieces=_exportGamepieces, - preferredUnits=selectedUnits, - robotWeight=_robotWeight, - exportMode=_mode, - compressOutput=compress, - exportAsPart=export_as_part_boolean, + gamepieces=_exportGamepieces, # TODO + preferredUnits=generalConfigTab.selectedUnits, + robotWeight=generalConfigTab.robotWeight, + exportMode=generalConfigTab.exportMode, + compressOutput=generalConfigTab.compress, + exportAsPart=generalConfigTab.exportAsPart, ) - Parser(exporterOptions).export() + # TODO: Swap back exporterOptions.writeToDesign() + # Parser(exporterOptions).export() # All selections should be reset AFTER a successful export and save. # If we run into an exporting error we should return back to the panel with all current options @@ -1184,37 +1202,37 @@ def notify(self, args): preSelected = preSelectedOcc if preSelectedOcc else preSelectedJoint - dropdownExportMode = INPUTS_ROOT.itemById("mode") - if preSelected and design: - if dropdownExportMode.selectedItem.index == 0: # Dynamic - if preSelected.entityToken in onSelect.allWheelPreselections: - self.cmd.setCursor( - IconPaths.mouseIcons["remove"], - 0, - 0, - ) - else: - self.cmd.setCursor( - IconPaths.mouseIcons["add"], - 0, - 0, - ) - - elif dropdownExportMode.selectedItem.index == 1: # Static - if preSelected.entityToken in onSelect.allGamepiecePreselections: - self.cmd.setCursor( - IconPaths.mouseIcons["remove"], - 0, - 0, - ) - else: - self.cmd.setCursor( - IconPaths.mouseIcons["add"], - 0, - 0, - ) - else: # Should literally be impossible? - Brandon - self.cmd.setCursor("", 0, 0) + # dropdownExportMode = INPUTS_ROOT.itemById("mode") + # if preSelected and design: + # if dropdownExportMode.selectedItem.index == 0: # Dynamic + # if preSelected.entityToken in onSelect.allWheelPreselections: + # self.cmd.setCursor( + # IconPaths.mouseIcons["remove"], + # 0, + # 0, + # ) + # else: + # self.cmd.setCursor( + # IconPaths.mouseIcons["add"], + # 0, + # 0, + # ) + + # elif dropdownExportMode.selectedItem.index == 1: # Static + # if preSelected.entityToken in onSelect.allGamepiecePreselections: + # self.cmd.setCursor( + # IconPaths.mouseIcons["remove"], + # 0, + # 0, + # ) + # else: + # self.cmd.setCursor( + # IconPaths.mouseIcons["add"], + # 0, + # 0, + # ) + # else: # Should literally be impossible? - Brandon + # self.cmd.setCursor("", 0, 0) except: if gm.ui: gm.ui.messageBox("Failed:\n{}".format(traceback.format_exc())) @@ -1279,37 +1297,38 @@ def reset(self): "Failed:\n{}".format(traceback.format_exc()) ) - def weight(self, isLbs=True): # maybe add a progress dialog?? - """### Get the total design weight using the predetermined units. + # Transition: AARD-1683 + # def weight(self, isLbs=True): # maybe add a progress dialog?? + # """### Get the total design weight using the predetermined units. - Args: - isLbs (bool, optional): Is selected mass unit pounds? Defaults to True. + # Args: + # isLbs (bool, optional): Is selected mass unit pounds? Defaults to True. - Returns: - value (float): weight value in specified unit - """ - try: - if gm.app.activeDocument.design: - massCalculation = FullMassCalculation() - totalMass = massCalculation.getTotalMass() + # Returns: + # value (float): weight value in specified unit + # """ + # try: + # if gm.app.activeDocument.design: + # massCalculation = FullMassCalculation() + # totalMass = massCalculation.getTotalMass() - value = float + # value = float - self.allWeights[0] = round(totalMass * 2.2046226218, 2) + # self.allWeights[0] = round(totalMass * 2.2046226218, 2) - self.allWeights[1] = round(totalMass, 2) + # self.allWeights[1] = round(totalMass, 2) - if isLbs: - value = self.allWeights[0] - else: - value = self.allWeights[1] + # if isLbs: + # value = self.allWeights[0] + # else: + # value = self.allWeights[1] - value = round(value, 2) # round weight to 2 decimals places - return value - except: - logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.{self.__class__.__name__}.weight()").error( - "Failed:\n{}".format(traceback.format_exc()) - ) + # value = round(value, 2) # round weight to 2 decimals places + # return value + # except: + # logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.{self.__class__.__name__}.weight()").error( + # "Failed:\n{}".format(traceback.format_exc()) + # ) def notify(self, args): try: @@ -1319,6 +1338,7 @@ def notify(self, args): # Transition: AARD-1685 # Should be how all input changed handles are done in the future jointConfigTab.handleInputChanged(args, INPUTS_ROOT) + generalConfigTab.handleInputChanged(args) MySelectHandler.lastInputCmd = cmdInput inputs = cmdInput.commandInputs @@ -1443,28 +1463,30 @@ def notify(self, args): weightInput = gamepieceTableInput.getInputAtPosition(row, 2) weightInput.tooltipDescription = "(in kilograms)" - elif cmdInput.id == "auto_calc_weight": - button = adsk.core.BoolValueCommandInput.cast(cmdInput) - - if button.value == True: # CALCULATE button pressed - if self.allWeights.count(None) == 2: # if button is pressed for the first time - if self.isLbs: # if pounds unit selected - self.allWeights[0] = self.weight() - weight_input.value = self.allWeights[0] - else: # if kg unit selected - self.allWeights[1] = self.weight(False) - weight_input.value = self.allWeights[1] - else: # if a mass value has already been configured - if ( - weight_input.value != self.allWeights[0] - or weight_input.value != self.allWeights[1] - or not weight_input.isValidExpression - ): - if self.isLbs: - weight_input.value = self.allWeights[0] - else: - weight_input.value = self.allWeights[1] - + # Transition: AARD-1683 + # elif cmdInput.id == "auto_calc_weight": + # button = adsk.core.BoolValueCommandInput.cast(cmdInput) + + # if button.value == True: # CALCULATE button pressed + # if self.allWeights.count(None) == 2: # if button is pressed for the first time + # if self.isLbs: # if pounds unit selected + # self.allWeights[0] = self.weight() + # weight_input.value = self.allWeights[0] + # else: # if kg unit selected + # self.allWeights[1] = self.weight(False) + # weight_input.value = self.allWeights[1] + # else: # if a mass value has already been configured + # if ( + # weight_input.value != self.allWeights[0] + # or weight_input.value != self.allWeights[1] + # or not weight_input.isValidExpression + # ): + # if self.isLbs: + # weight_input.value = self.allWeights[0] + # else: + # weight_input.value = self.allWeights[1] + + # TODO: Figure out gamepiece stuff elif cmdInput.id == "auto_calc_weight_f": button = adsk.core.BoolValueCommandInput.cast(cmdInput) diff --git a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py new file mode 100644 index 0000000000..fe60a3b939 --- /dev/null +++ b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py @@ -0,0 +1,216 @@ +import logging +import traceback + +import adsk.core +import adsk.fusion + +from ..general_imports import INTERNAL_ID +from ..Parser.ExporterOptions import ExporterOptions, ExportMode, PreferredUnits +from ..TypesTmp import KG, toKg, toLbs +from . import IconPaths +from .CreateCommandInputsHelper import createBooleanInput, createTableInput + + +class GeneralConfigTab: + generalOptionsTab: adsk.core.TabCommandInput + previousAutoCalcWeightCheckboxState: bool + previousSelectedUnitDropdownIndex: int + + def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: ExporterOptions) -> None: + try: + inputs = args.command.commandInputs + self.generalOptionsTab = inputs.addTabCommandInput("generalSettings", "General Settings") + self.generalOptionsTab.tooltip = "General configuration options for your robot export." + generalTabInputs = self.generalOptionsTab.children + + dropdownExportMode = generalTabInputs.addDropDownCommandInput( + "exportModeDropdown", + "Export Mode", + dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle, + ) + + dynamic = exporterOptions.exportMode == ExportMode.ROBOT + dropdownExportMode.listItems.add("Dynamic", dynamic) + dropdownExportMode.listItems.add("Static", not dynamic) + dropdownExportMode.tooltip = "Export Mode" + dropdownExportMode.tooltipDescription = "
Does this object move dynamically?" + + 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 + + createBooleanInput( + "autoCalcWeightButton", + "Auto Calculate Robot Weight", + generalTabInputs, + checked=exporterOptions.autoCalcWeight, + tooltip="Approximate the weight of your robot assembly.", + enabled=True, + ) + self.previousAutoCalcWeightCheckboxState = exporterOptions.autoCalcWeight + + self.currentUnits = exporterOptions.preferredUnits + imperialUnits = self.currentUnits == PreferredUnits.IMPERIAL + if imperialUnits: + # ExporterOptions always contains the metric value + displayWeight = exporterOptions.robotWeight * 2.2046226218 + else: + displayWeight = exporterOptions.robotWeight + + weightInput = generalTabInputs.addValueInput( + "weightInput", + "Weight Input", + "", + adsk.core.ValueInput.createByReal(displayWeight), + ) + weightInput.tooltip = "Robot weight" + weightInput.tooltipDescription = ( + """(in pounds)
This is the weight of the entire robot assembly.""" + ) + + weightUnitDropdown = generalTabInputs.addDropDownCommandInput( + "weightUnitDropdown", + "Weight Unit", + adsk.core.DropDownStyles.LabeledIconDropDownStyle, + ) + + 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", + "Compress Output", + generalTabInputs, + checked=exporterOptions.compressOutput, + tooltip="Compress the output file for a smaller file size.", + tooltipadvanced="
Use the GZIP compression system to compress the resulting file, " + "perfect if you want to share your robot design around.
", + enabled=True, + ) + + createBooleanInput( + "exportAsPartButton", + "Export As Part", + generalTabInputs, + checked=exporterOptions.exportAsPart, + tooltip="Use to export as a part for Mix And Match", + enabled=True, + ) + + except BaseException: + logging.getLogger("{INTERNAL_ID}.UI.GeneralConfigTab").error("Failed:\n{}".format(traceback.format_exc())) + + @property + def exportMode(self) -> ExportMode: + exportModeDropdown: adsk.core.DropDownCommandInput = self.generalOptionsTab.children.itemById( + "exportModeDropdown" + ) + if exportModeDropdown.selectedItem.index == 0: + return ExportMode.ROBOT + else: + assert exportModeDropdown.selectedItem.index == 1 + return ExportMode.FIELD + + @property + def compress(self) -> bool: + compressButton: adsk.core.BoolValueCommandInput = self.generalOptionsTab.children.itemById( + "compressOutputButton" + ) + return compressButton.value + + @property + def exportAsPart(self) -> bool: + exportAsPartButton: adsk.core.BoolValueCommandInput = self.generalOptionsTab.children.itemById( + "exportAsPartButton" + ) + return exportAsPartButton.value + + @property + def selectedUnits(self) -> PreferredUnits: + weightUnitDropdown: adsk.core.DropDownCommandInput = self.generalOptionsTab.children.itemById( + "weightTable" + ).getInputAtPosition(0, 2) + if weightUnitDropdown.selectedItem.index == 0: + return PreferredUnits.IMPERIAL + else: + assert weightUnitDropdown.selectedItem.index == 1 + return PreferredUnits.METRIC + + @property + def robotWeight(self) -> KG: + weightInput: adsk.core.ValueCommandInput = self.generalOptionsTab.children.itemById("weightTable").getInputAtPosition(0, 1) + return KG(weightInput.value) + + def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: + try: + commandInput = args.input + if commandInput.id == "weightUnitDropdown": + weightUnitDropdown = adsk.core.DropDownCommandInput.cast(commandInput) + weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") + weightInput: adsk.core.ValueCommandInput = weightTable.getInputAtPosition(0, 1) + if weightUnitDropdown.selectedItem.index == self.previousSelectedUnitDropdownIndex: + return + + if weightUnitDropdown.selectedItem.index == 0: + self.currentUnits = PreferredUnits.IMPERIAL + weightInput.value = toLbs(weightInput.value) + else: + assert weightUnitDropdown.selectedItem.index == 1 + self.currentUnits = PreferredUnits.METRIC + weightInput.value = toKg(weightInput.value) + + self.previousSelectedUnitDropdownIndex = weightUnitDropdown.selectedItem.index + + elif commandInput.id == "autoCalcWeightButton": + autoCalcWeightButton = adsk.core.BoolValueCommandInput.cast(commandInput) + if autoCalcWeightButton.value == self.previousAutoCalcWeightCheckboxState: + return + + weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") + weightInput: adsk.core.ValueCommandInput = weightTable.getInputAtPosition(0, 1) + + if autoCalcWeightButton.value: + robotMass = designMassCalculation() + weightInput.value = robotMass if self.currentUnits is PreferredUnits.METRIC else toLbs(robotMass) + weightInput.isEnabled = False + else: + weightInput.isEnabled = True + + self.previousAutoCalcWeightCheckboxState = autoCalcWeightButton.value + + except BaseException: + logging.getLogger(f"{INTERNAL_ID}.UI.GeneralConfigTab").error("Failed:\n{}".format(traceback.format_exc())) + + +# TODO: GH-1010 for failure logging with message box +# TODO: Perhaps move this into a different module +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 KG(mass) From cdcdf60e02b7616b4718738b8a99df36f6dfa52f Mon Sep 17 00:00:00 2001 From: arorad1 Date: Wed, 17 Jul 2024 19:53:24 -0700 Subject: [PATCH 010/344] Basic UI for ConfiguringRobotBrainPanel --- fission/src/Synthesis.tsx | 2 + fission/src/systems/simulation/Brain.ts | 4 +- fission/src/ui/components/MainHUD.tsx | 1 + .../configuring/ConfigureRobotBrainPanel.tsx | 86 +++++++++++++++++++ 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 57025c7b95..5a2dcd44c8 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -59,6 +59,7 @@ import Skybox from "./ui/components/Skybox.tsx" import ConfigureRobotModal from "./ui/modals/configuring/ConfigureRobotModal.tsx" import ResetAllInputsModal from "./ui/modals/configuring/ResetAllInputsModal.tsx" import ZoneConfigPanel from "./ui/panels/configuring/scoring/ZoneConfigPanel.tsx" +import ConfigureRobotBrainPanel from "./ui/panels/configuring/ConfigureRobotBrainPanel.tsx" const DEFAULT_MIRA_PATH = "/api/mira/Robots/Team 2471 (2018)_v7.mira" @@ -243,6 +244,7 @@ const initialPanels: ReactElement[] = [ , , , + , ] export default Synthesis diff --git a/fission/src/systems/simulation/Brain.ts b/fission/src/systems/simulation/Brain.ts index a047d65671..d784195e8e 100644 --- a/fission/src/systems/simulation/Brain.ts +++ b/fission/src/systems/simulation/Brain.ts @@ -3,8 +3,8 @@ import Mechanism from "../physics/Mechanism" abstract class Brain { protected _mechanism: Mechanism - constructor(mechansim: Mechanism) { - this._mechanism = mechansim + constructor(mechanism: Mechanism) { + this._mechanism = mechanism } public abstract Update(deltaT: number): void diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 717df814de..457ee80282 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -189,6 +189,7 @@ const MainHUD: React.FC = () => { }} /> } onClick={() => openModal("config-robot")} /> + } onClick={() => openPanel("config-robot-brain")} /> {userInfo ? ( = ({ 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 ? ( + + ) : ( + + )} +
+ + )} +
+ ); +} + +export default ConfigureRobotBrainPanel; \ No newline at end of file From 08eeed56f848bc3ea04f3c5cf2bb9b837e4f5754 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Thu, 18 Jul 2024 08:43:54 -0700 Subject: [PATCH 011/344] Auto calc weight saving integration --- .../src/UI/ConfigCommand.py | 706 +++++++++--------- .../src/UI/GeneralConfigTab.py | 18 +- 2 files changed, 369 insertions(+), 355 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index 1fdb9f5b5b..28ce7dd163 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -183,7 +183,7 @@ def notify(self, args): Creates the general tab. - Parent container for all the command inputs in the tab. """ - inputs = INPUTS_ROOT.addTabCommandInput("general_settings", "General").children + # inputs = INPUTS_ROOT.addTabCommandInput("general_settings", "General").children # ~~~~~~~~~~~~~~~~ HELP FILE ~~~~~~~~~~~~~~~~ """ @@ -215,66 +215,66 @@ def notify(self, args): Table for weight config. - Used this to align multiple commandInputs on the same row """ - weightTableInput = self.createTableInput( - "weight_table", - "Weight Table", - inputs, - 4, - "3:2:2:1", - 1, - ) - weightTableInput.tablePresentationStyle = 2 # set transparent background for table - - weight_name = inputs.addStringValueInput("weight_name", "Weight") - weight_name.value = "Weight" - weight_name.isReadOnly = True - - auto_calc_weight = self.createBooleanInput( - "auto_calc_weight", - "‎", - inputs, - checked=False, - tooltip="Approximate the weight of your robot assembly.", - tooltipadvanced="This may take a moment...", - enabled=True, - isCheckBox=False, - ) - auto_calc_weight.resourceFolder = IconPaths.stringIcons["calculate-enabled"] - auto_calc_weight.isFullWidth = True + # weightTableInput = self.createTableInput( + # "weight_table", + # "Weight Table", + # inputs, + # 4, + # "3:2:2:1", + # 1, + # ) + # weightTableInput.tablePresentationStyle = 2 # set transparent background for table - imperialUnits = exporterOptions.preferredUnits == PreferredUnits.IMPERIAL - if imperialUnits: - # ExporterOptions always contains the metric value - displayWeight = exporterOptions.robotWeight * 2.2046226218 - else: - displayWeight = exporterOptions.robotWeight + # weight_name = inputs.addStringValueInput("weight_name", "Weight") + # weight_name.value = "Weight" + # weight_name.isReadOnly = True - weight_input = inputs.addValueInput( - "weight_input", - "Weight Input", - "", - adsk.core.ValueInput.createByReal(displayWeight), - ) - weight_input.tooltip = "Robot weight" - weight_input.tooltipDescription = ( - """(in pounds)
This is the weight of the entire robot assembly.""" - ) + # auto_calc_weight = self.createBooleanInput( + # "auto_calc_weight", + # "‎", + # inputs, + # checked=False, + # tooltip="Approximate the weight of your robot assembly.", + # tooltipadvanced="This may take a moment...", + # enabled=True, + # isCheckBox=False, + # ) + # auto_calc_weight.resourceFolder = IconPaths.stringIcons["calculate-enabled"] + # auto_calc_weight.isFullWidth = True - weight_unit = inputs.addDropDownCommandInput( - "weight_unit", - "Weight Unit", - adsk.core.DropDownStyles.LabeledIconDropDownStyle, - ) + # imperialUnits = exporterOptions.preferredUnits == PreferredUnits.IMPERIAL + # if imperialUnits: + # # ExporterOptions always contains the metric value + # displayWeight = exporterOptions.robotWeight * 2.2046226218 + # else: + # displayWeight = exporterOptions.robotWeight + + # weight_input = inputs.addValueInput( + # "weight_input", + # "Weight Input", + # "", + # adsk.core.ValueInput.createByReal(displayWeight), + # ) + # weight_input.tooltip = "Robot weight" + # weight_input.tooltipDescription = ( + # """(in pounds)
This is the weight of the entire robot assembly.""" + # ) + + # weight_unit = inputs.addDropDownCommandInput( + # "weight_unit", + # "Weight Unit", + # adsk.core.DropDownStyles.LabeledIconDropDownStyle, + # ) - weight_unit.listItems.add("‎", imperialUnits, IconPaths.massIcons["LBS"]) - weight_unit.listItems.add("‎", not imperialUnits, IconPaths.massIcons["KG"]) - weight_unit.tooltip = "Unit of mass" - weight_unit.tooltipDescription = "
Configure the unit of mass for the weight calculation." + # weight_unit.listItems.add("‎", imperialUnits, IconPaths.massIcons["LBS"]) + # weight_unit.listItems.add("‎", not imperialUnits, IconPaths.massIcons["KG"]) + # weight_unit.tooltip = "Unit of mass" + # weight_unit.tooltipDescription = "
Configure the unit of mass for the weight calculation." - weightTableInput.addCommandInput(weight_name, 0, 0) # add command inputs to table - weightTableInput.addCommandInput(auto_calc_weight, 0, 1) # add command inputs to table - weightTableInput.addCommandInput(weight_input, 0, 2) # add command inputs to table - weightTableInput.addCommandInput(weight_unit, 0, 3) # add command inputs to table + # weightTableInput.addCommandInput(weight_name, 0, 0) # add command inputs to table + # weightTableInput.addCommandInput(auto_calc_weight, 0, 1) # add command inputs to table + # weightTableInput.addCommandInput(weight_input, 0, 2) # add command inputs to table + # weightTableInput.addCommandInput(weight_unit, 0, 3) # add command inputs to table global jointConfigTab jointConfigTab = JointConfigTab(args) @@ -310,134 +310,134 @@ def notify(self, args): Gamepiece group command input, isVisible=False by default - Container for gamepiece selection table """ - gamepieceConfig = inputs.addGroupCommandInput("gamepiece_config", "Gamepiece Configuration") - gamepieceConfig.isExpanded = True - gamepieceConfig.isVisible = False - gamepieceConfig.tooltip = "Select and define the gamepieces in your field." - gamepiece_inputs = gamepieceConfig.children - - # GAMEPIECE MASS CONFIGURATION - """ - Mass unit dropdown and calculation for gamepiece elements - """ - weightTableInput_f = self.createTableInput( - "weight_table_f", "Weight Table", gamepiece_inputs, 3, "6:2:1", 1 - ) - weightTableInput_f.tablePresentationStyle = 2 # set to clear background - - weight_name_f = gamepiece_inputs.addStringValueInput("weight_name", "Weight") - weight_name_f.value = "Unit of Mass" - weight_name_f.isReadOnly = True - - auto_calc_weight_f = self.createBooleanInput( # CALCULATE button - "auto_calc_weight_f", - "‎", - gamepiece_inputs, - checked=False, - tooltip="Approximate the weight of all your selected gamepieces.", - enabled=True, - isCheckBox=False, - ) - auto_calc_weight_f.resourceFolder = IconPaths.stringIcons["calculate-enabled"] - auto_calc_weight_f.isFullWidth = True - - weight_unit_f = gamepiece_inputs.addDropDownCommandInput( - "weight_unit_f", - "Unit of Mass", - adsk.core.DropDownStyles.LabeledIconDropDownStyle, - ) - weight_unit_f.listItems.add("‎", True, IconPaths.massIcons["LBS"]) # add listdropdown mass options - weight_unit_f.listItems.add("‎", False, IconPaths.massIcons["KG"]) # add listdropdown mass options - weight_unit_f.tooltip = "Unit of mass" - weight_unit_f.tooltipDescription = "
Configure the unit of mass for for the weight calculation." + # gamepieceConfig = inputs.addGroupCommandInput("gamepiece_config", "Gamepiece Configuration") + # gamepieceConfig.isExpanded = True + # gamepieceConfig.isVisible = False + # gamepieceConfig.tooltip = "Select and define the gamepieces in your field." + # gamepiece_inputs = gamepieceConfig.children + + # # GAMEPIECE MASS CONFIGURATION + # """ + # Mass unit dropdown and calculation for gamepiece elements + # """ + # weightTableInput_f = self.createTableInput( + # "weight_table_f", "Weight Table", gamepiece_inputs, 3, "6:2:1", 1 + # ) + # weightTableInput_f.tablePresentationStyle = 2 # set to clear background - weightTableInput_f.addCommandInput(weight_name_f, 0, 0) # add command inputs to table - weightTableInput_f.addCommandInput(auto_calc_weight_f, 0, 1) # add command inputs to table - weightTableInput_f.addCommandInput(weight_unit_f, 0, 2) # add command inputs to table + # weight_name_f = gamepiece_inputs.addStringValueInput("weight_name", "Weight") + # weight_name_f.value = "Unit of Mass" + # weight_name_f.isReadOnly = True - # GAMEPIECE SELECTION TABLE - """ - All selected gamepieces appear here - """ - gamepieceTableInput = self.createTableInput( - "gamepiece_table", - "Gamepiece", - gamepiece_inputs, - 4, - "1:8:5:12", - 50, - ) + # auto_calc_weight_f = self.createBooleanInput( # CALCULATE button + # "auto_calc_weight_f", + # "‎", + # gamepiece_inputs, + # checked=False, + # tooltip="Approximate the weight of all your selected gamepieces.", + # enabled=True, + # isCheckBox=False, + # ) + # auto_calc_weight_f.resourceFolder = IconPaths.stringIcons["calculate-enabled"] + # auto_calc_weight_f.isFullWidth = True - addFieldInput = gamepiece_inputs.addBoolValueInput("field_add", "Add", False) + # weight_unit_f = gamepiece_inputs.addDropDownCommandInput( + # "weight_unit_f", + # "Unit of Mass", + # adsk.core.DropDownStyles.LabeledIconDropDownStyle, + # ) + # weight_unit_f.listItems.add("‎", True, IconPaths.massIcons["LBS"]) # add listdropdown mass options + # weight_unit_f.listItems.add("‎", False, IconPaths.massIcons["KG"]) # add listdropdown mass options + # weight_unit_f.tooltip = "Unit of mass" + # weight_unit_f.tooltipDescription = "
Configure the unit of mass for for the weight calculation." + + # weightTableInput_f.addCommandInput(weight_name_f, 0, 0) # add command inputs to table + # weightTableInput_f.addCommandInput(auto_calc_weight_f, 0, 1) # add command inputs to table + # weightTableInput_f.addCommandInput(weight_unit_f, 0, 2) # add command inputs to table + + # # GAMEPIECE SELECTION TABLE + # """ + # All selected gamepieces appear here + # """ + # gamepieceTableInput = self.createTableInput( + # "gamepiece_table", + # "Gamepiece", + # gamepiece_inputs, + # 4, + # "1:8:5:12", + # 50, + # ) - removeFieldInput = gamepiece_inputs.addBoolValueInput("field_delete", "Remove", False) - addFieldInput.isEnabled = removeFieldInput.isEnabled = True + # addFieldInput = gamepiece_inputs.addBoolValueInput("field_add", "Add", False) - removeFieldInput.tooltip = "Remove a field element" - addFieldInput.tooltip = "Add a field element" + # removeFieldInput = gamepiece_inputs.addBoolValueInput("field_delete", "Remove", False) + # addFieldInput.isEnabled = removeFieldInput.isEnabled = True - gamepieceSelectInput = gamepiece_inputs.addSelectionInput( - "gamepiece_select", - "Selection", - "Select the unique gamepieces in your field.", - ) - gamepieceSelectInput.addSelectionFilter("Occurrences") - gamepieceSelectInput.setSelectionLimits(0) - gamepieceSelectInput.isEnabled = True - gamepieceSelectInput.isVisible = False + # removeFieldInput.tooltip = "Remove a field element" + # addFieldInput.tooltip = "Add a field element" - gamepieceTableInput.addToolbarCommandInput(addFieldInput) - gamepieceTableInput.addToolbarCommandInput(removeFieldInput) - - """ - Gamepiece table column headers. (the permanent captions in the first row of table) - """ - gamepieceTableInput.addCommandInput( - self.createTextBoxInput( - "e_header", - "Gamepiece name", - gamepiece_inputs, - "Gamepiece", - bold=False, - ), - 0, - 1, - ) + # gamepieceSelectInput = gamepiece_inputs.addSelectionInput( + # "gamepiece_select", + # "Selection", + # "Select the unique gamepieces in your field.", + # ) + # gamepieceSelectInput.addSelectionFilter("Occurrences") + # gamepieceSelectInput.setSelectionLimits(0) + # gamepieceSelectInput.isEnabled = True + # gamepieceSelectInput.isVisible = False + + # gamepieceTableInput.addToolbarCommandInput(addFieldInput) + # gamepieceTableInput.addToolbarCommandInput(removeFieldInput) + + # """ + # Gamepiece table column headers. (the permanent captions in the first row of table) + # """ + # gamepieceTableInput.addCommandInput( + # self.createTextBoxInput( + # "e_header", + # "Gamepiece name", + # gamepiece_inputs, + # "Gamepiece", + # bold=False, + # ), + # 0, + # 1, + # ) - gamepieceTableInput.addCommandInput( - self.createTextBoxInput( - "w_header", - "Gamepiece weight", - gamepiece_inputs, - "Weight", - background="#d9d9d9", - ), - 0, - 2, - ) + # gamepieceTableInput.addCommandInput( + # self.createTextBoxInput( + # "w_header", + # "Gamepiece weight", + # gamepiece_inputs, + # "Weight", + # background="#d9d9d9", + # ), + # 0, + # 2, + # ) - gamepieceTableInput.addCommandInput( - self.createTextBoxInput( - "f_header", - "Friction coefficient", - gamepiece_inputs, - "Friction coefficient", - background="#d9d9d9", - ), - 0, - 3, - ) + # gamepieceTableInput.addCommandInput( + # self.createTextBoxInput( + # "f_header", + # "Friction coefficient", + # gamepiece_inputs, + # "Friction coefficient", + # background="#d9d9d9", + # ), + # 0, + # 3, + # ) # ====================================== ADVANCED TAB ====================================== """ Creates the advanced tab, which is the parent container for internal command inputs """ # Transition: AARD-1683 - advancedSettings = INPUTS_ROOT.addTabCommandInput("advanced_settings", "Advanced") - advancedSettings.tooltip = ( - "Additional Advanced Settings to change how your model will be translated into Unity." - ) - a_input = advancedSettings.children + # advancedSettings = INPUTS_ROOT.addTabCommandInput("advanced_settings", "Advanced") + # advancedSettings.tooltip = ( + # "Additional Advanced Settings to change how your model will be translated into Unity." + # ) + # a_input = advancedSettings.children # Transition: AARD-1683 # ~~~~~~~~~~~~~~~~ EXPORTER SETTINGS ~~~~~~~~~~~~~~~~ @@ -473,55 +473,55 @@ def notify(self, args): """ Physics settings group command """ - physicsSettings = a_input.addGroupCommandInput("physics_settings", "Physics Settings") - - physicsSettings.isExpanded = False - physicsSettings.isEnabled = True - physicsSettings.tooltip = "tooltip" # TODO: update tooltip - physics_settings = physicsSettings.children - - # AARD-1687 - # Should also be commented out / removed? - # This would cause problems elsewhere but I can't tell i f - # this is even being used. - frictionOverrideTable = self.createTableInput( - "friction_override_table", - "", - physics_settings, - 2, - "1:2", - 1, - columnSpacing=25, - ) - frictionOverrideTable.tablePresentationStyle = 2 - # frictionOverrideTable.isFullWidth = True - - frictionOverride = self.createBooleanInput( - "friction_override", - "", - physics_settings, - checked=False, - tooltip="Manually override the default friction values on the bodies in the assembly.", - enabled=True, - isCheckBox=False, - ) - frictionOverride.resourceFolder = IconPaths.stringIcons["friction_override-enabled"] - frictionOverride.isFullWidth = True + # physicsSettings = a_input.addGroupCommandInput("physics_settings", "Physics Settings") + + # physicsSettings.isExpanded = False + # physicsSettings.isEnabled = True + # physicsSettings.tooltip = "tooltip" # TODO: update tooltip + # physics_settings = physicsSettings.children + + # # AARD-1687 + # # Should also be commented out / removed? + # # This would cause problems elsewhere but I can't tell i f + # # this is even being used. + # frictionOverrideTable = self.createTableInput( + # "friction_override_table", + # "", + # physics_settings, + # 2, + # "1:2", + # 1, + # columnSpacing=25, + # ) + # frictionOverrideTable.tablePresentationStyle = 2 + # # frictionOverrideTable.isFullWidth = True + + # frictionOverride = self.createBooleanInput( + # "friction_override", + # "", + # physics_settings, + # checked=False, + # tooltip="Manually override the default friction values on the bodies in the assembly.", + # enabled=True, + # isCheckBox=False, + # ) + # frictionOverride.resourceFolder = IconPaths.stringIcons["friction_override-enabled"] + # frictionOverride.isFullWidth = True - valueList = [1] - for i in range(20): - valueList.append(i / 20) + # valueList = [1] + # for i in range(20): + # valueList.append(i / 20) - frictionCoeff = physics_settings.addFloatSliderListCommandInput( - "friction_coeff_override", "Friction Coefficient", "", valueList - ) - frictionCoeff.isVisible = False - frictionCoeff.valueOne = 0.5 - frictionCoeff.tooltip = "Friction coefficient of field element." - frictionCoeff.tooltipDescription = "Friction coefficients range from 0 (ice) to 1 (rubber)." + # frictionCoeff = physics_settings.addFloatSliderListCommandInput( + # "friction_coeff_override", "Friction Coefficient", "", valueList + # ) + # frictionCoeff.isVisible = False + # frictionCoeff.valueOne = 0.5 + # frictionCoeff.tooltip = "Friction coefficient of field element." + # frictionCoeff.tooltipDescription = "Friction coefficients range from 0 (ice) to 1 (rubber)." - frictionOverrideTable.addCommandInput(frictionOverride, 0, 0) - frictionOverrideTable.addCommandInput(frictionCoeff, 0, 1) + # frictionOverrideTable.addCommandInput(frictionOverride, 0, 0) + # frictionOverrideTable.addCommandInput(frictionCoeff, 0, 1) # ~~~~~~~~~~~~~~~~ JOINT SETTINGS ~~~~~~~~~~~~~~~~ """ @@ -646,153 +646,153 @@ def notify(self, args): "Failed:\n{}".format(traceback.format_exc()) ) - # Transition: AARD-1685 - # Functionality will be fully moved to `CreateCommandInputsHelper` in AARD-1683 - def createBooleanInput( - self, - _id: str, - name: str, - inputs: adsk.core.CommandInputs, - tooltip="", - tooltipadvanced="", - checked=True, - enabled=True, - isCheckBox=True, - ) -> adsk.core.BoolValueCommandInput: - """### Simple helper to generate all of the options for me to create a boolean command input + # # Transition: AARD-1685 + # # Functionality will be fully moved to `CreateCommandInputsHelper` in AARD-1683 + # def createBooleanInput( + # self, + # _id: str, + # name: str, + # inputs: adsk.core.CommandInputs, + # tooltip="", + # tooltipadvanced="", + # checked=True, + # enabled=True, + # isCheckBox=True, + # ) -> adsk.core.BoolValueCommandInput: + # """### Simple helper to generate all of the options for me to create a boolean command input - Args: - _id (str): id value of the object - pretty much lowercase name - name (str): name as displayed by the command prompt - inputs (adsk.core.CommandInputs): parent command input container - tooltip (str, optional): Description on hover of the checkbox. Defaults to "". - tooltipadvanced (str, optional): Long hover description. Defaults to "". - checked (bool, optional): Is checked by default?. Defaults to True. + # Args: + # _id (str): id value of the object - pretty much lowercase name + # name (str): name as displayed by the command prompt + # inputs (adsk.core.CommandInputs): parent command input container + # tooltip (str, optional): Description on hover of the checkbox. Defaults to "". + # tooltipadvanced (str, optional): Long hover description. Defaults to "". + # checked (bool, optional): Is checked by default?. Defaults to True. - Returns: - adsk.core.BoolValueCommandInput: Recently created command input - """ - try: - _input = inputs.addBoolValueInput(_id, name, isCheckBox) - _input.value = checked - _input.isEnabled = enabled - _input.tooltip = tooltip - _input.tooltipDescription = tooltipadvanced - return _input - except: - logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.{self.__class__.__name__}.createBooleanInput()").error( - "Failed:\n{}".format(traceback.format_exc()) - ) + # Returns: + # adsk.core.BoolValueCommandInput: Recently created command input + # """ + # try: + # _input = inputs.addBoolValueInput(_id, name, isCheckBox) + # _input.value = checked + # _input.isEnabled = enabled + # _input.tooltip = tooltip + # _input.tooltipDescription = tooltipadvanced + # return _input + # except: + # logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.{self.__class__.__name__}.createBooleanInput()").error( + # "Failed:\n{}".format(traceback.format_exc()) + # ) - # Transition: AARD-1685 - # Functionality will be fully moved to `CreateCommandInputsHelper` in AARD-1683 - def createTableInput( - self, - _id: str, - name: str, - inputs: adsk.core.CommandInputs, - columns: int, - ratio: str, - maxRows: int, - minRows=1, - columnSpacing=0, - rowSpacing=0, - ) -> adsk.core.TableCommandInput: - """### Simple helper to generate all the TableCommandInput options. + # # Transition: AARD-1685 + # # Functionality will be fully moved to `CreateCommandInputsHelper` in AARD-1683 + # def createTableInput( + # self, + # _id: str, + # name: str, + # inputs: adsk.core.CommandInputs, + # columns: int, + # ratio: str, + # maxRows: int, + # minRows=1, + # columnSpacing=0, + # rowSpacing=0, + # ) -> adsk.core.TableCommandInput: + # """### Simple helper to generate all the TableCommandInput options. - Args: - _id (str): unique ID of command - name (str): displayed name - inputs (adsk.core.CommandInputs): parent command input container - columns (int): column count - ratio (str): column width ratio - maxRows (int): the maximum number of displayed rows possible - minRows (int, optional): the minimum number of displayed rows. Defaults to 1. - columnSpacing (int, optional): spacing in between the columns, in pixels. Defaults to 0. - rowSpacing (int, optional): spacing in between the rows, in pixels. Defaults to 0. + # Args: + # _id (str): unique ID of command + # name (str): displayed name + # inputs (adsk.core.CommandInputs): parent command input container + # columns (int): column count + # ratio (str): column width ratio + # maxRows (int): the maximum number of displayed rows possible + # minRows (int, optional): the minimum number of displayed rows. Defaults to 1. + # columnSpacing (int, optional): spacing in between the columns, in pixels. Defaults to 0. + # rowSpacing (int, optional): spacing in between the rows, in pixels. Defaults to 0. - Returns: - adsk.core.TableCommandInput: created tableCommandInput - """ - try: - _input = inputs.addTableCommandInput(_id, name, columns, ratio) - _input.minimumVisibleRows = minRows - _input.maximumVisibleRows = maxRows - _input.columnSpacing = columnSpacing - _input.rowSpacing = rowSpacing - return _input - except: - logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.{self.__class__.__name__}.createTableInput()").error( - "Failed:\n{}".format(traceback.format_exc()) - ) + # Returns: + # adsk.core.TableCommandInput: created tableCommandInput + # """ + # try: + # _input = inputs.addTableCommandInput(_id, name, columns, ratio) + # _input.minimumVisibleRows = minRows + # _input.maximumVisibleRows = maxRows + # _input.columnSpacing = columnSpacing + # _input.rowSpacing = rowSpacing + # return _input + # except: + # logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.{self.__class__.__name__}.createTableInput()").error( + # "Failed:\n{}".format(traceback.format_exc()) + # ) # Transition: AARD-1685 # Functionality will be fully moved to `CreateCommandInputsHelper` in AARD-1683 - def createTextBoxInput( - self, - _id: str, - name: str, - inputs: adsk.core.CommandInputs, - text: str, - italics=True, - bold=True, - fontSize=10, - alignment="center", - rowCount=1, - read=True, - background="whitesmoke", - tooltip="", - advanced_tooltip="", - ) -> adsk.core.TextBoxCommandInput: - """### Helper to generate a textbox input from inputted options. + # def createTextBoxInput( + # self, + # _id: str, + # name: str, + # inputs: adsk.core.CommandInputs, + # text: str, + # italics=True, + # bold=True, + # fontSize=10, + # alignment="center", + # rowCount=1, + # read=True, + # background="whitesmoke", + # tooltip="", + # advanced_tooltip="", + # ) -> adsk.core.TextBoxCommandInput: + # """### Helper to generate a textbox input from inputted options. - Args: - _id (str): unique ID - name (str): displayed name - inputs (adsk.core.CommandInputs): parent command input container - text (str): the user-visible text in command - italics (bool, optional): is italics? Defaults to True. - bold (bool, optional): isBold? Defaults to True. - fontSize (int, optional): fontsize. Defaults to 10. - alignment (str, optional): HTML style alignment (left, center, right). Defaults to "center". - rowCount (int, optional): number of rows in textbox. Defaults to 1. - read (bool, optional): read only? Defaults to True. - background (str, optional): background color (HTML color names or hex) Defaults to "whitesmoke". + # Args: + # _id (str): unique ID + # name (str): displayed name + # inputs (adsk.core.CommandInputs): parent command input container + # text (str): the user-visible text in command + # italics (bool, optional): is italics? Defaults to True. + # bold (bool, optional): isBold? Defaults to True. + # fontSize (int, optional): fontsize. Defaults to 10. + # alignment (str, optional): HTML style alignment (left, center, right). Defaults to "center". + # rowCount (int, optional): number of rows in textbox. Defaults to 1. + # read (bool, optional): read only? Defaults to True. + # background (str, optional): background color (HTML color names or hex) Defaults to "whitesmoke". - Returns: - adsk.core.TextBoxCommandInput: newly created textBoxCommandInput - """ - try: - i = ["", ""] - b = ["", ""] - - if bold: - b[0] = "" - b[1] = "" - if italics: - i[0] = "" - i[1] = "" - - # simple wrapper for html formatting - wrapper = """ -
-

- %s%s{}%s%s -

- - """.format( - text - ) - _text = wrapper % (background, alignment, fontSize, b[0], i[0], i[1], b[1]) + # Returns: + # adsk.core.TextBoxCommandInput: newly created textBoxCommandInput + # """ + # try: + # i = ["", ""] + # b = ["", ""] + + # if bold: + # b[0] = "" + # b[1] = "" + # if italics: + # i[0] = "" + # i[1] = "" + + # # simple wrapper for html formatting + # wrapper = """ + #
+ #

+ # %s%s{}%s%s + #

+ # + # """.format( + # text + # ) + # _text = wrapper % (background, alignment, fontSize, b[0], i[0], i[1], b[1]) - _input = inputs.addTextBoxCommandInput(_id, name, _text, rowCount, read) - _input.tooltip = tooltip - _input.tooltipDescription = advanced_tooltip - return _input - except: - logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.createTextBoxInput()").error( - "Failed:\n{}".format(traceback.format_exc()) - ) + # _input = inputs.addTextBoxCommandInput(_id, name, _text, rowCount, read) + # _input.tooltip = tooltip + # _input.tooltipDescription = advanced_tooltip + # return _input + # except: + # logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.createTextBoxInput()").error( + # "Failed:\n{}".format(traceback.format_exc()) + # ) class ConfigureCommandExecuteHandler(adsk.core.CommandEventHandler): @@ -938,11 +938,11 @@ def notify(self, args): exportMode=generalConfigTab.exportMode, compressOutput=generalConfigTab.compress, exportAsPart=generalConfigTab.exportAsPart, + autoCalcWeight=generalConfigTab.autoCalculateWeight, ) - # TODO: Swap back + Parser(exporterOptions).export() exporterOptions.writeToDesign() - # Parser(exporterOptions).export() # All selections should be reset AFTER a successful export and save. # If we run into an exporting error we should return back to the panel with all current options diff --git a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py index fe60a3b939..a92638b0f7 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py @@ -77,6 +77,7 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp weightInput.tooltipDescription = ( """(in pounds)
This is the weight of the entire robot assembly.""" ) + weightInput.isEnabled = not exporterOptions.autoCalcWeight weightUnitDropdown = generalTabInputs.addDropDownCommandInput( "weightUnitDropdown", @@ -155,8 +156,21 @@ def selectedUnits(self) -> PreferredUnits: @property def robotWeight(self) -> KG: - weightInput: adsk.core.ValueCommandInput = self.generalOptionsTab.children.itemById("weightTable").getInputAtPosition(0, 1) - return KG(weightInput.value) + 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) + + @property + def autoCalculateWeight(self) -> bool: + autoCalcWeightButton: adsk.core.BoolValueCommandInput = self.generalOptionsTab.children.itemById( + "autoCalcWeightButton" + ) + return autoCalcWeightButton.value def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: try: From ac500d5715c3c63c6cb5a85914e77ac1191e97d0 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Thu, 18 Jul 2024 08:48:47 -0700 Subject: [PATCH 012/344] Round to 2 decimal places --- exporter/SynthesisFusionAddin/src/TypesTmp.py | 4 ++-- exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/TypesTmp.py b/exporter/SynthesisFusionAddin/src/TypesTmp.py index 7926bcd360..14572aa1ba 100644 --- a/exporter/SynthesisFusionAddin/src/TypesTmp.py +++ b/exporter/SynthesisFusionAddin/src/TypesTmp.py @@ -8,8 +8,8 @@ class KG(float): def toLbs(kgs: float) -> LBS: - return LBS(kgs * 2.2062) + return LBS(round(kgs * 2.2062, 2)) def toKg(pounds: float) -> KG: - return KG(pounds / 2.2062) + return KG(round(pounds / 2.2062, 2)) diff --git a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py index a92638b0f7..052a721313 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py @@ -227,4 +227,4 @@ def designMassCalculation() -> KG: physical = body.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy) mass += physical.mass - return KG(mass) + return KG(round(mass, 2)) From 09ea0f3d5fee75386b149f8556906bb627f95b1d Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Thu, 18 Jul 2024 08:56:19 -0700 Subject: [PATCH 013/344] Added handling of unit description --- .../src/UI/ConfigCommand.py | 29 ++++++++++--------- .../src/UI/GeneralConfigTab.py | 11 ++++++- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index 28ce7dd163..ba1939d1b8 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -1428,21 +1428,22 @@ def notify(self, args): else: frictionCoeff.isVisible = False - elif cmdInput.id == "weight_unit": - unitDropdown = adsk.core.DropDownCommandInput.cast(cmdInput) - weightInput = weightTableInput.getInputAtPosition(0, 2) - if unitDropdown.selectedItem.index == 0: - self.isLbs = True - - weightInput.tooltipDescription = ( - """(in pounds)
This is the weight of the entire robot assembly.""" - ) - elif unitDropdown.selectedItem.index == 1: - self.isLbs = False + # Transition: AARD-1683 + # elif cmdInput.id == "weight_unit": + # unitDropdown = adsk.core.DropDownCommandInput.cast(cmdInput) + # weightInput = weightTableInput.getInputAtPosition(0, 2) + # if unitDropdown.selectedItem.index == 0: + # self.isLbs = True + + # weightInput.tooltipDescription = ( + # """(in pounds)
This is the weight of the entire robot assembly.""" + # ) + # elif unitDropdown.selectedItem.index == 1: + # self.isLbs = False - weightInput.tooltipDescription = ( - """(in kilograms)
This is the weight of the entire robot assembly.""" - ) + # weightInput.tooltipDescription = ( + # """(in kilograms)
This is the weight of the entire robot assembly.""" + # ) elif cmdInput.id == "weight_unit_f": unitDropdown = adsk.core.DropDownCommandInput.cast(cmdInput) diff --git a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py index 052a721313..8867847a34 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py @@ -75,7 +75,8 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp ) weightInput.tooltip = "Robot weight" weightInput.tooltipDescription = ( - """(in pounds)
This is the weight of the entire robot assembly.""" + f"(in {'pounds' if self.currentUnits == PreferredUnits.IMPERIAL else 'kilograms'})" + "
This is the weight of the entire robot assembly." ) weightInput.isEnabled = not exporterOptions.autoCalcWeight @@ -85,6 +86,8 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp 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" @@ -185,10 +188,16 @@ def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: 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 From cd8e7fa0a899b8867e1b940abe74081060952d05 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Thu, 18 Jul 2024 09:12:44 -0700 Subject: [PATCH 014/344] Handle transition tags --- .../src/UI/ConfigCommand.py | 678 ++---------------- 1 file changed, 70 insertions(+), 608 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index ba1939d1b8..73cec44751 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -16,12 +16,8 @@ from ..general_imports import * from ..Parser.ExporterOptions import ( ExporterOptions, - ExportMode, - Gamepiece, - PreferredUnits, ) from ..Parser.SynthesisParser.Parser import Parser -from ..Parser.SynthesisParser.Utilities import guid_occurrence from . import CustomGraphics, FileDialogConfig, Helper, IconPaths from .Configuration.SerialCommand import SerialCommand from .GeneralConfigTab import GeneralConfigTab @@ -48,9 +44,6 @@ """ GamepieceListGlobal = [] -# Default to compressed files -compress = True - def GUID(arg): """### Will return command object when given a string GUID, or the string GUID of an object (depending on arg value) @@ -93,44 +86,6 @@ class JointMotions(Enum): BALL = 6 -# Transition: AARD-1683 -# class FullMassCalculation: -# def __init__(self): -# self.totalMass = 0.0 -# self.bRepMassInRoot() -# self.traverseOccurrenceHierarchy() - -# def bRepMassInRoot(self): -# try: -# for body in gm.app.activeDocument.design.rootComponent.bRepBodies: -# if not body.isLightBulbOn: -# continue -# physical = body.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy) -# self.totalMass += physical.mass -# except: -# if gm.ui: -# gm.ui.messageBox("Failed:\n{}".format(traceback.format_exc())) - -# def traverseOccurrenceHierarchy(self): -# try: -# for occ in gm.app.activeDocument.design.rootComponent.allOccurrences: -# if not occ.isLightBulbOn: -# continue - -# for body in occ.component.bRepBodies: -# if not body.isLightBulbOn: -# continue -# physical = body.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy) -# self.totalMass += physical.mass -# except: -# pass -# if gm.ui: -# gm.ui.messageBox("Failed:\n{}".format(traceback.format_exc())) - -# def getTotalMass(self): -# return self.totalMass - - class ConfigureCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): """### Start the Command Input Object and define all of the input groups to create our ParserOptions object. @@ -178,13 +133,6 @@ def notify(self, args): global generalConfigTab generalConfigTab = GeneralConfigTab(args, exporterOptions) - # ====================================== GENERAL TAB ====================================== - """ - Creates the general tab. - - Parent container for all the command inputs in the tab. - """ - # inputs = INPUTS_ROOT.addTabCommandInput("general_settings", "General").children - # ~~~~~~~~~~~~~~~~ HELP FILE ~~~~~~~~~~~~~~~~ """ Sets the small "i" icon in bottom left of the panel. @@ -192,90 +140,6 @@ def notify(self, args): """ cmd.helpFile = os.path.join(".", "src", "Resources", "HTML", "info.html") - # Transition: AARD-1683 - # ~~~~~~~~~~~~~~~~ EXPORT MODE ~~~~~~~~~~~~~~~~ - """ - Dropdown to choose whether to export robot or field element - """ - # dropdownExportMode = inputs.addDropDownCommandInput( - # "mode", - # "Export Mode", - # dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle, - # ) - - # dynamic = exporterOptions.exportMode == ExportMode.ROBOT - # dropdownExportMode.listItems.add("Dynamic", dynamic) - # dropdownExportMode.listItems.add("Static", not dynamic) - - # dropdownExportMode.tooltip = "Export Mode" - # dropdownExportMode.tooltipDescription = "
Does this object move dynamically?" - - # ~~~~~~~~~~~~~~~~ WEIGHT CONFIGURATION ~~~~~~~~~~~~~~~~ - """ - Table for weight config. - - Used this to align multiple commandInputs on the same row - """ - # weightTableInput = self.createTableInput( - # "weight_table", - # "Weight Table", - # inputs, - # 4, - # "3:2:2:1", - # 1, - # ) - # weightTableInput.tablePresentationStyle = 2 # set transparent background for table - - # weight_name = inputs.addStringValueInput("weight_name", "Weight") - # weight_name.value = "Weight" - # weight_name.isReadOnly = True - - # auto_calc_weight = self.createBooleanInput( - # "auto_calc_weight", - # "‎", - # inputs, - # checked=False, - # tooltip="Approximate the weight of your robot assembly.", - # tooltipadvanced="This may take a moment...", - # enabled=True, - # isCheckBox=False, - # ) - # auto_calc_weight.resourceFolder = IconPaths.stringIcons["calculate-enabled"] - # auto_calc_weight.isFullWidth = True - - # imperialUnits = exporterOptions.preferredUnits == PreferredUnits.IMPERIAL - # if imperialUnits: - # # ExporterOptions always contains the metric value - # displayWeight = exporterOptions.robotWeight * 2.2046226218 - # else: - # displayWeight = exporterOptions.robotWeight - - # weight_input = inputs.addValueInput( - # "weight_input", - # "Weight Input", - # "", - # adsk.core.ValueInput.createByReal(displayWeight), - # ) - # weight_input.tooltip = "Robot weight" - # weight_input.tooltipDescription = ( - # """(in pounds)
This is the weight of the entire robot assembly.""" - # ) - - # weight_unit = inputs.addDropDownCommandInput( - # "weight_unit", - # "Weight Unit", - # adsk.core.DropDownStyles.LabeledIconDropDownStyle, - # ) - - # weight_unit.listItems.add("‎", imperialUnits, IconPaths.massIcons["LBS"]) - # weight_unit.listItems.add("‎", not imperialUnits, IconPaths.massIcons["KG"]) - # weight_unit.tooltip = "Unit of mass" - # weight_unit.tooltipDescription = "
Configure the unit of mass for the weight calculation." - - # weightTableInput.addCommandInput(weight_name, 0, 0) # add command inputs to table - # weightTableInput.addCommandInput(auto_calc_weight, 0, 1) # add command inputs to table - # weightTableInput.addCommandInput(weight_input, 0, 2) # add command inputs to table - # weightTableInput.addCommandInput(weight_unit, 0, 3) # add command inputs to table - global jointConfigTab jointConfigTab = JointConfigTab(args) @@ -428,170 +292,6 @@ def notify(self, args): # 3, # ) - # ====================================== ADVANCED TAB ====================================== - """ - Creates the advanced tab, which is the parent container for internal command inputs - """ - # Transition: AARD-1683 - # advancedSettings = INPUTS_ROOT.addTabCommandInput("advanced_settings", "Advanced") - # advancedSettings.tooltip = ( - # "Additional Advanced Settings to change how your model will be translated into Unity." - # ) - # a_input = advancedSettings.children - - # Transition: AARD-1683 - # ~~~~~~~~~~~~~~~~ EXPORTER SETTINGS ~~~~~~~~~~~~~~~~ - """ - Exporter settings group command - """ - # exporterSettings = a_input.addGroupCommandInput("exporter_settings", "Exporter Settings") - # exporterSettings.isExpanded = True - # exporterSettings.isEnabled = True - # exporterSettings.tooltip = "tooltip" # TODO: update tooltip - # exporter_settings = exporterSettings.children - - # self.createBooleanInput( - # "compress", - # "Compress Output", - # exporter_settings, - # checked=exporterOptions.compressOutput, - # tooltip="Compress the output file for a smaller file size.", - # tooltipadvanced="
Use the GZIP compression system to compress the resulting file which will be opened in the simulator, perfect if you want to share the file.
", - # enabled=True, - # ) - - # self.createBooleanInput( - # "export_as_part", - # "Export As Part", - # exporter_settings, - # checked=exporterOptions.exportAsPart, - # tooltip="Use to export as a part for Mix And Match", - # enabled=True, - # ) - - # ~~~~~~~~~~~~~~~~ PHYSICS SETTINGS ~~~~~~~~~~~~~~~~ - """ - Physics settings group command - """ - # physicsSettings = a_input.addGroupCommandInput("physics_settings", "Physics Settings") - - # physicsSettings.isExpanded = False - # physicsSettings.isEnabled = True - # physicsSettings.tooltip = "tooltip" # TODO: update tooltip - # physics_settings = physicsSettings.children - - # # AARD-1687 - # # Should also be commented out / removed? - # # This would cause problems elsewhere but I can't tell i f - # # this is even being used. - # frictionOverrideTable = self.createTableInput( - # "friction_override_table", - # "", - # physics_settings, - # 2, - # "1:2", - # 1, - # columnSpacing=25, - # ) - # frictionOverrideTable.tablePresentationStyle = 2 - # # frictionOverrideTable.isFullWidth = True - - # frictionOverride = self.createBooleanInput( - # "friction_override", - # "", - # physics_settings, - # checked=False, - # tooltip="Manually override the default friction values on the bodies in the assembly.", - # enabled=True, - # isCheckBox=False, - # ) - # frictionOverride.resourceFolder = IconPaths.stringIcons["friction_override-enabled"] - # frictionOverride.isFullWidth = True - - # valueList = [1] - # for i in range(20): - # valueList.append(i / 20) - - # frictionCoeff = physics_settings.addFloatSliderListCommandInput( - # "friction_coeff_override", "Friction Coefficient", "", valueList - # ) - # frictionCoeff.isVisible = False - # frictionCoeff.valueOne = 0.5 - # frictionCoeff.tooltip = "Friction coefficient of field element." - # frictionCoeff.tooltipDescription = "Friction coefficients range from 0 (ice) to 1 (rubber)." - - # frictionOverrideTable.addCommandInput(frictionOverride, 0, 0) - # frictionOverrideTable.addCommandInput(frictionCoeff, 0, 1) - - # ~~~~~~~~~~~~~~~~ JOINT SETTINGS ~~~~~~~~~~~~~~~~ - """ - Joint settings group command - """ - - # Transition: AARD-1689 - # Should possibly be implemented later? - - # jointsSettings = a_input.addGroupCommandInput( - # "joints_settings", "Joints Settings" - # ) - # jointsSettings.isExpanded = False - # jointsSettings.isEnabled = True - # jointsSettings.tooltip = "tooltip" # TODO: update tooltip - # joints_settings = jointsSettings.children - - # self.createBooleanInput( - # "kinematic_only", - # "Kinematic Only", - # joints_settings, - # checked=False, - # tooltip="tooltip", # TODO: update tooltip - # enabled=True, - # ) - - # self.createBooleanInput( - # "calculate_limits", - # "Calculate Limits", - # joints_settings, - # checked=True, - # tooltip="tooltip", # TODO: update tooltip - # enabled=True, - # ) - - # self.createBooleanInput( - # "auto_assign_ids", - # "Auto-Assign ID's", - # joints_settings, - # checked=True, - # tooltip="tooltip", # TODO: update tooltip - # enabled=True, - # ) - - # ~~~~~~~~~~~~~~~~ CONTROLLER SETTINGS ~~~~~~~~~~~~~~~~ - """ - Controller settings group command - """ - - # Transition: AARD-1689 - # Should possibly be implemented later? - - # controllerSettings = a_input.addGroupCommandInput( - # "controller_settings", "Controller Settings" - # ) - - # controllerSettings.isExpanded = False - # controllerSettings.isEnabled = True - # controllerSettings.tooltip = "tooltip" # TODO: update tooltip - # controller_settings = controllerSettings.children - - # self.createBooleanInput( # export signals checkbox - # "export_signals", - # "Export Signals", - # controller_settings, - # checked=True, - # tooltip="tooltip", - # enabled=True, - # ) - # Transition: AARD-1683 # Needed to comment this out because it throws if you don't login preventing anything from working @@ -646,154 +346,6 @@ def notify(self, args): "Failed:\n{}".format(traceback.format_exc()) ) - # # Transition: AARD-1685 - # # Functionality will be fully moved to `CreateCommandInputsHelper` in AARD-1683 - # def createBooleanInput( - # self, - # _id: str, - # name: str, - # inputs: adsk.core.CommandInputs, - # tooltip="", - # tooltipadvanced="", - # checked=True, - # enabled=True, - # isCheckBox=True, - # ) -> adsk.core.BoolValueCommandInput: - # """### Simple helper to generate all of the options for me to create a boolean command input - - # Args: - # _id (str): id value of the object - pretty much lowercase name - # name (str): name as displayed by the command prompt - # inputs (adsk.core.CommandInputs): parent command input container - # tooltip (str, optional): Description on hover of the checkbox. Defaults to "". - # tooltipadvanced (str, optional): Long hover description. Defaults to "". - # checked (bool, optional): Is checked by default?. Defaults to True. - - # Returns: - # adsk.core.BoolValueCommandInput: Recently created command input - # """ - # try: - # _input = inputs.addBoolValueInput(_id, name, isCheckBox) - # _input.value = checked - # _input.isEnabled = enabled - # _input.tooltip = tooltip - # _input.tooltipDescription = tooltipadvanced - # return _input - # except: - # logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.{self.__class__.__name__}.createBooleanInput()").error( - # "Failed:\n{}".format(traceback.format_exc()) - # ) - - # # Transition: AARD-1685 - # # Functionality will be fully moved to `CreateCommandInputsHelper` in AARD-1683 - # def createTableInput( - # self, - # _id: str, - # name: str, - # inputs: adsk.core.CommandInputs, - # columns: int, - # ratio: str, - # maxRows: int, - # minRows=1, - # columnSpacing=0, - # rowSpacing=0, - # ) -> adsk.core.TableCommandInput: - # """### Simple helper to generate all the TableCommandInput options. - - # Args: - # _id (str): unique ID of command - # name (str): displayed name - # inputs (adsk.core.CommandInputs): parent command input container - # columns (int): column count - # ratio (str): column width ratio - # maxRows (int): the maximum number of displayed rows possible - # minRows (int, optional): the minimum number of displayed rows. Defaults to 1. - # columnSpacing (int, optional): spacing in between the columns, in pixels. Defaults to 0. - # rowSpacing (int, optional): spacing in between the rows, in pixels. Defaults to 0. - - # Returns: - # adsk.core.TableCommandInput: created tableCommandInput - # """ - # try: - # _input = inputs.addTableCommandInput(_id, name, columns, ratio) - # _input.minimumVisibleRows = minRows - # _input.maximumVisibleRows = maxRows - # _input.columnSpacing = columnSpacing - # _input.rowSpacing = rowSpacing - # return _input - # except: - # logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.{self.__class__.__name__}.createTableInput()").error( - # "Failed:\n{}".format(traceback.format_exc()) - # ) - - # Transition: AARD-1685 - # Functionality will be fully moved to `CreateCommandInputsHelper` in AARD-1683 - # def createTextBoxInput( - # self, - # _id: str, - # name: str, - # inputs: adsk.core.CommandInputs, - # text: str, - # italics=True, - # bold=True, - # fontSize=10, - # alignment="center", - # rowCount=1, - # read=True, - # background="whitesmoke", - # tooltip="", - # advanced_tooltip="", - # ) -> adsk.core.TextBoxCommandInput: - # """### Helper to generate a textbox input from inputted options. - - # Args: - # _id (str): unique ID - # name (str): displayed name - # inputs (adsk.core.CommandInputs): parent command input container - # text (str): the user-visible text in command - # italics (bool, optional): is italics? Defaults to True. - # bold (bool, optional): isBold? Defaults to True. - # fontSize (int, optional): fontsize. Defaults to 10. - # alignment (str, optional): HTML style alignment (left, center, right). Defaults to "center". - # rowCount (int, optional): number of rows in textbox. Defaults to 1. - # read (bool, optional): read only? Defaults to True. - # background (str, optional): background color (HTML color names or hex) Defaults to "whitesmoke". - - # Returns: - # adsk.core.TextBoxCommandInput: newly created textBoxCommandInput - # """ - # try: - # i = ["", ""] - # b = ["", ""] - - # if bold: - # b[0] = "" - # b[1] = "" - # if italics: - # i[0] = "" - # i[1] = "" - - # # simple wrapper for html formatting - # wrapper = """ - #
- #

- # %s%s{}%s%s - #

- # - # """.format( - # text - # ) - # _text = wrapper % (background, alignment, fontSize, b[0], i[0], i[1], b[1]) - - # _input = inputs.addTextBoxCommandInput(_id, name, _text, rowCount, read) - # _input.tooltip = tooltip - # _input.tooltipDescription = advanced_tooltip - # return _input - # except: - # logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.createTextBoxInput()").error( - # "Failed:\n{}".format(traceback.format_exc()) - # ) - class ConfigureCommandExecuteHandler(adsk.core.CommandEventHandler): """### Called when Ok is pressed confirming the export @@ -828,26 +380,6 @@ def notify(self, args): self.log.error("Could not execute configuration due to failure") return - # Transition: AARD-1683 - # export_as_part_boolean = ( - # eventArgs.command.commandInputs.itemById("advanced_settings") - # .children.itemById("exporter_settings") - # .children.itemById("export_as_part") - # ).value - - # processedFileName = gm.app.activeDocument.name.replace(" ", "_") - # dropdownExportMode = INPUTS_ROOT.itemById("mode") - # if dropdownExportMode.selectedItem.index == 0: - # isRobot = True - # elif dropdownExportMode.selectedItem.index == 1: - # isRobot = False - - # if isRobot: - # savepath = FileDialogConfig.SaveFileDialog( - # defaultPath=exporterOptions.fileLocation, - # ext="Synthesis File (*.synth)", - # ) - # else: savepath = FileDialogConfig.SaveFileDialog(defaultPath=exporterOptions.fileLocation) if not savepath: @@ -865,11 +397,9 @@ def notify(self, args): version = design.rootComponent.name.rsplit(" ", 1)[1] _exportGamepieces = [] # TODO work on the code to populate Gamepiece - _robotWeight = 0.0 - _mode = ExportMode.ROBOT # Transition: AARD-1683 - # This was never being used it appears, should be looped back to. + # TODO: Implement gamepiece things """ Loops through all rows in the gamepiece table to extract the input values """ @@ -894,35 +424,6 @@ def notify(self, args): # ) # ) - """ - Robot Weight - """ - # weight_input = INPUTS_ROOT.itemById("weight_input") - # weight_unit = INPUTS_ROOT.itemById("weight_unit") - - # if weight_unit.selectedItem.index == 0: - # selectedUnits = PreferredUnits.IMPERIAL - # _robotWeight = float(weight_input.value) / 2.2046226218 - # else: - # selectedUnits = PreferredUnits.METRIC - # _robotWeight = float(weight_input.value) - - """ - Export Mode - """ - # dropdownExportMode = INPUTS_ROOT.itemById("mode") - # if dropdownExportMode.selectedItem.index == 0: - # _mode = ExportMode.ROBOT - # elif dropdownExportMode.selectedItem.index == 1: - # _mode = ExportMode.FIELD - - # global compress - # compress = ( - # eventArgs.command.commandInputs.itemById("advanced_settings") - # .children.itemById("exporter_settings") - # .children.itemById("compress") - # ).value - selectedJoints, selectedWheels = jointConfigTab.getSelectedJointsAndWheels() exporterOptions = ExporterOptions( @@ -1149,25 +650,28 @@ def notify(self, args: adsk.core.SelectionEventArgs): duplicateSelection = INPUTS_ROOT.itemById("duplicate_selection") # indicator = INPUTS_ROOT.itemById("algorithmic_indicator") - if self.selectedOcc: - self.cmd.setCursor("", 0, 0) - if dropdownExportMode.selectedItem.index == 1: - occurrenceList = gm.app.activeDocument.design.rootComponent.allOccurrencesByComponent( - self.selectedOcc.component - ) - for occ in occurrenceList: - if occ not in GamepieceListGlobal: - addGamepieceToTable(occ) - else: - removeGamePieceFromTable(GamepieceListGlobal.index(occ)) - - selectionInput.isEnabled = False - selectionInput.isVisible = False + + # Transition: AARD-1683 + # TODO: Implement gamepiece things + # if self.selectedOcc: + # self.cmd.setCursor("", 0, 0) + # if dropdownExportMode.selectedItem.index == 1: + # occurrenceList = gm.app.activeDocument.design.rootComponent.allOccurrencesByComponent( + # self.selectedOcc.component + # ) + # for occ in occurrenceList: + # if occ not in GamepieceListGlobal: + # addGamepieceToTable(occ) + # else: + # removeGamePieceFromTable(GamepieceListGlobal.index(occ)) + + # selectionInput.isEnabled = False + # selectionInput.isVisible = False # Transition: AARD-1685 # This is how all handle selection events should be done in the future although it will look # slightly differently for each type of handle. - elif self.selectedJoint: + if self.selectedJoint: jointConfigTab.handleSelectionEvent(args, self.selectedJoint) except: if gm.ui: @@ -1202,6 +706,8 @@ def notify(self, args): preSelected = preSelectedOcc if preSelectedOcc else preSelectedJoint + # Transition: AARD-1683 + # TODO: Implement gamepiece things # dropdownExportMode = INPUTS_ROOT.itemById("mode") # if preSelected and design: # if dropdownExportMode.selectedItem.index == 0: # Dynamic @@ -1297,39 +803,6 @@ def reset(self): "Failed:\n{}".format(traceback.format_exc()) ) - # Transition: AARD-1683 - # def weight(self, isLbs=True): # maybe add a progress dialog?? - # """### Get the total design weight using the predetermined units. - - # Args: - # isLbs (bool, optional): Is selected mass unit pounds? Defaults to True. - - # Returns: - # value (float): weight value in specified unit - # """ - # try: - # if gm.app.activeDocument.design: - # massCalculation = FullMassCalculation() - # totalMass = massCalculation.getTotalMass() - - # value = float - - # self.allWeights[0] = round(totalMass * 2.2046226218, 2) - - # self.allWeights[1] = round(totalMass, 2) - - # if isLbs: - # value = self.allWeights[0] - # else: - # value = self.allWeights[1] - - # value = round(value, 2) # round weight to 2 decimals places - # return value - # except: - # logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.{self.__class__.__name__}.weight()").error( - # "Failed:\n{}".format(traceback.format_exc()) - # ) - def notify(self, args): try: eventArgs = adsk.core.InputChangedEventArgs.cast(args) @@ -1428,43 +901,31 @@ def notify(self, args): else: frictionCoeff.isVisible = False + # Transition: AARD-1683 - # elif cmdInput.id == "weight_unit": + # TODO: Implement gamepiece things + # elif cmdInput.id == "weight_unit_f": # unitDropdown = adsk.core.DropDownCommandInput.cast(cmdInput) - # weightInput = weightTableInput.getInputAtPosition(0, 2) # if unitDropdown.selectedItem.index == 0: - # self.isLbs = True + # self.isLbs_f = True - # weightInput.tooltipDescription = ( - # """(in pounds)
This is the weight of the entire robot assembly.""" - # ) + # for row in range(gamepieceTableInput.rowCount): + # if row == 0: + # continue + # weightInput = gamepieceTableInput.getInputAtPosition(row, 2) + # weightInput.tooltipDescription = "(in pounds)" # elif unitDropdown.selectedItem.index == 1: - # self.isLbs = False - - # weightInput.tooltipDescription = ( - # """(in kilograms)
This is the weight of the entire robot assembly.""" - # ) + # self.isLbs_f = False - elif cmdInput.id == "weight_unit_f": - unitDropdown = adsk.core.DropDownCommandInput.cast(cmdInput) - if unitDropdown.selectedItem.index == 0: - self.isLbs_f = True - - for row in range(gamepieceTableInput.rowCount): - if row == 0: - continue - weightInput = gamepieceTableInput.getInputAtPosition(row, 2) - weightInput.tooltipDescription = "(in pounds)" - elif unitDropdown.selectedItem.index == 1: - self.isLbs_f = False - - for row in range(gamepieceTableInput.rowCount): - if row == 0: - continue - weightInput = gamepieceTableInput.getInputAtPosition(row, 2) - weightInput.tooltipDescription = "(in kilograms)" + # for row in range(gamepieceTableInput.rowCount): + # if row == 0: + # continue + # weightInput = gamepieceTableInput.getInputAtPosition(row, 2) + # weightInput.tooltipDescription = "(in kilograms)" # Transition: AARD-1683 + # TODO: Implement gamepiece things??? + # Is there even any here?? # elif cmdInput.id == "auto_calc_weight": # button = adsk.core.BoolValueCommandInput.cast(cmdInput) @@ -1487,37 +948,34 @@ def notify(self, args): # else: # weight_input.value = self.allWeights[1] - # TODO: Figure out gamepiece stuff - elif cmdInput.id == "auto_calc_weight_f": - button = adsk.core.BoolValueCommandInput.cast(cmdInput) - - if button.value == True: # CALCULATE button pressed - if self.isLbs_f: - for row in range(gamepieceTableInput.rowCount): - if row == 0: - continue - weightInput = gamepieceTableInput.getInputAtPosition(row, 2) - physical = GamepieceListGlobal[row - 1].component.getPhysicalProperties( - adsk.fusion.CalculationAccuracy.LowCalculationAccuracy - ) - value = round(physical.mass * 2.2046226218, 2) - weightInput.value = value - - else: - for row in range(gamepieceTableInput.rowCount): - if row == 0: - continue - weightInput = gamepieceTableInput.getInputAtPosition(row, 2) - physical = GamepieceListGlobal[row - 1].component.getPhysicalProperties( - adsk.fusion.CalculationAccuracy.LowCalculationAccuracy - ) - value = round(physical.mass, 2) - weightInput.value = value - elif cmdInput.id == "compress": - checkBox = adsk.core.BoolValueCommandInput.cast(cmdInput) - if checkBox.value: - global compress - compress = checkBox.value + + # Transition: AARD-1683 + # TODO: Implement gamepiece things + # elif cmdInput.id == "auto_calc_weight_f": + # button = adsk.core.BoolValueCommandInput.cast(cmdInput) + + # if button.value == True: # CALCULATE button pressed + # if self.isLbs_f: + # for row in range(gamepieceTableInput.rowCount): + # if row == 0: + # continue + # weightInput = gamepieceTableInput.getInputAtPosition(row, 2) + # physical = GamepieceListGlobal[row - 1].component.getPhysicalProperties( + # adsk.fusion.CalculationAccuracy.LowCalculationAccuracy + # ) + # value = round(physical.mass * 2.2046226218, 2) + # weightInput.value = value + + # else: + # for row in range(gamepieceTableInput.rowCount): + # if row == 0: + # continue + # weightInput = gamepieceTableInput.getInputAtPosition(row, 2) + # physical = GamepieceListGlobal[row - 1].component.getPhysicalProperties( + # adsk.fusion.CalculationAccuracy.LowCalculationAccuracy + # ) + # value = round(physical.mass, 2) + # weightInput.value = value except: if gm.ui: gm.ui.messageBox("Failed:\n{}".format(traceback.format_exc())) @@ -1559,6 +1017,8 @@ def notify(self, args): ) +# Transition: AARD-1683 +# TODO: Implement gamepiece things def addGamepieceToTable(gamepiece: adsk.fusion.Occurrence) -> None: """### Adds a gamepiece occurrence to its global list and gamepiece table. @@ -1634,6 +1094,8 @@ def addPreselections(child_occurrences): ) +# Transition: AARD-1683 +# TODO: Implement gamepiece things def removeGamePieceFromTable(index: int) -> None: """### Removes a gamepiece occurrence from its global list and gamepiece table. From fdc7d6725aad74787710d45868faf77ce8fd8829 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Thu, 18 Jul 2024 09:13:22 -0700 Subject: [PATCH 015/344] Formatting --- exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index 73cec44751..8e327d04d8 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -14,9 +14,7 @@ from ..APS.APS import getAuth, getUserInfo, refreshAuthToken from ..configure import NOTIFIED, write_configuration from ..general_imports import * -from ..Parser.ExporterOptions import ( - ExporterOptions, -) +from ..Parser.ExporterOptions import ExporterOptions from ..Parser.SynthesisParser.Parser import Parser from . import CustomGraphics, FileDialogConfig, Helper, IconPaths from .Configuration.SerialCommand import SerialCommand @@ -650,7 +648,6 @@ def notify(self, args: adsk.core.SelectionEventArgs): duplicateSelection = INPUTS_ROOT.itemById("duplicate_selection") # indicator = INPUTS_ROOT.itemById("algorithmic_indicator") - # Transition: AARD-1683 # TODO: Implement gamepiece things # if self.selectedOcc: @@ -901,7 +898,6 @@ def notify(self, args): else: frictionCoeff.isVisible = False - # Transition: AARD-1683 # TODO: Implement gamepiece things # elif cmdInput.id == "weight_unit_f": @@ -948,7 +944,6 @@ def notify(self, args): # else: # weight_input.value = self.allWeights[1] - # Transition: AARD-1683 # TODO: Implement gamepiece things # elif cmdInput.id == "auto_calc_weight_f": From 4cb61f24e317fbdbc8685af582e96d9f3c378ade Mon Sep 17 00:00:00 2001 From: arorad1 Date: Thu, 18 Jul 2024 14:38:17 -0700 Subject: [PATCH 016/344] Formatting --- fission/src/Synthesis.tsx | 7 +++- fission/src/ui/components/MainHUD.tsx | 6 +++- .../configuring/ConfigureRobotBrainPanel.tsx | 33 ++++++++----------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 5a2dcd44c8..1dff4b2c9d 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -244,7 +244,12 @@ const initialPanels: ReactElement[] = [ , , , - , + , ] export default Synthesis diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 457ee80282..9a138905dc 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -189,7 +189,11 @@ const MainHUD: React.FC = () => { }} /> } onClick={() => openModal("config-robot")} /> - } onClick={() => openPanel("config-robot-brain")} /> + } + onClick={() => openPanel("config-robot-brain")} + />
{userInfo ? ( = ({ panelId, openLocat panelId={panelId} openLocation={openLocation} sidePadding={sidePadding} - onAccept={() => { }} - onCancel={() => { }} + onAccept={() => {}} + onCancel={() => {}} > {selectedRobot?.ejectorPreferences == undefined ? ( <> @@ -58,7 +58,6 @@ const ConfigureRobotBrainPanel: React.FC = ({ panelId, openLocat ) : ( <> -
= ({ panelId, openLocat SynthesisBrain WIPLIBBrain - {viewType === ConfigureRobotBrainTypes.SYNTHESIS ? ( - - ) : ( - - )} + {viewType === ConfigureRobotBrainTypes.SYNTHESIS ? : }
)} - ); + ) } -export default ConfigureRobotBrainPanel; \ No newline at end of file +export default ConfigureRobotBrainPanel From c2e80f72fb191b4c5ac3862b027ef54d650e74ec Mon Sep 17 00:00:00 2001 From: arorad1 Date: Fri, 19 Jul 2024 13:12:13 -0700 Subject: [PATCH 017/344] Added UI --- .../configuring/ConfigureRobotBrainPanel.tsx | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx b/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx index 199fef056c..e13f244727 100644 --- a/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx @@ -1,12 +1,13 @@ import { MiraType } from "@/mirabuf/MirabufLoader" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import World from "@/systems/World" -import Label from "@/ui/components/Label" +import Label, { LabelSize } from "@/ui/components/Label" import Button from "@/ui/components/Button" import Panel, { PanelPropsImpl } from "@/ui/components/Panel" import { useMemo, useState } from "react" import { FaGear } from "react-icons/fa6" import { ToggleButton, ToggleButtonGroup } from "@/ui/components/ToggleButtonGroup" +import { Divider, styled } from "@mui/material" // eslint-disable-next-line react-refresh/only-export-components export enum ConfigureRobotBrainTypes { @@ -14,6 +15,15 @@ export enum ConfigureRobotBrainTypes { WIPLIB = 1, } +const LabelStyled = styled(Label)({ + fontWeight: 700, + margin: "0pt", +}) + +const DividerStyled = styled(Divider)({ + borderColor: "white", +}) + const ConfigureRobotBrainPanel: React.FC = ({ panelId, openLocation, sidePadding }) => { const [selectedRobot, setSelectedRobot] = useState(undefined) const [viewType, setViewType] = useState(ConfigureRobotBrainTypes.SYNTHESIS) @@ -70,7 +80,31 @@ const ConfigureRobotBrainPanel: React.FC = ({ panelId, openLocat SynthesisBrain WIPLIBBrain - {viewType === ConfigureRobotBrainTypes.SYNTHESIS ? : } + {viewType === ConfigureRobotBrainTypes.SYNTHESIS ? ( + <> + + Behaviors + + + + + thing2 + + + + + thing3 + + + + + thing4 + + + + ) : ( + + )}
)} From 628a1aec4a55d4941c9d764bb54d391248707a15 Mon Sep 17 00:00:00 2001 From: arorad1 Date: Fri, 19 Jul 2024 13:14:58 -0700 Subject: [PATCH 018/344] Updated wiplibbrain --- .../configuring/ConfigureRobotBrainPanel.tsx | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx b/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx index e13f244727..ad04d5bb68 100644 --- a/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx @@ -103,7 +103,27 @@ const ConfigureRobotBrainPanel: React.FC = ({ panelId, openLocat ) : ( - + <> + + Example WIPLIB Brain + + + + + Example 2 + + + + + Example 3 + + + + + Example 4 + + + )}
From 4f182f02735744565b81b7dcad0aa8d1eae0171f Mon Sep 17 00:00:00 2001 From: arorad1 Date: Fri, 19 Jul 2024 20:04:42 -0700 Subject: [PATCH 019/344] Adding basic Cascading Shadows implementation --- fission/src/systems/scene/CascadingShadows.ts | 94 +++++++++++++++++++ fission/src/systems/scene/SceneRenderer.ts | 36 ++++--- 2 files changed, 115 insertions(+), 15 deletions(-) create mode 100644 fission/src/systems/scene/CascadingShadows.ts diff --git a/fission/src/systems/scene/CascadingShadows.ts b/fission/src/systems/scene/CascadingShadows.ts new file mode 100644 index 0000000000..f80a61f237 --- /dev/null +++ b/fission/src/systems/scene/CascadingShadows.ts @@ -0,0 +1,94 @@ +import * as THREE from 'three'; + +class CascadingShadows { + private _camera: THREE.PerspectiveCamera; + private _shadowCameras: THREE.OrthographicCamera[] = []; + private _scene: THREE.Scene; + + private _renderer: THREE.WebGLRenderer; + private _light: THREE.DirectionalLight; + private _shadowMaps: THREE.WebGLRenderTarget[] = []; // One shadow map per cascade + + private _numCascades: number; + private _cascadeSplits: number[]; + + constructor(camera: THREE.PerspectiveCamera, scene: THREE.Scene, renderer: THREE.WebGLRenderer, numCascades: number = 5) { + this._camera = camera; + this._scene = scene; + this._renderer = renderer; + this._numCascades = numCascades; + + // Creating the directional light + this._light = new THREE.DirectionalLight(0xffffff, 3); + this._light.position.set(-1, 3, 2); + this._light.castShadow = true; + scene.add(this._light); + + // Split the camera frustum into numCascades cascades + this._cascadeSplits = new Array(numCascades).fill(0).map((_, i) => (i + 1) / numCascades); + + // Create the shadow camera and shadow maps + for (let i = 0; i < numCascades; i++) { + const shadowCamera = new THREE.OrthographicCamera(-10, 10, 10, -10, 0.5, 500); + shadowCamera.position.copy(this._light.position); + shadowCamera.lookAt(0, 0, 0); + this._shadowCameras.push(shadowCamera); + + const shadowMap = new THREE.WebGLRenderTarget(1024, 1024, { + minFilter: THREE.NearestFilter, + magFilter: THREE.NearestFilter, + format: THREE.RGBAFormat, + }); + this._shadowMaps.push(shadowMap); + } + } + + /** Updates the shadow maps */ + public Update() { + const frustum = new THREE.Frustum(); + const projScreenMatrix = new THREE.Matrix4(); + projScreenMatrix.multiplyMatrices(this._camera.projectionMatrix, this._camera.matrixWorldInverse); + frustum.setFromProjectionMatrix(projScreenMatrix); + + for (let i = 0; i < this._numCascades; i++) { + const near = this._camera.near + this._cascadeSplits[i] * (this._camera.far - this._camera.near); + const far = this._camera.near + this._cascadeSplits[i + 1] * (this._camera.far - this._camera.near); + + const cascadeFrustum = new THREE.Frustum(); + cascadeFrustum.setFromProjectionMatrix(this.GetCascadeMatrix(near, far)); + + const shadowCamera = this._shadowCameras[i]; + shadowCamera.position.copy(this._light.position); + shadowCamera.lookAt(0, 0, 0); + shadowCamera.updateMatrixWorld(); + shadowCamera.updateProjectionMatrix(); + + this._renderer.setRenderTarget(this._shadowMaps[i]); + this._renderer.render(this._scene, shadowCamera); + } + + this._renderer.setRenderTarget(null); + } + + /** Returns the projection matrix for the cascade */ + private GetCascadeMatrix(near: number, far: number): THREE.Matrix4 { + const matrix = new THREE.Matrix4(); + const projMatrix = new THREE.Matrix4().makePerspective( + this._camera.fov, + this._camera.aspect, + near, + far, + this._camera.near, + this._camera.far + ); + const invMatrix = this._camera.matrixWorld.invert(); + matrix.multiplyMatrices(projMatrix, invMatrix); + return matrix; + } + + public getShadowMap(index: number): THREE.WebGLRenderTarget { + return this._shadowMaps[index]; + } +} + +export default CascadingShadows; diff --git a/fission/src/systems/scene/SceneRenderer.ts b/fission/src/systems/scene/SceneRenderer.ts index 1590a15c9a..6458b6508a 100644 --- a/fission/src/systems/scene/SceneRenderer.ts +++ b/fission/src/systems/scene/SceneRenderer.ts @@ -14,6 +14,7 @@ import InputSystem from "../input/InputSystem" import { PixelSpaceCoord, SceneOverlayEvent, SceneOverlayEventKey } from "@/ui/components/SceneOverlayEvents" import {} from "@/ui/components/SceneOverlayEvents" import PreferencesSystem from "../preferences/PreferencesSystem" +import CascadingShadows from "./CascadingShadows" const CLEAR_COLOR = 0x121212 const GROUND_COLOR = 0x4066c7 @@ -34,6 +35,8 @@ class SceneRenderer extends WorldSystem { private _orbitControls: OrbitControls private _transformControls: Map // maps all rendered transform controls to their size + private _light: CascadingShadows + public get sceneObjects() { return this._sceneObjects } @@ -74,23 +77,25 @@ class SceneRenderer extends WorldSystem { this._renderer.shadowMap.type = THREE.PCFSoftShadowMap this._renderer.setSize(window.innerWidth, window.innerHeight) - const directionalLight = new THREE.DirectionalLight(0xffffff, 3.0) - directionalLight.position.set(-1.0, 3.0, 2.0) - directionalLight.castShadow = true - this._scene.add(directionalLight) + // const directionalLight = new THREE.DirectionalLight(0xffffff, 3.0) + // directionalLight.position.set(-1.0, 3.0, 2.0) + // directionalLight.castShadow = true + // this._scene.add(directionalLight) + + // const shadowMapSize = Math.min(4096, this._renderer.capabilities.maxTextureSize) + // const shadowCamSize = 15 + // console.debug(`Shadow Map Size: ${shadowMapSize}`) - const shadowMapSize = Math.min(4096, this._renderer.capabilities.maxTextureSize) - const shadowCamSize = 15 - console.debug(`Shadow Map Size: ${shadowMapSize}`) + // directionalLight.shadow.camera.top = shadowCamSize + // directionalLight.shadow.camera.bottom = -shadowCamSize + // directionalLight.shadow.camera.left = -shadowCamSize + // directionalLight.shadow.camera.right = shadowCamSize + // directionalLight.shadow.mapSize = new THREE.Vector2(shadowMapSize, shadowMapSize) + // directionalLight.shadow.blurSamples = 16 + // directionalLight.shadow.normalBias = 0.01 + // directionalLight.shadow.bias = 0.0 - directionalLight.shadow.camera.top = shadowCamSize - directionalLight.shadow.camera.bottom = -shadowCamSize - directionalLight.shadow.camera.left = -shadowCamSize - directionalLight.shadow.camera.right = shadowCamSize - directionalLight.shadow.mapSize = new THREE.Vector2(shadowMapSize, shadowMapSize) - directionalLight.shadow.blurSamples = 16 - directionalLight.shadow.normalBias = 0.01 - directionalLight.shadow.bias = 0.0 + this._light = new CascadingShadows(this._mainCamera, this._scene, this._renderer) const ambientLight = new THREE.AmbientLight(0xffffff, 0.1) this._scene.add(ambientLight) @@ -143,6 +148,7 @@ class SceneRenderer extends WorldSystem { this._sceneObjects.forEach(obj => { obj.Update() }) + this._light.Update() this._skybox.position.copy(this._mainCamera.position) From 323386e0b7e0900e8ed0eed854597ec81983ac20 Mon Sep 17 00:00:00 2001 From: arorad1 Date: Fri, 19 Jul 2024 20:08:16 -0700 Subject: [PATCH 020/344] Formatting --- fission/src/systems/scene/CascadingShadows.ts | 109 +++++++++--------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/fission/src/systems/scene/CascadingShadows.ts b/fission/src/systems/scene/CascadingShadows.ts index f80a61f237..65d7a11667 100644 --- a/fission/src/systems/scene/CascadingShadows.ts +++ b/fission/src/systems/scene/CascadingShadows.ts @@ -1,78 +1,83 @@ -import * as THREE from 'three'; +import * as THREE from "three" class CascadingShadows { - private _camera: THREE.PerspectiveCamera; - private _shadowCameras: THREE.OrthographicCamera[] = []; - private _scene: THREE.Scene; - - private _renderer: THREE.WebGLRenderer; - private _light: THREE.DirectionalLight; - private _shadowMaps: THREE.WebGLRenderTarget[] = []; // One shadow map per cascade - - private _numCascades: number; - private _cascadeSplits: number[]; - - constructor(camera: THREE.PerspectiveCamera, scene: THREE.Scene, renderer: THREE.WebGLRenderer, numCascades: number = 5) { - this._camera = camera; - this._scene = scene; - this._renderer = renderer; - this._numCascades = numCascades; + private _camera: THREE.PerspectiveCamera + private _shadowCameras: THREE.OrthographicCamera[] = [] + private _scene: THREE.Scene + + private _renderer: THREE.WebGLRenderer + private _light: THREE.DirectionalLight + private _shadowMaps: THREE.WebGLRenderTarget[] = [] // One shadow map per cascade + + private _numCascades: number + private _cascadeSplits: number[] + + constructor( + camera: THREE.PerspectiveCamera, + scene: THREE.Scene, + renderer: THREE.WebGLRenderer, + numCascades: number = 5 + ) { + this._camera = camera + this._scene = scene + this._renderer = renderer + this._numCascades = numCascades // Creating the directional light - this._light = new THREE.DirectionalLight(0xffffff, 3); - this._light.position.set(-1, 3, 2); - this._light.castShadow = true; - scene.add(this._light); - + this._light = new THREE.DirectionalLight(0xffffff, 3) + this._light.position.set(-1, 3, 2) + this._light.castShadow = true + scene.add(this._light) + // Split the camera frustum into numCascades cascades - this._cascadeSplits = new Array(numCascades).fill(0).map((_, i) => (i + 1) / numCascades); + this._cascadeSplits = new Array(numCascades).fill(0).map((_, i) => (i + 1) / numCascades) // Create the shadow camera and shadow maps for (let i = 0; i < numCascades; i++) { - const shadowCamera = new THREE.OrthographicCamera(-10, 10, 10, -10, 0.5, 500); - shadowCamera.position.copy(this._light.position); - shadowCamera.lookAt(0, 0, 0); - this._shadowCameras.push(shadowCamera); + const shadowCamera = new THREE.OrthographicCamera(-10, 10, 10, -10, 0.5, 500) + shadowCamera.position.copy(this._light.position) + shadowCamera.lookAt(0, 0, 0) + this._shadowCameras.push(shadowCamera) const shadowMap = new THREE.WebGLRenderTarget(1024, 1024, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat, - }); - this._shadowMaps.push(shadowMap); + }) + this._shadowMaps.push(shadowMap) } } /** Updates the shadow maps */ public Update() { - const frustum = new THREE.Frustum(); - const projScreenMatrix = new THREE.Matrix4(); - projScreenMatrix.multiplyMatrices(this._camera.projectionMatrix, this._camera.matrixWorldInverse); - frustum.setFromProjectionMatrix(projScreenMatrix); + const frustum = new THREE.Frustum() + const projScreenMatrix = new THREE.Matrix4() + projScreenMatrix.multiplyMatrices(this._camera.projectionMatrix, this._camera.matrixWorldInverse) + frustum.setFromProjectionMatrix(projScreenMatrix) for (let i = 0; i < this._numCascades; i++) { - const near = this._camera.near + this._cascadeSplits[i] * (this._camera.far - this._camera.near); - const far = this._camera.near + this._cascadeSplits[i + 1] * (this._camera.far - this._camera.near); + const near = this._camera.near + this._cascadeSplits[i] * (this._camera.far - this._camera.near) + const far = this._camera.near + this._cascadeSplits[i + 1] * (this._camera.far - this._camera.near) - const cascadeFrustum = new THREE.Frustum(); - cascadeFrustum.setFromProjectionMatrix(this.GetCascadeMatrix(near, far)); + const cascadeFrustum = new THREE.Frustum() + cascadeFrustum.setFromProjectionMatrix(this.GetCascadeMatrix(near, far)) - const shadowCamera = this._shadowCameras[i]; - shadowCamera.position.copy(this._light.position); - shadowCamera.lookAt(0, 0, 0); - shadowCamera.updateMatrixWorld(); - shadowCamera.updateProjectionMatrix(); + const shadowCamera = this._shadowCameras[i] + shadowCamera.position.copy(this._light.position) + shadowCamera.lookAt(0, 0, 0) + shadowCamera.updateMatrixWorld() + shadowCamera.updateProjectionMatrix() - this._renderer.setRenderTarget(this._shadowMaps[i]); - this._renderer.render(this._scene, shadowCamera); + this._renderer.setRenderTarget(this._shadowMaps[i]) + this._renderer.render(this._scene, shadowCamera) } - this._renderer.setRenderTarget(null); + this._renderer.setRenderTarget(null) } /** Returns the projection matrix for the cascade */ private GetCascadeMatrix(near: number, far: number): THREE.Matrix4 { - const matrix = new THREE.Matrix4(); + const matrix = new THREE.Matrix4() const projMatrix = new THREE.Matrix4().makePerspective( this._camera.fov, this._camera.aspect, @@ -80,15 +85,15 @@ class CascadingShadows { far, this._camera.near, this._camera.far - ); - const invMatrix = this._camera.matrixWorld.invert(); - matrix.multiplyMatrices(projMatrix, invMatrix); - return matrix; + ) + const invMatrix = this._camera.matrixWorld.invert() + matrix.multiplyMatrices(projMatrix, invMatrix) + return matrix } public getShadowMap(index: number): THREE.WebGLRenderTarget { - return this._shadowMaps[index]; + return this._shadowMaps[index] } } -export default CascadingShadows; +export default CascadingShadows From e716d0ba0c329f0ae3644981a36d0360f5536928 Mon Sep 17 00:00:00 2001 From: arorad1 Date: Mon, 22 Jul 2024 11:57:24 -0700 Subject: [PATCH 021/344] Changing light color and intensity and removing old lighting implementation --- fission/src/systems/scene/CascadingShadows.ts | 2 +- fission/src/systems/scene/SceneRenderer.ts | 18 ------------------ 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/fission/src/systems/scene/CascadingShadows.ts b/fission/src/systems/scene/CascadingShadows.ts index 65d7a11667..b1b9d5b4a2 100644 --- a/fission/src/systems/scene/CascadingShadows.ts +++ b/fission/src/systems/scene/CascadingShadows.ts @@ -24,7 +24,7 @@ class CascadingShadows { this._numCascades = numCascades // Creating the directional light - this._light = new THREE.DirectionalLight(0xffffff, 3) + this._light = new THREE.DirectionalLight(0xb6c6e3, 5.0) this._light.position.set(-1, 3, 2) this._light.castShadow = true scene.add(this._light) diff --git a/fission/src/systems/scene/SceneRenderer.ts b/fission/src/systems/scene/SceneRenderer.ts index 6458b6508a..a4af554078 100644 --- a/fission/src/systems/scene/SceneRenderer.ts +++ b/fission/src/systems/scene/SceneRenderer.ts @@ -77,24 +77,6 @@ class SceneRenderer extends WorldSystem { this._renderer.shadowMap.type = THREE.PCFSoftShadowMap this._renderer.setSize(window.innerWidth, window.innerHeight) - // const directionalLight = new THREE.DirectionalLight(0xffffff, 3.0) - // directionalLight.position.set(-1.0, 3.0, 2.0) - // directionalLight.castShadow = true - // this._scene.add(directionalLight) - - // const shadowMapSize = Math.min(4096, this._renderer.capabilities.maxTextureSize) - // const shadowCamSize = 15 - // console.debug(`Shadow Map Size: ${shadowMapSize}`) - - // directionalLight.shadow.camera.top = shadowCamSize - // directionalLight.shadow.camera.bottom = -shadowCamSize - // directionalLight.shadow.camera.left = -shadowCamSize - // directionalLight.shadow.camera.right = shadowCamSize - // directionalLight.shadow.mapSize = new THREE.Vector2(shadowMapSize, shadowMapSize) - // directionalLight.shadow.blurSamples = 16 - // directionalLight.shadow.normalBias = 0.01 - // directionalLight.shadow.bias = 0.0 - this._light = new CascadingShadows(this._mainCamera, this._scene, this._renderer) const ambientLight = new THREE.AmbientLight(0xffffff, 0.1) From adeccb677c6182190abd756b1ae4a78e4ebad740 Mon Sep 17 00:00:00 2001 From: arorad1 Date: Mon, 22 Jul 2024 15:05:05 -0700 Subject: [PATCH 022/344] Adding basic UI for displaying joints --- fission/src/mirabuf/MirabufSceneObject.ts | 4 + .../behavior/ArcadeDriveBehavior.ts | 4 + .../simulation/behavior/GenericArmBehavior.ts | 4 + .../behavior/GenericElevatorBehavior.ts | 4 + .../systems/simulation/driver/HingeDriver.ts | 4 + .../systems/simulation/driver/SliderDriver.ts | 4 + .../synthesis_brain/SynthesisBrain.ts | 4 + .../configuring/ConfigureRobotBrainPanel.tsx | 150 +++++++++++++++--- 8 files changed, 159 insertions(+), 19 deletions(-) diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 4c4cdd1414..8dfc12fa8a 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -56,6 +56,10 @@ class MirabufSceneObject extends SceneObject { return this._mechanism } + get brain() { + return this._brain + } + get assemblyName() { return this._assemblyName } diff --git a/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts b/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts index 374f5684d9..8d4392e349 100644 --- a/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts +++ b/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts @@ -12,6 +12,10 @@ class ArcadeDriveBehavior extends Behavior { private _driveSpeed = 30 private _turnSpeed = 30 + public get wheels(): WheelDriver[] { + return this.leftWheels.concat(this.rightWheels) + } + constructor( leftWheels: WheelDriver[], rightWheels: WheelDriver[], diff --git a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts index fc9e9bbd5d..6572bdb94a 100644 --- a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts @@ -11,6 +11,10 @@ class GenericArmBehavior extends Behavior { private _rotationalSpeed = 6 + public get hingeDriver(): HingeDriver { + return this._hingeDriver + } + constructor( hingeDriver: HingeDriver, hingeStimulus: HingeStimulus, diff --git a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts index f1c01bde56..811b9211ec 100644 --- a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts @@ -11,6 +11,10 @@ class GenericElevatorBehavior extends Behavior { private _linearSpeed = 2.5 + public get sliderDriver(): SliderDriver { + return this._sliderDriver + } + constructor( sliderDriver: SliderDriver, sliderStimulus: SliderStimulus, diff --git a/fission/src/systems/simulation/driver/HingeDriver.ts b/fission/src/systems/simulation/driver/HingeDriver.ts index 6940afbc8c..f747c99f48 100644 --- a/fission/src/systems/simulation/driver/HingeDriver.ts +++ b/fission/src/systems/simulation/driver/HingeDriver.ts @@ -10,6 +10,10 @@ class HingeDriver extends Driver { private _targetVelocity: number = 0.0 private _targetAngle: number + public get constraint(): Jolt.HingeConstraint { + return this._constraint + } + public get targetVelocity(): number { return this._targetVelocity } diff --git a/fission/src/systems/simulation/driver/SliderDriver.ts b/fission/src/systems/simulation/driver/SliderDriver.ts index 6bf361d445..68fff04d85 100644 --- a/fission/src/systems/simulation/driver/SliderDriver.ts +++ b/fission/src/systems/simulation/driver/SliderDriver.ts @@ -10,6 +10,10 @@ class SliderDriver extends Driver { private _targetVelocity: number = 0.0 private _targetPosition: number = 0.0 + public get constraint(): Jolt.SliderConstraint { + return this._constraint + } + public get targetVelocity(): number { return this._targetVelocity } diff --git a/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts b/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts index a44411acaf..77030adae0 100644 --- a/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts +++ b/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts @@ -28,6 +28,10 @@ class SynthesisBrain extends Brain { private _assemblyName: string private _assemblyIndex: number = 0 + public get behaviors(): Behavior[] { + return this._behaviors + } + // Tracks the number of each specific mira file spawned public static numberRobotsSpawned: { [key: string]: number } = {} diff --git a/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx b/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx index ad04d5bb68..5ed2db3995 100644 --- a/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx @@ -1,5 +1,5 @@ import { MiraType } from "@/mirabuf/MirabufLoader" -import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import MirabufSceneObject, { RigidNodeAssociate } from "@/mirabuf/MirabufSceneObject" import World from "@/systems/World" import Label, { LabelSize } from "@/ui/components/Label" import Button from "@/ui/components/Button" @@ -8,6 +8,11 @@ import { useMemo, useState } from "react" import { FaGear } from "react-icons/fa6" import { ToggleButton, ToggleButtonGroup } from "@/ui/components/ToggleButtonGroup" import { Divider, styled } from "@mui/material" +import ArcadeDriveBehavior from "@/systems/simulation/behavior/ArcadeDriveBehavior" +import GenericArmBehavior from "@/systems/simulation/behavior/GenericArmBehavior" +import GenericElevatorBehavior from "@/systems/simulation/behavior/GenericElevatorBehavior" +import Stack, { StackDirection } from "@/ui/components/Stack" +import { JSX } from "react/jsx-runtime" // eslint-disable-next-line react-refresh/only-export-components export enum ConfigureRobotBrainTypes { @@ -24,6 +29,127 @@ const DividerStyled = styled(Divider)({ borderColor: "white", }) +/** + * Retrieves the joints of a robot and generates JSX elements for each joint. + * @param robot The MirabufSceneObject representing the robot. + * @returns An array of JSX elements representing the joints of the robot. + */ +function GetJoints(robot: MirabufSceneObject): JSX.Element[] { + const output: JSX.Element[] = [] + let elementKey = 0 + + /* Iterate through each behavior of the robot */ + robot.brain?.behaviors.forEach(behavior => { + /* 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) @@ -83,29 +209,15 @@ const ConfigureRobotBrainPanel: React.FC = ({ panelId, openLocat {viewType === ConfigureRobotBrainTypes.SYNTHESIS ? ( <> - Behaviors - - - - - thing2 - - - - - thing3 + Behaviors - - - thing4 - - + {GetJoints(selectedRobot)} ) : ( <> - Example WIPLIB Brain + Example WIPLIB Brain @@ -122,7 +234,7 @@ const ConfigureRobotBrainPanel: React.FC = ({ panelId, openLocat Example 4 - + )} From 676130ea572dc889d7524185f252c6b30864aab1 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Tue, 23 Jul 2024 10:57:40 -0700 Subject: [PATCH 023/344] Working gamepiece config tab --- .../src/Parser/ExporterOptions.py | 5 +- .../src/UI/ConfigCommand.py | 49 ++- .../src/UI/GamepieceConfigTab.py | 326 ++++++++++++++++++ .../src/UI/GeneralConfigTab.py | 17 +- .../src/UI/JointConfigTab.py | 19 +- .../src/UI/ShowAPSAuthCommand.py | 9 +- 6 files changed, 382 insertions(+), 43 deletions(-) create mode 100644 exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py diff --git a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py index 603e71d382..b3a3507ce9 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py +++ b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py @@ -50,7 +50,7 @@ class Joint: @dataclass class Gamepiece: occurrenceToken: str = field(default=None) - weight: float = field(default=None) + weight: KG = field(default=None) friction: float = field(default=None) @@ -98,7 +98,8 @@ class ExporterOptions: # Always stored in kg regardless of 'preferredUnits' robotWeight: KG = field(default=0.0) - autoCalcWeight: bool = field(default=False) + autoCalcRobotWeight: bool = field(default=False) + autoCalcGamepieceWeight: bool = field(default=False) compressOutput: bool = field(default=True) exportAsPart: bool = field(default=False) diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index 8e327d04d8..7f6b52c5f3 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -18,6 +18,7 @@ from ..Parser.SynthesisParser.Parser import Parser from . import CustomGraphics, FileDialogConfig, Helper, IconPaths from .Configuration.SerialCommand import SerialCommand +from .GamepieceConfigTab import GamepieceConfigTab from .GeneralConfigTab import GeneralConfigTab # Transition: AARD-1685 @@ -29,6 +30,7 @@ generalConfigTab: GeneralConfigTab jointConfigTab: JointConfigTab +gamepieceConfigTab: GamepieceConfigTab """ INPUTS_ROOT (adsk.fusion.CommandInputs): @@ -131,6 +133,10 @@ def notify(self, args): global generalConfigTab generalConfigTab = GeneralConfigTab(args, exporterOptions) + global gamepieceConfigTab + gamepieceConfigTab = GamepieceConfigTab(args, exporterOptions) + gamepieceConfigTab.isVisible = True # TODO: Should not be visible by default. + # ~~~~~~~~~~~~~~~~ HELP FILE ~~~~~~~~~~~~~~~~ """ Sets the small "i" icon in bottom left of the panel. @@ -140,6 +146,7 @@ def notify(self, args): global jointConfigTab jointConfigTab = JointConfigTab(args) + jointConfigTab.isVisible = False # Transition: AARD-1685 # There remains some overlap between adding joints as wheels. @@ -437,7 +444,7 @@ def notify(self, args): exportMode=generalConfigTab.exportMode, compressOutput=generalConfigTab.compress, exportAsPart=generalConfigTab.exportAsPart, - autoCalcWeight=generalConfigTab.autoCalculateWeight, + autoCalcRobotWeight=generalConfigTab.autoCalculateWeight, ) Parser(exporterOptions).export() @@ -481,19 +488,20 @@ def notify(self, args): # Transition: AARD-1685 # This is how all preview handles should be done in the future jointConfigTab.handlePreviewEvent(args) + gamepieceConfigTab.handlePreviewEvent(args) - gamepieceTableInput = gamepieceTable() - if gamepieceTableInput.rowCount <= 1: - removeFieldInput.isEnabled = auto_calc_weight_f.isEnabled = False - else: - removeFieldInput.isEnabled = auto_calc_weight_f.isEnabled = True - - if not addFieldInput.isEnabled or not removeFieldInput: - for gamepiece in GamepieceListGlobal: - gamepiece.component.opacity = 0.25 - CustomGraphics.createTextGraphics(gamepiece, GamepieceListGlobal) - else: - gm.app.activeDocument.design.rootComponent.opacity = 1 + # gamepieceTableInput = gamepieceTable() + # if gamepieceTableInput.rowCount <= 1: + # removeFieldInput.isEnabled = auto_calc_weight_f.isEnabled = False + # else: + # removeFieldInput.isEnabled = auto_calc_weight_f.isEnabled = True + + # if not addFieldInput.isEnabled or not removeFieldInput: + # for gamepiece in GamepieceListGlobal: + # gamepiece.component.opacity = 0.25 + # CustomGraphics.createTextGraphics(gamepiece, GamepieceListGlobal) + # else: + # gm.app.activeDocument.design.rootComponent.opacity = 1 except AttributeError: pass except: @@ -665,11 +673,15 @@ def notify(self, args: adsk.core.SelectionEventArgs): # selectionInput.isEnabled = False # selectionInput.isVisible = False + if gamepieceConfigTab.isVisible: + self.cmd.setCursor("", 0, 0) # Reset select cursor back to normal cursor. + gamepieceConfigTab.handleSelectionEvent(args, args.selection.entity) + # Transition: AARD-1685 # This is how all handle selection events should be done in the future although it will look # slightly differently for each type of handle. - if self.selectedJoint: - jointConfigTab.handleSelectionEvent(args, self.selectedJoint) + if jointConfigTab.isVisible: + jointConfigTab.handleSelectionEvent(args, args.selection.entity) except: if gm.ui: gm.ui.messageBox("Failed:\n{}".format(traceback.format_exc())) @@ -807,9 +819,14 @@ def notify(self, args): # Transition: AARD-1685 # Should be how all input changed handles are done in the future - jointConfigTab.handleInputChanged(args, INPUTS_ROOT) generalConfigTab.handleInputChanged(args) + if jointConfigTab.isVisible: + jointConfigTab.handleInputChanged(args, INPUTS_ROOT) + + if gamepieceConfigTab.isVisible: + gamepieceConfigTab.handleInputChanged(args, INPUTS_ROOT) + MySelectHandler.lastInputCmd = cmdInput inputs = cmdInput.commandInputs onSelect = gm.handlers[3] diff --git a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py new file mode 100644 index 0000000000..d69be45c2b --- /dev/null +++ b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py @@ -0,0 +1,326 @@ +import adsk.core +import adsk.fusion + +from ..Parser.ExporterOptions import ExporterOptions, Gamepiece, PreferredUnits +from ..TypesTmp import toKg, toLbs +from . import IconPaths +from .CreateCommandInputsHelper import ( + createBooleanInput, + createTableInput, + createTextBoxInput, +) + + +class GamepieceConfigTab: + selectedGamepieceList: list[adsk.fusion.Occurrence] = [] + selectedGamepieceEntityIDs: set[str] = set() + gamepieceConfigTab: adsk.core.TabCommandInput + gamepieceTable: adsk.core.TableCommandInput + previousAutoCalcWeightCheckboxState: bool + previousSelectedUnitDropdownIndex: int + currentUnits: PreferredUnits + + def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: ExporterOptions) -> None: + inputs = args.command.commandInputs + self.gamepieceConfigTab = inputs.addTabCommandInput("gamepieceSettings", "Gamepiece Settings") + self.gamepieceConfigTab.tooltip = "Field gamepiece configuration options." + gamepieceTabInputs = self.gamepieceConfigTab.children + + # Invisible by default, only becomes visible when the current export mode is static or 'field'. + self.gamepieceConfigTab.isVisible = False + + createBooleanInput( + "autoCalcGamepieceWeight", + "Auto Calculate Gamepiece Weight", + gamepieceTabInputs, + checked=exporterOptions.autoCalcGamepieceWeight, + tooltip="Approximate the weight of each selected gamepiece.", + ) + 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", + gamepieceTabInputs, + 4, + "8:5:12", + ) + + self.gamepieceTable.addCommandInput( + createTextBoxInput("gamepieceNameHeader", "Name", gamepieceTabInputs, "Name", bold=False), 0, 0 + ) + self.gamepieceTable.addCommandInput( + createTextBoxInput("gamepieceWeightHeader", "Weight", gamepieceTabInputs, "Weight", bold=False), 0, 1 + ) + self.gamepieceTable.addCommandInput( + createTextBoxInput( + "frictionHeader", + "Gamepiece Friction Coefficient", + gamepieceTabInputs, + "Gamepiece Friction Coefficient", + background="#d9d9d9", + ), + 0, + 2, + ) + gamepieceTabInputs.addTextBoxCommandInput("gamepieceTabBlankSpacer", "", "", 1, True) + + gamepieceSelectCancelButton = gamepieceTabInputs.addBoolValueInput( + "gamepieceSelectCancelButton", "Cancel", False + ) + gamepieceSelectCancelButton.isEnabled = gamepieceSelectCancelButton.isVisible = False + + gamepieceAddButton = gamepieceTabInputs.addBoolValueInput("gamepieceAddButton", "Add", False) + gamepieceRemoveButton = gamepieceTabInputs.addBoolValueInput("gamepieceRemoveButton", "Remove", False) + gamepieceAddButton.isEnabled = gamepieceRemoveButton.isEnabled = True + + gamepieceSelect = gamepieceTabInputs.addSelectionInput( + "gamepieceSelect", "Selection", "Select the unique gamepieces in your field." + ) + gamepieceSelect.addSelectionFilter("Occurrences") + gamepieceSelect.setSelectionLimits(0) + gamepieceSelect.isEnabled = gamepieceSelect.isVisible = False # Visibility is triggered by `gamepieceAddButton` + + self.gamepieceTable.addToolbarCommandInput(gamepieceAddButton) + self.gamepieceTable.addToolbarCommandInput(gamepieceRemoveButton) + self.gamepieceTable.addToolbarCommandInput(gamepieceSelectCancelButton) + + @property + def isVisible(self) -> bool: + return self.gamepieceConfigTab.isVisible + + @isVisible.setter + def isVisible(self, value: bool) -> None: + self.gamepieceConfigTab.isVisible = value + + @property + def weightInputs(self) -> list[adsk.core.ValueCommandInput]: + gamepieceWeightInputs = [] + for row in range(1, self.gamepieceTable.rowCount): # Row is 1 indexed + gamepieceWeightInputs.append(self.gamepieceTable.getInputAtPosition(row, 1)) + + return gamepieceWeightInputs + + def addGamepiece(self, gamepiece: adsk.fusion.Occurrence, synGamepiece: Gamepiece | None = None) -> bool: + if gamepiece.entityToken in self.selectedGamepieceEntityIDs: + return False + + def addChildOccurrences(childOccurrences: adsk.fusion.OccurrenceList) -> None: + for occ in childOccurrences: + self.selectedGamepieceEntityIDs.add(occ.entityToken) + + if occ.childOccurrences: + addChildOccurrences(occ.childOccurrences) + + if gamepiece.childOccurrences: + addChildOccurrences(gamepiece.childOccurrences) + else: + self.selectedGamepieceEntityIDs.add(gamepiece.entityToken) + + self.selectedGamepieceList.append(gamepiece) + + commandInputs = self.gamepieceTable.commandInputs + gamepieceName = commandInputs.addTextBoxCommandInput( + "gamepieceName", "Occurrence Name", gamepiece.name, 1, True + ) + gamepieceName.tooltip = gamepiece.name + + valueList = [1] + [i / 20 for i in range(20)] + frictionCoefficient = commandInputs.addFloatSliderListCommandInput( + "gamepieceFrictionCoefficient", "", "", valueList + ) + frictionCoefficient.tooltip = "Friction coefficient of field element" + frictionCoefficient.tooltipDescription = "Friction coefficients range from 0 (ice) to 1 (rubber)." + if synGamepiece: + frictionCoefficient.valueOne = synGamepiece.friction + + physical = gamepiece.component.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy) + if self.currentUnits == PreferredUnits.IMPERIAL: + gamepieceMass = toLbs(physical.mass) + else: + gamepieceMass = round(physical.mass, 2) + + weight = commandInputs.addValueInput( + "gamepieceWeight", "Weight Input", "", adsk.core.ValueInput.createByString(str(gamepieceMass)) + ) + weight.tooltip = "Weight of field element" + + 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) + self.gamepieceTable.addCommandInput(frictionCoefficient, row, 2) + + return True + + def removeIndexedGamepiece(self, index: int) -> None: + self.removeGamepiece(self.selectedGamepieceList[index]) + + def removeGamepiece(self, gamepiece: adsk.fusion.Occurrence) -> None: + def removeChildOccurrences(childOccurrences: adsk.fusion.OccurrenceList) -> None: + for occ in childOccurrences: + self.selectedGamepieceEntityIDs.remove(occ.entityToken) + + if occ.childOccurrences: + removeChildOccurrences(occ.childOccurrences) + + if gamepiece.childOccurrences: + removeChildOccurrences(gamepiece.childOccurrences) + else: + self.selectedGamepieceEntityIDs.remove(gamepiece.entityToken) + + i = self.selectedGamepieceList.index(gamepiece) + self.selectedGamepieceList.remove(gamepiece) + self.gamepieceTable.deleteRow(i + 1) # Row is 1 indexed + + 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): + weightInput: adsk.core.ValueCommandInput = self.gamepieceTable.getInputAtPosition(row, 1) + weightInput.value = conversionFunc(weightInput.value) + + def calcGamepieceWeights(self) -> None: + for row in range(1, self.gamepieceTable.rowCount): # Row is 1 indexed + weightInput: adsk.core.ValueCommandInput = self.gamepieceTable.getInputAtPosition(row, 1) + 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) + + def handleInputChanged( + self, args: adsk.core.InputChangedEventArgs, globalCommandInputs: adsk.core.CommandInputs + ) -> None: + commandInput = args.input + if commandInput.id == "autoCalcGamepieceWeight": + autoCalcWeightButton = adsk.core.BoolValueCommandInput.cast(commandInput) + if autoCalcWeightButton.value == self.previousAutoCalcWeightCheckboxState: + return + + if autoCalcWeightButton.value: + self.calcGamepieceWeights() + for weightInput in self.weightInputs: + weightInput.isEnabled = False + else: + for weightInput in self.weightInputs: + weightInput.isEnabled = True + + 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": + gamepieceAddButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("gamepieceAddButton") + gamepieceRemoveButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById( + "gamepieceRemoveButton" + ) + gamepieceSelectCancelButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById( + "gamepieceSelectCancelButton" + ) + gamepieceSelection: adsk.core.SelectionCommandInput = self.gamepieceConfigTab.children.itemById( + "gamepieceSelect" + ) + + gamepieceSelection.isVisible = gamepieceSelection.isEnabled = True + gamepieceSelection.clearSelection() + gamepieceAddButton.isEnabled = gamepieceRemoveButton.isEnabled = False + gamepieceSelectCancelButton.isVisible = gamepieceSelectCancelButton.isEnabled = True + + elif commandInput.id == "gamepieceRemoveButton": + gamepieceAddButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("gamepieceAddButton") + gamepieceTable: adsk.core.TableCommandInput = args.inputs.itemById("gamepieceTable") + + gamepieceAddButton.isEnabled = True + if gamepieceTable.selectedRow == -1 or gamepieceTable.selectedRow == 0: + ui = adsk.core.Application.get().userInterface + ui.messageBox("Select a row to delete.") + else: + self.removeIndexedGamepiece(gamepieceTable.selectedRow - 1) # selectedRow is 1 indexed + + elif commandInput.id == "gamepieceSelectCancelButton": + gamepieceAddButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById("gamepieceAddButton") + gamepieceRemoveButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById( + "gamepieceRemoveButton" + ) + gamepieceSelectCancelButton: adsk.core.BoolValueCommandInput = globalCommandInputs.itemById( + "gamepieceSelectCancelButton" + ) + gamepieceSelection: adsk.core.SelectionCommandInput = self.gamepieceConfigTab.children.itemById( + "gamepieceSelect" + ) + + gamepieceSelection.isEnabled = gamepieceSelection.isVisible = False + gamepieceSelectCancelButton.isEnabled = gamepieceSelectCancelButton.isVisible = False + gamepieceAddButton.isEnabled = gamepieceRemoveButton.isEnabled = True + + def handleSelectionEvent(self, args: adsk.core.SelectionEventArgs, selectedOcc: adsk.fusion.Occurrence) -> None: + selectionInput = args.activeInput + rootComponent = adsk.core.Application.get().activeDocument.design.rootComponent + occurrenceList: list[adsk.fusion.Occurrence] = rootComponent.allOccurrencesByComponent(selectedOcc.component) + for occ in occurrenceList: + if not self.addGamepiece(occ): + ui = adsk.core.Application.get().userInterface + result = ui.messageBox( + "You have already selected this Gamepiece.\n" "Would you like to remove it?", + "Synthesis: Remove Gamepiece Confirmation", + adsk.core.MessageBoxButtonTypes.YesNoButtonType, + adsk.core.MessageBoxIconTypes.QuestionIconType, + ) + + if result == adsk.core.DialogResults.DialogYes: + self.removeGamepiece(occ) + + selectionInput.isEnabled = selectionInput.isVisible = False + + def handlePreviewEvent(self, args: adsk.core.CommandEventArgs) -> None: + commandInputs = args.command.commandInputs + gamepieceAddButton: adsk.core.BoolValueCommandInput = commandInputs.itemById("gamepieceAddButton") + gamepieceRemoveButton: adsk.core.BoolValueCommandInput = commandInputs.itemById("gamepieceRemoveButton") + gamepieceSelectCancelButton: adsk.core.BoolValueCommandInput = commandInputs.itemById( + "gamepieceSelectCancelButton" + ) + gamepieceSelection: adsk.core.SelectionCommandInput = self.gamepieceConfigTab.children.itemById( + "gamepieceSelect" + ) + + gamepieceRemoveButton.isEnabled = self.gamepieceTable.rowCount > 1 + if not gamepieceSelection.isEnabled: + gamepieceAddButton.isEnabled = True + gamepieceSelectCancelButton.isVisible = gamepieceSelectCancelButton.isEnabled = False diff --git a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py index 8867847a34..693068326f 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py @@ -15,6 +15,7 @@ class GeneralConfigTab: generalOptionsTab: adsk.core.TabCommandInput previousAutoCalcWeightCheckboxState: bool previousSelectedUnitDropdownIndex: int + currentUnits: PreferredUnits def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: ExporterOptions) -> None: try: @@ -53,11 +54,10 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp "autoCalcWeightButton", "Auto Calculate Robot Weight", generalTabInputs, - checked=exporterOptions.autoCalcWeight, + checked=exporterOptions.autoCalcRobotWeight, tooltip="Approximate the weight of your robot assembly.", - enabled=True, ) - self.previousAutoCalcWeightCheckboxState = exporterOptions.autoCalcWeight + self.previousAutoCalcWeightCheckboxState = exporterOptions.autoCalcRobotWeight self.currentUnits = exporterOptions.preferredUnits imperialUnits = self.currentUnits == PreferredUnits.IMPERIAL @@ -78,7 +78,7 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp f"(in {'pounds' if self.currentUnits == PreferredUnits.IMPERIAL else 'kilograms'})" "
This is the weight of the entire robot assembly." ) - weightInput.isEnabled = not exporterOptions.autoCalcWeight + weightInput.isEnabled = not exporterOptions.autoCalcRobotWeight weightUnitDropdown = generalTabInputs.addDropDownCommandInput( "weightUnitDropdown", @@ -148,14 +148,7 @@ def exportAsPart(self) -> bool: @property def selectedUnits(self) -> PreferredUnits: - weightUnitDropdown: adsk.core.DropDownCommandInput = self.generalOptionsTab.children.itemById( - "weightTable" - ).getInputAtPosition(0, 2) - if weightUnitDropdown.selectedItem.index == 0: - return PreferredUnits.IMPERIAL - else: - assert weightUnitDropdown.selectedItem.index == 1 - return PreferredUnits.METRIC + return self.currentUnits @property def robotWeight(self) -> KG: diff --git a/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py index 5f9be302b3..2b5fe08696 100644 --- a/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py @@ -117,6 +117,14 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs) -> None: except BaseException: logging.getLogger("{INTERNAL_ID}.UI.JointConfigTab").error("Failed:\n{}".format(traceback.format_exc())) + @property + def isVisible(self) -> bool: + return self.jointConfigTab.isVisible + + @isVisible.setter + def isVisible(self, value: bool) -> None: + self.jointConfigTab.isVisible = value + def addJoint(self, fusionJoint: adsk.fusion.Joint, synJoint: Joint | None = None) -> bool: try: if fusionJoint in self.selectedJointList: @@ -316,6 +324,9 @@ def addWheel(self, joint: adsk.fusion.Joint, wheel: Wheel | None = None) -> None signalType.tooltip = "Wheel signal type is linked with the respective joint signal type." i = self.selectedJointList.index(joint) jointSignalType = SignalType(self.jointConfigTable.getInputAtPosition(i + 1, 3).selectedItem.index + 1) + + # 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 signalType.listItems.add("‎", jointSignalType is SignalType.PWM, IconPaths.signalIcons["PWM"]) signalType.listItems.add("‎", jointSignalType is SignalType.CAN, IconPaths.signalIcons["CAN"]) signalType.listItems.add("‎", jointSignalType is SignalType.PASSIVE, IconPaths.signalIcons["PASSIVE"]) @@ -342,7 +353,7 @@ def removeJoint(self, joint: adsk.fusion.Joint) -> None: i = self.selectedJointList.index(joint) self.selectedJointList.remove(joint) self.previousWheelCheckboxState.pop(i) - self.jointConfigTable.deleteRow(i + 1) + self.jointConfigTable.deleteRow(i + 1) # Row is 1 indexed for row in range(1, self.jointConfigTable.rowCount): # Row is 1 indexed # TODO: Step through this in the debugger and figure out if this is all necessary. listItems = self.jointConfigTable.getInputAtPosition(row, 2).listItems @@ -534,11 +545,9 @@ def handlePreviewEvent(self, args: adsk.core.CommandEventArgs) -> None: jointSelectCancelButton: adsk.core.BoolValueCommandInput = commandInputs.itemById("jointSelectCancelButton") jointSelection: adsk.core.SelectionCommandInput = commandInputs.itemById("jointSelection") - if self.jointConfigTable.rowCount <= 1: - jointRemoveButton.isEnabled = False - + jointRemoveButton.isEnabled = self.jointConfigTable.rowCount > 1 if not jointSelection.isEnabled: - jointAddButton.isEnabled = jointRemoveButton.isEnabled = True + jointAddButton.isEnabled = True jointSelectCancelButton.isVisible = jointSelectCancelButton.isEnabled = False except BaseException: logging.getLogger("{INTERNAL_ID}.UI.JointConfigTab").error("Failed:\n{}".format(traceback.format_exc())) diff --git a/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py b/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py index bacc6e094b..12978dbc16 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py @@ -10,14 +10,7 @@ import adsk.core from src.APS.APS import CLIENT_ID, auth_path, convertAuthToken, getCodeChallenge -from src.general_imports import ( - APP_NAME, - DESCRIPTION, - INTERNAL_ID, - gm, - my_addin_path, - root_logger, -) +from src.general_imports import APP_NAME, DESCRIPTION, INTERNAL_ID, gm, my_addin_path palette = None From 2d6dca428f32a29bcb874ec2cfcfb2f4d4d23fe0 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Tue, 23 Jul 2024 10:59:51 -0700 Subject: [PATCH 024/344] Gamepiece config panel reset button --- exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py index d69be45c2b..8268c4abaf 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py @@ -99,6 +99,8 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp self.gamepieceTable.addToolbarCommandInput(gamepieceRemoveButton) self.gamepieceTable.addToolbarCommandInput(gamepieceSelectCancelButton) + self.reset() + @property def isVisible(self) -> bool: return self.gamepieceConfigTab.isVisible @@ -195,6 +197,10 @@ def removeChildOccurrences(childOccurrences: adsk.fusion.OccurrenceList) -> None self.selectedGamepieceList.remove(gamepiece) self.gamepieceTable.deleteRow(i + 1) # Row is 1 indexed + def reset(self) -> None: + self.selectedGamepieceEntityIDs.clear() + self.selectedGamepieceList.clear() + def updateWeightTableToUnits(self, units: PreferredUnits) -> None: assert units in {PreferredUnits.METRIC, PreferredUnits.IMPERIAL} conversionFunc = toKg if units == PreferredUnits.METRIC else toLbs From ffc2d3e3d3f36367b0e5d20c95ff61159865a766 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Tue, 23 Jul 2024 11:18:32 -0700 Subject: [PATCH 025/344] Swap between export modes now toggles active settings tabs --- .../src/UI/ConfigCommand.py | 4 ++-- .../src/UI/GeneralConfigTab.py | 23 ++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index 7f6b52c5f3..3e5d6089b1 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -135,7 +135,7 @@ def notify(self, args): global gamepieceConfigTab gamepieceConfigTab = GamepieceConfigTab(args, exporterOptions) - gamepieceConfigTab.isVisible = True # TODO: Should not be visible by default. + generalConfigTab.gamepieceConfigTab = gamepieceConfigTab # ~~~~~~~~~~~~~~~~ HELP FILE ~~~~~~~~~~~~~~~~ """ @@ -146,7 +146,7 @@ def notify(self, args): global jointConfigTab jointConfigTab = JointConfigTab(args) - jointConfigTab.isVisible = False + generalConfigTab.jointConfigTab = jointConfigTab # Transition: AARD-1685 # There remains some overlap between adding joints as wheels. diff --git a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py index 693068326f..074a53ec1a 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py @@ -9,13 +9,18 @@ from ..TypesTmp import KG, toKg, toLbs from . import IconPaths from .CreateCommandInputsHelper import createBooleanInput, createTableInput +from .GamepieceConfigTab import GamepieceConfigTab +from .JointConfigTab import JointConfigTab class GeneralConfigTab: generalOptionsTab: adsk.core.TabCommandInput previousAutoCalcWeightCheckboxState: bool previousSelectedUnitDropdownIndex: int + previousSelectedModeDropdownIndex: int currentUnits: PreferredUnits + jointConfigTab: JointConfigTab + gamepieceConfigTab: GamepieceConfigTab def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: ExporterOptions) -> None: try: @@ -35,6 +40,7 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp dropdownExportMode.listItems.add("Static", not dynamic) dropdownExportMode.tooltip = "Export Mode" dropdownExportMode.tooltipDescription = "
Does this object move dynamically?" + self.previousSelectedModeDropdownIndex = int(not dynamic) weightTableInput = createTableInput( "weightTable", @@ -171,7 +177,22 @@ def autoCalculateWeight(self) -> bool: def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: try: commandInput = args.input - if commandInput.id == "weightUnitDropdown": + if commandInput.id == "exportModeDropdown": + modeDropdown = adsk.core.DropDownCommandInput.cast(commandInput) + if modeDropdown.selectedItem.index == self.previousSelectedModeDropdownIndex: + return + + if modeDropdown.selectedItem.index == 0: + self.jointConfigTab.isVisible = True + self.gamepieceConfigTab.isVisible = False + else: + assert modeDropdown.selectedItem.index == 1 + self.jointConfigTab.isVisible = False + self.gamepieceConfigTab.isVisible = True + + self.previousSelectedModeDropdownIndex = modeDropdown.selectedItem.index + + elif commandInput.id == "weightUnitDropdown": weightUnitDropdown = adsk.core.DropDownCommandInput.cast(commandInput) weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") weightInput: adsk.core.ValueCommandInput = weightTable.getInputAtPosition(0, 1) From 1f936431a1166fc934c123144445398982181efd Mon Sep 17 00:00:00 2001 From: LucaHaverty Date: Tue, 23 Jul 2024 16:56:41 -0700 Subject: [PATCH 026/344] Basic parenting UI for sequential joints --- fission/src/Synthesis.tsx | 2 + .../simulation/behavior/GenericArmBehavior.ts | 6 + .../behavior/GenericElevatorBehavior.ts | 6 + fission/src/ui/components/MainHUD.tsx | 9 +- .../src/ui/panels/SequentialJointsPanel.tsx | 299 ++++++++++++++++++ .../configuring/ConfigureRobotBrainPanel.tsx | 6 +- 6 files changed, 322 insertions(+), 6 deletions(-) create mode 100644 fission/src/ui/panels/SequentialJointsPanel.tsx diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index dd3cf5985c..e39d2cddd1 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -67,6 +67,7 @@ import SceneOverlay from "./ui/components/SceneOverlay.tsx" import WPILibWSWorker from "@/systems/simulation/wpilib_brain/WPILibWSWorker.ts?worker" import WSViewPanel from "./ui/panels/WSViewPanel.tsx" import Lazy from "./util/Lazy.ts" +import SequentialJointsPanel from "./ui/panels/SequentialJointsPanel.tsx" const DEFAULT_MIRA_PATH = "/api/mira/Robots/Team 2471 (2018)_v7.mira" @@ -274,6 +275,7 @@ const initialPanels: ReactElement[] = [ sidePadding={8} />, , + , ] export default Synthesis diff --git a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts index 6572bdb94a..86243d0d45 100644 --- a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts @@ -6,6 +6,7 @@ import InputSystem from "@/systems/input/InputSystem" class GenericArmBehavior extends Behavior { private _hingeDriver: HingeDriver private _inputName: string + private _jointIndex: number private _assemblyName: string private _assemblyIndex: number @@ -15,6 +16,10 @@ class GenericArmBehavior extends Behavior { return this._hingeDriver } + public get jointIndex(): number { + return this._jointIndex + } + constructor( hingeDriver: HingeDriver, hingeStimulus: HingeStimulus, @@ -26,6 +31,7 @@ class GenericArmBehavior extends Behavior { this._hingeDriver = hingeDriver this._inputName = "joint " + jointIndex + this._jointIndex = jointIndex this._assemblyName = assemblyName this._assemblyIndex = assemblyIndex } diff --git a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts index 811b9211ec..90a8737e1a 100644 --- a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts @@ -6,6 +6,7 @@ import InputSystem from "@/systems/input/InputSystem" class GenericElevatorBehavior extends Behavior { private _sliderDriver: SliderDriver private _inputName: string + private _jointIndex: number private _assemblyName: string private _assemblyIndex: number @@ -15,6 +16,10 @@ class GenericElevatorBehavior extends Behavior { return this._sliderDriver } + public get jointIndex(): number { + return this._jointIndex + } + constructor( sliderDriver: SliderDriver, sliderStimulus: SliderStimulus, @@ -26,6 +31,7 @@ class GenericElevatorBehavior extends Behavior { this._sliderDriver = sliderDriver this._inputName = "joint " + jointIndex + this._jointIndex = jointIndex this._assemblyName = assemblyName this._assemblyIndex = assemblyIndex } diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 083a89f1fd..1715198771 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -181,7 +181,7 @@ const MainHUD: React.FC = () => { openPanel("scoring-zones") }} /> - } onClick={() => openModal("drivetrain")} /> + {/* } onClick={() => openModal("drivetrain")} /> } @@ -205,8 +205,13 @@ const MainHUD: React.FC = () => { const type: ToastType = ["info", "warning", "error"][Math.floor(Random() * 3)] as ToastType addToast(type, type, "This is a test toast to test the toast system") }} - /> + /> */} } onClick={() => openModal("config-robot")} /> + } + onClick={() => openPanel("sequential-joints")} + /> } diff --git a/fission/src/ui/panels/SequentialJointsPanel.tsx b/fission/src/ui/panels/SequentialJointsPanel.tsx new file mode 100644 index 0000000000..16b75585b2 --- /dev/null +++ b/fission/src/ui/panels/SequentialJointsPanel.tsx @@ -0,0 +1,299 @@ +import React, { useMemo, useReducer, useState } from "react" +import Panel, { PanelPropsImpl } from "../components/Panel" +import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import Label, { LabelSize } from "../components/Label" +import World from "@/systems/World" +import { MiraType } from "@/mirabuf/MirabufLoader" +import { FaArrowLeft, FaArrowRight, FaChevronDown, FaChevronUp, FaGear } from "react-icons/fa6" +import { Box, Button as MUIButton, Divider, styled } from "@mui/material" +import Button from "../components/Button" +import GenericArmBehavior from "@/systems/simulation/behavior/GenericArmBehavior" +import GenericElevatorBehavior from "@/systems/simulation/behavior/GenericElevatorBehavior" +import Behavior from "@/systems/simulation/behavior/Behavior" + +const ShiftRight = +const ShiftLeft = +const UpArrow = +const DownArrow = + +const LabelStyled = styled(Label)({ + fontWeight: 700, + margin: "0pt", +}) + +const ChildLabelStyled = styled(Label)({ + fontWeight: 700, + margin: "0pt", + color: "grey", +}) + +const DividerStyled = styled(Divider)({ + borderColor: "white", +}) + +function shiftArrayElementUp(arr: T[], elem: T): void { + const index = arr.indexOf(elem) + if (index > 0 && index < arr.length) { + let temp = arr[index] + arr[index] = arr[index - 1] + arr[index - 1] = temp + } +} + +function shiftArrayElementDown(arr: T[], elem: T): void { + const index = arr.indexOf(elem) + if (index >= 0 && index < arr.length - 1) { + let temp = arr[index] + arr[index] = arr[index + 1] + arr[index + 1] = temp + } +} + +type BehaviorConfiguration = { + behavior: Behavior + child: boolean +} + +interface BehaviorCardProps { + elementKey: number + name: string + behavior: BehaviorConfiguration + update: () => void + shiftUp: () => void + shiftDown: () => void + canShiftUp: boolean + canShiftDown: boolean + canBecomeChild: boolean +} + +const BehaviorCard: React.FC = ({ + elementKey, + name, + behavior, + update, + shiftUp, + shiftDown, + canShiftUp, + canShiftDown, + canBecomeChild, +}) => { + return ( + + + {behavior.child ? ( + <> + + + {name} + + + ) : ( + <> + + {name} + + + )} + + + + <> + + ) + })} + + + ) : ( + <> + + Behaviors + + + {GetBehaviorCards(behaviors, update)} + + )} + + ) +} + +export default SequentialJointsPanel diff --git a/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx b/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx index 5ed2db3995..ba3c7b85b3 100644 --- a/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureRobotBrainPanel.tsx @@ -77,8 +77,7 @@ function GetJoints(robot: MirabufSceneObject): JSX.Element[] { }) output.push() } else if (behavior instanceof GenericArmBehavior) { - - /* Adds the joints that the arm is associated with */ + /* 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() @@ -111,8 +110,7 @@ function GetJoints(robot: MirabufSceneObject): JSX.Element[] { ) elementKey++ } else if (behavior instanceof GenericElevatorBehavior) { - - /* Adds the joints that the elevator is associated with */ + /* 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() From 57c2f4a02c7b0b30c45200f7bcdedefaddfa1eed Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Wed, 24 Jul 2024 10:20:10 -0700 Subject: [PATCH 027/344] Working so so integration --- .../src/UI/ConfigCommand.py | 600 +++--------------- .../src/UI/GamepieceConfigTab.py | 17 +- 2 files changed, 98 insertions(+), 519 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index 5db66b066d..8b93885316 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -143,6 +143,14 @@ def notify(self, args): gamepieceConfigTab = GamepieceConfigTab(args, exporterOptions) generalConfigTab.gamepieceConfigTab = gamepieceConfigTab + if not exporterOptions.exportMode == ExportMode.FIELD: + gamepieceConfigTab.isVisible = False + + if exporterOptions.gamepieces: + for synGamepiece in exporterOptions.gamepieces: + fusionOccurrence = gm.app.activeDocument.design.findEntityByToken(synGamepiece.occurrenceToken)[0] + gamepieceConfigTab.addGamepiece(fusionOccurrence, synGamepiece) + # ~~~~~~~~~~~~~~~~ HELP FILE ~~~~~~~~~~~~~~~~ """ Sets the small "i" icon in bottom left of the panel. @@ -150,109 +158,13 @@ def notify(self, args): """ cmd.helpFile = os.path.join(".", "src", "Resources", "HTML", "info.html") - # ~~~~~~~~~~~~~~~~ EXPORT MODE ~~~~~~~~~~~~~~~~ - """ - Dropdown to choose whether to export robot or field element - """ - # TODO: Integration with GH-1004 - # dropdownExportMode = inputs.addDropDownCommandInput( - # "mode", - # "Export Mode", - # dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle, - # ) - - # dynamic = exporterOptions.exportMode == ExportMode.ROBOT - # dropdownExportMode.listItems.add("Dynamic", dynamic) - # dropdownExportMode.listItems.add("Static", not dynamic) - - # dropdownExportMode.tooltip = "Export Mode" - # dropdownExportMode.tooltipDescription = "
Does this object move dynamically?" - - # ~~~~~~~~~~~~~~~~ EXPORT LOCATION ~~~~~~~~~~~~~~~~~~ - - # dropdownExportLocation = inputs.addDropDownCommandInput( - # "location", "Export Location", dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle - # ) - - # upload: bool = exporterOptions.exportLocation == ExportLocation.UPLOAD - # dropdownExportLocation.listItems.add("Upload", upload) - # dropdownExportLocation.listItems.add("Download", not upload) - - # dropdownExportLocation.tooltip = "Export Location" - # dropdownExportLocation.tooltipDescription = ( - # "
Do you want to upload this mirabuf file to APS, or download it to your local machine?" - # ) - - # ~~~~~~~~~~~~~~~~ WEIGHT CONFIGURATION ~~~~~~~~~~~~~~~~ - """ - Table for weight config. - - Used this to align multiple commandInputs on the same row - """ - # weightTableInput = self.createTableInput( - # "weight_table", - # "Weight Table", - # inputs, - # 4, - # "3:2:2:1", - # 1, - # ) - # weightTableInput.tablePresentationStyle = 2 # set transparent background for table - - # weight_name = inputs.addStringValueInput("weight_name", "Weight") - # weight_name.value = "Weight" - # weight_name.isReadOnly = True - - # auto_calc_weight = self.createBooleanInput( - # "auto_calc_weight", - # "‎", - # inputs, - # checked=False, - # tooltip="Approximate the weight of your robot assembly.", - # tooltipadvanced="This may take a moment...", - # enabled=True, - # isCheckBox=False, - # ) - # auto_calc_weight.resourceFolder = IconPaths.stringIcons["calculate-enabled"] - # auto_calc_weight.isFullWidth = True - - # imperialUnits = exporterOptions.preferredUnits == PreferredUnits.IMPERIAL - # if imperialUnits: - # # ExporterOptions always contains the metric value - # displayWeight = exporterOptions.robotWeight * 2.2046226218 - # else: - # displayWeight = exporterOptions.robotWeight - - # weight_input = inputs.addValueInput( - # "weight_input", - # "Weight Input", - # "", - # adsk.core.ValueInput.createByReal(displayWeight), - # ) - # weight_input.tooltip = "Robot weight" - # weight_input.tooltipDescription = ( - # """(in pounds)
This is the weight of the entire robot assembly.""" - # ) - - # weight_unit = inputs.addDropDownCommandInput( - # "weight_unit", - # "Weight Unit", - # adsk.core.DropDownStyles.LabeledIconDropDownStyle, - # ) - - # weight_unit.listItems.add("‎", imperialUnits, IconPaths.massIcons["LBS"]) - # weight_unit.listItems.add("‎", not imperialUnits, IconPaths.massIcons["KG"]) - # weight_unit.tooltip = "Unit of mass" - # weight_unit.tooltipDescription = "
Configure the unit of mass for the weight calculation." - - # weightTableInput.addCommandInput(weight_name, 0, 0) # add command inputs to table - # weightTableInput.addCommandInput(auto_calc_weight, 0, 1) # add command inputs to table - # weightTableInput.addCommandInput(weight_input, 0, 2) # add command inputs to table - # weightTableInput.addCommandInput(weight_unit, 0, 3) # add command inputs to table - global jointConfigTab jointConfigTab = JointConfigTab(args) generalConfigTab.jointConfigTab = jointConfigTab + if not exporterOptions.exportMode == ExportMode.ROBOT: + jointConfigTab.isVisible = False + # Transition: AARD-1685 # There remains some overlap between adding joints as wheels. # Should investigate changes to improve performance. @@ -279,129 +191,6 @@ def notify(self, args): fusionJoint = gm.app.activeDocument.design.findEntityByToken(wheel.jointToken)[0] jointConfigTab.addWheel(fusionJoint, wheel) - # ~~~~~~~~~~~~~~~~ GAMEPIECE CONFIGURATION ~~~~~~~~~~~~~~~~ - """ - Gamepiece group command input, isVisible=False by default - - Container for gamepiece selection table - """ - # gamepieceConfig = inputs.addGroupCommandInput("gamepiece_config", "Gamepiece Configuration") - # gamepieceConfig.isExpanded = True - # gamepieceConfig.isVisible = False - # gamepieceConfig.tooltip = "Select and define the gamepieces in your field." - # gamepiece_inputs = gamepieceConfig.children - - # # GAMEPIECE MASS CONFIGURATION - # """ - # Mass unit dropdown and calculation for gamepiece elements - # """ - # weightTableInput_f = self.createTableInput( - # "weight_table_f", "Weight Table", gamepiece_inputs, 3, "6:2:1", 1 - # ) - # weightTableInput_f.tablePresentationStyle = 2 # set to clear background - - # weight_name_f = gamepiece_inputs.addStringValueInput("weight_name", "Weight") - # weight_name_f.value = "Unit of Mass" - # weight_name_f.isReadOnly = True - - # auto_calc_weight_f = self.createBooleanInput( # CALCULATE button - # "auto_calc_weight_f", - # "‎", - # gamepiece_inputs, - # checked=False, - # tooltip="Approximate the weight of all your selected gamepieces.", - # enabled=True, - # isCheckBox=False, - # ) - # auto_calc_weight_f.resourceFolder = IconPaths.stringIcons["calculate-enabled"] - # auto_calc_weight_f.isFullWidth = True - - # weight_unit_f = gamepiece_inputs.addDropDownCommandInput( - # "weight_unit_f", - # "Unit of Mass", - # adsk.core.DropDownStyles.LabeledIconDropDownStyle, - # ) - # weight_unit_f.listItems.add("‎", True, IconPaths.massIcons["LBS"]) # add listdropdown mass options - # weight_unit_f.listItems.add("‎", False, IconPaths.massIcons["KG"]) # add listdropdown mass options - # weight_unit_f.tooltip = "Unit of mass" - # weight_unit_f.tooltipDescription = "
Configure the unit of mass for for the weight calculation." - - # weightTableInput_f.addCommandInput(weight_name_f, 0, 0) # add command inputs to table - # weightTableInput_f.addCommandInput(auto_calc_weight_f, 0, 1) # add command inputs to table - # weightTableInput_f.addCommandInput(weight_unit_f, 0, 2) # add command inputs to table - - # # GAMEPIECE SELECTION TABLE - # """ - # All selected gamepieces appear here - # """ - # gamepieceTableInput = self.createTableInput( - # "gamepiece_table", - # "Gamepiece", - # gamepiece_inputs, - # 4, - # "1:8:5:12", - # 50, - # ) - - # addFieldInput = gamepiece_inputs.addBoolValueInput("field_add", "Add", False) - - # removeFieldInput = gamepiece_inputs.addBoolValueInput("field_delete", "Remove", False) - # addFieldInput.isEnabled = removeFieldInput.isEnabled = True - - # removeFieldInput.tooltip = "Remove a field element" - # addFieldInput.tooltip = "Add a field element" - - # gamepieceSelectInput = gamepiece_inputs.addSelectionInput( - # "gamepiece_select", - # "Selection", - # "Select the unique gamepieces in your field.", - # ) - # gamepieceSelectInput.addSelectionFilter("Occurrences") - # gamepieceSelectInput.setSelectionLimits(0) - # gamepieceSelectInput.isEnabled = True - # gamepieceSelectInput.isVisible = False - - # gamepieceTableInput.addToolbarCommandInput(addFieldInput) - # gamepieceTableInput.addToolbarCommandInput(removeFieldInput) - - # """ - # Gamepiece table column headers. (the permanent captions in the first row of table) - # """ - # gamepieceTableInput.addCommandInput( - # self.createTextBoxInput( - # "e_header", - # "Gamepiece name", - # gamepiece_inputs, - # "Gamepiece", - # bold=False, - # ), - # 0, - # 1, - # ) - - # gamepieceTableInput.addCommandInput( - # self.createTextBoxInput( - # "w_header", - # "Gamepiece weight", - # gamepiece_inputs, - # "Weight", - # background="#d9d9d9", - # ), - # 0, - # 2, - # ) - - # gamepieceTableInput.addCommandInput( - # self.createTextBoxInput( - # "f_header", - # "Friction coefficient", - # gamepiece_inputs, - # "Friction coefficient", - # background="#d9d9d9", - # ), - # 0, - # 3, - # ) - # ~~~~~~~~~~~~~~~~ PHYSICS SETTINGS ~~~~~~~~~~~~~~~~ """ Physics settings group command @@ -594,20 +383,21 @@ def notify(self, args): return processedFileName = gm.app.activeDocument.name.replace(" ", "_") - dropdownExportMode = INPUTS_ROOT.itemById("mode") - if dropdownExportMode.selectedItem.index == 0: - isRobot = True - elif dropdownExportMode.selectedItem.index == 1: - isRobot = False + # dropdownExportMode = INPUTS_ROOT.itemById("mode") + # if dropdownExportMode.selectedItem.index == 0: + # isRobot = True + # elif dropdownExportMode.selectedItem.index == 1: + # isRobot = False - processedFileName = gm.app.activeDocument.name.replace(" ", "_") - dropdownExportMode = INPUTS_ROOT.itemById("mode") - if dropdownExportMode.selectedItem.index == 0: - isRobot = True - elif dropdownExportMode.selectedItem.index == 1: - isRobot = False - dropdownExportLocation = INPUTS_ROOT.itemById("location") - if dropdownExportLocation.selectedItem.index == 1: # Download + # processedFileName = gm.app.activeDocument.name.replace(" ", "_") + # dropdownExportMode = INPUTS_ROOT.itemById("mode") + # if dropdownExportMode.selectedItem.index == 0: + # isRobot = True + # elif dropdownExportMode.selectedItem.index == 1: + # isRobot = False + # dropdownExportLocation = INPUTS_ROOT.itemById("location") + + if True: savepath = FileDialogConfig.saveFileDialog(defaultPath=exporterOptions.fileLocation) if savepath == False: @@ -626,8 +416,7 @@ def notify(self, args): name = design.rootComponent.name.rsplit(" ", 1)[0] version = design.rootComponent.name.rsplit(" ", 1)[1] - _exportGamepieces = [] # TODO work on the code to populate Gamepiece - # _location: ExportLocation + # _exportGamepieces = [] # TODO work on the code to populate Gamepiece # Transition: AARD-1683 # TODO: Implement gamepiece things @@ -655,16 +444,14 @@ def notify(self, args): # ) # ) - """ - Export Location - """ - # dropdownExportLocation = INPUTS_ROOT.itemById("location") - # if dropdownExportLocation.selectedItem.index == 0: - # _location = ExportLocation.UPLOAD - # elif dropdownExportLocation.selectedItem.index == 1: - # _location = ExportLocation.DOWNLOAD - 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( savepath, @@ -673,8 +460,8 @@ def notify(self, args): materials=0, joints=selectedJoints, wheels=selectedWheels, - gamepieces=_exportGamepieces, # TODO - preferredUnits=generalConfigTab.selectedUnits, + gamepieces=selectedGamepieces, + preferredUnits=units, robotWeight=generalConfigTab.robotWeight, exportMode=generalConfigTab.exportMode, exportLocation=generalConfigTab.exportLocation, @@ -690,6 +477,7 @@ def notify(self, args): # If we run into an exporting error we should return back to the panel with all current options # still in tact. Even if they did not save. jointConfigTab.reset() + gamepieceConfigTab.reset() except: if gm.ui: gm.ui.messageBox("Failed:\n{}".format(traceback.format_exc())) @@ -886,29 +674,6 @@ def notify(self, args: adsk.core.SelectionEventArgs): self.selectedOcc = adsk.fusion.Occurrence.cast(args.selection.entity) self.selectedJoint = args.selection.entity - selectionInput = args.activeInput - - dropdownExportMode = INPUTS_ROOT.itemById("mode") - duplicateSelection = INPUTS_ROOT.itemById("duplicate_selection") - # indicator = INPUTS_ROOT.itemById("algorithmic_indicator") - - # Transition: AARD-1683 - # TODO: Implement gamepiece things - # if self.selectedOcc: - # self.cmd.setCursor("", 0, 0) - # if dropdownExportMode.selectedItem.index == 1: - # occurrenceList = gm.app.activeDocument.design.rootComponent.allOccurrencesByComponent( - # self.selectedOcc.component - # ) - # for occ in occurrenceList: - # if occ not in GamepieceListGlobal: - # addGamepieceToTable(occ) - # else: - # removeGamePieceFromTable(GamepieceListGlobal.index(occ)) - - # selectionInput.isEnabled = False - # selectionInput.isVisible = False - if gamepieceConfigTab.isVisible: self.cmd.setCursor("", 0, 0) # Reset select cursor back to normal cursor. gamepieceConfigTab.handleSelectionEvent(args, args.selection.entity) @@ -953,7 +718,8 @@ def notify(self, args): preSelected = preSelectedOcc if preSelectedOcc else preSelectedJoint # Transition: AARD-1683 - # TODO: Implement gamepiece things + # Leaving this section of commented code here to be used in the `ConfigCommand.py` refactor. + # dropdownExportMode = INPUTS_ROOT.itemById("mode") # if preSelected and design: # if dropdownExportMode.selectedItem.index == 0: # Dynamic @@ -1065,166 +831,80 @@ def notify(self, args): gamepieceConfigTab.handleInputChanged(args, INPUTS_ROOT) MySelectHandler.lastInputCmd = cmdInput - inputs = cmdInput.commandInputs - onSelect = gm.handlers[3] frictionCoeff = INPUTS_ROOT.itemById("friction_override_coeff") - gamepieceSelect = inputs.itemById("gamepiece_select") - gamepieceTableInput = gamepieceTable() - weightTableInput = inputs.itemById("weight_table") - - weight_input = INPUTS_ROOT.itemById("weight_input") - gamepieceConfig = inputs.itemById("gamepiece_config") - addFieldInput = INPUTS_ROOT.itemById("field_add") - - indicator = INPUTS_ROOT.itemById("algorithmic_indicator") - # gm.ui.messageBox(cmdInput.id) # DEBUG statement, displays CommandInput user-defined id - position = int + # position = int - if cmdInput.id == "mode": - modeDropdown = adsk.core.DropDownCommandInput.cast(cmdInput) + # if cmdInput.id == "mode": + # modeDropdown = adsk.core.DropDownCommandInput.cast(cmdInput) - if modeDropdown.selectedItem.index == 0: - if gamepieceConfig: - gm.ui.activeSelections.clear() - gm.app.activeDocument.design.rootComponent.opacity = 1 + # if modeDropdown.selectedItem.index == 0: + # if gamepieceConfig: + # gm.ui.activeSelections.clear() + # gm.app.activeDocument.design.rootComponent.opacity = 1 - gamepieceConfig.isVisible = False - weightTableInput.isVisible = True + # gamepieceConfig.isVisible = False + # weightTableInput.isVisible = True - addFieldInput.isEnabled = True + # addFieldInput.isEnabled = True - elif modeDropdown.selectedItem.index == 1: - if gamepieceConfig: - gm.ui.activeSelections.clear() - gm.app.activeDocument.design.rootComponent.opacity = 1 + # elif modeDropdown.selectedItem.index == 1: + # if gamepieceConfig: + # gm.ui.activeSelections.clear() + # gm.app.activeDocument.design.rootComponent.opacity = 1 - elif cmdInput.id == "blank_gp" or cmdInput.id == "name_gp" or cmdInput.id == "weight_gp": - self.reset() + # elif cmdInput.id == "blank_gp" or cmdInput.id == "name_gp" or cmdInput.id == "weight_gp": + # self.reset() - gamepieceSelect.isEnabled = False - addFieldInput.isEnabled = True + # gamepieceSelect.isEnabled = False + # addFieldInput.isEnabled = True - cmdInput_str = cmdInput.id + # cmdInput_str = cmdInput.id - if cmdInput_str == "name_gp": - position = gamepieceTableInput.getPosition(adsk.core.TextBoxCommandInput.cast(cmdInput))[1] - 1 - elif cmdInput_str == "weight_gp": - position = gamepieceTableInput.getPosition(adsk.core.ValueCommandInput.cast(cmdInput))[1] - 1 - elif cmdInput_str == "blank_gp": - position = gamepieceTableInput.getPosition(adsk.core.ImageCommandInput.cast(cmdInput))[1] - 1 - else: - position = gamepieceTableInput.getPosition(adsk.core.FloatSliderCommandInput.cast(cmdInput))[1] - 1 + # if cmdInput_str == "name_gp": + # position = gamepieceTableInput.getPosition(adsk.core.TextBoxCommandInput.cast(cmdInput))[1] - 1 + # elif cmdInput_str == "weight_gp": + # position = gamepieceTableInput.getPosition(adsk.core.ValueCommandInput.cast(cmdInput))[1] - 1 + # elif cmdInput_str == "blank_gp": + # position = gamepieceTableInput.getPosition(adsk.core.ImageCommandInput.cast(cmdInput))[1] - 1 + # else: + # position = gamepieceTableInput.getPosition(adsk.core.FloatSliderCommandInput.cast(cmdInput))[1] - 1 - gm.ui.activeSelections.add(GamepieceListGlobal[position]) + # gm.ui.activeSelections.add(GamepieceListGlobal[position]) - elif cmdInput.id == "field_add": - self.reset() + # elif cmdInput.id == "field_add": + # self.reset() - gamepieceSelect.isVisible = True - gamepieceSelect.isEnabled = True - gamepieceSelect.clearSelection() - addFieldInput.isEnabled = False + # gamepieceSelect.isVisible = True + # gamepieceSelect.isEnabled = True + # gamepieceSelect.clearSelection() + # addFieldInput.isEnabled = False - elif cmdInput.id == "field_delete": - gm.ui.activeSelections.clear() + # elif cmdInput.id == "field_delete": + # gm.ui.activeSelections.clear() - addFieldInput.isEnabled = True + # addFieldInput.isEnabled = True - if gamepieceTableInput.selectedRow == -1 or gamepieceTableInput.selectedRow == 0: - gamepieceTableInput.selectedRow = gamepieceTableInput.rowCount - 1 - gm.ui.messageBox("Select a row to delete.") - else: - index = gamepieceTableInput.selectedRow - 1 - removeGamePieceFromTable(index) + # if gamepieceTableInput.selectedRow == -1 or gamepieceTableInput.selectedRow == 0: + # gamepieceTableInput.selectedRow = gamepieceTableInput.rowCount - 1 + # gm.ui.messageBox("Select a row to delete.") + # else: + # index = gamepieceTableInput.selectedRow - 1 + # removeGamePieceFromTable(index) - elif cmdInput.id == "gamepiece_select": - addFieldInput.isEnabled = True + # elif cmdInput.id == "gamepiece_select": + # addFieldInput.isEnabled = True - elif cmdInput.id == "friction_override": + if cmdInput.id == "friction_override": boolValue = adsk.core.BoolValueCommandInput.cast(cmdInput) if boolValue.value: frictionCoeff.isVisible = True else: frictionCoeff.isVisible = False - - # Transition: AARD-1683 - # TODO: Implement gamepiece things - # elif cmdInput.id == "weight_unit_f": - # unitDropdown = adsk.core.DropDownCommandInput.cast(cmdInput) - # if unitDropdown.selectedItem.index == 0: - # self.isLbs_f = True - - # for row in range(gamepieceTableInput.rowCount): - # if row == 0: - # continue - # weightInput = gamepieceTableInput.getInputAtPosition(row, 2) - # weightInput.tooltipDescription = "(in pounds)" - # elif unitDropdown.selectedItem.index == 1: - # self.isLbs_f = False - - # for row in range(gamepieceTableInput.rowCount): - # if row == 0: - # continue - # weightInput = gamepieceTableInput.getInputAtPosition(row, 2) - # weightInput.tooltipDescription = "(in kilograms)" - - # Transition: AARD-1683 - # TODO: Implement gamepiece things??? - # Is there even any here?? - # elif cmdInput.id == "auto_calc_weight": - # button = adsk.core.BoolValueCommandInput.cast(cmdInput) - - # if button.value == True: # CALCULATE button pressed - # if self.allWeights.count(None) == 2: # if button is pressed for the first time - # if self.isLbs: # if pounds unit selected - # self.allWeights[0] = self.weight() - # weight_input.value = self.allWeights[0] - # else: # if kg unit selected - # self.allWeights[1] = self.weight(False) - # weight_input.value = self.allWeights[1] - # else: # if a mass value has already been configured - # if ( - # weight_input.value != self.allWeights[0] - # or weight_input.value != self.allWeights[1] - # or not weight_input.isValidExpression - # ): - # if self.isLbs: - # weight_input.value = self.allWeights[0] - # else: - # weight_input.value = self.allWeights[1] - - # Transition: AARD-1683 - # TODO: Implement gamepiece things - # elif cmdInput.id == "auto_calc_weight_f": - # button = adsk.core.BoolValueCommandInput.cast(cmdInput) - - # if button.value == True: # CALCULATE button pressed - # if self.isLbs_f: - # for row in range(gamepieceTableInput.rowCount): - # if row == 0: - # continue - # weightInput = gamepieceTableInput.getInputAtPosition(row, 2) - # physical = GamepieceListGlobal[row - 1].component.getPhysicalProperties( - # adsk.fusion.CalculationAccuracy.LowCalculationAccuracy - # ) - # value = round(physical.mass * 2.2046226218, 2) - # weightInput.value = value - - # else: - # for row in range(gamepieceTableInput.rowCount): - # if row == 0: - # continue - # weightInput = gamepieceTableInput.getInputAtPosition(row, 2) - # physical = GamepieceListGlobal[row - 1].component.getPhysicalProperties( - # adsk.fusion.CalculationAccuracy.LowCalculationAccuracy - # ) - # value = round(physical.mass, 2) - # weightInput.value = value except: if gm.ui: gm.ui.messageBox("Failed:\n{}".format(traceback.format_exc())) @@ -1264,115 +944,3 @@ def notify(self, args): logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.{self.__class__.__name__}").error( "Failed:\n{}".format(traceback.format_exc()) ) - - -# Transition: AARD-1683 -# TODO: Implement gamepiece things -def addGamepieceToTable(gamepiece: adsk.fusion.Occurrence) -> None: - """### Adds a gamepiece occurrence to its global list and gamepiece table. - - Args: - gamepiece (adsk.fusion.Occurrence): Gamepiece occurrence to be added - """ - try: - onSelect = gm.handlers[3] - gamepieceTableInput = gamepieceTable() - - def addPreselections(child_occurrences): - for occ in child_occurrences: - onSelect.allGamepiecePreselections.append(occ.entityToken) - - if occ.childOccurrences: - addPreselections(occ.childOccurrences) - - if gamepiece.childOccurrences: - addPreselections(gamepiece.childOccurrences) - else: - onSelect.allGamepiecePreselections.append(gamepiece.entityToken) - - GamepieceListGlobal.append(gamepiece) - cmdInputs = adsk.core.CommandInputs.cast(gamepieceTableInput.commandInputs) - blankIcon = cmdInputs.addImageCommandInput("blank_gp", "Blank", IconPaths.gamepieceIcons["blank"]) - - type = cmdInputs.addTextBoxCommandInput("name_gp", "Occurrence name", gamepiece.name, 1, True) - - value = 0.0 - physical = gamepiece.component.getPhysicalProperties(adsk.fusion.CalculationAccuracy.LowCalculationAccuracy) - value = physical.mass - - # check if dropdown unit is kg or lbs. bool value taken from ConfigureCommandInputChanged - massUnitInString = "" - onInputChanged = gm.handlers[1] - if onInputChanged.isLbs_f: - value = round(value * 2.2046226218, 2) # lbs - massUnitInString = "(in pounds)" - else: - value = round(value, 2) # kg - massUnitInString = "(in kilograms)" - - weight = cmdInputs.addValueInput( - "weight_gp", - "Weight Input", - "", - adsk.core.ValueInput.createByString(str(value)), - ) - - valueList = [1] - for i in range(20): - valueList.append(i / 20) - - friction_coeff = cmdInputs.addFloatSliderListCommandInput("friction_coeff", "", "", valueList) - friction_coeff.valueOne = 0.5 - - type.tooltip = gamepiece.name - - weight.tooltip = "Weight of field element" - weight.tooltipDescription = massUnitInString - - friction_coeff.tooltip = "Friction coefficient of field element" - friction_coeff.tooltipDescription = "Friction coefficients range from 0 (ice) to 1 (rubber)." - row = gamepieceTableInput.rowCount - - gamepieceTableInput.addCommandInput(blankIcon, row, 0) - gamepieceTableInput.addCommandInput(type, row, 1) - gamepieceTableInput.addCommandInput(weight, row, 2) - gamepieceTableInput.addCommandInput(friction_coeff, row, 3) - except: - logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.addGamepieceToTable()").error( - "Failed:\n{}".format(traceback.format_exc()) - ) - - -# Transition: AARD-1683 -# TODO: Implement gamepiece things -def removeGamePieceFromTable(index: int) -> None: - """### Removes a gamepiece occurrence from its global list and gamepiece table. - - Args: - index (int): index of gamepiece item in its global list. - """ - onSelect = gm.handlers[3] - gamepieceTableInput = gamepieceTable() - gamepiece = GamepieceListGlobal[index] - - def removePreselections(child_occurrences): - for occ in child_occurrences: - onSelect.allGamepiecePreselections.remove(occ.entityToken) - - if occ.childOccurrences: - removePreselections(occ.childOccurrences) - - try: - if gamepiece.childOccurrences: - removePreselections(GamepieceListGlobal[index].childOccurrences) - else: - onSelect.allGamepiecePreselections.remove(gamepiece.entityToken) - - del GamepieceListGlobal[index] - gamepieceTableInput.deleteRow(index + 1) - except IndexError: - pass - except: - logging.getLogger("{INTERNAL_ID}.UI.ConfigCommand.removeGamePieceFromTable()").error( - "Failed:\n{}".format(traceback.format_exc()) - ) diff --git a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py index 8268c4abaf..31b1a42358 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py @@ -26,9 +26,6 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp self.gamepieceConfigTab.tooltip = "Field gamepiece configuration options." gamepieceTabInputs = self.gamepieceConfigTab.children - # Invisible by default, only becomes visible when the current export mode is static or 'field'. - self.gamepieceConfigTab.isVisible = False - createBooleanInput( "autoCalcGamepieceWeight", "Auto Calculate Gamepiece Weight", @@ -109,6 +106,10 @@ def isVisible(self) -> bool: def isVisible(self, value: bool) -> None: self.gamepieceConfigTab.isVisible = value + @property + def selectedUnits(self) -> PreferredUnits: + return self.currentUnits + @property def weightInputs(self) -> list[adsk.core.ValueCommandInput]: gamepieceWeightInputs = [] @@ -197,6 +198,16 @@ def removeChildOccurrences(childOccurrences: adsk.fusion.OccurrenceList) -> None self.selectedGamepieceList.remove(gamepiece) self.gamepieceTable.deleteRow(i + 1) # Row is 1 indexed + 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 + gamepieceFrictionCoefficient = self.gamepieceTable.getInputAtPosition(row, 2).valueOne + gamepieces.append(Gamepiece(gamepieceEntityToken, gamepieceWeight, gamepieceFrictionCoefficient)) + + return gamepieces + def reset(self) -> None: self.selectedGamepieceEntityIDs.clear() self.selectedGamepieceList.clear() From 2bf1319afcd4a10168a45fed139e77db360b7617 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Wed, 24 Jul 2024 10:42:52 -0700 Subject: [PATCH 028/344] Smooth end to end gamepiece config tab integration --- .../src/UI/ConfigCommand.py | 1 + .../src/UI/GeneralConfigTab.py | 20 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index 8b93885316..c5d2cc5fe4 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -164,6 +164,7 @@ def notify(self, args): if not exporterOptions.exportMode == ExportMode.ROBOT: jointConfigTab.isVisible = False + # INPUTS_ROOT.itemById("weightTable").isVisible = False # Transition: AARD-1685 # There remains some overlap between adding joints as wheels. diff --git a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py index b5529f1b99..76e60c98e0 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py @@ -73,7 +73,7 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp weightName.value = "Weight" weightName.isReadOnly = True - createBooleanInput( + autoCalcWeightButton = createBooleanInput( "autoCalcWeightButton", "Auto Calculate Robot Weight", generalTabInputs, @@ -132,7 +132,7 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp enabled=True, ) - createBooleanInput( + exportAsPartButton = createBooleanInput( "exportAsPartButton", "Export As Part", generalTabInputs, @@ -141,6 +141,11 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp enabled=True, ) + if exporterOptions.exportMode == ExportMode.FIELD: + autoCalcWeightButton.isVisible = False + exportAsPartButton.isVisible = False + weightInput.isVisible = weightTableInput.isVisible = False + except BaseException: logging.getLogger("{INTERNAL_ID}.UI.GeneralConfigTab").error("Failed:\n{}".format(traceback.format_exc())) @@ -207,17 +212,28 @@ def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: commandInput = args.input if commandInput.id == "exportModeDropdown": modeDropdown = adsk.core.DropDownCommandInput.cast(commandInput) + autoCalcWeightButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("autoCalcWeightButton") + weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") + exportAsPartButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("exportAsPartButton") if modeDropdown.selectedItem.index == self.previousSelectedModeDropdownIndex: return if modeDropdown.selectedItem.index == 0: self.jointConfigTab.isVisible = True self.gamepieceConfigTab.isVisible = False + + autoCalcWeightButton.isVisible = True + weightTable.isVisible = True + exportAsPartButton.isVisible = True else: assert modeDropdown.selectedItem.index == 1 self.jointConfigTab.isVisible = False self.gamepieceConfigTab.isVisible = True + autoCalcWeightButton.isVisible = False + weightTable.isVisible = False + exportAsPartButton.isVisible = False + self.previousSelectedModeDropdownIndex = modeDropdown.selectedItem.index elif commandInput.id == "weightUnitDropdown": From b535be1abfe5480aa60461f510ed04a84303ba4c Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Wed, 24 Jul 2024 10:47:24 -0700 Subject: [PATCH 029/344] Removed replaced code --- .../src/UI/ConfigCommand.py | 186 +----------------- 1 file changed, 5 insertions(+), 181 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index c5d2cc5fe4..80bedbb0ac 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -14,15 +14,9 @@ from ..APS.APS import getAuth, getUserInfo, refreshAuthToken from ..configure import NOTIFIED, write_configuration from ..general_imports import * -from ..Parser.ExporterOptions import ( - ExporterOptions, - ExportLocation, - ExportMode, - Gamepiece, - PreferredUnits, -) +from ..Parser.ExporterOptions import ExporterOptions, ExportMode from ..Parser.SynthesisParser.Parser import Parser -from . import CustomGraphics, FileDialogConfig, Helper, IconPaths +from . import FileDialogConfig, Helper from .Configuration.SerialCommand import SerialCommand from .GamepieceConfigTab import GamepieceConfigTab from .GeneralConfigTab import GeneralConfigTab @@ -44,12 +38,6 @@ """ INPUTS_ROOT = None -""" -These lists are crucial, and contain all of the relevant object selections. -- GamepieceListGlobal: list of gamepieces (adsk.fusion.Occurrence) -""" -GamepieceListGlobal = [] - def GUID(arg): """### Will return command object when given a string GUID, or the string GUID of an object (depending on arg value) @@ -67,15 +55,6 @@ def GUID(arg): return arg.entityToken -def gamepieceTable(): - """### Returns the gamepiece table command input - - Returns: - adsk.fusion.TableCommandInput - """ - return INPUTS_ROOT.itemById("gamepiece_table") - - class JointMotions(Enum): """### Corresponds to the API JointMotions enum @@ -134,8 +113,6 @@ def notify(self, args): global INPUTS_ROOT # Global CommandInputs arg INPUTS_ROOT = cmd.commandInputs - # Transition: AARD-1683 - # Working on replacing all of the general tab stuff global generalConfigTab generalConfigTab = GeneralConfigTab(args, exporterOptions) @@ -164,7 +141,6 @@ def notify(self, args): if not exporterOptions.exportMode == ExportMode.ROBOT: jointConfigTab.isVisible = False - # INPUTS_ROOT.itemById("weightTable").isVisible = False # Transition: AARD-1685 # There remains some overlap between adding joints as wheels. @@ -417,34 +393,6 @@ def notify(self, args): name = design.rootComponent.name.rsplit(" ", 1)[0] version = design.rootComponent.name.rsplit(" ", 1)[1] - # _exportGamepieces = [] # TODO work on the code to populate Gamepiece - - # Transition: AARD-1683 - # TODO: Implement gamepiece things - """ - Loops through all rows in the gamepiece table to extract the input values - """ - # gamepieceTableInput = gamepieceTable() - # weight_unit_f = INPUTS_ROOT.itemById("weight_unit_f") - # for row in range(gamepieceTableInput.rowCount): - # if row == 0: - # continue - - # weightValue = gamepieceTableInput.getInputAtPosition(row, 2).value # weight/mass input, float - - # if weight_unit_f.selectedItem.index == 0: - # weightValue /= 2.2046226218 - - # frictionValue = gamepieceTableInput.getInputAtPosition(row, 3).valueOne # friction value, float - - # _exportGamepieces.append( - # Gamepiece( - # guid_occurrence(GamepieceListGlobal[row - 1]), - # weightValue, - # frictionValue, - # ) - # ) - selectedJoints, selectedWheels = jointConfigTab.getSelectedJointsAndWheels() selectedGamepieces = gamepieceConfigTab.getGamepieces() @@ -502,31 +450,8 @@ def notify(self, args): args (CommandEventArgs): command event argument """ try: - eventArgs = adsk.core.CommandEventArgs.cast(args) - # inputs = eventArgs.command.commandInputs # equivalent to INPUTS_ROOT global - - auto_calc_weight_f = INPUTS_ROOT.itemById("auto_calc_weight_f") - - addFieldInput = INPUTS_ROOT.itemById("field_add") - removeFieldInput = INPUTS_ROOT.itemById("field_delete") - - # Transition: AARD-1685 - # This is how all preview handles should be done in the future jointConfigTab.handlePreviewEvent(args) gamepieceConfigTab.handlePreviewEvent(args) - - # gamepieceTableInput = gamepieceTable() - # if gamepieceTableInput.rowCount <= 1: - # removeFieldInput.isEnabled = auto_calc_weight_f.isEnabled = False - # else: - # removeFieldInput.isEnabled = auto_calc_weight_f.isEnabled = True - - # if not addFieldInput.isEnabled or not removeFieldInput: - # for gamepiece in GamepieceListGlobal: - # gamepiece.component.opacity = 0.25 - # CustomGraphics.createTextGraphics(gamepiece, GamepieceListGlobal) - # else: - # gm.app.activeDocument.design.rootComponent.opacity = 1 except AttributeError: pass except: @@ -717,41 +642,8 @@ def notify(self, args): return preSelected = preSelectedOcc if preSelectedOcc else preSelectedJoint - - # Transition: AARD-1683 - # Leaving this section of commented code here to be used in the `ConfigCommand.py` refactor. - - # dropdownExportMode = INPUTS_ROOT.itemById("mode") - # if preSelected and design: - # if dropdownExportMode.selectedItem.index == 0: # Dynamic - # if preSelected.entityToken in onSelect.allWheelPreselections: - # self.cmd.setCursor( - # IconPaths.mouseIcons["remove"], - # 0, - # 0, - # ) - # else: - # self.cmd.setCursor( - # IconPaths.mouseIcons["add"], - # 0, - # 0, - # ) - - # elif dropdownExportMode.selectedItem.index == 1: # Static - # if preSelected.entityToken in onSelect.allGamepiecePreselections: - # self.cmd.setCursor( - # IconPaths.mouseIcons["remove"], - # 0, - # 0, - # ) - # else: - # self.cmd.setCursor( - # IconPaths.mouseIcons["add"], - # 0, - # 0, - # ) - # else: # Should literally be impossible? - Brandon - # self.cmd.setCursor("", 0, 0) + if not preSelected: + self.cmd.setCursor("", 0, 0) except: if gm.ui: gm.ui.messageBox("Failed:\n{}".format(traceback.format_exc())) @@ -835,70 +727,6 @@ def notify(self, args): frictionCoeff = INPUTS_ROOT.itemById("friction_override_coeff") - # gm.ui.messageBox(cmdInput.id) # DEBUG statement, displays CommandInput user-defined id - - # position = int - - # if cmdInput.id == "mode": - # modeDropdown = adsk.core.DropDownCommandInput.cast(cmdInput) - - # if modeDropdown.selectedItem.index == 0: - # if gamepieceConfig: - # gm.ui.activeSelections.clear() - # gm.app.activeDocument.design.rootComponent.opacity = 1 - - # gamepieceConfig.isVisible = False - # weightTableInput.isVisible = True - - # addFieldInput.isEnabled = True - - # elif modeDropdown.selectedItem.index == 1: - # if gamepieceConfig: - # gm.ui.activeSelections.clear() - # gm.app.activeDocument.design.rootComponent.opacity = 1 - - # elif cmdInput.id == "blank_gp" or cmdInput.id == "name_gp" or cmdInput.id == "weight_gp": - # self.reset() - - # gamepieceSelect.isEnabled = False - # addFieldInput.isEnabled = True - - # cmdInput_str = cmdInput.id - - # if cmdInput_str == "name_gp": - # position = gamepieceTableInput.getPosition(adsk.core.TextBoxCommandInput.cast(cmdInput))[1] - 1 - # elif cmdInput_str == "weight_gp": - # position = gamepieceTableInput.getPosition(adsk.core.ValueCommandInput.cast(cmdInput))[1] - 1 - # elif cmdInput_str == "blank_gp": - # position = gamepieceTableInput.getPosition(adsk.core.ImageCommandInput.cast(cmdInput))[1] - 1 - # else: - # position = gamepieceTableInput.getPosition(adsk.core.FloatSliderCommandInput.cast(cmdInput))[1] - 1 - - # gm.ui.activeSelections.add(GamepieceListGlobal[position]) - - # elif cmdInput.id == "field_add": - # self.reset() - - # gamepieceSelect.isVisible = True - # gamepieceSelect.isEnabled = True - # gamepieceSelect.clearSelection() - # addFieldInput.isEnabled = False - - # elif cmdInput.id == "field_delete": - # gm.ui.activeSelections.clear() - - # addFieldInput.isEnabled = True - - # if gamepieceTableInput.selectedRow == -1 or gamepieceTableInput.selectedRow == 0: - # gamepieceTableInput.selectedRow = gamepieceTableInput.rowCount - 1 - # gm.ui.messageBox("Select a row to delete.") - # else: - # index = gamepieceTableInput.selectedRow - 1 - # removeGamePieceFromTable(index) - - # elif cmdInput.id == "gamepiece_select": - # addFieldInput.isEnabled = True - if cmdInput.id == "friction_override": boolValue = adsk.core.BoolValueCommandInput.cast(cmdInput) @@ -926,12 +754,8 @@ def __init__(self): def notify(self, args): try: - onSelect = gm.handlers[3] - jointConfigTab.reset() - GamepieceListGlobal.clear() - onSelect.allWheelPreselections.clear() - onSelect.wheelJointList.clear() + gamepieceConfigTab.reset() for group in gm.app.activeDocument.design.rootComponent.customGraphicsGroups: group.deleteMe() From 96ca9586339bcc4c3819213be50249acf0bb0472 Mon Sep 17 00:00:00 2001 From: LucaHaverty Date: Wed, 24 Jul 2024 11:21:48 -0700 Subject: [PATCH 030/344] Working behavior parenting with a 'set' button, highlighting, and sorting --- fission/src/Synthesis.tsx | 4 +- .../ui/panels/SequentialBehaviorsPanel.tsx | 274 ++++++++++++++++ .../src/ui/panels/SequentialJointsPanel.tsx | 299 ------------------ 3 files changed, 276 insertions(+), 301 deletions(-) create mode 100644 fission/src/ui/panels/SequentialBehaviorsPanel.tsx delete mode 100644 fission/src/ui/panels/SequentialJointsPanel.tsx diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index e39d2cddd1..e8613d339a 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -67,7 +67,7 @@ import SceneOverlay from "./ui/components/SceneOverlay.tsx" import WPILibWSWorker from "@/systems/simulation/wpilib_brain/WPILibWSWorker.ts?worker" import WSViewPanel from "./ui/panels/WSViewPanel.tsx" import Lazy from "./util/Lazy.ts" -import SequentialJointsPanel from "./ui/panels/SequentialJointsPanel.tsx" +import SequentialBehaviorsPanel from "./ui/panels/SequentialBehaviorsPanel.tsx" const DEFAULT_MIRA_PATH = "/api/mira/Robots/Team 2471 (2018)_v7.mira" @@ -275,7 +275,7 @@ const initialPanels: ReactElement[] = [ sidePadding={8} />, , - , + , ] export default Synthesis diff --git a/fission/src/ui/panels/SequentialBehaviorsPanel.tsx b/fission/src/ui/panels/SequentialBehaviorsPanel.tsx new file mode 100644 index 0000000000..f0a10484de --- /dev/null +++ b/fission/src/ui/panels/SequentialBehaviorsPanel.tsx @@ -0,0 +1,274 @@ +import React, { useMemo, useReducer, useState } from "react" +import Panel, { PanelPropsImpl } from "../components/Panel" +import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import Label, { LabelSize } from "../components/Label" +import World from "@/systems/World" +import { MiraType } from "@/mirabuf/MirabufLoader" +import { FaGear, FaXmark } from "react-icons/fa6" +import { Box, Button as MUIButton, Divider, styled, alpha } from "@mui/material" +import Button, { ButtonSize } from "../components/Button" +import GenericArmBehavior from "@/systems/simulation/behavior/GenericArmBehavior" +import GenericElevatorBehavior from "@/systems/simulation/behavior/GenericElevatorBehavior" +import Behavior from "@/systems/simulation/behavior/Behavior" + +const UnselectParent = + +const LabelStyled = styled(Label)({ + fontWeight: 700, + margin: "0pt", +}) + +const ChildLabelStyled = styled(Label)({ + fontWeight: 700, + margin: "0pt", + color: "grey", +}) + +const DividerStyled = styled(Divider)({ + borderColor: "white", +}) + +const CustomButton = styled(MUIButton)({ + "borderStyle": "solid", + "borderWidth": "1px", + "transition": "border-color 0.3s ease", + "&:hover": { + borderColor: "white", + }, + "position": "relative", + "overflow": "hidden", + "& .MuiTouchRipple-root span": { + backgroundColor: alpha("#ffffff", 0.3), // Set your desired ripple color here + animationDuration: "300ms", + }, +}) + +type BehaviorConfiguration = { + behavior: Behavior + parent: BehaviorConfiguration | undefined +} + +interface BehaviorCardProps { + elementKey: number + name: string + behavior: BehaviorConfiguration + update: () => void + onParentPressed: () => void + parentPressed: BehaviorConfiguration | undefined + onBehaviorSelected: () => void + hasChild: boolean +} + +const BehaviorCard: React.FC = ({ + elementKey, + name, + behavior, + update, + onParentPressed, + parentPressed, + onBehaviorSelected, + hasChild, +}) => { + return ( + + + + {behavior.parent != undefined ? ( + <> + + {name} + + + ) : ( + <> + + {name} + + + )} + + { + onBehaviorSelected() + update() + }} + disabled={parentPressed == undefined || parentPressed == behavior || behavior.parent != undefined} + sx={{ + borderColor: + parentPressed == undefined || parentPressed == behavior || behavior.parent != undefined + ? "transparent" + : "#888888", + }} + /> + + + + + ) + })} + + + ) : ( + <> + + Set Parent Behaviors + + + {behaviors.map(behavior => { + if ( + behavior.behavior instanceof GenericArmBehavior || + behavior.behavior instanceof GenericElevatorBehavior + ) { + const jointIndex = behavior.behavior.jointIndex + return ( + { + if (behavior.parent != undefined) { + behavior.parent = undefined + update() + } else { + setLookingForParent(lookingForParent == behavior ? undefined : behavior) + } + update() + }} + parentPressed={lookingForParent} + onBehaviorSelected={() => { + if (lookingForParent) lookingForParent.parent = behavior + setLookingForParent(undefined) + update() + }} + hasChild={behaviors.some(b => b.parent == behavior)} + /> + ) + } else { + throw new Error("Behavior not of correct type") + } + })} + + )} + + ) +} + +export default SequentialBehaviorsPanel diff --git a/fission/src/ui/panels/SequentialJointsPanel.tsx b/fission/src/ui/panels/SequentialJointsPanel.tsx deleted file mode 100644 index 16b75585b2..0000000000 --- a/fission/src/ui/panels/SequentialJointsPanel.tsx +++ /dev/null @@ -1,299 +0,0 @@ -import React, { useMemo, useReducer, useState } from "react" -import Panel, { PanelPropsImpl } from "../components/Panel" -import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" -import Label, { LabelSize } from "../components/Label" -import World from "@/systems/World" -import { MiraType } from "@/mirabuf/MirabufLoader" -import { FaArrowLeft, FaArrowRight, FaChevronDown, FaChevronUp, FaGear } from "react-icons/fa6" -import { Box, Button as MUIButton, Divider, styled } from "@mui/material" -import Button from "../components/Button" -import GenericArmBehavior from "@/systems/simulation/behavior/GenericArmBehavior" -import GenericElevatorBehavior from "@/systems/simulation/behavior/GenericElevatorBehavior" -import Behavior from "@/systems/simulation/behavior/Behavior" - -const ShiftRight = -const ShiftLeft = -const UpArrow = -const DownArrow = - -const LabelStyled = styled(Label)({ - fontWeight: 700, - margin: "0pt", -}) - -const ChildLabelStyled = styled(Label)({ - fontWeight: 700, - margin: "0pt", - color: "grey", -}) - -const DividerStyled = styled(Divider)({ - borderColor: "white", -}) - -function shiftArrayElementUp(arr: T[], elem: T): void { - const index = arr.indexOf(elem) - if (index > 0 && index < arr.length) { - let temp = arr[index] - arr[index] = arr[index - 1] - arr[index - 1] = temp - } -} - -function shiftArrayElementDown(arr: T[], elem: T): void { - const index = arr.indexOf(elem) - if (index >= 0 && index < arr.length - 1) { - let temp = arr[index] - arr[index] = arr[index + 1] - arr[index + 1] = temp - } -} - -type BehaviorConfiguration = { - behavior: Behavior - child: boolean -} - -interface BehaviorCardProps { - elementKey: number - name: string - behavior: BehaviorConfiguration - update: () => void - shiftUp: () => void - shiftDown: () => void - canShiftUp: boolean - canShiftDown: boolean - canBecomeChild: boolean -} - -const BehaviorCard: React.FC = ({ - elementKey, - name, - behavior, - update, - shiftUp, - shiftDown, - canShiftUp, - canShiftDown, - canBecomeChild, -}) => { - return ( - - - {behavior.child ? ( - <> - - - {name} - - - ) : ( - <> - - {name} - - - )} - - - - <> - - ) - })} - - - ) : ( - <> - - Behaviors - - - {GetBehaviorCards(behaviors, update)} - - )} - - ) -} - -export default SequentialJointsPanel From f66720a57127e9b6493f351ea38580551a085560 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Wed, 24 Jul 2024 11:32:07 -0700 Subject: [PATCH 031/344] Fix merge conflicts --- .../src/UI/ConfigCommand.py | 99 ++++++++++--------- .../src/UI/GamepieceConfigTab.py | 7 ++ .../src/UI/ShowAPSAuthCommand.py | 7 -- 3 files changed, 57 insertions(+), 56 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index 89eff97461..7b733e7220 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -11,8 +11,8 @@ from ..APS.APS import getAuth, getUserInfo, refreshAuthToken from ..general_imports import * -from ..Parser.ExporterOptions import ExporterOptions, ExportMode from ..Logging import getLogger, logFailure +from ..Parser.ExporterOptions import ExporterOptions, ExportLocation, ExportMode from ..Parser.SynthesisParser.Parser import Parser from . import FileDialogConfig, Helper from .Configuration.SerialCommand import SerialCommand @@ -99,13 +99,6 @@ def notify(self, args): global INPUTS_ROOT # Global CommandInputs arg INPUTS_ROOT = cmd.commandInputs - # ====================================== GENERAL TAB ====================================== - """ - Creates the general tab. - - Parent container for all the command inputs in the tab. - """ - inputs = INPUTS_ROOT.addTabCommandInput("general_settings", "General").children - # ~~~~~~~~~~~~~~~~ HELP FILE ~~~~~~~~~~~~~~~~ """ Sets the small "i" icon in bottom left of the panel. @@ -113,20 +106,6 @@ def notify(self, args): """ cmd.helpFile = os.path.join(".", "src", "Resources", "HTML", "info.html") - # ~~~~~~~~~~~~~~~~ EXPORT MODE ~~~~~~~~~~~~~~~~ - """ - Dropdown to choose whether to export robot or field element - """ - dropdownExportMode = inputs.addDropDownCommandInput( - "mode", - "Export Mode", - dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle, - ) - - dynamic = exporterOptions.exportMode == ExportMode.ROBOT - dropdownExportMode.listItems.add("Dynamic", dynamic) - dropdownExportMode.listItems.add("Static", not dynamic) - global generalConfigTab generalConfigTab = GeneralConfigTab(args, exporterOptions) @@ -149,6 +128,32 @@ def notify(self, args): if not exporterOptions.exportMode == ExportMode.ROBOT: jointConfigTab.isVisible = False + # Transition: AARD-1685 + # There remains some overlap between adding joints as wheels. + # Should investigate changes to improve performance. + if exporterOptions.joints: + for synJoint in exporterOptions.joints: + fusionJoint = gm.app.activeDocument.design.findEntityByToken(synJoint.jointToken)[0] + jointConfigTab.addJoint(fusionJoint, synJoint) + else: + for joint in [ + *gm.app.activeDocument.design.rootComponent.allJoints, + *gm.app.activeDocument.design.rootComponent.allAsBuiltJoints, + ]: + if ( + joint.jointMotion.jointType in (JointMotions.REVOLUTE.value, JointMotions.SLIDER.value) + and not joint.isSuppressed + ): + jointConfigTab.addJoint(joint) + + # Adding saved wheels must take place after joints are added as a result of how the two types are connected. + # Transition: AARD-1685 + # Should consider changing how the parser handles wheels and joints to avoid overlap + if exporterOptions.wheels: + for wheel in exporterOptions.wheels: + fusionJoint = gm.app.activeDocument.design.findEntityByToken(wheel.jointToken)[0] + jointConfigTab.addWheel(fusionJoint, wheel) + # frictionOverrideInput = self.createBooleanInput( # "friction_override", # "Friction Override", @@ -202,13 +207,13 @@ def notify(self, args): # enabled=True, # ) - getAuth() - user_info = getUserInfo() - apsSettings = INPUTS_ROOT.addTabCommandInput( - "aps_settings", f"APS Settings ({user_info.given_name if user_info else 'Not Signed In'})" - ) - apsSettings.tooltip = "Configuration settings for Autodesk Platform Services." - aps_input = apsSettings.children + # getAuth() + # user_info = getUserInfo() + # apsSettings = INPUTS_ROOT.addTabCommandInput( + # "aps_settings", f"APS Settings ({user_info.given_name if user_info else 'Not Signed In'})" + # ) + # apsSettings.tooltip = "Configuration settings for Autodesk Platform Services." + # aps_input = apsSettings.children # clear all selections before instantiating handlers. gm.ui.activeSelections.clear() @@ -287,33 +292,25 @@ def notify(self, args): logger.error("Could not execute configuration due to failure") return - # processedFileName = gm.app.activeDocument.name.replace(" ", "_") - # dropdownExportLocation = INPUTS_ROOT.itemById("location") - # if dropdownExportLocation.selectedItem.index == 1: # Download - # savepath = FileDialogConfig.saveFileDialog(defaultPath=exporterOptions.fileLocation) + processedFileName = gm.app.activeDocument.name.replace(" ", "_") + if generalConfigTab.exportLocation == ExportLocation.DOWNLOAD: + savepath = FileDialogConfig.saveFileDialog(defaultPath=exporterOptions.fileLocation) - # if savepath == False: - # # save was canceled - # return + if savepath == False: + # save was canceled + return - # if True: - # savepath = FileDialogConfig.saveFileDialog(defaultPath=exporterOptions.fileLocation) - # processedFileName = gm.app.activeDocument.name.replace(" ", "_") - # dropdownExportLocation = INPUTS_ROOT.itemById("location") - # if dropdownExportLocation.selectedItem.index == 1: # Download - # savepath = FileDialogConfig.saveFileDialog(defaultPath=exporterOptions.fileLocation) + updatedPath = pathlib.Path(savepath).parent + if updatedPath != self.current.filePath: + self.current.filePath = str(updatedPath) + else: + savepath = processedFileName adsk.doEvents() - # get active document design = gm.app.activeDocument.design name = design.rootComponent.name.rsplit(" ", 1)[0] version = design.rootComponent.name.rsplit(" ", 1)[1] - _exportGamepieces = [] # TODO work on the code to populate Gamepiece - _robotWeight = float - _mode: ExportMode - _location: ExportLocation - selectedJoints, selectedWheels = jointConfigTab.getSelectedJointsAndWheels() selectedGamepieces = gamepieceConfigTab.getGamepieces() @@ -333,13 +330,17 @@ def notify(self, args): gamepieces=selectedGamepieces, preferredUnits=units, robotWeight=generalConfigTab.robotWeight, + autoCalcRobotWeight=generalConfigTab.autoCalculateWeight, + autoCalcGamepieceWeight=gamepieceConfigTab.autoCalculateWeight, exportMode=generalConfigTab.exportMode, exportLocation=generalConfigTab.exportLocation, compressOutput=generalConfigTab.compress, exportAsPart=generalConfigTab.exportAsPart, - autoCalcRobotWeight=generalConfigTab.autoCalculateWeight, ) + Parser(exporterOptions).export() + exporterOptions.writeToDesign() + # All selections should be reset AFTER a successful export and save. # If we run into an exporting error we should return back to the panel with all current options # still in tact. Even if they did not save. diff --git a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py index 31b1a42358..57b61b0fa4 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py @@ -110,6 +110,13 @@ def isVisible(self, value: bool) -> None: def selectedUnits(self) -> PreferredUnits: return self.currentUnits + @property + def autoCalculateWeight(self) -> bool: + autoCalcWeightButton: adsk.core.BoolValueCommandInput = self.gamepieceConfigTab.children.itemById( + "autoCalcGamepieceWeight" + ) + return autoCalcWeightButton.value + @property def weightInputs(self) -> list[adsk.core.ValueCommandInput]: gamepieceWeightInputs = [] diff --git a/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py b/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py index 615284d534..bd45cfc062 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py @@ -10,13 +10,6 @@ from src.APS.APS import CLIENT_ID, auth_path, convertAuthToken, getCodeChallenge from src.general_imports import APP_NAME, DESCRIPTION, INTERNAL_ID, gm, my_addin_path -from src.general_imports import ( - APP_NAME, - DESCRIPTION, - INTERNAL_ID, - gm, - my_addin_path, -) from src.Logging import getLogger logger = getLogger() From 96a809c5df49513d32daed15cf2c1417ce05779a Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Wed, 24 Jul 2024 11:37:37 -0700 Subject: [PATCH 032/344] Fix add gamepiece weight input enabled state --- exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py | 1 + 1 file changed, 1 insertion(+) diff --git a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py index 57b61b0fa4..033f2080d2 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py @@ -168,6 +168,7 @@ def addChildOccurrences(childOccurrences: adsk.fusion.OccurrenceList) -> None: "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" From 9d77d9495012a6546053533912b77b6d2e880f28 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Wed, 24 Jul 2024 11:42:32 -0700 Subject: [PATCH 033/344] Integrate GH-1010 --- .../src/UI/GamepieceConfigTab.py | 12 + .../src/UI/GeneralConfigTab.py | 383 +++++++++--------- 2 files changed, 199 insertions(+), 196 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py index 033f2080d2..661c2cb6e6 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py @@ -1,6 +1,7 @@ import adsk.core import adsk.fusion +from ..Logging import logFailure from ..Parser.ExporterOptions import ExporterOptions, Gamepiece, PreferredUnits from ..TypesTmp import toKg, toLbs from . import IconPaths @@ -20,6 +21,7 @@ class GamepieceConfigTab: previousSelectedUnitDropdownIndex: int currentUnits: PreferredUnits + @logFailure def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: ExporterOptions) -> None: inputs = args.command.commandInputs self.gamepieceConfigTab = inputs.addTabCommandInput("gamepieceSettings", "Gamepiece Settings") @@ -117,6 +119,7 @@ def autoCalculateWeight(self) -> bool: ) return autoCalcWeightButton.value + @logFailure @property def weightInputs(self) -> list[adsk.core.ValueCommandInput]: gamepieceWeightInputs = [] @@ -125,6 +128,7 @@ def weightInputs(self) -> list[adsk.core.ValueCommandInput]: return gamepieceWeightInputs + @logFailure def addGamepiece(self, gamepiece: adsk.fusion.Occurrence, synGamepiece: Gamepiece | None = None) -> bool: if gamepiece.entityToken in self.selectedGamepieceEntityIDs: return False @@ -186,9 +190,11 @@ def addChildOccurrences(childOccurrences: adsk.fusion.OccurrenceList) -> None: return True + @logFailure def removeIndexedGamepiece(self, index: int) -> None: self.removeGamepiece(self.selectedGamepieceList[index]) + @logFailure def removeGamepiece(self, gamepiece: adsk.fusion.Occurrence) -> None: def removeChildOccurrences(childOccurrences: adsk.fusion.OccurrenceList) -> None: for occ in childOccurrences: @@ -206,6 +212,7 @@ def removeChildOccurrences(childOccurrences: adsk.fusion.OccurrenceList) -> None self.selectedGamepieceList.remove(gamepiece) self.gamepieceTable.deleteRow(i + 1) # Row is 1 indexed + @logFailure def getGamepieces(self) -> list[Gamepiece]: gamepieces: list[Gamepiece] = [] for row in range(1, self.gamepieceTable.rowCount): # Row is 1 indexed @@ -220,6 +227,7 @@ 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 @@ -227,6 +235,7 @@ def updateWeightTableToUnits(self, units: PreferredUnits) -> None: 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 weightInput: adsk.core.ValueCommandInput = self.gamepieceTable.getInputAtPosition(row, 1) @@ -238,6 +247,7 @@ def calcGamepieceWeights(self) -> None: else: weightInput.value = round(physical.mass, 2) + @logFailure def handleInputChanged( self, args: adsk.core.InputChangedEventArgs, globalCommandInputs: adsk.core.CommandInputs ) -> None: @@ -315,6 +325,7 @@ def handleInputChanged( gamepieceSelectCancelButton.isEnabled = gamepieceSelectCancelButton.isVisible = False gamepieceAddButton.isEnabled = gamepieceRemoveButton.isEnabled = True + @logFailure def handleSelectionEvent(self, args: adsk.core.SelectionEventArgs, selectedOcc: adsk.fusion.Occurrence) -> None: selectionInput = args.activeInput rootComponent = adsk.core.Application.get().activeDocument.design.rootComponent @@ -334,6 +345,7 @@ def handleSelectionEvent(self, args: adsk.core.SelectionEventArgs, selectedOcc: selectionInput.isEnabled = selectionInput.isVisible = False + @logFailure def handlePreviewEvent(self, args: adsk.core.CommandEventArgs) -> None: commandInputs = args.command.commandInputs gamepieceAddButton: adsk.core.BoolValueCommandInput = commandInputs.itemById("gamepieceAddButton") diff --git a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py index 76e60c98e0..e7050b4949 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py @@ -1,10 +1,7 @@ -import logging -import traceback - import adsk.core import adsk.fusion -from ..general_imports import INTERNAL_ID +from ..Logging import logFailure from ..Parser.ExporterOptions import ( ExporterOptions, ExportLocation, @@ -27,127 +24,124 @@ class GeneralConfigTab: jointConfigTab: JointConfigTab gamepieceConfigTab: GamepieceConfigTab + @logFailure def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: ExporterOptions) -> None: - try: - inputs = args.command.commandInputs - self.generalOptionsTab = inputs.addTabCommandInput("generalSettings", "General Settings") - self.generalOptionsTab.tooltip = "General configuration options for your robot export." - generalTabInputs = self.generalOptionsTab.children - - dropdownExportMode = generalTabInputs.addDropDownCommandInput( - "exportModeDropdown", - "Export Mode", - dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle, - ) - - dynamic = exporterOptions.exportMode == ExportMode.ROBOT - dropdownExportMode.listItems.add("Dynamic", dynamic) - dropdownExportMode.listItems.add("Static", not dynamic) - dropdownExportMode.tooltip = "Export Mode" - dropdownExportMode.tooltipDescription = "
Does this object move dynamically?" - self.previousSelectedModeDropdownIndex = int(not dynamic) - - dropdownExportLocation = generalTabInputs.addDropDownCommandInput( - "exportLocation", "Export Location", dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle - ) - - upload: bool = exporterOptions.exportLocation == ExportLocation.UPLOAD - dropdownExportLocation.listItems.add("Upload", upload) - dropdownExportLocation.listItems.add("Download", not upload) - dropdownExportLocation.tooltip = "Export Location" - dropdownExportLocation.tooltipDescription = ( - "
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", - generalTabInputs, - checked=exporterOptions.autoCalcRobotWeight, - tooltip="Approximate the weight of your robot assembly.", - ) - self.previousAutoCalcWeightCheckboxState = exporterOptions.autoCalcRobotWeight - - self.currentUnits = exporterOptions.preferredUnits - imperialUnits = self.currentUnits == PreferredUnits.IMPERIAL - if imperialUnits: - # ExporterOptions always contains the metric value - displayWeight = exporterOptions.robotWeight * 2.2046226218 - else: - displayWeight = exporterOptions.robotWeight - - weightInput = generalTabInputs.addValueInput( - "weightInput", - "Weight Input", - "", - 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", - "Compress Output", - generalTabInputs, - checked=exporterOptions.compressOutput, - tooltip="Compress the output file for a smaller file size.", - tooltipadvanced="
Use the GZIP compression system to compress the resulting file, " - "perfect if you want to share your robot design around.
", - enabled=True, - ) - - exportAsPartButton = createBooleanInput( - "exportAsPartButton", - "Export As Part", - generalTabInputs, - checked=exporterOptions.exportAsPart, - tooltip="Use to export as a part for Mix And Match", - enabled=True, - ) - - if exporterOptions.exportMode == ExportMode.FIELD: - autoCalcWeightButton.isVisible = False - exportAsPartButton.isVisible = False - weightInput.isVisible = weightTableInput.isVisible = False + inputs = args.command.commandInputs + self.generalOptionsTab = inputs.addTabCommandInput("generalSettings", "General Settings") + self.generalOptionsTab.tooltip = "General configuration options for your robot export." + generalTabInputs = self.generalOptionsTab.children + + dropdownExportMode = generalTabInputs.addDropDownCommandInput( + "exportModeDropdown", + "Export Mode", + dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle, + ) + + dynamic = exporterOptions.exportMode == ExportMode.ROBOT + dropdownExportMode.listItems.add("Dynamic", dynamic) + dropdownExportMode.listItems.add("Static", not dynamic) + dropdownExportMode.tooltip = "Export Mode" + dropdownExportMode.tooltipDescription = "
Does this object move dynamically?" + self.previousSelectedModeDropdownIndex = int(not dynamic) + + dropdownExportLocation = generalTabInputs.addDropDownCommandInput( + "exportLocation", "Export Location", dropDownStyle=adsk.core.DropDownStyles.LabeledIconDropDownStyle + ) + + upload: bool = exporterOptions.exportLocation == ExportLocation.UPLOAD + dropdownExportLocation.listItems.add("Upload", upload) + dropdownExportLocation.listItems.add("Download", not upload) + dropdownExportLocation.tooltip = "Export Location" + dropdownExportLocation.tooltipDescription = ( + "
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", + generalTabInputs, + checked=exporterOptions.autoCalcRobotWeight, + tooltip="Approximate the weight of your robot assembly.", + ) + self.previousAutoCalcWeightCheckboxState = exporterOptions.autoCalcRobotWeight + + self.currentUnits = exporterOptions.preferredUnits + imperialUnits = self.currentUnits == PreferredUnits.IMPERIAL + if imperialUnits: + # ExporterOptions always contains the metric value + displayWeight = exporterOptions.robotWeight * 2.2046226218 + else: + displayWeight = exporterOptions.robotWeight + + weightInput = generalTabInputs.addValueInput( + "weightInput", + "Weight Input", + "", + 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 - except BaseException: - logging.getLogger("{INTERNAL_ID}.UI.GeneralConfigTab").error("Failed:\n{}".format(traceback.format_exc())) + 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", + "Compress Output", + generalTabInputs, + checked=exporterOptions.compressOutput, + tooltip="Compress the output file for a smaller file size.", + tooltipadvanced="
Use the GZIP compression system to compress the resulting file, " + "perfect if you want to share your robot design around.
", + enabled=True, + ) + + exportAsPartButton = createBooleanInput( + "exportAsPartButton", + "Export As Part", + generalTabInputs, + checked=exporterOptions.exportAsPart, + tooltip="Use to export as a part for Mix And Match", + enabled=True, + ) + + if exporterOptions.exportMode == ExportMode.FIELD: + autoCalcWeightButton.isVisible = False + exportAsPartButton.isVisible = False + weightInput.isVisible = weightTableInput.isVisible = False @property def exportMode(self) -> ExportMode: @@ -207,81 +201,78 @@ def exportLocation(self) -> ExportLocation: assert exportLocationDropdown.selectedItem.index == 1 return ExportLocation.DOWNLOAD + @logFailure def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: - try: - commandInput = args.input - if commandInput.id == "exportModeDropdown": - modeDropdown = adsk.core.DropDownCommandInput.cast(commandInput) - autoCalcWeightButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("autoCalcWeightButton") - weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") - exportAsPartButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("exportAsPartButton") - if modeDropdown.selectedItem.index == self.previousSelectedModeDropdownIndex: - return - - if modeDropdown.selectedItem.index == 0: - self.jointConfigTab.isVisible = True - self.gamepieceConfigTab.isVisible = False - - autoCalcWeightButton.isVisible = True - weightTable.isVisible = True - exportAsPartButton.isVisible = True - else: - assert modeDropdown.selectedItem.index == 1 - self.jointConfigTab.isVisible = False - self.gamepieceConfigTab.isVisible = True - - autoCalcWeightButton.isVisible = False - weightTable.isVisible = False - exportAsPartButton.isVisible = False - - self.previousSelectedModeDropdownIndex = modeDropdown.selectedItem.index - - elif commandInput.id == "weightUnitDropdown": - weightUnitDropdown = adsk.core.DropDownCommandInput.cast(commandInput) - weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") - weightInput: adsk.core.ValueCommandInput = weightTable.getInputAtPosition(0, 1) - 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 - - weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") - weightInput: adsk.core.ValueCommandInput = weightTable.getInputAtPosition(0, 1) - - if autoCalcWeightButton.value: - robotMass = designMassCalculation() - weightInput.value = robotMass if self.currentUnits is PreferredUnits.METRIC else toLbs(robotMass) - weightInput.isEnabled = False - else: - weightInput.isEnabled = True - - self.previousAutoCalcWeightCheckboxState = autoCalcWeightButton.value - - except BaseException: - logging.getLogger(f"{INTERNAL_ID}.UI.GeneralConfigTab").error("Failed:\n{}".format(traceback.format_exc())) - - -# TODO: GH-1010 for failure logging with message box + commandInput = args.input + if commandInput.id == "exportModeDropdown": + modeDropdown = adsk.core.DropDownCommandInput.cast(commandInput) + autoCalcWeightButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("autoCalcWeightButton") + weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") + exportAsPartButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("exportAsPartButton") + if modeDropdown.selectedItem.index == self.previousSelectedModeDropdownIndex: + return + + if modeDropdown.selectedItem.index == 0: + self.jointConfigTab.isVisible = True + self.gamepieceConfigTab.isVisible = False + + autoCalcWeightButton.isVisible = True + weightTable.isVisible = True + exportAsPartButton.isVisible = True + else: + assert modeDropdown.selectedItem.index == 1 + self.jointConfigTab.isVisible = False + self.gamepieceConfigTab.isVisible = True + + autoCalcWeightButton.isVisible = False + weightTable.isVisible = False + exportAsPartButton.isVisible = False + + self.previousSelectedModeDropdownIndex = modeDropdown.selectedItem.index + + elif commandInput.id == "weightUnitDropdown": + weightUnitDropdown = adsk.core.DropDownCommandInput.cast(commandInput) + weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") + weightInput: adsk.core.ValueCommandInput = weightTable.getInputAtPosition(0, 1) + 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 + + weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") + weightInput: adsk.core.ValueCommandInput = weightTable.getInputAtPosition(0, 1) + + if autoCalcWeightButton.value: + robotMass = designMassCalculation() + weightInput.value = robotMass if self.currentUnits is PreferredUnits.METRIC else toLbs(robotMass) + weightInput.isEnabled = False + else: + weightInput.isEnabled = True + + self.previousAutoCalcWeightCheckboxState = autoCalcWeightButton.value + + # TODO: Perhaps move this into a different module +@logFailure def designMassCalculation() -> KG: app = adsk.core.Application.get() mass = 0.0 From 965bed1d1b4808ea6a7ceee60e748dba203e7ad2 Mon Sep 17 00:00:00 2001 From: LucaHaverty Date: Wed, 24 Jul 2024 12:06:33 -0700 Subject: [PATCH 034/344] Comments, cleanup, and format --- .../systems/simulation/behavior/Behavior.ts | 4 + .../simulation/behavior/GenericArmBehavior.ts | 4 +- .../behavior/GenericElevatorBehavior.ts | 4 +- fission/src/ui/components/MainHUD.tsx | 14 +- .../ui/panels/SequentialBehaviorsPanel.tsx | 191 ++++++++++-------- 5 files changed, 117 insertions(+), 100 deletions(-) diff --git a/fission/src/systems/simulation/behavior/Behavior.ts b/fission/src/systems/simulation/behavior/Behavior.ts index 2dd0fcb190..f9125f0514 100644 --- a/fission/src/systems/simulation/behavior/Behavior.ts +++ b/fission/src/systems/simulation/behavior/Behavior.ts @@ -20,4 +20,8 @@ abstract class Behavior { public abstract Update(deltaT: number): void } +export interface SequenceableBehavior { + get jointIndex(): number +} + export default Behavior diff --git a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts index 86243d0d45..6c184f2f92 100644 --- a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts @@ -1,9 +1,9 @@ import HingeDriver from "../driver/HingeDriver" import HingeStimulus from "../stimulus/HingeStimulus" -import Behavior from "./Behavior" +import Behavior, { SequenceableBehavior } from "./Behavior" import InputSystem from "@/systems/input/InputSystem" -class GenericArmBehavior extends Behavior { +class GenericArmBehavior extends Behavior implements SequenceableBehavior { private _hingeDriver: HingeDriver private _inputName: string private _jointIndex: number diff --git a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts index 90a8737e1a..f79989c60f 100644 --- a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts @@ -1,9 +1,9 @@ import SliderDriver from "../driver/SliderDriver" import SliderStimulus from "../stimulus/SliderStimulus" -import Behavior from "./Behavior" +import Behavior, { SequenceableBehavior } from "./Behavior" import InputSystem from "@/systems/input/InputSystem" -class GenericElevatorBehavior extends Behavior { +class GenericElevatorBehavior extends Behavior implements SequenceableBehavior { private _sliderDriver: SliderDriver private _inputName: string private _jointIndex: number diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 1715198771..43aca20504 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -160,6 +160,11 @@ const MainHUD: React.FC = () => { icon={} onClick={() => openPanel("driver-station")} /> + } + onClick={() => openPanel("sequential-joints")} + /> {/* MiraMap and OPFS Temp Buttons */} { openPanel("scoring-zones") }} /> - {/* } onClick={() => openModal("drivetrain")} /> + } onClick={() => openModal("drivetrain")} /> } @@ -205,13 +210,8 @@ const MainHUD: React.FC = () => { const type: ToastType = ["info", "warning", "error"][Math.floor(Random() * 3)] as ToastType addToast(type, type, "This is a test toast to test the toast system") }} - /> */} - } onClick={() => openModal("config-robot")} /> - } - onClick={() => openPanel("sequential-joints")} /> + } onClick={() => openModal("config-robot")} /> } diff --git a/fission/src/ui/panels/SequentialBehaviorsPanel.tsx b/fission/src/ui/panels/SequentialBehaviorsPanel.tsx index f0a10484de..c5107e1c62 100644 --- a/fission/src/ui/panels/SequentialBehaviorsPanel.tsx +++ b/fission/src/ui/panels/SequentialBehaviorsPanel.tsx @@ -8,16 +8,17 @@ import { FaGear, FaXmark } from "react-icons/fa6" import { Box, Button as MUIButton, Divider, styled, alpha } from "@mui/material" import Button, { ButtonSize } from "../components/Button" import GenericArmBehavior from "@/systems/simulation/behavior/GenericArmBehavior" -import GenericElevatorBehavior from "@/systems/simulation/behavior/GenericElevatorBehavior" -import Behavior from "@/systems/simulation/behavior/Behavior" +import { SequenceableBehavior } from "@/systems/simulation/behavior/Behavior" const UnselectParent = +/** White label for a behavior name */ const LabelStyled = styled(Label)({ fontWeight: 700, margin: "0pt", }) +/** Grey label for a child behavior name */ const ChildLabelStyled = styled(Label)({ fontWeight: 700, margin: "0pt", @@ -28,6 +29,7 @@ const DividerStyled = styled(Divider)({ borderColor: "white", }) +/** A button used to select a parent behavior. Appears at a grey outline when the 'set' button is pressed on a different behavior */ const CustomButton = styled(MUIButton)({ "borderStyle": "solid", "borderWidth": "1px", @@ -44,17 +46,19 @@ const CustomButton = styled(MUIButton)({ }) type BehaviorConfiguration = { - behavior: Behavior + behavior: SequenceableBehavior parent: BehaviorConfiguration | undefined } interface BehaviorCardProps { elementKey: number name: string + // The behavior displayed in this card behavior: BehaviorConfiguration + // The behavior whose 'set' button was just pressed + lookingForParent: BehaviorConfiguration | undefined update: () => void - onParentPressed: () => void - parentPressed: BehaviorConfiguration | undefined + onSetPressed: () => void onBehaviorSelected: () => void hasChild: boolean } @@ -64,91 +68,108 @@ const BehaviorCard: React.FC = ({ name, behavior, update, - onParentPressed, - parentPressed, + onSetPressed, + lookingForParent, onBehaviorSelected, hasChild, }) => { return ( + /* Box containing the entire card */ + {/* Box containing the label */} - + {/* Indentation before the name */} + + {/* Label for joint index and type (grey if child) */} {behavior.parent != undefined ? ( - <> - - {name} - - + + {name} + ) : ( - <> - - {name} - - + + {name} + )} + + {/* Button used for selecting a parent (shows up as an outline) */} { onBehaviorSelected() update() }} - disabled={parentPressed == undefined || parentPressed == behavior || behavior.parent != undefined} + disabled={lookingForParent == undefined || lookingForParent == behavior || behavior.parent != undefined} sx={{ borderColor: - parentPressed == undefined || parentPressed == behavior || behavior.parent != undefined + lookingForParent == undefined || lookingForParent == behavior || behavior.parent != undefined ? "transparent" : "#888888", }} /> - - - - ) -} - -const ButtonSecondary: React.FC = ({ value, onClick }) => { - return ( - - ) -} - -const ButtonIcon: React.FC = ({ value, onClick }) => { - return ( - - ) -} +import Panel, { PanelPropsImpl } from "@/ui/components/Panel" interface ItemCardProps { id: string name: string primaryButtonNode: ReactNode - secondaryButtonNode?: ReactNode primaryOnClick: () => void secondaryOnClick?: () => void } -const ItemCard: React.FC = ({ - id, - name, - primaryButtonNode, - secondaryButtonNode, - primaryOnClick, - secondaryOnClick, -}) => { +const ItemCard: React.FC = ({ id, name, primaryButtonNode, primaryOnClick, secondaryOnClick }) => { return ( = ({ alignItems={"center"} gap={"1rem"} > - {name.replace(/.mira$/, "")} + {name.replace(/.mira$/, "")} = ({ justifyContent={"center"} alignItems={"center"} > - - {secondaryButtonNode && secondaryOnClick && ( - - )} + + {secondaryOnClick && DeleteButton(secondaryOnClick)} ) @@ -298,7 +245,6 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { console.log(`Selecting cached robot: ${info.cacheKey}`) selectCache(info, MiraType.ROBOT) }, - secondaryButtonNode: DeleteIcon, secondaryOnClick: () => { console.log(`Deleting cache of: ${info.cacheKey}`) MirabufCachingService.Remove(info.cacheKey, info.id, MiraType.ROBOT) @@ -322,7 +268,6 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { console.log(`Selecting cached field: ${info.cacheKey}`) selectCache(info, MiraType.FIELD) }, - secondaryButtonNode: DeleteIcon, secondaryOnClick: () => { console.log(`Deleting cache of: ${info.cacheKey}`) MirabufCachingService.Remove(info.cacheKey, info.id, MiraType.FIELD) @@ -410,22 +355,22 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { {viewType == MiraType.ROBOT ? ( <> - + {cachedRobotElements ? `${cachedRobotElements.length} Saved Robot${cachedRobotElements.length == 1 ? "" : "s"}` : "Loading Saved Robots"} - - + + {cachedRobotElements} ) : ( <> - + {cachedFieldElements ? `${cachedFieldElements.length} Saved Field${cachedFieldElements.length == 1 ? "" : "s"}` : "Loading Saved Fields"} - - + + {cachedFieldElements} )} @@ -438,37 +383,33 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { justifyContent={"center"} alignItems={"center"} > - + {hubElements ? `${hubElements.length} Remote Asset${hubElements.length == 1 ? "" : "s"}` : filesStatus.message} - - {hubElements && filesStatus.isDone ? ( - RequestMirabufFiles()} /> - ) : ( - <> - )} + + {hubElements && filesStatus.isDone ? RefreshButton(() => RequestMirabufFiles()) : <>} - + {hubElements} {viewType == MiraType.ROBOT ? ( <> - + {remoteRobotElements ? `${remoteRobotElements.length} Default Robot${remoteRobotElements.length == 1 ? "" : "s"}` : "Loading Default Robots"} - - + + {remoteRobotElements} ) : ( <> - + {remoteFieldElements ? `${remoteFieldElements.length} Default Field${remoteFieldElements.length == 1 ? "" : "s"}` : "Loading Default Fields"} - - + + {remoteFieldElements} )} From 698658fb477d5bf318329982fe9fae7517c5f4db Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Thu, 25 Jul 2024 11:25:46 -0700 Subject: [PATCH 043/344] fix behavior inputs --- .../simulation/synthesis_brain/SynthesisBrain.ts | 6 +++--- .../java/com/autodesk/synthesis/ctre/TalonFX.java | 14 +++++++++----- .../JavaSample/src/main/java/frc/robot/Robot.java | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts b/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts index feb87d70bf..d6565d56a3 100644 --- a/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts +++ b/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts @@ -4,16 +4,16 @@ import Behavior from "../behavior/Behavior" import World from "@/systems/World" import WheelDriver from "../driver/WheelDriver" import WheelRotationStimulus from "../stimulus/WheelStimulus" -import ArcadeDriveBehavior from "../behavior/ArcadeDriveBehavior" +import ArcadeDriveBehavior from "../behavior/synthesis/ArcadeDriveBehavior" import { SimulationLayer } from "../SimulationSystem" import Jolt from "@barclah/jolt-physics" import JOLT from "@/util/loading/JoltSyncLoader" import HingeDriver from "../driver/HingeDriver" import HingeStimulus from "../stimulus/HingeStimulus" -import GenericArmBehavior from "../behavior/GenericArmBehavior" +import GenericArmBehavior from "../behavior/synthesis/GenericArmBehavior" import SliderDriver from "../driver/SliderDriver" import SliderStimulus from "../stimulus/SliderStimulus" -import GenericElevatorBehavior from "../behavior/GenericElevatorBehavior" +import GenericElevatorBehavior from "../behavior/synthesis/GenericElevatorBehavior" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import InputSystem from "@/systems/input/InputSystem" diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java index e2e4016e9a..6b54db84b9 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java @@ -1,7 +1,9 @@ package com.autodesk.synthesis.ctre; import com.autodesk.synthesis.CANMotor; -import com.ctre.phoenix.motorcontrol.NeutralMode; +// import com.ctre.phoenix6.configs.TalonFXConfigurator; +// import com.ctre.phoenix6.configs.TalonFXConfiguration; +import com.ctre.phoenix6.signals.NeutralModeValue; public class TalonFX extends com.ctre.phoenix6.hardware.TalonFX { private CANMotor m_motor; @@ -11,19 +13,21 @@ public TalonFX(int deviceNumber) { m_motor = new CANMotor("SYN TalonFX", deviceNumber, 0.0, false, 0.3); } - + public void set(double speed) { super.set(speed); this.m_motor.setPercentOutput(speed); } - public void setNeutralMode(NeutralMode mode) { + public void setNeutralMode(NeutralModeValue mode) { super.setNeutralMode(mode); - this.m_motor.setBrakeMode(mode == NeutralMode.Brake); + this.m_motor.setBrakeMode(mode == NeutralModeValue.Brake); } - public void configureNeutralDeadaband(double deadband) { + public void configureNeutralDeadband(double deadband) { + //super.configureNeutralDeadband(deadband); + // TODO: Find actual deadband config method this.m_motor.setNeutralDeadband(deadband); } } diff --git a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java index 2d2bea7a07..f2f913bb8c 100644 --- a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java +++ b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java @@ -4,8 +4,8 @@ package frc.robot; -import com.ctre.phoenix6.hardware.TalonFX; -import com.revrobotics.CANSparkBase.IdleMode; +// import com.ctre.phoenix6.hardware.TalonFX; +// import com.revrobotics.CANSparkBase.IdleMode; // import com.revrobotics.CANSparkMax; import com.revrobotics.CANSparkLowLevel.MotorType; From 337914e277d4bc77de07b1926fe1a9de67617b77 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Thu, 25 Jul 2024 15:35:00 -0700 Subject: [PATCH 044/344] Re-enable APS --- .../SynthesisFusionAddin/src/UI/ConfigCommand.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index 28969c01b0..13401aacb1 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -264,13 +264,12 @@ def notify(self, args): # enabled=True, # ) - # getAuth() - # user_info = getUserInfo() - # apsSettings = INPUTS_ROOT.addTabCommandInput( - # "aps_settings", f"APS Settings ({user_info.given_name if user_info else 'Not Signed In'})" - # ) - # apsSettings.tooltip = "Configuration settings for Autodesk Platform Services." - # aps_input = apsSettings.children + getAuth() + user_info = getUserInfo() + apsSettings = INPUTS_ROOT.addTabCommandInput( + "aps_settings", f"APS Settings ({user_info.given_name if user_info else 'Not Signed In'})" + ) + apsSettings.tooltip = "Configuration settings for Autodesk Platform Services." # clear all selections before instantiating handlers. gm.ui.activeSelections.clear() From 511c0ac04f147225ada14b115583ff9ddfa59c85 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Thu, 25 Jul 2024 16:02:49 -0700 Subject: [PATCH 045/344] Integration with GH-1014 --- .../src/UI/ConfigCommand.py | 41 +---------------- .../src/UI/GeneralConfigTab.py | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index 13401aacb1..843fbb1d8e 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -156,45 +156,6 @@ def notify(self, args): fusionJoint = gm.app.activeDocument.design.findEntityByToken(wheel.jointToken)[0] jointConfigTab.addWheel(fusionJoint, wheel) - # Transition: AARD-1769 - # Waiting for GH-1014 to merge - # ~~~~~~~~~~~~~~~~ PHYSICS SETTINGS ~~~~~~~~~~~~~~~~ - """ - Physics settings group command - """ - # physicsSettings: adsk.core.GroupCommandInput = a_input.addGroupCommandInput( - # "physics_settings", "Physics Settings" - # ) - - # physicsSettings.isExpanded = True - # physicsSettings.isEnabled = True - # physicsSettings.tooltip = "Settings relating to the custom physics of the robot, like the wheel friction" - # physics_settings: adsk.core.CommandInputs = physicsSettings.children - - # frictionOverrideInput = self.createBooleanInput( - # "friction_override", - # "Friction Override", - # physics_settings, - # checked=True, # object is missing attribute - # tooltip="Manually override the default friction values on the bodies in the assembly.", - # enabled=True, - # isCheckBox=False, - # ) - # frictionOverrideInput.resourceFolder = IconPaths.stringIcons["friction_override-enabled"] - # frictionOverrideInput.isFullWidth = True - - # valueList = [1] - # for i in range(20): - # valueList.append(i / 20) - - # frictionCoeffSlider: adsk.core.FloatSliderCommandInput = physics_settings.addFloatSliderListCommandInput( - # "friction_override_coeff", "Friction Coefficient", "", valueList - # ) - # frictionCoeffSlider.isVisible = True - # frictionCoeffSlider.valueOne = 0.5 - # frictionCoeffSlider.tooltip = "Friction coefficient of field element." - # frictionCoeffSlider.tooltipDescription = "Friction coefficients range from 0 (ice) to 1 (rubber)." - # ~~~~~~~~~~~~~~~~ JOINT SETTINGS ~~~~~~~~~~~~~~~~ """ Joint settings group command @@ -384,6 +345,8 @@ def notify(self, args): exportLocation=generalConfigTab.exportLocation, compressOutput=generalConfigTab.compress, exportAsPart=generalConfigTab.exportAsPart, + frictionOverride=generalConfigTab.overrideFriction, + frictionOverrideCoeff=generalConfigTab.frictionOverrideCoeff, ) Parser(exporterOptions).export() diff --git a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py index d74b51f22d..b6e088581e 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py @@ -18,6 +18,7 @@ class GeneralConfigTab: generalOptionsTab: adsk.core.TabCommandInput previousAutoCalcWeightCheckboxState: bool + previousFrictionOverrideCheckboxState: bool previousSelectedUnitDropdownIndex: int previousSelectedModeDropdownIndex: int currentUnits: PreferredUnits @@ -138,10 +139,26 @@ def __init__(self, args: adsk.core.CommandCreatedEventArgs, exporterOptions: Exp enabled=True, ) + frictionOverrideButton = createBooleanInput( + "frictionOverride", + "Friction Override", + generalTabInputs, + checked=exporterOptions.frictionOverride, + tooltip="Manually override the default friction values on the bodies in the assembly.", + ) + self.previousFrictionOverrideCheckboxState = exporterOptions.frictionOverride + + valueList = [1] + [i / 20 for i in range(20)] + frictionCoefficient = generalTabInputs.addFloatSliderListCommandInput("frictionCoefficient", "", "", valueList) + frictionCoefficient.valueOne = exporterOptions.frictionOverrideCoeff + frictionCoefficient.tooltip = "Friction coefficients range from 0 (ice) to 1 (rubber)." + frictionCoefficient.isVisible = exporterOptions.frictionOverride + if exporterOptions.exportMode == ExportMode.FIELD: autoCalcWeightButton.isVisible = False exportAsPartButton.isVisible = False weightInput.isVisible = weightTableInput.isVisible = False + frictionOverrideButton.isVisible = frictionCoefficient.isVisible = False @property def exportMode(self) -> ExportMode: @@ -201,6 +218,20 @@ def exportLocation(self) -> ExportLocation: assert exportLocationDropdown.selectedItem.index == 1 return ExportLocation.DOWNLOAD + @property + def overrideFriction(self) -> bool: + overrideFrictionButton: adsk.core.BoolValueCommandInput = self.generalOptionsTab.children.itemById( + "frictionOverride" + ) + return overrideFrictionButton.value + + @property + def frictionOverrideCoeff(self) -> float: + frictionSlider: adsk.core.FloatSliderCommandInput = self.generalOptionsTab.children.itemById( + "frictionCoefficient" + ) + return frictionSlider.valueOne + @logFailure def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: commandInput = args.input @@ -209,6 +240,8 @@ def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: autoCalcWeightButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("autoCalcWeightButton") weightTable: adsk.core.TableCommandInput = args.inputs.itemById("weightTable") exportAsPartButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("exportAsPartButton") + overrideFrictionButton: adsk.core.BoolValueCommandInput = args.inputs.itemById("frictionOverride") + frictionSlider: adsk.core.FloatSliderCommandInput = args.inputs.itemById("frictionCoefficient") if modeDropdown.selectedItem.index == self.previousSelectedModeDropdownIndex: return @@ -219,6 +252,8 @@ def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: autoCalcWeightButton.isVisible = True weightTable.isVisible = True exportAsPartButton.isVisible = True + overrideFrictionButton.isVisible = True + frictionSlider.isVisible = overrideFrictionButton.value else: assert modeDropdown.selectedItem.index == 1 self.jointConfigTab.isVisible = False @@ -227,6 +262,7 @@ def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: autoCalcWeightButton.isVisible = False weightTable.isVisible = False exportAsPartButton.isVisible = False + overrideFrictionButton.isVisible = frictionSlider.isVisible = False self.previousSelectedModeDropdownIndex = modeDropdown.selectedItem.index @@ -270,6 +306,16 @@ def handleInputChanged(self, args: adsk.core.InputChangedEventArgs) -> None: self.previousAutoCalcWeightCheckboxState = autoCalcWeightButton.value + elif commandInput.id == "frictionOverride": + frictionOverrideButton = adsk.core.BoolValueCommandInput.cast(commandInput) + frictionSlider: adsk.core.FloatSliderCommandInput = args.inputs.itemById("frictionCoefficient") + if frictionOverrideButton.value == self.previousFrictionOverrideCheckboxState: + return + + frictionSlider.isVisible = frictionOverrideButton.value + + self.previousFrictionOverrideCheckboxState = frictionOverrideButton.value + # TODO: Perhaps move this into a different module @logFailure From 28dab58f3782d4f929f5300ec147d5c5664c3a83 Mon Sep 17 00:00:00 2001 From: Dhruv Arora Date: Thu, 25 Jul 2024 16:24:12 -0700 Subject: [PATCH 046/344] Adding quality changing functionality Co-authored-by: BrandonPacewic --- fission/src/systems/scene/SceneRenderer.ts | 47 +++++++++++++++---- .../ui/modals/configuring/SettingsModal.tsx | 4 +- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/fission/src/systems/scene/SceneRenderer.ts b/fission/src/systems/scene/SceneRenderer.ts index 2567e0a6b6..8905670b64 100644 --- a/fission/src/systems/scene/SceneRenderer.ts +++ b/fission/src/systems/scene/SceneRenderer.ts @@ -79,18 +79,14 @@ class SceneRenderer extends WorldSystem { this._renderer.setSize(window.innerWidth, window.innerHeight) // this._light = new CascadingShadows(this._mainCamera, this._scene, this._renderer) - const shadowMapSize = Math.min(4096, this._renderer.capabilities.maxTextureSize) this._csm = new CSM({ - maxFar: 30, - cascades: 3, - mode: 'custom', parent: this._scene, - shadowMapSize: shadowMapSize, + camera: this._mainCamera, + cascades: 3, lightDirection: new THREE.Vector3( 0.5, -0.5, 0.5 ).normalize(), lightIntensity: 3, - camera: this._mainCamera, customSplitsCallback: (cascades: number, near: number, far: number, breaks: number[]) => { - const blend = 0.4; + const blend = 0.7; for (let i = 1; i < cascades; i++) { const uniformFactor = (near + (far - near) * i / cascades) / far; const logarithmicFactor = (near * (far / near) ** (i / cascades)) / far; @@ -103,7 +99,9 @@ class SceneRenderer extends WorldSystem { } }) this._csm.fade = true - + + this.ChangeQuality(PreferencesSystem.getGlobalPreference("QualitySettings")) + // this._csmHelper = new CSMHelper(this._csm) // this._csmHelper.visible = true // this._scene.add(this._csmHelper) @@ -168,6 +166,7 @@ class SceneRenderer extends WorldSystem { this._mainCamera.updateMatrixWorld() this._csm.update() + console.log(this._csm.shadowMapSize) // this._csmHelper.update() this._skybox.position.copy(this._mainCamera.position) @@ -192,6 +191,38 @@ class SceneRenderer extends WorldSystem { this.RemoveAllSceneObjects() } + public ChangeQuality(quality: string): void { + console.log("Changing quality to", quality) + + if (quality === "Low") { + + this._csm.shadowMapSize = Math.min(1024, this._renderer.capabilities.maxTextureSize) + this._csm.mode = "uniform" + this._csm.maxFar = 10 + + } else if (quality === "Medium") { + + this.renderer.setPixelRatio(window.devicePixelRatio) + + this._csm.shadowMapSize = Math.min(2048, this._renderer.capabilities.maxTextureSize) + this._csm.mode = "practical" + this._csm.maxFar = 20 + + } else if (quality === "High") { + + this.renderer.setPixelRatio(window.devicePixelRatio) + + this._csm.shadowMapSize = Math.min(4096, this._renderer.capabilities.maxTextureSize) + this._csm.mode = "custom" + this._csm.maxFar = 30 + + } + + this._csm.initCascades() + this._csm.updateShadowBounds() + this._csm.updateFrustums() + } + public RegisterSceneObject(obj: T): number { const id = nextSceneObjectId++ obj.id = id diff --git a/fission/src/ui/modals/configuring/SettingsModal.tsx b/fission/src/ui/modals/configuring/SettingsModal.tsx index 4c001c5771..bdde73f20b 100644 --- a/fission/src/ui/modals/configuring/SettingsModal.tsx +++ b/fission/src/ui/modals/configuring/SettingsModal.tsx @@ -9,6 +9,7 @@ import Slider from "@/components/Slider" import Checkbox from "@/components/Checkbox" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import { SceneOverlayEvent, SceneOverlayEventKey } from "@/ui/components/SceneOverlayEvents" +import World from "@/systems/World" const moveElementToTop = (arr: string[], element: string | undefined) => { if (element == undefined) { @@ -20,7 +21,7 @@ const moveElementToTop = (arr: string[], element: string | undefined) => { } const screenModeOptions = ["Windowed", "Fullscreen"] -const qualitySettingsOptions = ["Low", "Medium", "High", "Ultra"] +const qualitySettingsOptions = ["Low", "Medium", "High"] const SettingsModal: React.FC = ({ modalId }) => { const { openModal } = useModalControlContext() @@ -91,6 +92,7 @@ const SettingsModal: React.FC = ({ modalId }) => { )} onSelect={selected => { setQualitySettings(selected) + World.SceneRenderer.ChangeQuality(selected) }} /> + ) +} + const AssemblyCard: React.FC = ({ mira, update }) => { + const { openPanel } = usePanelControlContext() + const { closeModal } = useModalControlContext() + + const brain = useMemo(() => (mira.brain as SynthesisBrain)?.brainIndex, [mira]) + return (
- -
) } const ManageAssembliesModal: React.FC = ({ modalId }) => { - // update tooltip based on type of drivetrain, receive message from Synthesis - // const { showTooltip } = useTooltipControlContext() - const [_, update] = useReducer(x => !x, false) const assemblies = [...World.SceneRenderer.sceneObjects.entries()] @@ -43,23 +73,14 @@ const ManageAssembliesModal: React.FC = ({ modalId }) => { .map(x => x[1] as MirabufSceneObject) return ( - } - modalId={modalId} - onAccept={() => { - // showTooltip("controls", [ - // { control: "WASD", description: "Drive" }, - // { control: "E", description: "Intake" }, - // { control: "Q", description: "Dispense" }, - // ]); - }} - > + } modalId={modalId} acceptEnabled={false} cancelName="Back">
- {assemblies.map(x => AssemblyCard({ mira: x, update: update }))} + {assemblies.map(x => ( + + ))}
) diff --git a/fission/src/ui/panels/DebugPanel.tsx b/fission/src/ui/panels/DebugPanel.tsx index c8494cb87e..9b2187a8b1 100644 --- a/fission/src/ui/panels/DebugPanel.tsx +++ b/fission/src/ui/panels/DebugPanel.tsx @@ -16,6 +16,7 @@ import Jolt from "@barclah/jolt-physics" import Label from "../components/Label" import { colorNameToVar } from "../ThemeContext" import { FaScrewdriverWrench } from "react-icons/fa6" +import { useModalControlContext } from "../ModalContext" const LabelStyled = styled(Label)({ fontWeight: 700, @@ -50,6 +51,7 @@ async function TestGodMode() { const DebugPanel: React.FC = ({ panelId }) => { const { openPanel } = usePanelControlContext() + const { openModal } = useModalControlContext() return ( = ({ panelId }) => { } }} /> - - ) - })} - - {/* TODO: remove the accept button on this version */} - - ) : ( - <> - {/* Button for user to select the parent node */} - trySetSelectedNode(body.GetID())} - /> - - {/* Slider for user to set velocity of ejector configuration */} - { - setZoneSize(vel as number) - }} - step={0.01} - /> - { - if (transformGizmo) { - const robotTransformation = JoltMat44_ThreeMatrix4( - World.PhysicsSystem.GetBody(selectedRobot.GetRootNodeId()!).GetWorldTransform() - ) - transformGizmo.mesh.position.setFromMatrixPosition(robotTransformation) - transformGizmo.mesh.rotation.setFromRotationMatrix(robotTransformation) - } - setZoneSize((MIN_ZONE_SIZE + MAX_ZONE_SIZE) / 2.0) - setSelectedNode(selectedRobot?.rootNodeId) - }} - /> - - )} - + <> + {/* Button for user to select the parent node */} + trySetSelectedNode(body.GetID())} + /> + + {/* Slider for user to set velocity of ejector configuration */} + { + setZoneSize(vel as number) + }} + step={0.01} + /> + { + if (transformGizmo) { + const robotTransformation = JoltMat44_ThreeMatrix4( + World.PhysicsSystem.GetBody(selectedRobot.GetRootNodeId()!).GetWorldTransform() + ) + transformGizmo.mesh.position.setFromMatrixPosition(robotTransformation) + transformGizmo.mesh.rotation.setFromRotationMatrix(robotTransformation) + } + setZoneSize((MIN_ZONE_SIZE + MAX_ZONE_SIZE) / 2.0) + setSelectedNode(selectedRobot?.rootNodeId) + }} + /> + ) } -export default ConfigureGamepiecePickupPanel +export default ConfigureGamepiecePickupInterface diff --git a/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigureShotTrajectoryInterface.tsx similarity index 57% rename from fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx rename to fission/src/ui/panels/configuring/assembly-config/ConfigureShotTrajectoryInterface.tsx index 6f8d2cd046..29ff7ad713 100644 --- a/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigureShotTrajectoryInterface.tsx @@ -1,17 +1,12 @@ import * as THREE from "three" import { useCallback, useEffect, useMemo, useState } from "react" -import { FaGear } from "react-icons/fa6" -import Panel, { PanelPropsImpl } from "@/components/Panel" import SelectButton from "@/components/SelectButton" import TransformGizmos from "@/ui/components/TransformGizmos" import Slider from "@/ui/components/Slider" import Jolt from "@barclah/jolt-physics" -import Label from "@/ui/components/Label" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" -import Button from "@/ui/components/Button" import MirabufSceneObject, { RigidNodeAssociate } from "@/mirabuf/MirabufSceneObject" import World from "@/systems/World" -import { MiraType } from "@/mirabuf/MirabufLoader" import { Array_ThreeMatrix4, JoltMat44_ThreeMatrix4, @@ -21,6 +16,7 @@ import { import { useTheme } from "@/ui/ThemeContext" import LabeledButton, { LabelPlacement } from "@/ui/components/LabeledButton" import { RigidNodeId } from "@/mirabuf/MirabufParser" +import { ConfigurationSavedEvent } from "./ConfigureAssembliesPanel" // slider constants const MIN_VELOCITY = 0.0 @@ -77,25 +73,36 @@ function save( PreferencesSystem.savePreferences() } -const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openLocation, sidePadding }) => { +interface ConfigEjectorProps { + selectedRobot: MirabufSceneObject +} + +const ConfigureShotTrajectoryInterface: React.FC = ({ selectedRobot }) => { const { currentTheme, themes } = useTheme() const theme = useMemo(() => { return themes[currentTheme] }, [currentTheme, themes]) - const [selectedRobot, setSelectedRobot] = useState(undefined) const [selectedNode, setSelectedNode] = useState(undefined) const [ejectorVelocity, setEjectorVelocity] = useState((MIN_VELOCITY + MAX_VELOCITY) / 2.0) const [transformGizmo, setTransformGizmo] = 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 saveEvent = useCallback(() => { + if (transformGizmo && selectedRobot) { + save(ejectorVelocity, transformGizmo, selectedRobot, selectedNode) + const currentGp = selectedRobot.activeEjectable + selectedRobot.SetEjectable(undefined, true) + selectedRobot.SetEjectable(currentGp) + } + }, [transformGizmo, selectedRobot, selectedNode, ejectorVelocity]) + + useEffect(() => { + ConfigurationSavedEvent.Listen(saveEvent) + + return () => { + ConfigurationSavedEvent.RemoveListener(saveEvent) + } + }, [saveEvent]) // Not sure I like this, but made it a state and effect rather than a memo to add the cleanup to the end useEffect(() => { @@ -176,84 +183,45 @@ const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openL ) return ( - } - panelId={panelId} - openLocation={openLocation} - sidePadding={sidePadding} - onAccept={() => { - if (transformGizmo && selectedRobot) { - save(ejectorVelocity, transformGizmo, selectedRobot, selectedNode) - const currentGp = selectedRobot.activeEjectable - selectedRobot.SetEjectable(undefined, true) - selectedRobot.SetEjectable(currentGp) - } - }} - onCancel={() => {}} - acceptEnabled={selectedRobot?.ejectorPreferences != undefined} - > - {selectedRobot?.ejectorPreferences == undefined ? ( - <> - - {/** Scroll view for selecting a robot to configure */} -
- {robots.map(mirabufSceneObject => { - return ( - - ) - })} -
- {/* TODO: remove the accept button on this version */} - - ) : ( - <> - {/* Button for user to select the parent node */} - trySetSelectedNode(body.GetID())} - /> - - {/* Slider for user to set velocity of ejector configuration */} - { - setEjectorVelocity(vel as number) - }} - step={0.01} - /> - { - if (transformGizmo) { - const robotTransformation = JoltMat44_ThreeMatrix4( - World.PhysicsSystem.GetBody(selectedRobot.GetRootNodeId()!).GetWorldTransform() - ) - transformGizmo.mesh.position.setFromMatrixPosition(robotTransformation) - transformGizmo.mesh.rotation.setFromRotationMatrix(robotTransformation) - } - setEjectorVelocity((MIN_VELOCITY + MAX_VELOCITY) / 2.0) - setSelectedNode(selectedRobot?.rootNodeId) - }} - /> - - )} -
+ <> + {/* Button for user to select the parent node */} + trySetSelectedNode(body.GetID())} + /> + + {/* Slider for user to set velocity of ejector configuration */} + { + setEjectorVelocity(vel as number) + }} + step={0.01} + /> + { + if (transformGizmo) { + const robotTransformation = JoltMat44_ThreeMatrix4( + World.PhysicsSystem.GetBody(selectedRobot.GetRootNodeId()!).GetWorldTransform() + ) + transformGizmo.mesh.position.setFromMatrixPosition(robotTransformation) + transformGizmo.mesh.rotation.setFromRotationMatrix(robotTransformation) + } + setEjectorVelocity((MIN_VELOCITY + MAX_VELOCITY) / 2.0) + setSelectedNode(selectedRobot?.rootNodeId) + }} + /> + ) } -export default ConfigureShotTrajectoryPanel +export default ConfigureShotTrajectoryInterface From 18bbff40bd26d9f09d05c73f8f7c51937137398b Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Tue, 30 Jul 2024 17:36:40 -0700 Subject: [PATCH 095/344] Initial setup of exporter MacOS installer Co-authored-by: PepperLola --- .gitignore | 1 + installer/Linux/.gitignore | 6 - installer/Linux/README.md | 58 --- installer/Linux/Synthesis.AppDir/.DirIcon | 1 - installer/Linux/Synthesis.AppDir/AppRun | 89 ----- .../Linux/Synthesis.AppDir/synthesis.desktop | 6 - .../Linux/Synthesis.AppDir/synthesis.png | Bin 42175 -> 0 bytes installer/Linux/package.sh | 96 ----- installer/OSX-DMG/.gitignore | 9 - installer/OSX-DMG/README.md | 25 -- .../SynthesisMacInstallerBackground.png | Bin 5014 -> 0 bytes .../OSX-DMG/exporter-install-instructions.md | 12 - installer/OSX-DMG/license.txt | 188 ---------- installer/OSX-DMG/make-installer.sh | 22 -- installer/OSX/.gitignore | 12 - installer/OSX/App/payload/Contents/Info.plist | 39 -- installer/OSX/App/payload/Contents/README.md | 1 - installer/OSX/App/scripts/postinstall | 7 - installer/OSX/App/scripts/preinstall | 2 - .../OSX/Assets/payload/Contents/Info.plist | 39 -- .../payload/Contents/Synthesis/README.md | 1 - installer/OSX/Assets/scripts/postinstall | 5 - installer/OSX/Assets/scripts/preinstall | 2 - .../OSX/Exporter/payload/Contents/Info.plist | 39 -- .../SynthesisInventorGltfExporter/README.md | 1 - installer/OSX/Exporter/scripts/postinstall | 4 - installer/OSX/Exporter/scripts/preinstall | 3 - installer/OSX/Installer/Distribution.xml | 28 -- .../OSX/Installer/Resources/conclusion.html | 27 -- .../OSX/Installer/Resources/license.html | 206 ----------- .../OSX/Installer/Resources/welcome.html | 12 - installer/OSX/README.md | 36 -- installer/OSX/Resources/LICENSE.txt | 1 + installer/OSX/build.sh | 8 + installer/OSX/distribution.xml | 15 + installer/OSX/pkginstall | 12 - installer/Windows/.gitignore | 11 - installer/Windows/.gitkeep | 0 installer/Windows/MainInstaller.nsi | 332 ------------------ installer/Windows/README.md | 23 -- installer/Windows/W21_SYN_sidebar.bmp | Bin 152310 -> 0 bytes installer/Windows/orange-install-nsis.ico | Bin 25214 -> 0 bytes installer/Windows/orange-r.bmp | Bin 9744 -> 0 bytes installer/Windows/synthesis-logo-64x64.ico | Bin 184765 -> 0 bytes installer/exporter/.gitignore | 2 - installer/exporter/README.md | 14 - installer/exporter/create.sh | 11 - installer/exporter/install.bat | 10 - installer/exporter/install.sh | 10 - installer/exporter/setup.bat | 9 - 50 files changed, 25 insertions(+), 1410 deletions(-) delete mode 100644 installer/Linux/.gitignore delete mode 100644 installer/Linux/README.md delete mode 120000 installer/Linux/Synthesis.AppDir/.DirIcon delete mode 100755 installer/Linux/Synthesis.AppDir/AppRun delete mode 100755 installer/Linux/Synthesis.AppDir/synthesis.desktop delete mode 100644 installer/Linux/Synthesis.AppDir/synthesis.png delete mode 100755 installer/Linux/package.sh delete mode 100644 installer/OSX-DMG/.gitignore delete mode 100644 installer/OSX-DMG/README.md delete mode 100644 installer/OSX-DMG/SynthesisMacInstallerBackground.png delete mode 100644 installer/OSX-DMG/exporter-install-instructions.md delete mode 100755 installer/OSX-DMG/license.txt delete mode 100755 installer/OSX-DMG/make-installer.sh delete mode 100644 installer/OSX/.gitignore delete mode 100755 installer/OSX/App/payload/Contents/Info.plist delete mode 100644 installer/OSX/App/payload/Contents/README.md delete mode 100755 installer/OSX/App/scripts/postinstall delete mode 100755 installer/OSX/App/scripts/preinstall delete mode 100755 installer/OSX/Assets/payload/Contents/Info.plist delete mode 100644 installer/OSX/Assets/payload/Contents/Synthesis/README.md delete mode 100755 installer/OSX/Assets/scripts/postinstall delete mode 100755 installer/OSX/Assets/scripts/preinstall delete mode 100755 installer/OSX/Exporter/payload/Contents/Info.plist delete mode 100644 installer/OSX/Exporter/payload/Contents/SynthesisInventorGltfExporter/README.md delete mode 100755 installer/OSX/Exporter/scripts/postinstall delete mode 100755 installer/OSX/Exporter/scripts/preinstall delete mode 100755 installer/OSX/Installer/Distribution.xml delete mode 100755 installer/OSX/Installer/Resources/conclusion.html delete mode 100755 installer/OSX/Installer/Resources/license.html delete mode 100755 installer/OSX/Installer/Resources/welcome.html delete mode 100644 installer/OSX/README.md create mode 100644 installer/OSX/Resources/LICENSE.txt create mode 100755 installer/OSX/build.sh create mode 100644 installer/OSX/distribution.xml delete mode 100644 installer/OSX/pkginstall delete mode 100644 installer/Windows/.gitignore create mode 100644 installer/Windows/.gitkeep delete mode 100644 installer/Windows/MainInstaller.nsi delete mode 100644 installer/Windows/README.md delete mode 100644 installer/Windows/W21_SYN_sidebar.bmp delete mode 100644 installer/Windows/orange-install-nsis.ico delete mode 100644 installer/Windows/orange-r.bmp delete mode 100644 installer/Windows/synthesis-logo-64x64.ico delete mode 100644 installer/exporter/.gitignore delete mode 100644 installer/exporter/README.md delete mode 100755 installer/exporter/create.sh delete mode 100644 installer/exporter/install.bat delete mode 100755 installer/exporter/install.sh delete mode 100644 installer/exporter/setup.bat diff --git a/.gitignore b/.gitignore index a7ea447430..5057e53d24 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /build/ *.log .DS_Store +*.pkg diff --git a/installer/Linux/.gitignore b/installer/Linux/.gitignore deleted file mode 100644 index 2bdd6e8d6c..0000000000 --- a/installer/Linux/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -Synthesis.AppDir/usr/bin/* -Synthesis.AppDir/fields/* -Synthesis.AppDir/robots/* - -*.md5 -*.AppImage diff --git a/installer/Linux/README.md b/installer/Linux/README.md deleted file mode 100644 index 46e539d52a..0000000000 --- a/installer/Linux/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# `>_` Synthesis App Image - -For running Synthesis we have decided to package our application as an AppImage. It allows Synthesis to be packaged as a single .AppImage file. This also allows for users to run Synthesis without needing a specific distribution. - -### Initial Setup ### -It is recommended that you update the packages on your system. For arch users, run `pacman -Syu` as root. Debian users can run `apt update && apt upgrade` as root. - -### Dependencies ### -Certain dependencies are necessary in order to package Synthesis as an AppImage. - -##### FUSE ##### -For Arch, you may need to run: `pacman -S fuse` as root. For Debian, run: `apt install fuse` as root. -If you are still encountering issues, refer to this page: https://docs.appimage.org/user-guide/troubleshooting/fuse.html#ref-install-fuse - -##### appimagetool ##### -You will also end up needing appimagetool to actually create the AppImage file. However the `package.sh` script will prompt you and install it automatically. If you wish to install in manually, download the latest release from here: https://github.com/AppImage/AppImageKit/releases and make it executable. It should be called appimagetool-$ARCH.AppImage ($ARCH = whatever architecture you are using to package synthesis; most likely x86_64). - -### Initial Setup ### -In order to acheive the proper package structure for proper extraction, you must first compile a Unity build as: `Synthesis.x86_64` and store it along with all other files and directories that came with it somewhere on your machine. - -Note: It is important that you do not modify or remove any of the files and folders that come built with the `Synthesis.x86_64` file. - -It is also strongly recommended that you have some fields and robots exported in the Mirabuf format. - -### Packaging ### -The recommended way of creating the AppImage is by using the `package.sh` script. You may also opt to package Synthesis manually. There is some documentation for that process but it is recommended that you have a good understanding of what you are doing if you choose this option. - -To run the script, you will likely need to make it executable by running: `chmod +x package.sh` in your preferred terminal. You may also right click on `package.sh` in a file browser and select the option to make it executable. - -Now run the script and specify input directories for the version of synthesis you compiled as well as fields and robots: `./init.sh -f /path/to/fields/ -r /path/to/robots/ -b /path/to/synthesis/` - -Note: While it is not strictly necessary to include fields and robots, it is strongly recommended to include at least one of each - -If it is not already installed, the script will ask to install appimagetool. We recommended answering yes as it will install appimagetool.AppImage to the `~/Applications/` directory and is necessary for creating AppImage files. - -### Installing appimagetool ### -appimagetool is the name of the program that is used to create AppImages. You can download and install appimagetool through the official website https://appimage.github.io/appimagetool/ or get it through your distribution's package manager. - -Note: appimagetool is usually packaged under AppImageKit rather than as a standalone application. - -### Manual Packaging ### - -##### File locations ##### -Certain files must be moved to the correct locations. First you should move any robot files to `Synthesis.AppDir/robots` (create it if it doesn't exist). Do the same for field files but put them in `Synthesis.AppDir/fields` (create it if it doesn't exist). Finally, move all files and directories in your Synthesis build directory into `Synthesis.AppDir/usr/bin/` (once again, create it if it doesn't exist). - -##### Creating The AppImage ##### -Finally you can create your AppImage! Make sure you have all dependencies installed and run: `ARCH=x86_64 appimagetool Synthesis.AppDir` which will create the Synthesis AppImage. - -Note: Run this instead if you installed appimagetool locally: `ARCH=x86_64 /path/to/appimagetool Synthesis.AppDir` - -### Final Note ### -When the end user is downloading the AppImage file, it is strongly recommended to have them put it in the `~/Applications/` directory. This allows it to be found by appimaged as well as itself when running uninstall. It is also recommended to allow them to download the checksum.md5 file so that the file integrity can be verified using `md5sum -c checksum.md5` in the same directory as the AppImage and md5 files. - -### Troubleshooting ### -Refer to the AppImage troubleshooting page first if you are having issues: https://docs.appimage.org/user-guide/troubleshooting/index.html -The general documentation may be of use as well: https://docs.appimage.org/index.html -If the issues persist, open a github issue with details about the problem. - diff --git a/installer/Linux/Synthesis.AppDir/.DirIcon b/installer/Linux/Synthesis.AppDir/.DirIcon deleted file mode 120000 index 8c7bb9a13f..0000000000 --- a/installer/Linux/Synthesis.AppDir/.DirIcon +++ /dev/null @@ -1 +0,0 @@ -synthesis.png \ No newline at end of file diff --git a/installer/Linux/Synthesis.AppDir/AppRun b/installer/Linux/Synthesis.AppDir/AppRun deleted file mode 100755 index da2da947f5..0000000000 --- a/installer/Linux/Synthesis.AppDir/AppRun +++ /dev/null @@ -1,89 +0,0 @@ -#!/bin/sh - -show_help() { - echo "-h display help message" - echo "-u run uninstall script" -} - -install_appimaged() { - mkdir -p ~/Applications - wget -c https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases -O - | grep "appimaged-.*-x86_64.AppImage" | head -n 1 | cut -d '"' -f 2) -P ~/Applications/ - chmod +x ~/Applications/appimaged-*.AppImage - - # Launch - ~/Applications/appimaged-*.AppImage & -} - -uninstall_synthesis() { - rm -R ~/.config/Autodesk/Synthesis/ - if [ -e ~/Applications/Synthesis*.AppImage ] - then - rm ~/Applications/Synthesis*.AppImage - fi - if [ -e ~/Applications/appimaged-*.AppImage ] - then - while true; do - read -p "Do You wish to try and uninstall appimaged? (recommended) (y/n): " yn - case $yn in - [Yy]* ) - rm ~/Applications/appimaged-*.AppImage - break - ;; - [Nn]* ) - break - ;; - * ) - echo "Please answer yes or no." - ;; - esac - done - fi -} - -run_synthesis() { - mkdir -p ~/.config/Autodesk/Synthesis/Mira/Fields - cp "$HERE/fields/"*.mira ~/.config/Autodesk/Synthesis/Mira/Fields - cp "$HERE/robots/"*.mira ~/.config/Autodesk/Synthesis/Mira/ - - if [ ! -e ~/Applications/appimaged-*.AppImage ] - then - while true; do - read -p "Do You wish to install and start appimaged? (recommended) (y/n): " yn - case $yn in - [Yy]* ) - install_appimaged; - break - ;; - [Nn]* ) - break - ;; - * ) - echo "Please answer yes or no." - ;; - esac - done - fi - - exec "$EXEC" -} - -HERE="$(dirname "$(readlink -f "${0}")")" -EXEC="$HERE/usr/bin/Synthesis.x86_64" - -OPTIND=1 -while getopts ":hu" opt; do - case "$opt" in - h|\?) - show_help - exit 0 - ;; - u) - uninstall_synthesis - exit 0 - ;; - esac -done - -shift $((OPTIND-1)) - -run_synthesis diff --git a/installer/Linux/Synthesis.AppDir/synthesis.desktop b/installer/Linux/Synthesis.AppDir/synthesis.desktop deleted file mode 100755 index 424ddebffb..0000000000 --- a/installer/Linux/Synthesis.AppDir/synthesis.desktop +++ /dev/null @@ -1,6 +0,0 @@ -[Desktop Entry] -Name=Synthesis -Exec=Synthesis.x86_64 -Icon=synthesis -Type=Application -Categories=Game diff --git a/installer/Linux/Synthesis.AppDir/synthesis.png b/installer/Linux/Synthesis.AppDir/synthesis.png deleted file mode 100644 index 35f6df073646ad75f8fa7dbe386068e65eaddbab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42175 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 ipQP(Ag4dq|DF1&1p4FHJdVSdd0000 $INIT_DIR/checksum.md5 - md5sum -c $INIT_DIR/checksum.md5 -} - -OPTIND=1 - -ARCH="x86_64" -APP_NAME="Synthesis" -INIT_DIR="$(dirname "$(readlink -f "${0}")")" -APP_DIR="$INIT_DIR/$APP_NAME.AppDir" - -mkdir -p "$APP_DIR/usr/bin/" -mkdir -p "$APP_DIR/fields" -mkdir -p "$APP_DIR/robots" - -while getopts "h?f:r:b:" opt; do - case "$opt" in - h|\?) - show_help - exit 0 - ;; - f) - fields="$OPTARG" - ;; - r) - robots="$OPTARG" - ;; - b) - build="$OPTARG" - ;; - esac -done - -shift $((OPTIND-1)) - -if [ ! -n "$build" ] ; then - echo "Specify synthesis build directory using \"-b\"" - exit 1 -fi - -if [ -n "$fields" ] ; then - cp "$fields/"*.mira "$APP_DIR/fields/" -fi - -if [ -n "$robots" ] ; then - cp "$robots/"*.mira "$APP_DIR/robots/" -fi - -cp -R "$build/"* "$APP_DIR/usr/bin" -chmod +x "$APP_DIR/AppRun" -chmod +x "$APP_DIR/synthesis.desktop" -chmod +x "$APP_DIR/usr/bin/Synthesis.x86_64" - -if [ ! -e ~/Applications/appimagetool-*.AppImage ] ; then - while true; do - read -p "Do you wish to install appimagetool (Needed for creating the AppImage file) (y/n): " yn - case $yn in - [Yy]* ) - install_appimagetool - break - ;; - [Nn]* ) - break - ;; - * ) - echo "Please answer yes or no." - ;; - esac - done -fi - -if [ -e ~/Applications/appimagetool-x86_64.AppImage ] ; then - create_appimage -else - echo "Install appimagetool before creating AppImage" -fi diff --git a/installer/OSX-DMG/.gitignore b/installer/OSX-DMG/.gitignore deleted file mode 100644 index 5ddb15821b..0000000000 --- a/installer/OSX-DMG/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -source_folder/Synthesis.app/ -exporter_source_folder/ -Synthesis-Installer.dmg -create-dmg/ -addins-folder-link - -exporter-install-instructions.pdf - -*.dmg diff --git a/installer/OSX-DMG/README.md b/installer/OSX-DMG/README.md deleted file mode 100644 index 18cd11dd1c..0000000000 --- a/installer/OSX-DMG/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Synthesis OSX Installer (DMG Version) - -## Setup -1. Install `create-dmg`: -``` -$ git clone git@github.com:create-dmg/create-dmg.git -``` - -2. Copy the signed Synthesis app into source_folder: -``` -$ cp -r [Location of app] ./source_folder/Synthesis.app -``` - -2. Compile the `exporter-install-instructions.md` into a PDF. I recommend using the Yzane extension in VSCode. - -## Create Disk Image -Run the `make-installer.sh` shell script: -``` -$ ./make-installer.sh -``` - -Disk Image will be created at `/installer/OSX-DMG/Synthesis-Installer.dmg` - -## Notes -Update `source_folder/license.html` as needed as well as settings for the `create-dmg` command inside of `make-installer.sh`. See [create-dmg repository](https://github.com/create-dmg/create-dmg) for configuration information. \ No newline at end of file diff --git a/installer/OSX-DMG/SynthesisMacInstallerBackground.png b/installer/OSX-DMG/SynthesisMacInstallerBackground.png deleted file mode 100644 index 0d4d27e9a140ec497ca72b0a09c210c7b4f5f5a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5014 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 yl7(osty*X- - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - Synthesis - CFBundleGetInfoString - Unity Player version 2019.4.0f1 (0af376155913). (c) 2020 Unity Technologies ApS. All rights reserved. - CFBundleIconFile - PlayerIcon.icns - CFBundleIdentifier - com.Autodesk.Synthesis - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Synthesis - CFBundlePackageType - APPL - CFBundleShortVersionString - 0.1 - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - 0 - LSApplicationCategoryType - public.app-category.games - LSMinimumSystemVersion - 10.9.0 - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - - diff --git a/installer/OSX/App/payload/Contents/README.md b/installer/OSX/App/payload/Contents/README.md deleted file mode 100644 index 3b82ea5db1..0000000000 --- a/installer/OSX/App/payload/Contents/README.md +++ /dev/null @@ -1 +0,0 @@ -## Synthesis.zip diff --git a/installer/OSX/App/scripts/postinstall b/installer/OSX/App/scripts/postinstall deleted file mode 100755 index 2c990d0d53..0000000000 --- a/installer/OSX/App/scripts/postinstall +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -mv $2/Contents/Synthesis.zip /Applications/ -rm -rf $2 -cd /Applications/ -unzip /Applications/Synthesis.zip -rm -rf Synthesis.zip -exit 0 diff --git a/installer/OSX/App/scripts/preinstall b/installer/OSX/App/scripts/preinstall deleted file mode 100755 index 06bd986563..0000000000 --- a/installer/OSX/App/scripts/preinstall +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -exit 0 diff --git a/installer/OSX/Assets/payload/Contents/Info.plist b/installer/OSX/Assets/payload/Contents/Info.plist deleted file mode 100755 index 734c2b7a8a..0000000000 --- a/installer/OSX/Assets/payload/Contents/Info.plist +++ /dev/null @@ -1,39 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - Synthesis - CFBundleGetInfoString - Unity Player version 2019.4.0f1 (0af376155913). (c) 2020 Unity Technologies ApS. All rights reserved. - CFBundleIconFile - PlayerIcon.icns - CFBundleIdentifier - com.Autodesk.Synthesis - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Synthesis - CFBundlePackageType - APPL - CFBundleShortVersionString - 0.1 - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - 0 - LSApplicationCategoryType - public.app-category.games - LSMinimumSystemVersion - 10.9.0 - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - - diff --git a/installer/OSX/Assets/payload/Contents/Synthesis/README.md b/installer/OSX/Assets/payload/Contents/Synthesis/README.md deleted file mode 100644 index 970708317f..0000000000 --- a/installer/OSX/Assets/payload/Contents/Synthesis/README.md +++ /dev/null @@ -1 +0,0 @@ -## Asset/Data Files diff --git a/installer/OSX/Assets/scripts/postinstall b/installer/OSX/Assets/scripts/postinstall deleted file mode 100755 index 421ba3de1c..0000000000 --- a/installer/OSX/Assets/scripts/postinstall +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -rm -rf ~/.config/Autodesk/Synthesis -mv $2/Contents/Synthesis ~/.config/Autodesk/ -rm -rf $2 -exit 0 diff --git a/installer/OSX/Assets/scripts/preinstall b/installer/OSX/Assets/scripts/preinstall deleted file mode 100755 index 06bd986563..0000000000 --- a/installer/OSX/Assets/scripts/preinstall +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -exit 0 diff --git a/installer/OSX/Exporter/payload/Contents/Info.plist b/installer/OSX/Exporter/payload/Contents/Info.plist deleted file mode 100755 index 734c2b7a8a..0000000000 --- a/installer/OSX/Exporter/payload/Contents/Info.plist +++ /dev/null @@ -1,39 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - Synthesis - CFBundleGetInfoString - Unity Player version 2019.4.0f1 (0af376155913). (c) 2020 Unity Technologies ApS. All rights reserved. - CFBundleIconFile - PlayerIcon.icns - CFBundleIdentifier - com.Autodesk.Synthesis - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Synthesis - CFBundlePackageType - APPL - CFBundleShortVersionString - 0.1 - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - 0 - LSApplicationCategoryType - public.app-category.games - LSMinimumSystemVersion - 10.9.0 - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - - diff --git a/installer/OSX/Exporter/payload/Contents/SynthesisInventorGltfExporter/README.md b/installer/OSX/Exporter/payload/Contents/SynthesisInventorGltfExporter/README.md deleted file mode 100644 index 0ec8650013..0000000000 --- a/installer/OSX/Exporter/payload/Contents/SynthesisInventorGltfExporter/README.md +++ /dev/null @@ -1 +0,0 @@ -## Exporter Files diff --git a/installer/OSX/Exporter/scripts/postinstall b/installer/OSX/Exporter/scripts/postinstall deleted file mode 100755 index 4000f1e866..0000000000 --- a/installer/OSX/Exporter/scripts/postinstall +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -mv $2/Contents/SynthesisFusionAddin ~/Library/Application\ Support/Autodesk/Autodesk\ Fusion\ 360/API/AddIns/ -rm -rf $2 -exit 0 diff --git a/installer/OSX/Exporter/scripts/preinstall b/installer/OSX/Exporter/scripts/preinstall deleted file mode 100755 index 495e3edbba..0000000000 --- a/installer/OSX/Exporter/scripts/preinstall +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -mkdir -p ~/Library/Application Support/Autodesk/Autodesk Fusion 360/API/AddIns/ -exit 0 diff --git a/installer/OSX/Installer/Distribution.xml b/installer/OSX/Installer/Distribution.xml deleted file mode 100755 index 4d54db2521..0000000000 --- a/installer/OSX/Installer/Distribution.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - App.pkg - - - - Assets.pkg - - - - Exporter.pkg - diff --git a/installer/OSX/Installer/Resources/conclusion.html b/installer/OSX/Installer/Resources/conclusion.html deleted file mode 100755 index 9151c5f509..0000000000 --- a/installer/OSX/Installer/Resources/conclusion.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - -
-

Synthesis: An Autodesk Technology | 5.0.0.0 ALPHA

-

Thank you for installing Synthesis: An Autodesk Technology

-
-
-

In order to improve this product and understand how it is used, we collect non-personal product usage information. This usage information may consist of custom events like Replay Mode, Driver Practice Mode, Tutorial Link Clicked, etc. This information is not used to identify or contact you. You can turn data collection off from the Control Panel within the simulator. By installing, you agree that you have read the terms of service agreement and data collection statement above.

-
-
-

Resources

-

Go through the following link for additional information.

- -
-
-
-

Copyright © 2021 Autodesk inc.

-
- - diff --git a/installer/OSX/Installer/Resources/license.html b/installer/OSX/Installer/Resources/license.html deleted file mode 100755 index 6be72e4428..0000000000 --- a/installer/OSX/Installer/Resources/license.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - -
-

- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/

- -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

- -1. Definitions.

- -"License" shall mean the terms and conditions for use, reproduction, -and distribution as defined by Sections 1 through 9 of this document. -


-"Licensor" shall mean the copyright owner or entity authorized by -the copyright owner that is granting the License. -


-"Legal Entity" shall mean the union of the acting entity and all -other entities that control, are controlled by, or are under common -control with that entity. For the purposes of this definition, -"control" means (i) the power, direct or indirect, to cause the -direction or management of such entity, whether by contract or -otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. -


-"You" (or "Your") shall mean an individual or Legal Entity -exercising permissions granted by this License. -


-"Source" form shall mean the preferred form for making modifications, -including but not limited to software source code, documentation -source, and configuration files. -


-"Object" form shall mean any form resulting from mechanical -transformation or translation of a Source form, including but -not limited to compiled object code, generated documentation, -and conversions to other media types. -


-"Work" shall mean the work of authorship, whether in Source or -Object form, made available under the License, as indicated by a -copyright notice that is included in or attached to the work -(an example is provided in the Appendix below). -


-"Derivative Works" shall mean any work, whether in Source or Object -form, that is based on (or derived from) the Work and for which the -editorial revisions, annotations, elaborations, or other modifications -represent, as a whole, an original work of authorship. For the purposes -of this License, Derivative Works shall not include works that remain -separable from, or merely link (or bind by name) to the interfaces of, -the Work and Derivative Works thereof. -


-"Contribution" shall mean any work of authorship, including -the original version of the Work and any modifications or additions -to that Work or Derivative Works thereof, that is intentionally -submitted to Licensor for inclusion in the Work by the copyright owner -or by an individual or Legal Entity authorized to submit on behalf of -the copyright owner. For the purposes of this definition, "submitted" -means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, -and issue tracking systems that are managed by, or on behalf of, the -Licensor for the purpose of discussing and improving the Work, but -excluding communication that is conspicuously marked or otherwise -designated in writing by the copyright owner as "Not a Contribution." -


-"Contributor" shall mean Licensor and any individual or Legal Entity -on behalf of whom a Contribution has been received by Licensor and -subsequently incorporated within the Work. -


-2. Grant of Copyright License. Subject to the terms and conditions of -this License, each Contributor hereby grants to You a perpetual, -worldwide, non-exclusive, no-charge, royalty-free, irrevocable -copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the -Work and such Derivative Works in Source or Object form. -


-3. Grant of Patent License. Subject to the terms and conditions of -this License, each Contributor hereby grants to You a perpetual, -worldwide, non-exclusive, no-charge, royalty-free, irrevocable -(except as stated in this section) patent license to make, have made, -use, offer to sell, sell, import, and otherwise transfer the Work, -where such license applies only to those patent claims licensable -by such Contributor that are necessarily infringed by their -Contribution(s) alone or by combination of their Contribution(s) -with the Work to which such Contribution(s) was submitted. If You -institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work -or a Contribution incorporated within the Work constitutes direct -or contributory patent infringement, then any patent licenses -granted to You under this License for that Work shall terminate -as of the date such litigation is filed. -


-4. Redistribution. You may reproduce and distribute copies of the -Work or Derivative Works thereof in any medium, with or without -modifications, and in Source or Object form, provided that You -meet the following conditions: -


-(a) You must give any other recipients of the Work or -Derivative Works a copy of this License; and -


-(b) You must cause any modified files to carry prominent notices -stating that You changed the files; and -


-(c) You must retain, in the Source form of any Derivative Works -that You distribute, all copyright, patent, trademark, and -attribution notices from the Source form of the Work, -excluding those notices that do not pertain to any part of -the Derivative Works; and -


-(d) If the Work includes a "NOTICE" text file as part of its -distribution, then any Derivative Works that You distribute must -include a readable copy of the attribution notices contained -within such NOTICE file, excluding those notices that do not -pertain to any part of the Derivative Works, in at least one -of the following places: within a NOTICE text file distributed -as part of the Derivative Works; within the Source form or -documentation, if provided along with the Derivative Works; or, -within a display generated by the Derivative Works, if and -wherever such third-party notices normally appear. The contents -of the NOTICE file are for informational purposes only and -do not modify the License. You may add Your own attribution -notices within Derivative Works that You distribute, alongside -or as an addendum to the NOTICE text from the Work, provided -that such additional attribution notices cannot be construed -as modifying the License. -


-You may add Your own copyright statement to Your modifications and -may provide additional or different license terms and conditions -for use, reproduction, or distribution of Your modifications, or -for any such Derivative Works as a whole, provided Your use, -reproduction, and distribution of the Work otherwise complies with -the conditions stated in this License. -


-5. Submission of Contributions. Unless You explicitly state otherwise, -any Contribution intentionally submitted for inclusion in the Work -by You to the Licensor shall be under the terms and conditions of -this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify -the terms of any separate license agreement you may have executed -with Licensor regarding such Contributions. -


-6. Trademarks. This License does not grant permission to use the trade -names, trademarks, service marks, or product names of the Licensor, -except as required for reasonable and customary use in describing the -origin of the Work and reproducing the content of the NOTICE file. -


-7. Disclaimer of Warranty. Unless required by applicable law or -agreed to in writing, Licensor provides the Work (and each -Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -implied, including, without limitation, any warranties or conditions -of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A -PARTICULAR PURPOSE. You are solely responsible for determining the -appropriateness of using or redistributing the Work and assume any -risks associated with Your exercise of permissions under this License. -


-8. Limitation of Liability. In no event and under no legal theory, -whether in tort (including negligence), contract, or otherwise, -unless required by applicable law (such as deliberate and grossly -negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, -incidental, or consequential damages of any character arising as a -result of this License or out of the use or inability to use the -Work (including but not limited to damages for loss of goodwill, -work stoppage, computer failure or malfunction, or any and all -other commercial damages or losses), even if such Contributor -has been advised of the possibility of such damages. -


-9. Accepting Warranty or Additional Liability. While redistributing -the Work or Derivative Works thereof, You may choose to offer, -and charge a fee for, acceptance of support, warranty, indemnity, -or other liability obligations and/or rights consistent with this -License. However, in accepting such obligations, You may act only -on Your own behalf and on Your sole responsibility, not on behalf -of any other Contributor, and only if You agree to indemnify, -defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason -of your accepting any such warranty or additional liability. -


-END OF TERMS AND CONDITIONS -


-APPENDIX: How to apply the Apache License to your work. -


-Copyright 2021 Autodesk inc. -


-Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at -


-http://www.apache.org/licenses/LICENSE-2.0 -


-Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -


-

-

Click “Continue" to continue the setup

-
- - diff --git a/installer/OSX/Installer/Resources/welcome.html b/installer/OSX/Installer/Resources/welcome.html deleted file mode 100755 index dcea4723b3..0000000000 --- a/installer/OSX/Installer/Resources/welcome.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - -
-

This will install Synthesis: An Autodesk Application 5.0.0.0 ALPHA on your computer. You will be guided through the steps necessary to install this software.

-

Click “Continue" to continue the setup

-
- - diff --git a/installer/OSX/README.md b/installer/OSX/README.md deleted file mode 100644 index 846f17c8b2..0000000000 --- a/installer/OSX/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# OSX packager - -## Build Steps : - -1. Get signed Synthesis.app - -2. Zip Synthesis.app - -3. Copy Synthesis.zip to file system ` cp [Synthesis.zip] [synthesis/installer/OSX/App/payload/Contents] ` - - - Remove the Synthesis.zip placeholder ` rm [synthesis/installer/OSX/App/payload/Contents/README.md] ` - -3. Add data files to ` synthesis/installer/OSX/Assets/payload/Contents/Synthesis ` - - - Remove the data file placeholder ` rm [synthesis/installer/OSX/Assets/payload/Contents/Synthesis/README.md] ` - -4. Add unzipped exporter files to ` synthesis/installer/OSX/Exporter/payload/Contents/SynthesisFusionGltfExporter ` - - - Remove the exporter file placeholder ` rm [synthesis/installer/OSX/Exporter/payload/Contents/SynthesisFusionGltfExporter/README.md] ` - -5. Change directories to the OSX installer directory ` cd [synthesis/installer/OSX] ` - -6. Run the pkginstall script ` ./pkginstall ` - -### Optional Build Steps - -Update the license, welcome and conclusion installer menus located in ` [synthesis/installer/OSX/Installer/Resources] ` - -## Package - -Publish the newly created Synthesis.pkg - -## Important Note - -**Do not** rename or move files - diff --git a/installer/OSX/Resources/LICENSE.txt b/installer/OSX/Resources/LICENSE.txt new file mode 100644 index 0000000000..b10760de2f --- /dev/null +++ b/installer/OSX/Resources/LICENSE.txt @@ -0,0 +1 @@ +../../../LICENSE.txt diff --git a/installer/OSX/build.sh b/installer/OSX/build.sh new file mode 100755 index 0000000000..c6a2095a77 --- /dev/null +++ b/installer/OSX/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +FUSION_ADDIN_LOCATION="Support/Autodesk/Autodesk\ Fusion\ 360/API/AddIns/" +EXPORTER_SOURCE_DIR=../../exporter/ +FUSION_ADDIN_LOCATION=~/Documents/ + +pkgbuild --root $EXPORTER_SOURCE_DIR --identifier com.Autodesk.Synthesis --version 1.0 --scripts Scripts --install-location $FUSION_ADDIN_LOCATION MyApp.pkg +productbuild --distribution distribution.xml --package-path . MyAppInstaller.pkg diff --git a/installer/OSX/distribution.xml b/installer/OSX/distribution.xml new file mode 100644 index 0000000000..534c1465b1 --- /dev/null +++ b/installer/OSX/distribution.xml @@ -0,0 +1,15 @@ + + + Synthesis Exporter Installer + + + + + + + + + + + #MyApp.pkg + diff --git a/installer/OSX/pkginstall b/installer/OSX/pkginstall deleted file mode 100644 index 359e862088..0000000000 --- a/installer/OSX/pkginstall +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -#Priviledges -chmod -R 777 ./Assets/payload/Contents/Synthesis -#Build app -pkgbuild --install-location ~/.config/Autodesk/SynthesisAppInstall --root ./App/payload/ --scripts ./App/scripts/ --identifier org.autodesk.synthesis.app ./Installer/App.pkg -#Build data files -pkgbuild --install-location ~/.config/Autodesk/SynthesisAssetsInstall --root ./Assets/payload/ --scripts ./Assets/scripts/ --identifier org.autodesk.synthesis.assets ./Installer/Assets.pkg -#Build exporter -pkgbuild --install-location ~/.config/Autodesk/SynthesisExporterInstall --root ./Exporter/payload/ --scripts ./Exporter/scripts/ --identifier org.autodesk.synthesis.exporter ./Installer/Exporter.pkg -#Build installer -cd Installer -productbuild --distribution Distribution.xml --resources Resources/ ../Synthesis.pkg diff --git a/installer/Windows/.gitignore b/installer/Windows/.gitignore deleted file mode 100644 index 65fac130c8..0000000000 --- a/installer/Windows/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -Robots/ -Fields/ -Exporter/ -Themes/ -MixAndMatch/ -Synthesis/ - -Robots.zip -Fields.zip -Themes.zip -MixAndMatch.zip diff --git a/installer/Windows/.gitkeep b/installer/Windows/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/installer/Windows/MainInstaller.nsi b/installer/Windows/MainInstaller.nsi deleted file mode 100644 index 7cf07b658e..0000000000 --- a/installer/Windows/MainInstaller.nsi +++ /dev/null @@ -1,332 +0,0 @@ -!include MUI2.nsh -!include x64.nsh -!define PRODUCT_VERSION "6.0.0" - -Name "Synthesis" - -;Icon "W16_SYN_launch.ico" -Icon "synthesis-logo-64x64.ico" - -Caption "Synthesis ${PRODUCT_VERSION} Setup" - -OutFile "SynthesisWin${PRODUCT_VERSION}.exe" - -InstallDir $PROGRAMFILES64\Autodesk\Synthesis - -InstallDirRegKey HKLM "Software\Synthesis" "Install_Dir" - -RequestExecutionLevel admin - - Section - ${If} ${RunningX64} - goto install_stuff - ${Else} - MessageBox MB_OK "ERROR: This install requires a 64 bit system." - Quit - ${EndIf} - install_stuff: - SectionEnd - -;-------------------------------- - -;Interface Settings - !define MUI_WELCOMEFINISHPAGE_BITMAP "W21_SYN_sidebar.bmp" - !define MUI_UNWELCOMEFINISHPAGE_BITMAP "W21_SYN_sidebar.bmp" - !define MUI_ICON "synthesis-logo-64x64.ico" - !define MUI_UNICON "synthesis-logo-64x64.ico" - !define MUI_HEADERIMAGE - !define MUI_HEADERIMAGE_BITMAP "orange-r.bmp" - !define MUI_HEADERIMAGE_RIGHT - !define MUI_ABORTWARNING - !define MUI_FINISHPAGE_TEXT 'Synthesis has been successfully installed on your system. $\r$\n $\r$\nIn order to improve this product and understand how it is used, we collect non-personal product usage information. This usage information may consist of custom events like Replay Mode, Driver Practice Mode, etc. $\r$\nThis information is not used to identify or contact you. $\r$\nYou can turn data collection off from the Control Panel within the simulator. $\r$\n $\r$\nBy clicking Finish, you agree that you have read the terms of service agreement and data collection statement above.' - !define MUI_FINISHPAGE_LINK "Synthesis Discord" - !define MUI_FINISHPAGE_LINK_LOCATION "https://www.discord.gg/hHcF9AVgZA" - -;-------------------------------- - - ; Installer GUI Pages - !insertmacro MUI_PAGE_WELCOME - !insertmacro MUI_PAGE_LICENSE "..\..\LICENSE.txt" - !insertmacro MUI_PAGE_COMPONENTS - !insertmacro MUI_PAGE_INSTFILES - !insertmacro MUI_PAGE_FINISH - - ; Uninstaller GUI Pages - !insertmacro MUI_UNPAGE_WELCOME - !insertmacro MUI_UNPAGE_CONFIRM - !insertmacro MUI_UNPAGE_INSTFILES - !insertmacro MUI_UNPAGE_FINISH - -;-------------------------------- - - ; Default Language - !insertmacro MUI_LANGUAGE "English" - -Section - -IfFileExists "$APPDATA\Autodesk\Synthesis" +1 +28 - MessageBox MB_YESNO "You appear to have Synthesis installed; would you like to reinstall it?" IDYES true IDNO false - true: - DeleteRegKey HKLM SOFTWARE\Synthesis - - ; Remove fusion plugins - RMDir /r "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\FusionRobotExporter" - RMDir /r "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis" - RMDir /r "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\FusionExporter" - RMDir /r "$APPDATA\Autodesk\ApplicationPlugins\FusionRobotExporter.bundle" - RMDir /r "$APPDATA\Autodesk\ApplicationPlugins\FusionSynth.bundle" - - ; Remove inventor plugins - Delete "$APPDATA\Autodesk\Inventor 2021\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2020\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2019\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2018\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2017\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete "$APPDATA\Autodesk\ApplicationPlugins\Autodesk.InventorRobotExporter.Inventor.addin" - RMDir /r "$APPDATA\Autodesk\ApplicationPlugins\InventorRobotExporter" - - ; Remove deprecated bxd inventor plugins - Delete "$APPDATA\Autodesk\Inventor 2020\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2019\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2019\Addins\autodesk.BxDFieldExporter.inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2018\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2018\Addins\autodesk.BxDFieldExporter.inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2017\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2017\Addins\autodesk.BxDFieldExporter.inventor.addin" - Delete "$APPDATA\Autodesk\ApplicationPlugins\Autodesk.BxDRobotExporter.Inventor.addin" - RMDir /r "$APPDATA\Autodesk\ApplicationPlugins\BxDRobotExporter" - RMDIR /r $APPDATA\RobotViewer - - ; Remove excess shortcuts - Delete "$SMPROGRAMS\Synthesis.lnk" - Delete "$DESKTOP\Synthesis.lnk" - Delete "$SMPROGRAMS\BXD Synthesis.lnk" - Delete "$DESKTOP\BXD Synthesis.lnk" - Delete "$SMPROGRAMS\Autodesk Synthesis.lnk" - Delete "$DESKTOP\Autodesk Synthesis.lnk" - Delete "$DESKTOP\FieldExporter.lnk" - - ; Remove obsolete directories - RMDir /r $INSTDIR - RMDir /r $APPDATA\Synthesis - RMDir /r $APPDATA\BXD_Aardvark - RMDir /r $PROGRAMFILES\Autodesk\Synthesis - - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" - DeleteRegKey HKCU "SOFTWARE\Autodesk\Synthesis" - ;DeleteRegKey HKCU "SOFTWARE\Autodesk\BXD Synthesis" - - Goto next - - false: - Quit - - next: - -# default section end -SectionEnd - -Section "Synthesis (required)" Synthesis - - SectionIn RO - - ; Set output path to the installation directory. - SetOutPath $INSTDIR - - File /r "Synthesis\*" - - CreateShortCut "$SMPROGRAMS\Synthesis.lnk" "$INSTDIR\Synthesis.exe" - CreateShortCut "$DESKTOP\Synthesis.lnk" "$INSTDIR\Synthesis.exe" - - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" \ - "DisplayName" "Autodesk Synthesis" - - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" \ - "DisplayIcon" "$INSTDIR\uninstall.exe" - - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" \ - "Publisher" "Autodesk" - - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" \ - "URLInfoAbout" "synthesis.autodesk.com/tutorials" - - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" \ - "DisplayVersion" "${PRODUCT_VERSION}" - - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" \ - "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" - - - WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synthesis" "NoModify" 1 - WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synthesis" "NoRepair" 1 - WriteUninstaller "uninstall.exe" - -SectionEnd - -/* -Section "Inventor Exporter Plugin" iExporter - - ; Set extraction path to Inventor plugin directory - SetOutPath $INSTDIR\Exporter - File /r "InventorExporter\*" - - SetOutPath $APPDATA\Autodesk\ApplicationPlugins - File /r "InventorExporter\Autodesk.InventorRobotExporter.Inventor.addin" - -SectionEnd -*/ - -Section "Fusion Exporter Plugin" fExporter - - ; Set extraction path to Fusion plugin directories - SetOutPath "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis" - File /r "Exporter\*" - ; File /r "..\..\exporter\SynthesisFusionAddin\*" - - ; SetOutPath "$APPDATA\Autodesk\ApplicationPlugins\FusionRobotExporter.bundle\Contents\" - ; File /r "FusionExporter\FusionRobotExporter.dll" - -SectionEnd - -Section "Robots and Fields" RobotFiles - - ; Set extraction path for preloaded robot files - SetOutPath $APPDATA\Autodesk\Synthesis\Mira - File /r "Robots\*" - - SetOutPath $APPDATA\Autodesk\Synthesis\Mira\Fields - File /r "Fields\*" - -SectionEnd - -Section "PartBuilder Samples" PartBuilder - - ; Set extraction path for preloaded robot files - SetOutPath $APPDATA\Autodesk\Synthesis\MixAndMatch - File /r "MixAndMatch\*" - -SectionEnd - -Section "Themes" Themes - - ; Set extraction path for preloaded robot files - SetOutPath $APPDATA\Autodesk\Synthesis\Themes - File /r "Themes\*" - -SectionEnd - -/* -Section "Code Emulator" Emulator - - ; INetC.dll must be installed to proper NSIS Plugins x86 directories - inetc::get "https://qemu.weilnetz.de/w64/2019/qemu-w64-setup-20190724.exe" "$PLUGINSDIR\qemu-w64-setup-20190724.exe" - Pop $R0 ;Get the return value - - ${If} $R0 == "OK" ;Return value should be "OK" - SetOutPath $APPDATA\Autodesk\Synthesis\Emulator - File /r "Emulator\*" - HideWindow - ExecWait '"$PLUGINSDIR\qemu-w64-setup-20190724.exe" /SILENT' - ShowWindow hwnd show_state - ${Else} - MessageBox MB_ICONSTOP "Error: $R0" ;Show cancel/error message - ${EndIf} - -SectionEnd -*/ - -;-------------------------------- -;Component Descriptions - - LangString DESC_Synthesis ${LANG_ENGLISH} "The Simulator Engine is the real-time physics environment which simulates the robots and fields." - ; LangString DESC_iExporter ${LANG_ENGLISH} "The Inventor Exporter Plugin is an Inventor addin used to export Autodesk Inventor Assemblies directly into the simulator" - LangString DESC_fExporter ${LANG_ENGLISH} "The Fusion360 Exporter Plugin is a Fusion addin used to export Autodesk Fusion Assemblies directly into the simulator" - LangString DESC_RobotFiles ${LANG_ENGLISH} "A library of sample robots and fields pre-loaded into the simulator" - LangString DESC_PartBuilder ${LANG_ENGLISH} "A library of sample parts to use in Robot Builder" - LangString DESC_Themes ${LANG_ENGLISH} "Preinstalled themes" - ; LangString DESC_Emulator ${LANG_ENGLISH} "The Robot Code Emulator allows you to emulate your C++ & JAVA robot code in the simulator" - - !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN - !insertmacro MUI_DESCRIPTION_TEXT ${Synthesis} $(DESC_Synthesis) - ; !insertmacro MUI_DESCRIPTION_TEXT ${iExporter} $(DESC_iExporter) - !insertmacro MUI_DESCRIPTION_TEXT ${fExporter} $(DESC_fExporter) - !insertmacro MUI_DESCRIPTION_TEXT ${RobotFiles} $(DESC_RobotFiles) - !insertmacro MUI_DESCRIPTION_TEXT ${PartBuilder} $(DESC_PartBuilder) - !insertmacro MUI_DESCRIPTION_TEXT ${Themes} $(DESC_Themes) - ; !insertmacro MUI_DESCRIPTION_TEXT ${Emulator} $(DESC_Emulator) - !insertmacro MUI_FUNCTION_DESCRIPTION_END - -;-------------------------------- - -Section "Uninstall" - - MessageBox MB_YESNO "Would you like to remove your robot files?" IDNO NawFam - RMDir /r /REBOOTOK $APPDATA\Synthesis - RMDir /r /REBOOTOK $APPDATA\Autodesk\Synthesis - - NawFam: - ; Remove registry keys - DeleteRegKey HKLM SOFTWARE\Synthesis - DeleteRegKey HKCU SOFTWARE\Autodesk\Synthesis - DeleteRegKey HKCU "SOFTWARE\Autodesk\BXD Synthesis" - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" - - ; Remove installation directories - RMDir /r /REBOOTOK $INSTDIR - RMDir /r /REBOOTOK $PROGRAMFILES\Autodesk\Synthesis - RMDir /r /REBOOTOK $APPDATA\BXD_Aardvark - RMDir /r /REBOOTOK $APPDATA\SynthesisTEMP - - ; Remove fusion plugins - RMDir /r "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\FusionRobotExporter" - RMDir /r "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis" - RMDir /r "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\FusionExporter" - RMDir /r "$APPDATA\Autodesk\ApplicationPlugins\FusionRobotExporter.bundle" - RMDir /r "$APPDATA\Autodesk\ApplicationPlugins\FusionSynth.bundle" - - ; Remove inventor plugins - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2022\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2021\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2020\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2019\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2018\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2017\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\ApplicationPlugins\Autodesk.InventorRobotExporter.Inventor.addin" - - ; Remove deprecated bxd inventor plugins - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2020\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2019\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2019\Addins\autodesk.BxDFieldExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2018\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2018\Addins\autodesk.BxDFieldExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2017\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2017\Addins\autodesk.BxDFieldExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\ApplicationPlugins\Autodesk.BxDRobotExporter.Inventor.addin" - RMDir /REBOOTOK "$APPDATA\Autodesk\ApplicationPlugins\BxDRobotExporter" - - ; Remove excess shortcuts - Delete "$SMPROGRAMS\Synthesis.lnk" - Delete "$DESKTOP\Synthesis.lnk" - Delete "$SMPROGRAMS\BXD Synthesis.lnk" - Delete "$DESKTOP\BXD Synthesis.lnk" - Delete "$SMPROGRAMS\Autodesk Synthesis.lnk" - Delete "$DESKTOP\Autodesk Synthesis.lnk" - Delete "$DESKTOP\FieldExporter.lnk" - - /* - ; Execute QEMU uninstaller - IfFileExists "$PROGRAMFILES64\qemu" file_found uninstall_complete - - file_found: - MessageBox MB_YESNO "Would you like to uninstall QEMU as well?" IDNO uninstall_complete - Exec '"$PROGRAMFILES64\qemu\qemu-uninstall.exe"' - Quit - - uninstall_complete: - */ - -SectionEnd - -Function .OnInstSuccess - Exec "$INSTDIR\Synthesis.exe" -FunctionEnd diff --git a/installer/Windows/README.md b/installer/Windows/README.md deleted file mode 100644 index 21ea8340c9..0000000000 --- a/installer/Windows/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# logoNullsoft Scriptable Install System - -For installation on Windows Operating Systems, we use our own custom written NSIS installer in order to extract all the necessary files to their proper locations on the system. - -- [MainInstaller(x64)](https://github.com/Autodesk/synthesis/blob/prod/installer/Windows/MainInstaller.nsi) - (Used for the full installation of Synthesis, only compatible on 64 bit operating systems.) -- [EngInstaller(x86)](https://github.com/Autodesk/synthesis/blob/prod/installer/Windows/EngInstaller(x86).nsi) - (Used only for installation on 32 bit operating systems and extracts just the Unity Engine.) - -### Compiling NSIS: -In order to compile the NSIS configuration properly, you must compile all of the individual components of Synthesis pertaining to the particular script you are trying to compile. Then the compiled components must be stored in the same directory as the NSIS script, in order for them to be packaged during NSIS compilation. For details on this process, feel free to contact matthew.moradi@autodesk.com - -### NSIS FAQ: - -Q: Will I need admin privileges to run the installer? - -A: Yup. - -Q: If I download an updated Synthesis installer, will running it replace all of my custom robot export files? - -A: No, _reinstalling_ Synthesis will only replace all of the application components, but your custom robots will be saved. - -Q: Is it possible to accidently install multiple versions of Synthesis? - -A: It shouldn't be. The installer will always replace any existing Synthesis installations on your system. diff --git a/installer/Windows/W21_SYN_sidebar.bmp b/installer/Windows/W21_SYN_sidebar.bmp deleted file mode 100644 index 60c007ab76e40e022f2eb73aa36a4fdd86a7bbbd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 152310 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 diff --git a/installer/Windows/orange-install-nsis.ico b/installer/Windows/orange-install-nsis.ico deleted file mode 100644 index ef3975f56c9bd8c1a06cef83c67c535010a65ee8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25214 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 KKeFO?tN#O)lB@v$ diff --git a/installer/Windows/orange-r.bmp b/installer/Windows/orange-r.bmp deleted file mode 100644 index c74fbdd511d240983ed66bb3e37aec500df99f84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9744 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 j4Y6aeH~t00000NkvXXu0mjfPEVab diff --git a/installer/exporter/.gitignore b/installer/exporter/.gitignore deleted file mode 100644 index 290dfd77ff..0000000000 --- a/installer/exporter/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/Synthesis -/*.zip diff --git a/installer/exporter/README.md b/installer/exporter/README.md deleted file mode 100644 index 631eb3527b..0000000000 --- a/installer/exporter/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Synthesis Exporter Installer - -## Creating the installer -### Windows -1. Run `setup.bat`. This will copy the Synthesis exporter into the current directory. -2. Zip together the `install.bat` script and the `Synthesis` directory. - -### MacOS -1. Run `create.sh`. This will copy the Synthesis exporter into the current directory and create a zip file with the necessary files. - -## Using the installer -1. Download the zip file. -2. Unzip it anywhere (likely your Downloads folder). -3. Run the `install.bat` (`install.sh` for MacOS) script. diff --git a/installer/exporter/create.sh b/installer/exporter/create.sh deleted file mode 100755 index 22efb4ac4b..0000000000 --- a/installer/exporter/create.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -if test -d ./Synthesis; then - rm -rf ./Synthesis -fi - -cp -r ../../exporter/SynthesisFusionAddin/ ./Synthesis -echo "Copied over Synthesis exporter!" - -zip -r SynthesisExporter Synthesis/ install.sh -echo "Created zip installer!" diff --git a/installer/exporter/install.bat b/installer/exporter/install.bat deleted file mode 100644 index 293bac431a..0000000000 --- a/installer/exporter/install.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off - -if exist "%AppData%\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis\" ( - echo "Removing existing Synthesis exporter..." - rmdir "%AppData%\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis\" /Q/S -) - -echo "Copying to %AppData%\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis..." -xcopy Synthesis "%AppData%\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis\" /E -echo "Synthesis Exporter Successfully Installed!" diff --git a/installer/exporter/install.sh b/installer/exporter/install.sh deleted file mode 100755 index 4ab0801918..0000000000 --- a/installer/exporter/install.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -if test -d ~/Library/Application\ Support/Autodesk/Autodesk\ Fusion\ 360/API/AddIns/SynthesisFusionAddin; then - echo "Removing existing Synthesis exporter..." - rm -rf ~/Library/Application\ Support/Autodesk/Autodesk\ Fusion\ 360/API/AddIns/SynthesisFusionAddin -fi - -cp -r Synthesis/ ~/Library/Application\ Support/Autodesk/Autodesk\ Fusion\ 360/API/AddIns/SynthesisFusionAddin - -echo "Synthesis successfully copied!" diff --git a/installer/exporter/setup.bat b/installer/exporter/setup.bat deleted file mode 100644 index a6ec90bb68..0000000000 --- a/installer/exporter/setup.bat +++ /dev/null @@ -1,9 +0,0 @@ -@echo off - -if exist Synthesis\ ( - rmdir Synthesis /Q/S - echo Removed .\Synthesis -) - -xcopy ..\..\exporter\SynthesisFusionAddin Synthesis\ /E -echo Copied exporter into .\Synthesis From dce50890903aafb1194093315918c995f9680cad Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Wed, 31 Jul 2024 09:29:38 -0700 Subject: [PATCH 096/344] Basic installer done --- installer/OSX/build.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/installer/OSX/build.sh b/installer/OSX/build.sh index c6a2095a77..383b1519ae 100755 --- a/installer/OSX/build.sh +++ b/installer/OSX/build.sh @@ -1,8 +1,12 @@ #!/bin/bash -FUSION_ADDIN_LOCATION="Support/Autodesk/Autodesk\ Fusion\ 360/API/AddIns/" -EXPORTER_SOURCE_DIR=../../exporter/ -FUSION_ADDIN_LOCATION=~/Documents/ +FUSION_ADDIN_LOCATION=~/Library/Application\ Support/Autodesk/Autodesk\ Fusion\ 360/API/AddIns/ +EXPORTER_SOURCE_DIR=../../exporter/SynthesisFusionAddin/ -pkgbuild --root $EXPORTER_SOURCE_DIR --identifier com.Autodesk.Synthesis --version 1.0 --scripts Scripts --install-location $FUSION_ADDIN_LOCATION MyApp.pkg +mkdir -p tmp/ +cp -r "$EXPORTER_SOURCE_DIR"/* tmp/ + +pkgbuild --root tmp/ --identifier com.Autodesk.Synthesis --version 1.0 --install-location "$FUSION_ADDIN_LOCATION" MyApp.pkg productbuild --distribution distribution.xml --package-path . MyAppInstaller.pkg + +rm -r tmp/ From 56ae665d6666e7c3383e8e435eaf32ee474ba7e8 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 31 Jul 2024 09:52:43 -0700 Subject: [PATCH 097/344] Fixed --- fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx index 194cfa03bf..6bcd867d90 100644 --- a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx +++ b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx @@ -57,7 +57,7 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { ]) const hashBuffer = await selectedFile.arrayBuffer() - await MirabufCachingService.CacheAndGetLocal(hashBuffer, MiraType.ROBOT) + await MirabufCachingService.CacheAndGetLocal(hashBuffer, miraType) .then(x => CreateMirabuf(x!)) .then(x => { if (x) { From 0d51cf7294c6501833234a43be9fe1769a97e47e Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Wed, 31 Jul 2024 10:41:55 -0700 Subject: [PATCH 098/344] Bump exporter version --- exporter/SynthesisFusionAddin/Synthesis.manifest | 2 +- installer/OSX/build.sh | 5 +++-- installer/OSX/distribution.xml | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exporter/SynthesisFusionAddin/Synthesis.manifest b/exporter/SynthesisFusionAddin/Synthesis.manifest index f8e96ae366..47434ab6a2 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.manifest +++ b/exporter/SynthesisFusionAddin/Synthesis.manifest @@ -6,7 +6,7 @@ "description": { "": "Synthesis Exporter" }, - "version": "1.0.0", + "version": "2.0.0", "runOnStartup": true, "supportedOS": "windows|mac", "editEnabled": true diff --git a/installer/OSX/build.sh b/installer/OSX/build.sh index 383b1519ae..673c7c80a7 100755 --- a/installer/OSX/build.sh +++ b/installer/OSX/build.sh @@ -6,7 +6,8 @@ EXPORTER_SOURCE_DIR=../../exporter/SynthesisFusionAddin/ mkdir -p tmp/ cp -r "$EXPORTER_SOURCE_DIR"/* tmp/ -pkgbuild --root tmp/ --identifier com.Autodesk.Synthesis --version 1.0 --install-location "$FUSION_ADDIN_LOCATION" MyApp.pkg -productbuild --distribution distribution.xml --package-path . MyAppInstaller.pkg +pkgbuild --root tmp/ --identifier com.Autodesk.Synthesis --version 2.0.0 --install-location "$FUSION_ADDIN_LOCATION" SynthesisExporter.pkg +productbuild --distribution distribution.xml --package-path . SynthesisExporterInstaller.pkg +rm SynthesisExporter.pkg rm -r tmp/ diff --git a/installer/OSX/distribution.xml b/installer/OSX/distribution.xml index 534c1465b1..e46f643e6f 100644 --- a/installer/OSX/distribution.xml +++ b/installer/OSX/distribution.xml @@ -10,6 +10,5 @@ - - #MyApp.pkg + #SynthesisExporter.pkg From 1b141da50e878bdeb6f3d9fe743ace5980b11183 Mon Sep 17 00:00:00 2001 From: LucaHaverty Date: Wed, 31 Jul 2024 11:47:10 -0700 Subject: [PATCH 099/344] Scoring zone config in configure assemblies panel --- fission/src/Synthesis.tsx | 4 - fission/src/ui/components/MainHUD.tsx | 8 - .../ConfigureAssembliesPanel.tsx | 23 +- .../ConfigureGamepiecePickupInterface.tsx | 2 +- .../ConfigureShotTrajectoryInterface.tsx | 2 +- .../ConfigureScoringZonesInterface.tsx | 47 ++++ .../scoring/ManageZonesInterface.tsx | 156 +++++++++++++ .../scoring/ZoneConfigInterface.tsx} | 202 ++++++++--------- .../configuring/scoring/ScoringZonesPanel.tsx | 206 ------------------ 9 files changed, 319 insertions(+), 331 deletions(-) rename fission/src/ui/panels/configuring/assembly-config/{ => interfaces}/ConfigureGamepiecePickupInterface.tsx (99%) rename fission/src/ui/panels/configuring/assembly-config/{ => interfaces}/ConfigureShotTrajectoryInterface.tsx (99%) create mode 100644 fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx create mode 100644 fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageZonesInterface.tsx rename fission/src/ui/panels/configuring/{scoring/ZoneConfigPanel.tsx => assembly-config/interfaces/scoring/ZoneConfigInterface.tsx} (58%) delete mode 100644 fission/src/ui/panels/configuring/scoring/ScoringZonesPanel.tsx diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 5ce7e04b9d..88a719202d 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -37,7 +37,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 ScoringZonesPanel from "@/panels/configuring/scoring/ScoringZonesPanel" import ScoreboardPanel from "@/panels/information/ScoreboardPanel" import DriverStationPanel from "@/panels/simulation/DriverStationPanel" import PokerPanel from "@/panels/PokerPanel.tsx" @@ -50,7 +49,6 @@ import Skybox from "./ui/components/Skybox.tsx" import ChooseInputSchemePanel from "./ui/panels/configuring/ChooseInputSchemePanel.tsx" import ProgressNotifications from "./ui/components/ProgressNotification.tsx" import ResetAllInputsModal from "./ui/modals/configuring/ResetAllInputsModal.tsx" -import ZoneConfigPanel from "./ui/panels/configuring/scoring/ZoneConfigPanel.tsx" import SceneOverlay from "./ui/components/SceneOverlay.tsx" import WPILibWSWorker from "@/systems/simulation/wpilib_brain/WPILibWSWorker.ts?worker" @@ -231,8 +229,6 @@ const initialPanels: ReactElement[] = [ , , , - , - , , , , diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 3450c0deab..2b0bb212c8 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -4,7 +4,6 @@ import { // FaMagnifyingGlass, FaPlus, FaGamepad, - FaBasketball, FaFileImport, FaWrench, FaScrewdriverWrench, @@ -112,13 +111,6 @@ const MainHUD: React.FC = () => { />
- } - onClick={() => { - openPanel("scoring-zones") - }} - /> } onClick={() => openModal("config-robot")} /> @@ -122,7 +123,12 @@ const ConfigInterface: React.FC = ({ configMode, assembly case ConfigMode.CONTROLS: return case ConfigMode.SCORING_ZONES: - return + const zones = assembly.fieldPreferences?.scoringZones + if (zones == undefined) { + console.error("Field does not contain scoring zone preferences!") + return + } + return default: throw new Error(`Config mode ${configMode} has no associated interface`) } @@ -159,6 +165,7 @@ const ConfigureAssembliesPanel: React.FC = ({ panelId }) => { onAccept={() => { new ConfigurationSavedEvent() }} + acceptName="Close" >
= ({ panelId }) => { onChange={(_, v) => { v != null && setAssemblyType(v) setSelectedAssembly(undefined) - setSelectedConfigMode(undefined) + if (selectedConfigMode != undefined) { + new ConfigurationSavedEvent() + setSelectedConfigMode(undefined) + } }} sx={{ alignSelf: "center", @@ -179,7 +189,10 @@ const ConfigureAssembliesPanel: React.FC = ({ panelId }) => { { - if (selectedConfigMode != undefined) new ConfigurationSavedEvent() + if (selectedConfigMode != undefined) { + new ConfigurationSavedEvent() + setSelectedConfigMode(undefined) + } setSelectedAssembly(a) }} /> diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigureGamepiecePickupInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx similarity index 99% rename from fission/src/ui/panels/configuring/assembly-config/ConfigureGamepiecePickupInterface.tsx rename to fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx index 63a7fcbba5..6e30d2d81c 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigureGamepiecePickupInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx @@ -17,7 +17,7 @@ import { import LabeledButton, { LabelPlacement } from "@/ui/components/LabeledButton" import { useTheme } from "@/ui/ThemeContext" import React from "react" -import { ConfigurationSavedEvent } from "./ConfigureAssembliesPanel" +import { ConfigurationSavedEvent } from "../ConfigureAssembliesPanel" // slider constants const MIN_ZONE_SIZE = 0.1 diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigureShotTrajectoryInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureShotTrajectoryInterface.tsx similarity index 99% rename from fission/src/ui/panels/configuring/assembly-config/ConfigureShotTrajectoryInterface.tsx rename to fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureShotTrajectoryInterface.tsx index 29ff7ad713..99ebb95ac8 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigureShotTrajectoryInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureShotTrajectoryInterface.tsx @@ -16,7 +16,7 @@ import { import { useTheme } from "@/ui/ThemeContext" import LabeledButton, { LabelPlacement } from "@/ui/components/LabeledButton" import { RigidNodeId } from "@/mirabuf/MirabufParser" -import { ConfigurationSavedEvent } from "./ConfigureAssembliesPanel" +import { ConfigurationSavedEvent } from "../ConfigureAssembliesPanel" // slider constants const MIN_VELOCITY = 0.0 diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx new file mode 100644 index 0000000000..ff48d4f917 --- /dev/null +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx @@ -0,0 +1,47 @@ +import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import { ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" +import React, { useState } from "react" +import ManageZonesInterface from "./ManageZonesInterface" +import ZoneConfigInterface from "./ZoneConfigInterface" +import PreferencesSystem from "@/systems/preferences/PreferencesSystem" + +const saveZones = (zones: ScoringZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { + if (!zones || !field) return + + const fieldPrefs = field.fieldPreferences + if (fieldPrefs) fieldPrefs.scoringZones = zones + + PreferencesSystem.savePreferences() + field.UpdateScoringZones() +} + +interface ConfigureZonesProps { + selectedField: MirabufSceneObject + initialZones: ScoringZonePreferences[] +} + +const ConfigureScoringZonesInterface: React.FC = ({ selectedField, initialZones }) => { + const [selectedZone, setSelectedZone] = useState(undefined) + + return ( + <> + {selectedZone == undefined ? ( + + ) : ( + { + saveZones(selectedField.fieldPreferences?.scoringZones, selectedField) + }} + /> + )} + + ) +} + +export default ConfigureScoringZonesInterface diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageZonesInterface.tsx new file mode 100644 index 0000000000..61fa0743dc --- /dev/null +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageZonesInterface.tsx @@ -0,0 +1,156 @@ +import { useCallback, useEffect, useState } from "react" +import Button from "@/components/Button" +import Label, { LabelSize } from "@/components/Label" +import ScrollView from "@/components/ScrollView" +import Stack, { StackDirection } from "@/components/Stack" +import { ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" +import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import { AiOutlinePlus } from "react-icons/ai" +import { IoPencil, IoTrashBin } from "react-icons/io5" +import World from "@/systems/World" +import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import { Box } from "@mui/material" +import { ConfigurationSavedEvent } from "../../ConfigureAssembliesPanel" + +const AddIcon = +const DeleteIcon = +const EditIcon = + +const saveZones = (zones: ScoringZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { + if (!zones || !field) return + + const fieldPrefs = field.fieldPreferences + if (fieldPrefs) fieldPrefs.scoringZones = zones + + PreferencesSystem.savePreferences() + field.UpdateScoringZones() +} + +type ScoringZoneRowProps = { + zone: ScoringZonePreferences + save: () => void + deleteZone: () => void + selectZone: (zone: ScoringZonePreferences) => void +} + +const ScoringZoneRow: React.FC = ({ zone, save, deleteZone, selectZone }) => { + return ( + + +
+ + + + + + +
- + {/** When checked, points will stay even when a gamepiece leaves the zone */} + + + {/** Switch between transform control modes */} + + { + if (v == undefined) return + + setTransformMode(v) + transformGizmo?.SwitchGizmo(v, 1.5) + }} + sx={{ + alignSelf: "center", + }} + > + Move + Scale + Rotate + +
) } -export default ZoneConfigPanel +export default ZoneConfigInterface diff --git a/fission/src/ui/panels/configuring/scoring/ScoringZonesPanel.tsx b/fission/src/ui/panels/configuring/scoring/ScoringZonesPanel.tsx deleted file mode 100644 index d8e3a281e4..0000000000 --- a/fission/src/ui/panels/configuring/scoring/ScoringZonesPanel.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import { useEffect, useMemo, useState } from "react" -import { usePanelControlContext } from "@/ui/PanelContext" -import Button from "@/components/Button" -import Label, { LabelSize } from "@/components/Label" -import Panel, { PanelPropsImpl } from "@/components/Panel" -import ScrollView from "@/components/ScrollView" -import Stack, { StackDirection } from "@/components/Stack" -import { ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" -import PreferencesSystem from "@/systems/preferences/PreferencesSystem" -import { AiOutlinePlus } from "react-icons/ai" -import { IoPencil, IoTrashBin } from "react-icons/io5" -import World from "@/systems/World" -import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" -import { MiraType } from "@/mirabuf/MirabufLoader" -import { Box } from "@mui/material" -import { FaBasketball } from "react-icons/fa6" - -const AddIcon = -const DeleteIcon = -const EditIcon = - -type ScoringZoneRowProps = { - zone: ScoringZonePreferences - field: MirabufSceneObject - save: () => void - openPanel: (id: string) => void - deleteZone: () => void -} - -export class SelectedZone { - public static field: MirabufSceneObject | undefined - public static zone: ScoringZonePreferences -} - -const saveZones = (zones: ScoringZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { - if (!zones || !field) return - - const fieldPrefs = field.fieldPreferences - if (fieldPrefs) fieldPrefs.scoringZones = zones - - PreferencesSystem.savePreferences() - field.UpdateScoringZones() -} - -const ScoringZoneRow: React.FC = ({ zone, save, field, openPanel, deleteZone }) => { - return ( - - -
- - - - - - - - ) - })} -
- - ) : ( - <> - {zones?.length > 0 ? ( - - {zones.map((zonePrefs: ScoringZonePreferences, i: number) => ( - { - return zonePrefs - })()} - field={selectedField} - openPanel={openPanel} - save={() => saveZones(zones, selectedField)} - deleteZone={() => { - setZones(zones.filter((_, idx) => idx !== i)) - saveZones( - zones.filter((_, idx) => idx !== i), - selectedField - ) - }} - /> - ))} - - ) : ( - - )} -
= ({ modalId }) => { return ( = ({ modalId }) => { >
- + ) + })} +
+ + ) : ( + <> + {/* {selectedRobot.mechanism.constraints.filter(x=> x.constraint.GetSubType() == JOLT.EConstraintSubType_Slider).length > 0 ? ( + + {selectedRobot.mechanism.constraints.filter(x=> x.constraint.GetSubType() == JOLT.EConstraintSubType_Slider).map((mechanism: MechanismConstraint, i: number) => ( + + { + return driver + })()} + /> + + ))} + + ) : ( + + )} */} + + {drivers ? ( + + {drivers.filter(x => x instanceof SliderDriver).map((driver: Driver, i: number) => ( + { + return driver + })()} + /> + + ))} + + ) : ( + + )} + + )} + + + + + ) +} + +export default ConfigureJointsPanel \ No newline at end of file From bed9f1655427830e0afaecf707197db175a4ca1d Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 31 Jul 2024 14:37:07 -0700 Subject: [PATCH 104/344] Clean up --- .../configuring/ConfigureJointsPanel.tsx | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx index 96ac87751a..dc1f66b195 100644 --- a/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx @@ -1,6 +1,5 @@ import { MiraType } from "@/mirabuf/MirabufLoader" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" -import { MechanismConstraint } from "@/systems/physics/Mechanism" import Driver from "@/systems/simulation/driver/Driver" import SliderDriver from "@/systems/simulation/driver/SliderDriver" import World from "@/systems/World" @@ -12,7 +11,7 @@ import Slider from "@/ui/components/Slider" import Stack, { StackDirection } from "@/ui/components/Stack" import { useTheme } from "@/ui/ThemeContext" import { Box } from "@mui/material" -import { useEffect, useMemo, useState } from "react" +import { useMemo, useState } from "react" import { FaGear } from "react-icons/fa6" type JointRowProps = { @@ -113,22 +112,6 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, ) : ( <> - {/* {selectedRobot.mechanism.constraints.filter(x=> x.constraint.GetSubType() == JOLT.EConstraintSubType_Slider).length > 0 ? ( - - {selectedRobot.mechanism.constraints.filter(x=> x.constraint.GetSubType() == JOLT.EConstraintSubType_Slider).map((mechanism: MechanismConstraint, i: number) => ( - - { - return driver - })()} - /> - - ))} - - ) : ( - - )} */} {drivers ? ( From ee6cf471512a36a9425d573e1ee80833c74befd4 Mon Sep 17 00:00:00 2001 From: PepperLola Date: Wed, 31 Jul 2024 14:38:13 -0700 Subject: [PATCH 105/344] ran formatter --- fission/src/ui/modals/APSManagementModal.tsx | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/fission/src/ui/modals/APSManagementModal.tsx b/fission/src/ui/modals/APSManagementModal.tsx index ab1ec57e7c..fa4d8ddaaa 100644 --- a/fission/src/ui/modals/APSManagementModal.tsx +++ b/fission/src/ui/modals/APSManagementModal.tsx @@ -1,22 +1,22 @@ import React, { useState } from "react" import Modal, { ModalPropsImpl } from "@/components/Modal" import Stack, { StackDirection } from "@/components/Stack" -import Label, { LabelSize } from "@/components/Label" import { HiUser } from "react-icons/hi" import APS from "@/aps/APS" const APSManagementModal: React.FC = ({ modalId }) => { - const [userInfo, setUserInfo] = useState(APS.userInfo) + const [userInfo, _] = useState(APS.userInfo) return ( - : - - } modalId={modalId} acceptName="Logout" onAccept={() => { - APS.logout() - }}> - - + : } + modalId={modalId} + acceptName="Logout" + onAccept={() => { + APS.logout() + }} + > + ) } From 459c8ff524692c243aca3184d5c6fd1f1353aa29 Mon Sep 17 00:00:00 2001 From: Brandon Pacewic Date: Wed, 31 Jul 2024 14:45:42 -0700 Subject: [PATCH 106/344] Remove confirmed `TODO` --- exporter/SynthesisFusionAddin/src/Dependencies.py | 1 - 1 file changed, 1 deletion(-) diff --git a/exporter/SynthesisFusionAddin/src/Dependencies.py b/exporter/SynthesisFusionAddin/src/Dependencies.py index f55643dfae..b9ad31f7be 100644 --- a/exporter/SynthesisFusionAddin/src/Dependencies.py +++ b/exporter/SynthesisFusionAddin/src/Dependencies.py @@ -108,7 +108,6 @@ def resolveDependencies() -> bool | None: progressBar.reset() progressBar.show("Synthesis", f"Installing dependencies...", 0, len(PIP_DEPENDENCY_VERSION_MAP) * 2 + 2, 0) - # TODO: Is this really true? Do we need this? waheusnta eho? - Brandon # Install pip manually on macos as it is not included by default? Really? if system == "Darwin" and not os.path.exists(os.path.join(pythonFolder, "pip")): pipInstallScriptPath = os.path.join(pythonFolder, "get-pip.py") From 16b988af92caaad59892cae5d308056844ec5b1e Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 31 Jul 2024 14:47:19 -0700 Subject: [PATCH 107/344] Merge conflicts --- fission/src/ui/components/MainHUD.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 0cc48c2b14..c29d1b1a5c 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -10,6 +10,7 @@ import APS, { APS_USER_INFO_UPDATE_EVENT } from "@/aps/APS" import { UserIcon } from "./UserIcon" import { Button } from "@mui/base/Button" import { ButtonIcon, SynthesisIcons } from "./StyledComponents" +import Synthesis from "@/Synthesis" type ButtonProps = { value: string @@ -119,8 +120,8 @@ const MainHUD: React.FC = () => { openPanel("scoring-zones") }} /> - } onClick={() => openModal("config-robot")} /> - } onClick={() => openPanel("joint-config")} /> + openModal("config-robot")} /> + openPanel("joint-config")} /> Date: Wed, 31 Jul 2024 14:55:23 -0700 Subject: [PATCH 108/344] Removed toggle button group border color on select --- .../src/ui/components/ToggleButtonGroup.tsx | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/fission/src/ui/components/ToggleButtonGroup.tsx b/fission/src/ui/components/ToggleButtonGroup.tsx index 5ca0d2707a..8159c97b97 100644 --- a/fission/src/ui/components/ToggleButtonGroup.tsx +++ b/fission/src/ui/components/ToggleButtonGroup.tsx @@ -14,25 +14,52 @@ export const ToggleButton = styled(ToggleButtonMUI)({ borderColor: "transparent", }, "&:focus": { + borderColor: "transparent !important", + outline: "none", + }, + "&:selected": { + outline: "none", borderColor: "transparent", }, "&:hover": { - borderColor: "white", + outline: "none", + borderColor: "transparent", }, "&:focus-visible": { + outline: "none", borderColor: "transparent", }, "&:active": { + outline: "none", borderColor: "transparent", }, "&::-moz-focus-inner": { + outline: "none", borderColor: "transparent", }, }) export const ToggleButtonGroup = styled(ToggleButtonGroupMUI)({ - backgroundColor: colorNameToVar("Background"), - fontFamily: "Artifakt", - fontWeight: 700, - width: "fit-content", + "backgroundColor": colorNameToVar("Background"), + "fontFamily": "Artifakt", + "fontWeight": 700, + "width": "fit-content", + "&:focus": { + borderColor: "transparent", + }, + "&:selected": { + borderColor: "transparent", + }, + "&:hover": { + borderColor: "transparent", + }, + "&:focus-visible": { + borderColor: "transparent", + }, + "&:active": { + borderColor: "transparent", + }, + "&::-moz-focus-inner": { + borderColor: "transparent", + }, }) From d9271f13ee2982810a0c7e419597f7c24f0e7e1e Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Wed, 31 Jul 2024 15:56:30 -0700 Subject: [PATCH 109/344] added deadband suport for talonFX --- .../simulation/wpilib_brain/WPILibBrain.ts | 17 +++++------ .../java/com/autodesk/synthesis/CANMotor.java | 28 ++++++++++++------- .../com/autodesk/synthesis/ctre/TalonFX.java | 24 ++++++++++++---- .../synthesis/revrobotics/CANSparkMax.java | 23 +++++++++------ .../src/main/java/frc/robot/Robot.java | 7 +++-- 5 files changed, 63 insertions(+), 36 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index f565f42425..75eeda0328 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -210,21 +210,22 @@ worker.addEventListener("message", (eventData: MessageEvent) => { try { data = JSON.parse(eventData.data) } catch (e) { - console.warn(`Failed to parse data:\n${JSON.stringify(eventData.data)}`) + console.error(`Failed to parse data:\n${JSON.stringify(eventData.data)}`) + return } } - if (!data || !data.type) { + if (!data?.type) { console.log("No data, bailing out") return } - const device = data.device - const updateData = data.data - - if (!Object.values(SimType).includes(data.type)) return + if (!Object.values(SimType).includes(data.type)) { + console.log("Unknown data type, bailing out") + return + } - UpdateSimMap(data.type, device, updateData) + UpdateSimMap(data.type, data.device, data.data) }) function UpdateSimMap(type: SimType, device: string, updateData: any) { @@ -240,7 +241,7 @@ function UpdateSimMap(type: SimType, device: string, updateData: any) { typeMap.set(device, currentData) } - Object.entries(updateData).forEach(([key, value]) => (currentData[key] = value)) + Object.entries(updateData).forEach(([key, value]) => currentData[key] = value) window.dispatchEvent(new SimMapUpdateEvent(false)) } diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java index 1793aabddc..7dd36e8e93 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java @@ -6,9 +6,11 @@ import edu.wpi.first.hal.SimDouble; /** - * CANMotor class for easy implementation of documentation-compliant simulation data. + * CANMotor class for easy implementation of documentation-compliant simulation + * data. * - * See https://github.com/wpilibsuite/allwpilib/blob/6478ba6e3fa317ee041b8a41e562d925602b6ea4/simulation/halsim_ws_core/doc/hardware_ws_api.md + * See + * https://github.com/wpilibsuite/allwpilib/blob/6478ba6e3fa317ee041b8a41e562d925602b6ea4/simulation/halsim_ws_core/doc/hardware_ws_api.md * for documentation on the WebSocket API Specification. */ public class CANMotor { @@ -24,20 +26,26 @@ public class CANMotor { private SimDouble m_busVoltage; /** - * Creates a CANMotor sim device in accordance with the WebSocket API Specification. + * Creates a CANMotor sim device in accordance with the WebSocket API + * Specification. * - * @param name Name of the CAN Motor. This is generally the class name of the originating motor, prefixed with something (ie. "SYN CANSparkMax"). - * @param deviceId CAN Device ID. - * @param defaultPercentOutput Default PercentOutput value. [-1.0, 1.0] - * @param defaultBrakeMode Default BrakeMode value. (true/false) - * @param defaultNeutralDeadband Default Neutral Deadband value. This is used to determine when braking should be enabled. [0.0, 1.0] + * @param name Name of the CAN Motor. This is generally the + * class name of the originating motor, prefixed + * with something (ie. "SYN CANSparkMax"). + * @param deviceId CAN Device ID. + * @param defaultPercentOutput Default PercentOutput value. [-1.0, 1.0] + * @param defaultBrakeMode Default BrakeMode value. (true/false) + * @param defaultNeutralDeadband Default Neutral Deadband value. This is used to + * determine when braking should be enabled. [0.0, + * 1.0] */ - public CANMotor(String name, int deviceId, double defaultPercentOutput, boolean defaultBrakeMode, double defaultNeutralDeadband) { + public CANMotor(String name, int deviceId, double defaultPercentOutput, boolean defaultBrakeMode, + double defaultNeutralDeadband) { m_device = SimDevice.create(name, deviceId); m_percentOutput = m_device.createDouble("percentOutput", Direction.kOutput, 0.0); m_brakeMode = m_device.createBoolean("brakeMode", Direction.kOutput, false); - m_neutralDeadband = m_device.createDouble("neutralDeadband", Direction.kOutput, deviceId); + m_neutralDeadband = m_device.createDouble("neutralDeadband", Direction.kOutput, 0.5); m_supplyCurrent = m_device.createDouble("supplyCurrent", Direction.kInput, 120.0); m_motorCurrent = m_device.createDouble("motorCurrent", Direction.kInput, 120.0); diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java index 6b54db84b9..90da0a8e5e 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java @@ -1,33 +1,45 @@ package com.autodesk.synthesis.ctre; import com.autodesk.synthesis.CANMotor; -// import com.ctre.phoenix6.configs.TalonFXConfigurator; -// import com.ctre.phoenix6.configs.TalonFXConfiguration; import com.ctre.phoenix6.signals.NeutralModeValue; +import com.ctre.phoenix6.configs.TalonFXConfigurator; +import com.ctre.phoenix6.hardware.DeviceIdentifier; public class TalonFX extends com.ctre.phoenix6.hardware.TalonFX { private CANMotor m_motor; + /** + * Creates a new TalonFX, wrapped with simulation support. + * + * @param deviceId CAN Devic ID. + */ public TalonFX(int deviceNumber) { super(deviceNumber); - m_motor = new CANMotor("SYN TalonFX", deviceNumber, 0.0, false, 0.3); + this.m_motor = new CANMotor("SYN TalonFX", deviceNumber, 0.0, false, 0.3); } + @Override public void set(double speed) { super.set(speed); this.m_motor.setPercentOutput(speed); } + @Override public void setNeutralMode(NeutralModeValue mode) { super.setNeutralMode(mode); this.m_motor.setBrakeMode(mode == NeutralModeValue.Brake); } - public void configureNeutralDeadband(double deadband) { - //super.configureNeutralDeadband(deadband); - // TODO: Find actual deadband config method + @Override + public TalonFXConfigurator getConfigurator() { + DeviceIdentifier id = this.deviceIdentifier; + return new com.autodesk.synthesis.ctre.TalonFXConfigurator(id, this); + } + + // called internally by the configurator to set the deadband, not for user use + public void setNeutralDeadband(double deadband) { this.m_motor.setNeutralDeadband(deadband); } } diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java index e60f82cc4d..5575836744 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java @@ -2,6 +2,7 @@ import com.autodesk.synthesis.CANEncoder; import com.autodesk.synthesis.CANMotor; +import com.revrobotics.CANSparkBase; import com.revrobotics.REVLibError; /** @@ -22,30 +23,34 @@ public class CANSparkMax extends com.revrobotics.CANSparkMax { public CANSparkMax(int deviceId, MotorType motorType) { super(deviceId, motorType); - m_motor = new CANMotor("SYN CANSparkMax", deviceId, 0.0, false, 0.3); - m_encoder = new CANEncoder("SYN CANSparkMax/Encoder", deviceId); + this.m_motor = new CANMotor("SYN CANSparkMax", deviceId, 0.0, false, 0.3); + this.m_encoder = new CANEncoder("SYN CANSparkMax/Encoder", deviceId); } @Override public void set(double percent) { super.set(percent); - m_motor.setPercentOutput(percent); - } - - public void test() { - System.out.println("test"); + this.m_motor.setPercentOutput(percent); } public void setNeutralDeadband(double n) { - m_motor.setNeutralDeadband(n); + this.m_motor.setNeutralDeadband(n); } @Override public REVLibError setIdleMode(com.revrobotics.CANSparkBase.IdleMode mode) { if (mode != null) { - m_motor.setBrakeMode(mode.equals(com.revrobotics.CANSparkBase.IdleMode.kBrake)); + this.m_motor.setBrakeMode(mode.equals(com.revrobotics.CANSparkBase.IdleMode.kBrake)); } return super.setIdleMode(mode); } + + @Override + public REVLibError follow(CANSparkBase leader) { + REVLibError err = super.follow(leader); + // figure out how to have primitives follow + this.m_motor.m_percentOuput = leader.x; + return err; + } } diff --git a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java index 6f78dca9c9..9b15b83188 100644 --- a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java +++ b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java @@ -7,7 +7,7 @@ import com.revrobotics.CANSparkLowLevel.MotorType; import edu.wpi.first.wpilibj.TimedRobot; -import edu.wpi.first.wpilibj.motorcontrol.Spark; +//import edu.wpi.first.wpilibj.motorcontrol.Spark; import edu.wpi.first.wpilibj.smartdashboard.SendableChooser; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; @@ -31,7 +31,7 @@ public class Robot extends TimedRobot { // private Spark m_Spark = new Spark(0); private CANSparkMax m_SparkMax = new CANSparkMax(1, MotorType.kBrushless); - private TalonFX m_Talon = new TalonFX(2); + private com.autodesk.synthesis.ctre.TalonFX m_Talon = new com.autodesk.synthesis.ctre.TalonFX(2); /** * This function is run when the robot is first started up and should be used @@ -89,9 +89,10 @@ public void autonomousPeriodic() { // m_Spark.set(0.5); m_SparkMax.set(1.0); - // m_SparkMax.setNeutralDeadband(0.01); // FIXME: for some reason I can't set + //m_SparkMax.setNeutralDeadband(0.01); // FIXME: for some reason I can't set // the deadband even after defining the function in the child m_Talon.set(-1.0); + m_Talon.configureNeutralDeadband(0.2); switch (m_autoSelected) { case kCustomAuto: From 797ee1a89db200cd42bb1935d3bfd1e4db7ada9d Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 31 Jul 2024 17:23:29 -0700 Subject: [PATCH 110/344] Saves in prefs --- fission/src/systems/physics/PhysicsSystem.ts | 26 ++++++---- .../systems/preferences/PreferenceTypes.ts | 8 +++ .../systems/simulation/driver/SliderDriver.ts | 15 ++---- fission/src/ui/components/MainHUD.tsx | 1 - .../configuring/ConfigureJointsPanel.tsx | 52 +++++++++++++++---- 5 files changed, 71 insertions(+), 31 deletions(-) diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 2ff61d3725..4dd011d066 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -23,6 +23,7 @@ import { OnContactValidateData, PhysicsEvent, } from "./ContactEvents" +import PreferencesSystem from "../preferences/PreferencesSystem" export type JoltBodyIndexAndSequence = number @@ -353,12 +354,20 @@ class PhysicsSystem extends WorldSystem { const constraints: [Jolt.Constraint, number][] = [] let listener: Jolt.PhysicsStepListener | undefined = undefined - const motor = jointData.motorDefinitions![jDef.motorReference] - + const motors = PreferencesSystem.getRobotPreferences(parser.assembly.info?.name ?? "").motors + let maxForce = jointData.motorDefinitions![jDef.motorReference].simpleMotor?.stallTorque let maxVel = undefined; if (parser.assembly.data?.joints?.motorDefinitions && parser.assembly.data?.joints?.motorDefinitions![jDef.motorReference] && parser.assembly.data?.joints?.motorDefinitions![jDef.motorReference].simpleMotor) { maxVel = parser.assembly.data?.joints?.motorDefinitions![jDef.motorReference].simpleMotor?.maxVelocity } + if (motors) { + const thisMotor = motors.filter(x => x.name == jInst.info?.name) + if (thisMotor && thisMotor[0]) { + maxVel = thisMotor[0].maxVelocity + maxForce = thisMotor[0].maxForce + } + + } switch (jDef.jointMotionType!) { case mirabuf.joint.JointMotion.REVOLUTE: @@ -388,12 +397,13 @@ class PhysicsSystem extends WorldSystem { } } else { constraints.push( - [this.CreateHingeConstraint(jInst, jDef, bodyA, bodyB, parser.assembly.info!.version!), maxVel ? maxVel /5 : 40] + [this.CreateHingeConstraint(jInst, jDef, bodyA, bodyB, parser.assembly.info!.version!), maxVel ? maxVel : 40] ) } break case mirabuf.joint.JointMotion.SLIDER: - constraints.push([this.CreateSliderConstraint(jInst, jDef, motor, bodyA, bodyB), maxVel ? maxVel / 5 : 40]) + + constraints.push([this.CreateSliderConstraint(jInst, jDef, maxForce ?? 200, bodyA, bodyB), maxVel ? maxVel : 40]) break default: console.debug("Unsupported joint detected. Skipping...") @@ -499,7 +509,7 @@ class PhysicsSystem extends WorldSystem { private CreateSliderConstraint( jointInstance: mirabuf.joint.JointInstance, jointDefinition: mirabuf.joint.Joint, - motor: mirabuf.motor.IMotor, + maxForce: number, bodyA: Jolt.Body, bodyB: Jolt.Body ): Jolt.Constraint { @@ -545,10 +555,8 @@ class PhysicsSystem extends WorldSystem { sliderConstraintSettings.mLimitsMin = -halfRange } - if (motor.simpleMotor?.stallTorque) { - sliderConstraintSettings.mMotorSettings.mMaxForceLimit = motor.simpleMotor?.stallTorque * 5 - sliderConstraintSettings.mMotorSettings.mMinForceLimit = -sliderConstraintSettings.mMotorSettings.mMaxForceLimit - } + sliderConstraintSettings.mMotorSettings.mMaxForceLimit = maxForce + sliderConstraintSettings.mMotorSettings.mMinForceLimit = -sliderConstraintSettings.mMotorSettings.mMaxForceLimit const constraint = sliderConstraintSettings.Create(bodyA, bodyB) diff --git a/fission/src/systems/preferences/PreferenceTypes.ts b/fission/src/systems/preferences/PreferenceTypes.ts index 37b04a83ce..27c1821ac6 100644 --- a/fission/src/systems/preferences/PreferenceTypes.ts +++ b/fission/src/systems/preferences/PreferenceTypes.ts @@ -45,10 +45,17 @@ export type EjectorPreferences = { export type RobotPreferences = { inputsSchemes: InputScheme[] + motors: MotorPreferences[] intake: IntakePreferences ejector: EjectorPreferences } +export type MotorPreferences = { + name: string + maxVelocity: number + maxForce: number +} + export type Alliance = "red" | "blue" export type ScoringZonePreferences = { @@ -70,6 +77,7 @@ export type FieldPreferences = { export function DefaultRobotPreferences(): RobotPreferences { return { inputsSchemes: [], + motors: [], intake: { deltaTransformation: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], zoneDiameter: 0.5, diff --git a/fission/src/systems/simulation/driver/SliderDriver.ts b/fission/src/systems/simulation/driver/SliderDriver.ts index f1de635a9f..06dcb2e830 100644 --- a/fission/src/systems/simulation/driver/SliderDriver.ts +++ b/fission/src/systems/simulation/driver/SliderDriver.ts @@ -3,6 +3,7 @@ import Driver, { DriverControlMode } from "./Driver" import { GetLastDeltaT } from "@/systems/physics/PhysicsSystem" import JOLT from "@/util/loading/JoltSyncLoader" import { mirabuf } from "@/proto/mirabuf" +import PreferencesSystem from "@/systems/preferences/PreferencesSystem" class SliderDriver extends Driver { private _constraint: Jolt.SliderConstraint @@ -37,19 +38,13 @@ class SliderDriver extends Driver { ) } - public get minForceLimit(): number { - return this._constraint.GetMotorSettings().get_mMinForceLimit() + public get maxForce(): number { + return this._constraint.GetMotorSettings().mMaxForceLimit } - public set minForceLimit(newtons: number) { - const motorSettings = this._constraint.GetMotorSettings() - motorSettings.mMinForceLimit = newtons - } - public get maxForceLimit(): number { - return this._constraint.GetMotorSettings().get_mMaxForceLimit() - } - public set maxForceLimit(newtons: number) { + public set maxForce(newtons: number) { const motorSettings = this._constraint.GetMotorSettings() motorSettings.mMaxForceLimit = newtons + motorSettings.mMinForceLimit = -newtons } public get controlMode(): DriverControlMode { diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index c29d1b1a5c..af43cd3521 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -120,7 +120,6 @@ const MainHUD: React.FC = () => { openPanel("scoring-zones") }} /> - openModal("config-robot")} /> openPanel("joint-config")} /> = ({ driver }) => { +const JointRow: React.FC = ({ robot, driver }) => { const [velocity, setVelocity] = useState((driver as SliderDriver).maxVelocity) - const [force, setForce] = useState((driver as SliderDriver).maxForceLimit) + const [force, setForce] = useState((driver as SliderDriver).maxForce) return ( @@ -34,7 +36,22 @@ const JointRow: React.FC = ({ driver }) => { format={{ minimumFractionDigits: 2, maximumFractionDigits: 2 }} onChange={(_, vel: number | number[]) => { setVelocity(vel as number); - (driver as SliderDriver).maxVelocity = velocity + (driver as SliderDriver).maxVelocity = vel as number + 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 as number, + maxForce: force}) + + PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor + PreferencesSystem.savePreferences() + } }} step={0.01} /> @@ -46,8 +63,22 @@ const JointRow: React.FC = ({ driver }) => { format={{ minimumFractionDigits: 2, maximumFractionDigits: 2 }} onChange={(_, forc: number | number[]) => { setForce(forc as number); - (driver as SliderDriver).maxForceLimit = force; - (driver as SliderDriver).minForceLimit = -force + (driver as SliderDriver).maxForce = forc as number + 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: velocity, + maxForce: forc as number}) + + PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor + PreferencesSystem.savePreferences() + } }} step={0.01} /> @@ -58,10 +89,6 @@ const JointRow: React.FC = ({ driver }) => { const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, sidePadding}) => { - const { currentTheme, themes } = useTheme() - const theme = useMemo(() => { - return themes[currentTheme] - }, [currentTheme, themes]) const [selectedRobot, setSelectedRobot] = useState(undefined) @@ -88,7 +115,7 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, panelId={panelId} openLocation={openLocation} sidePadding={sidePadding} - onAccept={() => {}} + onAccept={() => { PreferencesSystem.savePreferences()}} onCancel={() => {/* Back to original */ }} acceptEnabled={true} > @@ -118,6 +145,9 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, {drivers.filter(x => x instanceof SliderDriver).map((driver: Driver, i: number) => ( { + return selectedRobot + })()} driver={(() => { return driver })()} From 85966701b149167f5c2cdd33dfa7ae7c368748ec Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 31 Jul 2024 18:04:59 -0700 Subject: [PATCH 111/344] HingeDrivers --- fission/src/systems/physics/PhysicsSystem.ts | 15 +-- .../systems/simulation/SimulationSystem.ts | 2 +- .../behavior/synthesis/GenericArmBehavior.ts | 4 +- .../systems/simulation/driver/HingeDriver.ts | 26 ++-- .../systems/simulation/driver/SliderDriver.ts | 1 - .../configuring/ConfigureJointsPanel.tsx | 112 +++++++++++------- 6 files changed, 94 insertions(+), 66 deletions(-) diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 4dd011d066..5f438fd913 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -355,18 +355,14 @@ class PhysicsSystem extends WorldSystem { let listener: Jolt.PhysicsStepListener | undefined = undefined const motors = PreferencesSystem.getRobotPreferences(parser.assembly.info?.name ?? "").motors + let maxVel = jointData.motorDefinitions![jDef.motorReference].simpleMotor?.maxVelocity let maxForce = jointData.motorDefinitions![jDef.motorReference].simpleMotor?.stallTorque - let maxVel = undefined; - if (parser.assembly.data?.joints?.motorDefinitions && parser.assembly.data?.joints?.motorDefinitions![jDef.motorReference] && parser.assembly.data?.joints?.motorDefinitions![jDef.motorReference].simpleMotor) { - maxVel = parser.assembly.data?.joints?.motorDefinitions![jDef.motorReference].simpleMotor?.maxVelocity - } if (motors) { const thisMotor = motors.filter(x => x.name == jInst.info?.name) - if (thisMotor && thisMotor[0]) { + if (thisMotor[0]) { maxVel = thisMotor[0].maxVelocity maxForce = thisMotor[0].maxForce } - } switch (jDef.jointMotionType!) { @@ -397,7 +393,7 @@ class PhysicsSystem extends WorldSystem { } } else { constraints.push( - [this.CreateHingeConstraint(jInst, jDef, bodyA, bodyB, parser.assembly.info!.version!), maxVel ? maxVel : 40] + [this.CreateHingeConstraint(jInst, jDef, maxForce ?? 200, bodyA, bodyB, parser.assembly.info!.version!), maxVel ? maxVel : 40] ) } break @@ -441,6 +437,7 @@ class PhysicsSystem extends WorldSystem { private CreateHingeConstraint( jointInstance: mirabuf.joint.JointInstance, jointDefinition: mirabuf.joint.Joint, + torque: number, bodyA: Jolt.Body, bodyB: Jolt.Body, versionNum: number @@ -490,6 +487,10 @@ class PhysicsSystem extends WorldSystem { hingeConstraintSettings.mLimitsMax = -lower } + hingeConstraintSettings.mMotorSettings.mMaxTorqueLimit = torque + hingeConstraintSettings.mMotorSettings.mMinTorqueLimit = -torque + + const constraint = hingeConstraintSettings.Create(bodyA, bodyB) this._joltPhysSystem.AddConstraint(constraint) diff --git a/fission/src/systems/simulation/SimulationSystem.ts b/fission/src/systems/simulation/SimulationSystem.ts index cd141d98ba..93e585c369 100644 --- a/fission/src/systems/simulation/SimulationSystem.ts +++ b/fission/src/systems/simulation/SimulationSystem.ts @@ -85,7 +85,7 @@ class SimulationLayer { this._mechanism.constraints.forEach(x => { if (x.constraint.GetSubType() == JOLT.EConstraintSubType_Hinge) { const hinge = JOLT.castObject(x.constraint, JOLT.HingeConstraint) - const driver = new HingeDriver(hinge, x.info) + const driver = new HingeDriver(hinge, x.maxVelocity, x.info) this._drivers.push(driver) const stim = new HingeStimulus(hinge) this._stimuli.push(stim) diff --git a/fission/src/systems/simulation/behavior/synthesis/GenericArmBehavior.ts b/fission/src/systems/simulation/behavior/synthesis/GenericArmBehavior.ts index a9d19c5c03..1d55bfd9e6 100644 --- a/fission/src/systems/simulation/behavior/synthesis/GenericArmBehavior.ts +++ b/fission/src/systems/simulation/behavior/synthesis/GenericArmBehavior.ts @@ -8,8 +8,6 @@ class GenericArmBehavior extends Behavior { private _inputName: string private _brainIndex: number - private _rotationalSpeed = 6 - constructor(hingeDriver: HingeDriver, hingeStimulus: HingeStimulus, jointIndex: number, brainIndex: number) { super([hingeDriver], [hingeStimulus]) @@ -24,7 +22,7 @@ class GenericArmBehavior extends Behavior { } public Update(_: number): void { - this.rotateArm(InputSystem.getInput(this._inputName, this._brainIndex) * this._rotationalSpeed) + this.rotateArm(InputSystem.getInput(this._inputName, this._brainIndex)) } } diff --git a/fission/src/systems/simulation/driver/HingeDriver.ts b/fission/src/systems/simulation/driver/HingeDriver.ts index 29ab7fc6af..aa9ef3aeb5 100644 --- a/fission/src/systems/simulation/driver/HingeDriver.ts +++ b/fission/src/systems/simulation/driver/HingeDriver.ts @@ -10,6 +10,15 @@ class HingeDriver extends Driver { private _controlMode: DriverControlMode = DriverControlMode.Velocity private _targetVelocity: number = 0.0 private _targetAngle: number + private _maxVelocity: number + + public get maxVelocity(): number { + return this._maxVelocity + } + + public set maxVelocity(radsPerSec: number) { + this._maxVelocity = radsPerSec + } public get targetVelocity(): number { return this._targetVelocity @@ -25,13 +34,14 @@ class HingeDriver extends Driver { this._targetAngle = Math.max(this._constraint.GetLimitsMin(), Math.min(this._constraint.GetLimitsMax(), rads)) } - public set minTorqueLimit(nm: number) { - const motorSettings = this._constraint.GetMotorSettings() - motorSettings.mMinTorqueLimit = nm + public get maxTorque() { + return this._constraint.GetMotorSettings().mMaxTorqueLimit } - public set maxTorqueLimit(nm: number) { + + public set maxTorque(nm: number) { const motorSettings = this._constraint.GetMotorSettings() motorSettings.mMaxTorqueLimit = nm + motorSettings.mMinTorqueLimit = -nm } public get controlMode(): DriverControlMode { @@ -53,7 +63,7 @@ class HingeDriver extends Driver { } } - public constructor(constraint: Jolt.HingeConstraint, info?: mirabuf.IInfo) { + public constructor(constraint: Jolt.HingeConstraint, maxVelocity: number, info?: mirabuf.IInfo) { super(info) this._constraint = constraint @@ -66,18 +76,18 @@ class HingeDriver extends Driver { springSettings.mDamping = 0.995 motorSettings.mSpringSettings = springSettings - motorSettings.mMinTorqueLimit = -200.0 - motorSettings.mMaxTorqueLimit = 200.0 this._targetAngle = this._constraint.GetCurrentAngle() + this._maxVelocity = maxVelocity this.controlMode = DriverControlMode.Velocity } public Update(_: number): void { if (this._controlMode == DriverControlMode.Velocity) { - this._constraint.SetTargetAngularVelocity(this._targetVelocity) + this._constraint.SetTargetAngularVelocity(this._targetVelocity * this._maxVelocity) } else if (this._controlMode == DriverControlMode.Position) { + //TODO add maxVel to diff this._constraint.SetTargetAngle(this._targetAngle) } } diff --git a/fission/src/systems/simulation/driver/SliderDriver.ts b/fission/src/systems/simulation/driver/SliderDriver.ts index 06dcb2e830..f00249f521 100644 --- a/fission/src/systems/simulation/driver/SliderDriver.ts +++ b/fission/src/systems/simulation/driver/SliderDriver.ts @@ -3,7 +3,6 @@ import Driver, { DriverControlMode } from "./Driver" import { GetLastDeltaT } from "@/systems/physics/PhysicsSystem" import JOLT from "@/util/loading/JoltSyncLoader" import { mirabuf } from "@/proto/mirabuf" -import PreferencesSystem from "@/systems/preferences/PreferencesSystem" class SliderDriver extends Driver { private _constraint: Jolt.SliderConstraint diff --git a/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx index 4847c69ab5..f3545d308d 100644 --- a/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx @@ -2,6 +2,7 @@ import { MiraType } from "@/mirabuf/MirabufLoader" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import Driver from "@/systems/simulation/driver/Driver" +import HingeDriver from "@/systems/simulation/driver/HingeDriver" import SliderDriver from "@/systems/simulation/driver/SliderDriver" import World from "@/systems/World" import Button from "@/ui/components/Button" @@ -10,9 +11,8 @@ 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 { useTheme } from "@/ui/ThemeContext" import { Box } from "@mui/material" -import { useEffect, useMemo, useState } from "react" +import { useMemo, useState } from "react" import { FaGear } from "react-icons/fa6" type JointRowProps = { @@ -21,64 +21,84 @@ type JointRowProps = { } const JointRow: React.FC = ({ robot, driver }) => { - const [velocity, setVelocity] = useState((driver as SliderDriver).maxVelocity) - const [force, setForce] = useState((driver as SliderDriver).maxForce) + const [velocity, setVelocity] = useState(() => { + switch (driver.constructor) { + case SliderDriver: + return (driver as SliderDriver).maxVelocity + case HingeDriver: + return (driver as HingeDriver).maxVelocity + default: + return 40 + } + }) + const [force, setForce] = useState(() => { + switch (driver.constructor) { + case SliderDriver: + return (driver as SliderDriver).maxForce + case HingeDriver: + return (driver as HingeDriver).maxTorque + default: + return 40 + } + }) + + const onChange = (vel: number, force: number) => { + (driver as SliderDriver || driver as HingeDriver).maxVelocity = vel + switch (driver.constructor) { + case SliderDriver: + (driver as SliderDriver).maxForce = force + break + case HingeDriver: + (driver as HingeDriver).maxTorque = force + break + default: + return 40 + } + + // 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 + PreferencesSystem.savePreferences() + } + } + return ( - + { - setVelocity(vel as number); - (driver as SliderDriver).maxVelocity = vel as number - 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 as number, - maxForce: force}) - - PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor - PreferencesSystem.savePreferences() - } + onChange={(_, _velocity: number | number[]) => { + setVelocity(_velocity as number); + onChange(_velocity as number, force) }} step={0.01} /> {return driver instanceof HingeDriver ? 20 : 100})()} + max={(() => {return driver instanceof HingeDriver ? 200 : 800})()} value={force} - label="Force" + label={(() => {return driver instanceof HingeDriver ? "Max Torque" : "Max Force"})()} format={{ minimumFractionDigits: 2, maximumFractionDigits: 2 }} - onChange={(_, forc: number | number[]) => { - setForce(forc as number); - (driver as SliderDriver).maxForce = forc as number - 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: velocity, - maxForce: forc as number}) - - PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor - PreferencesSystem.savePreferences() - } + onChange={(_, _force: number | number[]) => { + setForce(_force as number); + onChange(velocity, _force as number) }} step={0.01} /> @@ -142,7 +162,7 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, {drivers ? ( - {drivers.filter(x => x instanceof SliderDriver).map((driver: Driver, i: number) => ( + {drivers.filter(x => x instanceof SliderDriver || x instanceof HingeDriver).map((driver: Driver, i: number) => ( { @@ -156,7 +176,7 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, ))} ) : ( - + )} )} From 77dc66012c302d8f00d92cbf95086dcb7df57867 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Thu, 1 Aug 2024 10:21:17 -0700 Subject: [PATCH 112/344] Friciton override bug fix --- exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py index 121c5cb623..9151a7115d 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py +++ b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py @@ -51,7 +51,7 @@ class ExporterOptions: autoCalcGamepieceWeight: bool = field(default=False) frictionOverride: bool = field(default=False) - frictionOverrideCoeff: float | None = field(default=None) + frictionOverrideCoeff: float = field(default=0.5) compressOutput: bool = field(default=True) exportAsPart: bool = field(default=False) From 68d4801b57f522b9d4dc9ab3a594ca5b5db06dd7 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Thu, 1 Aug 2024 10:44:01 -0700 Subject: [PATCH 113/344] Javascript language detection workaround --- .gitattributes | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitattributes b/.gitattributes index 176a458f94..648af249d0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,6 @@ * text=auto + +# Workaround for a bug it GitHub's language detection, +# previously half of our code was being counted as JavaScript.. for some reason... +# and we certainly can't have that. - Brandon +*.js linguist-detectable=false From f3df103eaeccf7519de39e2952dd715adcbfaf4a Mon Sep 17 00:00:00 2001 From: Brandon Pacewic Date: Thu, 1 Aug 2024 11:15:14 -0700 Subject: [PATCH 114/344] Fix typo --- .gitattributes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 648af249d0..ebbf058172 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,6 @@ * text=auto -# Workaround for a bug it GitHub's language detection, +# Workaround for a bug in GitHub's language detection, # previously half of our code was being counted as JavaScript.. for some reason... # and we certainly can't have that. - Brandon *.js linguist-detectable=false From b4a233fad0069ded85d96ba636571d824ebc4fa8 Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Thu, 1 Aug 2024 11:59:45 -0700 Subject: [PATCH 115/344] Manual exporter install readme & codelab --- README.md | 4 +- installer/Linux/.gitignore | 6 - installer/Linux/README.md | 58 --- installer/Linux/Synthesis.AppDir/.DirIcon | 1 - installer/Linux/Synthesis.AppDir/AppRun | 89 ----- .../Linux/Synthesis.AppDir/synthesis.desktop | 6 - .../Linux/Synthesis.AppDir/synthesis.png | Bin 42175 -> 0 bytes installer/Linux/package.sh | 96 ----- installer/OSX-DMG/.gitignore | 9 - installer/OSX-DMG/README.md | 25 -- .../SynthesisMacInstallerBackground.png | Bin 5014 -> 0 bytes .../OSX-DMG/exporter-install-instructions.md | 12 - installer/OSX-DMG/license.txt | 188 ---------- installer/OSX-DMG/make-installer.sh | 22 -- installer/OSX/.gitignore | 12 - installer/OSX/App/payload/Contents/Info.plist | 39 -- installer/OSX/App/payload/Contents/README.md | 1 - installer/OSX/App/scripts/postinstall | 7 - installer/OSX/App/scripts/preinstall | 2 - .../OSX/Assets/payload/Contents/Info.plist | 39 -- .../payload/Contents/Synthesis/README.md | 1 - installer/OSX/Assets/scripts/postinstall | 5 - installer/OSX/Assets/scripts/preinstall | 2 - .../OSX/Exporter/payload/Contents/Info.plist | 39 -- .../SynthesisInventorGltfExporter/README.md | 1 - installer/OSX/Exporter/scripts/postinstall | 4 - installer/OSX/Exporter/scripts/preinstall | 3 - installer/OSX/Installer/Distribution.xml | 28 -- .../OSX/Installer/Resources/conclusion.html | 27 -- .../OSX/Installer/Resources/license.html | 206 ----------- .../OSX/Installer/Resources/welcome.html | 12 - installer/OSX/README.md | 36 -- installer/OSX/pkginstall | 12 - installer/README.md | 31 ++ installer/Windows/.gitignore | 11 - installer/Windows/MainInstaller.nsi | 332 ------------------ installer/Windows/README.md | 23 -- installer/Windows/W21_SYN_sidebar.bmp | Bin 152310 -> 0 bytes installer/Windows/orange-install-nsis.ico | Bin 25214 -> 0 bytes installer/Windows/orange-r.bmp | Bin 9744 -> 0 bytes installer/Windows/synthesis-logo-64x64.ico | Bin 184765 -> 0 bytes installer/exporter/.gitignore | 2 - installer/exporter/README.md | 14 - installer/exporter/create.sh | 11 - installer/exporter/install.bat | 10 - installer/exporter/install.sh | 10 - installer/exporter/setup.bat | 9 - tutorials/FusionExporter.md | 39 +- tutorials/img/fusion/fusion-add-addin.png | Bin 0 -> 1788931 bytes .../img/fusion/fusion-addin-synthesis.png | Bin 0 -> 1762982 bytes .../img/fusion/fusion-addins-highlight.png | Bin 0 -> 1482281 bytes tutorials/img/fusion/fusion-addins-panel.png | Bin 0 -> 1694013 bytes tutorials/img/fusion/fusion-empty.png | Bin 0 -> 1393412 bytes .../fusion-utilities-with-synthesis.png | Bin 0 -> 1443682 bytes 54 files changed, 67 insertions(+), 1417 deletions(-) delete mode 100644 installer/Linux/.gitignore delete mode 100644 installer/Linux/README.md delete mode 120000 installer/Linux/Synthesis.AppDir/.DirIcon delete mode 100755 installer/Linux/Synthesis.AppDir/AppRun delete mode 100755 installer/Linux/Synthesis.AppDir/synthesis.desktop delete mode 100644 installer/Linux/Synthesis.AppDir/synthesis.png delete mode 100755 installer/Linux/package.sh delete mode 100644 installer/OSX-DMG/.gitignore delete mode 100644 installer/OSX-DMG/README.md delete mode 100644 installer/OSX-DMG/SynthesisMacInstallerBackground.png delete mode 100644 installer/OSX-DMG/exporter-install-instructions.md delete mode 100755 installer/OSX-DMG/license.txt delete mode 100755 installer/OSX-DMG/make-installer.sh delete mode 100644 installer/OSX/.gitignore delete mode 100755 installer/OSX/App/payload/Contents/Info.plist delete mode 100644 installer/OSX/App/payload/Contents/README.md delete mode 100755 installer/OSX/App/scripts/postinstall delete mode 100755 installer/OSX/App/scripts/preinstall delete mode 100755 installer/OSX/Assets/payload/Contents/Info.plist delete mode 100644 installer/OSX/Assets/payload/Contents/Synthesis/README.md delete mode 100755 installer/OSX/Assets/scripts/postinstall delete mode 100755 installer/OSX/Assets/scripts/preinstall delete mode 100755 installer/OSX/Exporter/payload/Contents/Info.plist delete mode 100644 installer/OSX/Exporter/payload/Contents/SynthesisInventorGltfExporter/README.md delete mode 100755 installer/OSX/Exporter/scripts/postinstall delete mode 100755 installer/OSX/Exporter/scripts/preinstall delete mode 100755 installer/OSX/Installer/Distribution.xml delete mode 100755 installer/OSX/Installer/Resources/conclusion.html delete mode 100755 installer/OSX/Installer/Resources/license.html delete mode 100755 installer/OSX/Installer/Resources/welcome.html delete mode 100644 installer/OSX/README.md delete mode 100644 installer/OSX/pkginstall create mode 100644 installer/README.md delete mode 100644 installer/Windows/.gitignore delete mode 100644 installer/Windows/MainInstaller.nsi delete mode 100644 installer/Windows/README.md delete mode 100644 installer/Windows/W21_SYN_sidebar.bmp delete mode 100644 installer/Windows/orange-install-nsis.ico delete mode 100644 installer/Windows/orange-r.bmp delete mode 100644 installer/Windows/synthesis-logo-64x64.ico delete mode 100644 installer/exporter/.gitignore delete mode 100644 installer/exporter/README.md delete mode 100755 installer/exporter/create.sh delete mode 100644 installer/exporter/install.bat delete mode 100755 installer/exporter/install.sh delete mode 100644 installer/exporter/setup.bat create mode 100644 tutorials/img/fusion/fusion-add-addin.png create mode 100644 tutorials/img/fusion/fusion-addin-synthesis.png create mode 100644 tutorials/img/fusion/fusion-addins-highlight.png create mode 100644 tutorials/img/fusion/fusion-addins-panel.png create mode 100644 tutorials/img/fusion/fusion-empty.png create mode 100644 tutorials/img/fusion/fusion-utilities-with-synthesis.png diff --git a/README.md b/README.md index b9c8e56aa0..3a6135d588 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,13 @@ If you're a developer who wants to contribute to Synthesis, you're in the right - [Fission (Core Web App)](/fission/README.md) - [Fusion Exporter (Fusion exporter to Mirabuf file format)](/exporter/SynthesisFusionAddin/README.md) -- [Installers](/installer/) +- [Fusion Exporter Installer](/installer/) Follow the above links to the respective READMEs on how to build and run each component. ### Compatibility Notes -As Fusion is not supported on linux, the linux installer does not come with the Fusion Addin for exporting robots and fields. +As Fusion is not officially supported on Linux, we do not provide an installer for the Fusion Exporter on Linux. ## Contributing diff --git a/installer/Linux/.gitignore b/installer/Linux/.gitignore deleted file mode 100644 index 2bdd6e8d6c..0000000000 --- a/installer/Linux/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -Synthesis.AppDir/usr/bin/* -Synthesis.AppDir/fields/* -Synthesis.AppDir/robots/* - -*.md5 -*.AppImage diff --git a/installer/Linux/README.md b/installer/Linux/README.md deleted file mode 100644 index 46e539d52a..0000000000 --- a/installer/Linux/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# `>_` Synthesis App Image - -For running Synthesis we have decided to package our application as an AppImage. It allows Synthesis to be packaged as a single .AppImage file. This also allows for users to run Synthesis without needing a specific distribution. - -### Initial Setup ### -It is recommended that you update the packages on your system. For arch users, run `pacman -Syu` as root. Debian users can run `apt update && apt upgrade` as root. - -### Dependencies ### -Certain dependencies are necessary in order to package Synthesis as an AppImage. - -##### FUSE ##### -For Arch, you may need to run: `pacman -S fuse` as root. For Debian, run: `apt install fuse` as root. -If you are still encountering issues, refer to this page: https://docs.appimage.org/user-guide/troubleshooting/fuse.html#ref-install-fuse - -##### appimagetool ##### -You will also end up needing appimagetool to actually create the AppImage file. However the `package.sh` script will prompt you and install it automatically. If you wish to install in manually, download the latest release from here: https://github.com/AppImage/AppImageKit/releases and make it executable. It should be called appimagetool-$ARCH.AppImage ($ARCH = whatever architecture you are using to package synthesis; most likely x86_64). - -### Initial Setup ### -In order to acheive the proper package structure for proper extraction, you must first compile a Unity build as: `Synthesis.x86_64` and store it along with all other files and directories that came with it somewhere on your machine. - -Note: It is important that you do not modify or remove any of the files and folders that come built with the `Synthesis.x86_64` file. - -It is also strongly recommended that you have some fields and robots exported in the Mirabuf format. - -### Packaging ### -The recommended way of creating the AppImage is by using the `package.sh` script. You may also opt to package Synthesis manually. There is some documentation for that process but it is recommended that you have a good understanding of what you are doing if you choose this option. - -To run the script, you will likely need to make it executable by running: `chmod +x package.sh` in your preferred terminal. You may also right click on `package.sh` in a file browser and select the option to make it executable. - -Now run the script and specify input directories for the version of synthesis you compiled as well as fields and robots: `./init.sh -f /path/to/fields/ -r /path/to/robots/ -b /path/to/synthesis/` - -Note: While it is not strictly necessary to include fields and robots, it is strongly recommended to include at least one of each - -If it is not already installed, the script will ask to install appimagetool. We recommended answering yes as it will install appimagetool.AppImage to the `~/Applications/` directory and is necessary for creating AppImage files. - -### Installing appimagetool ### -appimagetool is the name of the program that is used to create AppImages. You can download and install appimagetool through the official website https://appimage.github.io/appimagetool/ or get it through your distribution's package manager. - -Note: appimagetool is usually packaged under AppImageKit rather than as a standalone application. - -### Manual Packaging ### - -##### File locations ##### -Certain files must be moved to the correct locations. First you should move any robot files to `Synthesis.AppDir/robots` (create it if it doesn't exist). Do the same for field files but put them in `Synthesis.AppDir/fields` (create it if it doesn't exist). Finally, move all files and directories in your Synthesis build directory into `Synthesis.AppDir/usr/bin/` (once again, create it if it doesn't exist). - -##### Creating The AppImage ##### -Finally you can create your AppImage! Make sure you have all dependencies installed and run: `ARCH=x86_64 appimagetool Synthesis.AppDir` which will create the Synthesis AppImage. - -Note: Run this instead if you installed appimagetool locally: `ARCH=x86_64 /path/to/appimagetool Synthesis.AppDir` - -### Final Note ### -When the end user is downloading the AppImage file, it is strongly recommended to have them put it in the `~/Applications/` directory. This allows it to be found by appimaged as well as itself when running uninstall. It is also recommended to allow them to download the checksum.md5 file so that the file integrity can be verified using `md5sum -c checksum.md5` in the same directory as the AppImage and md5 files. - -### Troubleshooting ### -Refer to the AppImage troubleshooting page first if you are having issues: https://docs.appimage.org/user-guide/troubleshooting/index.html -The general documentation may be of use as well: https://docs.appimage.org/index.html -If the issues persist, open a github issue with details about the problem. - diff --git a/installer/Linux/Synthesis.AppDir/.DirIcon b/installer/Linux/Synthesis.AppDir/.DirIcon deleted file mode 120000 index 8c7bb9a13f..0000000000 --- a/installer/Linux/Synthesis.AppDir/.DirIcon +++ /dev/null @@ -1 +0,0 @@ -synthesis.png \ No newline at end of file diff --git a/installer/Linux/Synthesis.AppDir/AppRun b/installer/Linux/Synthesis.AppDir/AppRun deleted file mode 100755 index da2da947f5..0000000000 --- a/installer/Linux/Synthesis.AppDir/AppRun +++ /dev/null @@ -1,89 +0,0 @@ -#!/bin/sh - -show_help() { - echo "-h display help message" - echo "-u run uninstall script" -} - -install_appimaged() { - mkdir -p ~/Applications - wget -c https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases -O - | grep "appimaged-.*-x86_64.AppImage" | head -n 1 | cut -d '"' -f 2) -P ~/Applications/ - chmod +x ~/Applications/appimaged-*.AppImage - - # Launch - ~/Applications/appimaged-*.AppImage & -} - -uninstall_synthesis() { - rm -R ~/.config/Autodesk/Synthesis/ - if [ -e ~/Applications/Synthesis*.AppImage ] - then - rm ~/Applications/Synthesis*.AppImage - fi - if [ -e ~/Applications/appimaged-*.AppImage ] - then - while true; do - read -p "Do You wish to try and uninstall appimaged? (recommended) (y/n): " yn - case $yn in - [Yy]* ) - rm ~/Applications/appimaged-*.AppImage - break - ;; - [Nn]* ) - break - ;; - * ) - echo "Please answer yes or no." - ;; - esac - done - fi -} - -run_synthesis() { - mkdir -p ~/.config/Autodesk/Synthesis/Mira/Fields - cp "$HERE/fields/"*.mira ~/.config/Autodesk/Synthesis/Mira/Fields - cp "$HERE/robots/"*.mira ~/.config/Autodesk/Synthesis/Mira/ - - if [ ! -e ~/Applications/appimaged-*.AppImage ] - then - while true; do - read -p "Do You wish to install and start appimaged? (recommended) (y/n): " yn - case $yn in - [Yy]* ) - install_appimaged; - break - ;; - [Nn]* ) - break - ;; - * ) - echo "Please answer yes or no." - ;; - esac - done - fi - - exec "$EXEC" -} - -HERE="$(dirname "$(readlink -f "${0}")")" -EXEC="$HERE/usr/bin/Synthesis.x86_64" - -OPTIND=1 -while getopts ":hu" opt; do - case "$opt" in - h|\?) - show_help - exit 0 - ;; - u) - uninstall_synthesis - exit 0 - ;; - esac -done - -shift $((OPTIND-1)) - -run_synthesis diff --git a/installer/Linux/Synthesis.AppDir/synthesis.desktop b/installer/Linux/Synthesis.AppDir/synthesis.desktop deleted file mode 100755 index 424ddebffb..0000000000 --- a/installer/Linux/Synthesis.AppDir/synthesis.desktop +++ /dev/null @@ -1,6 +0,0 @@ -[Desktop Entry] -Name=Synthesis -Exec=Synthesis.x86_64 -Icon=synthesis -Type=Application -Categories=Game diff --git a/installer/Linux/Synthesis.AppDir/synthesis.png b/installer/Linux/Synthesis.AppDir/synthesis.png deleted file mode 100644 index 35f6df073646ad75f8fa7dbe386068e65eaddbab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42175 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 ipQP(Ag4dq|DF1&1p4FHJdVSdd0000 $INIT_DIR/checksum.md5 - md5sum -c $INIT_DIR/checksum.md5 -} - -OPTIND=1 - -ARCH="x86_64" -APP_NAME="Synthesis" -INIT_DIR="$(dirname "$(readlink -f "${0}")")" -APP_DIR="$INIT_DIR/$APP_NAME.AppDir" - -mkdir -p "$APP_DIR/usr/bin/" -mkdir -p "$APP_DIR/fields" -mkdir -p "$APP_DIR/robots" - -while getopts "h?f:r:b:" opt; do - case "$opt" in - h|\?) - show_help - exit 0 - ;; - f) - fields="$OPTARG" - ;; - r) - robots="$OPTARG" - ;; - b) - build="$OPTARG" - ;; - esac -done - -shift $((OPTIND-1)) - -if [ ! -n "$build" ] ; then - echo "Specify synthesis build directory using \"-b\"" - exit 1 -fi - -if [ -n "$fields" ] ; then - cp "$fields/"*.mira "$APP_DIR/fields/" -fi - -if [ -n "$robots" ] ; then - cp "$robots/"*.mira "$APP_DIR/robots/" -fi - -cp -R "$build/"* "$APP_DIR/usr/bin" -chmod +x "$APP_DIR/AppRun" -chmod +x "$APP_DIR/synthesis.desktop" -chmod +x "$APP_DIR/usr/bin/Synthesis.x86_64" - -if [ ! -e ~/Applications/appimagetool-*.AppImage ] ; then - while true; do - read -p "Do you wish to install appimagetool (Needed for creating the AppImage file) (y/n): " yn - case $yn in - [Yy]* ) - install_appimagetool - break - ;; - [Nn]* ) - break - ;; - * ) - echo "Please answer yes or no." - ;; - esac - done -fi - -if [ -e ~/Applications/appimagetool-x86_64.AppImage ] ; then - create_appimage -else - echo "Install appimagetool before creating AppImage" -fi diff --git a/installer/OSX-DMG/.gitignore b/installer/OSX-DMG/.gitignore deleted file mode 100644 index 5ddb15821b..0000000000 --- a/installer/OSX-DMG/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -source_folder/Synthesis.app/ -exporter_source_folder/ -Synthesis-Installer.dmg -create-dmg/ -addins-folder-link - -exporter-install-instructions.pdf - -*.dmg diff --git a/installer/OSX-DMG/README.md b/installer/OSX-DMG/README.md deleted file mode 100644 index 18cd11dd1c..0000000000 --- a/installer/OSX-DMG/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Synthesis OSX Installer (DMG Version) - -## Setup -1. Install `create-dmg`: -``` -$ git clone git@github.com:create-dmg/create-dmg.git -``` - -2. Copy the signed Synthesis app into source_folder: -``` -$ cp -r [Location of app] ./source_folder/Synthesis.app -``` - -2. Compile the `exporter-install-instructions.md` into a PDF. I recommend using the Yzane extension in VSCode. - -## Create Disk Image -Run the `make-installer.sh` shell script: -``` -$ ./make-installer.sh -``` - -Disk Image will be created at `/installer/OSX-DMG/Synthesis-Installer.dmg` - -## Notes -Update `source_folder/license.html` as needed as well as settings for the `create-dmg` command inside of `make-installer.sh`. See [create-dmg repository](https://github.com/create-dmg/create-dmg) for configuration information. \ No newline at end of file diff --git a/installer/OSX-DMG/SynthesisMacInstallerBackground.png b/installer/OSX-DMG/SynthesisMacInstallerBackground.png deleted file mode 100644 index 0d4d27e9a140ec497ca72b0a09c210c7b4f5f5a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5014 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 yl7(osty*X- - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - Synthesis - CFBundleGetInfoString - Unity Player version 2019.4.0f1 (0af376155913). (c) 2020 Unity Technologies ApS. All rights reserved. - CFBundleIconFile - PlayerIcon.icns - CFBundleIdentifier - com.Autodesk.Synthesis - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Synthesis - CFBundlePackageType - APPL - CFBundleShortVersionString - 0.1 - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - 0 - LSApplicationCategoryType - public.app-category.games - LSMinimumSystemVersion - 10.9.0 - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - - diff --git a/installer/OSX/App/payload/Contents/README.md b/installer/OSX/App/payload/Contents/README.md deleted file mode 100644 index 3b82ea5db1..0000000000 --- a/installer/OSX/App/payload/Contents/README.md +++ /dev/null @@ -1 +0,0 @@ -## Synthesis.zip diff --git a/installer/OSX/App/scripts/postinstall b/installer/OSX/App/scripts/postinstall deleted file mode 100755 index 2c990d0d53..0000000000 --- a/installer/OSX/App/scripts/postinstall +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -mv $2/Contents/Synthesis.zip /Applications/ -rm -rf $2 -cd /Applications/ -unzip /Applications/Synthesis.zip -rm -rf Synthesis.zip -exit 0 diff --git a/installer/OSX/App/scripts/preinstall b/installer/OSX/App/scripts/preinstall deleted file mode 100755 index 06bd986563..0000000000 --- a/installer/OSX/App/scripts/preinstall +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -exit 0 diff --git a/installer/OSX/Assets/payload/Contents/Info.plist b/installer/OSX/Assets/payload/Contents/Info.plist deleted file mode 100755 index 734c2b7a8a..0000000000 --- a/installer/OSX/Assets/payload/Contents/Info.plist +++ /dev/null @@ -1,39 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - Synthesis - CFBundleGetInfoString - Unity Player version 2019.4.0f1 (0af376155913). (c) 2020 Unity Technologies ApS. All rights reserved. - CFBundleIconFile - PlayerIcon.icns - CFBundleIdentifier - com.Autodesk.Synthesis - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Synthesis - CFBundlePackageType - APPL - CFBundleShortVersionString - 0.1 - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - 0 - LSApplicationCategoryType - public.app-category.games - LSMinimumSystemVersion - 10.9.0 - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - - diff --git a/installer/OSX/Assets/payload/Contents/Synthesis/README.md b/installer/OSX/Assets/payload/Contents/Synthesis/README.md deleted file mode 100644 index 970708317f..0000000000 --- a/installer/OSX/Assets/payload/Contents/Synthesis/README.md +++ /dev/null @@ -1 +0,0 @@ -## Asset/Data Files diff --git a/installer/OSX/Assets/scripts/postinstall b/installer/OSX/Assets/scripts/postinstall deleted file mode 100755 index 421ba3de1c..0000000000 --- a/installer/OSX/Assets/scripts/postinstall +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -rm -rf ~/.config/Autodesk/Synthesis -mv $2/Contents/Synthesis ~/.config/Autodesk/ -rm -rf $2 -exit 0 diff --git a/installer/OSX/Assets/scripts/preinstall b/installer/OSX/Assets/scripts/preinstall deleted file mode 100755 index 06bd986563..0000000000 --- a/installer/OSX/Assets/scripts/preinstall +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -exit 0 diff --git a/installer/OSX/Exporter/payload/Contents/Info.plist b/installer/OSX/Exporter/payload/Contents/Info.plist deleted file mode 100755 index 734c2b7a8a..0000000000 --- a/installer/OSX/Exporter/payload/Contents/Info.plist +++ /dev/null @@ -1,39 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - Synthesis - CFBundleGetInfoString - Unity Player version 2019.4.0f1 (0af376155913). (c) 2020 Unity Technologies ApS. All rights reserved. - CFBundleIconFile - PlayerIcon.icns - CFBundleIdentifier - com.Autodesk.Synthesis - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Synthesis - CFBundlePackageType - APPL - CFBundleShortVersionString - 0.1 - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - 0 - LSApplicationCategoryType - public.app-category.games - LSMinimumSystemVersion - 10.9.0 - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - - diff --git a/installer/OSX/Exporter/payload/Contents/SynthesisInventorGltfExporter/README.md b/installer/OSX/Exporter/payload/Contents/SynthesisInventorGltfExporter/README.md deleted file mode 100644 index 0ec8650013..0000000000 --- a/installer/OSX/Exporter/payload/Contents/SynthesisInventorGltfExporter/README.md +++ /dev/null @@ -1 +0,0 @@ -## Exporter Files diff --git a/installer/OSX/Exporter/scripts/postinstall b/installer/OSX/Exporter/scripts/postinstall deleted file mode 100755 index 4000f1e866..0000000000 --- a/installer/OSX/Exporter/scripts/postinstall +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -mv $2/Contents/SynthesisFusionAddin ~/Library/Application\ Support/Autodesk/Autodesk\ Fusion\ 360/API/AddIns/ -rm -rf $2 -exit 0 diff --git a/installer/OSX/Exporter/scripts/preinstall b/installer/OSX/Exporter/scripts/preinstall deleted file mode 100755 index 495e3edbba..0000000000 --- a/installer/OSX/Exporter/scripts/preinstall +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -mkdir -p ~/Library/Application Support/Autodesk/Autodesk Fusion 360/API/AddIns/ -exit 0 diff --git a/installer/OSX/Installer/Distribution.xml b/installer/OSX/Installer/Distribution.xml deleted file mode 100755 index 4d54db2521..0000000000 --- a/installer/OSX/Installer/Distribution.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - App.pkg - - - - Assets.pkg - - - - Exporter.pkg - diff --git a/installer/OSX/Installer/Resources/conclusion.html b/installer/OSX/Installer/Resources/conclusion.html deleted file mode 100755 index 9151c5f509..0000000000 --- a/installer/OSX/Installer/Resources/conclusion.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - -
-

Synthesis: An Autodesk Technology | 5.0.0.0 ALPHA

-

Thank you for installing Synthesis: An Autodesk Technology

-
-
-

In order to improve this product and understand how it is used, we collect non-personal product usage information. This usage information may consist of custom events like Replay Mode, Driver Practice Mode, Tutorial Link Clicked, etc. This information is not used to identify or contact you. You can turn data collection off from the Control Panel within the simulator. By installing, you agree that you have read the terms of service agreement and data collection statement above.

-
-
-

Resources

-

Go through the following link for additional information.

- -
-
-
-

Copyright © 2021 Autodesk inc.

-
- - diff --git a/installer/OSX/Installer/Resources/license.html b/installer/OSX/Installer/Resources/license.html deleted file mode 100755 index 6be72e4428..0000000000 --- a/installer/OSX/Installer/Resources/license.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - -
-

- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/

- -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

- -1. Definitions.

- -"License" shall mean the terms and conditions for use, reproduction, -and distribution as defined by Sections 1 through 9 of this document. -


-"Licensor" shall mean the copyright owner or entity authorized by -the copyright owner that is granting the License. -


-"Legal Entity" shall mean the union of the acting entity and all -other entities that control, are controlled by, or are under common -control with that entity. For the purposes of this definition, -"control" means (i) the power, direct or indirect, to cause the -direction or management of such entity, whether by contract or -otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. -


-"You" (or "Your") shall mean an individual or Legal Entity -exercising permissions granted by this License. -


-"Source" form shall mean the preferred form for making modifications, -including but not limited to software source code, documentation -source, and configuration files. -


-"Object" form shall mean any form resulting from mechanical -transformation or translation of a Source form, including but -not limited to compiled object code, generated documentation, -and conversions to other media types. -


-"Work" shall mean the work of authorship, whether in Source or -Object form, made available under the License, as indicated by a -copyright notice that is included in or attached to the work -(an example is provided in the Appendix below). -


-"Derivative Works" shall mean any work, whether in Source or Object -form, that is based on (or derived from) the Work and for which the -editorial revisions, annotations, elaborations, or other modifications -represent, as a whole, an original work of authorship. For the purposes -of this License, Derivative Works shall not include works that remain -separable from, or merely link (or bind by name) to the interfaces of, -the Work and Derivative Works thereof. -


-"Contribution" shall mean any work of authorship, including -the original version of the Work and any modifications or additions -to that Work or Derivative Works thereof, that is intentionally -submitted to Licensor for inclusion in the Work by the copyright owner -or by an individual or Legal Entity authorized to submit on behalf of -the copyright owner. For the purposes of this definition, "submitted" -means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, -and issue tracking systems that are managed by, or on behalf of, the -Licensor for the purpose of discussing and improving the Work, but -excluding communication that is conspicuously marked or otherwise -designated in writing by the copyright owner as "Not a Contribution." -


-"Contributor" shall mean Licensor and any individual or Legal Entity -on behalf of whom a Contribution has been received by Licensor and -subsequently incorporated within the Work. -


-2. Grant of Copyright License. Subject to the terms and conditions of -this License, each Contributor hereby grants to You a perpetual, -worldwide, non-exclusive, no-charge, royalty-free, irrevocable -copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the -Work and such Derivative Works in Source or Object form. -


-3. Grant of Patent License. Subject to the terms and conditions of -this License, each Contributor hereby grants to You a perpetual, -worldwide, non-exclusive, no-charge, royalty-free, irrevocable -(except as stated in this section) patent license to make, have made, -use, offer to sell, sell, import, and otherwise transfer the Work, -where such license applies only to those patent claims licensable -by such Contributor that are necessarily infringed by their -Contribution(s) alone or by combination of their Contribution(s) -with the Work to which such Contribution(s) was submitted. If You -institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work -or a Contribution incorporated within the Work constitutes direct -or contributory patent infringement, then any patent licenses -granted to You under this License for that Work shall terminate -as of the date such litigation is filed. -


-4. Redistribution. You may reproduce and distribute copies of the -Work or Derivative Works thereof in any medium, with or without -modifications, and in Source or Object form, provided that You -meet the following conditions: -


-(a) You must give any other recipients of the Work or -Derivative Works a copy of this License; and -


-(b) You must cause any modified files to carry prominent notices -stating that You changed the files; and -


-(c) You must retain, in the Source form of any Derivative Works -that You distribute, all copyright, patent, trademark, and -attribution notices from the Source form of the Work, -excluding those notices that do not pertain to any part of -the Derivative Works; and -


-(d) If the Work includes a "NOTICE" text file as part of its -distribution, then any Derivative Works that You distribute must -include a readable copy of the attribution notices contained -within such NOTICE file, excluding those notices that do not -pertain to any part of the Derivative Works, in at least one -of the following places: within a NOTICE text file distributed -as part of the Derivative Works; within the Source form or -documentation, if provided along with the Derivative Works; or, -within a display generated by the Derivative Works, if and -wherever such third-party notices normally appear. The contents -of the NOTICE file are for informational purposes only and -do not modify the License. You may add Your own attribution -notices within Derivative Works that You distribute, alongside -or as an addendum to the NOTICE text from the Work, provided -that such additional attribution notices cannot be construed -as modifying the License. -


-You may add Your own copyright statement to Your modifications and -may provide additional or different license terms and conditions -for use, reproduction, or distribution of Your modifications, or -for any such Derivative Works as a whole, provided Your use, -reproduction, and distribution of the Work otherwise complies with -the conditions stated in this License. -


-5. Submission of Contributions. Unless You explicitly state otherwise, -any Contribution intentionally submitted for inclusion in the Work -by You to the Licensor shall be under the terms and conditions of -this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify -the terms of any separate license agreement you may have executed -with Licensor regarding such Contributions. -


-6. Trademarks. This License does not grant permission to use the trade -names, trademarks, service marks, or product names of the Licensor, -except as required for reasonable and customary use in describing the -origin of the Work and reproducing the content of the NOTICE file. -


-7. Disclaimer of Warranty. Unless required by applicable law or -agreed to in writing, Licensor provides the Work (and each -Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -implied, including, without limitation, any warranties or conditions -of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A -PARTICULAR PURPOSE. You are solely responsible for determining the -appropriateness of using or redistributing the Work and assume any -risks associated with Your exercise of permissions under this License. -


-8. Limitation of Liability. In no event and under no legal theory, -whether in tort (including negligence), contract, or otherwise, -unless required by applicable law (such as deliberate and grossly -negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, -incidental, or consequential damages of any character arising as a -result of this License or out of the use or inability to use the -Work (including but not limited to damages for loss of goodwill, -work stoppage, computer failure or malfunction, or any and all -other commercial damages or losses), even if such Contributor -has been advised of the possibility of such damages. -


-9. Accepting Warranty or Additional Liability. While redistributing -the Work or Derivative Works thereof, You may choose to offer, -and charge a fee for, acceptance of support, warranty, indemnity, -or other liability obligations and/or rights consistent with this -License. However, in accepting such obligations, You may act only -on Your own behalf and on Your sole responsibility, not on behalf -of any other Contributor, and only if You agree to indemnify, -defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason -of your accepting any such warranty or additional liability. -


-END OF TERMS AND CONDITIONS -


-APPENDIX: How to apply the Apache License to your work. -


-Copyright 2021 Autodesk inc. -


-Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at -


-http://www.apache.org/licenses/LICENSE-2.0 -


-Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -


-

-

Click “Continue" to continue the setup

-
- - diff --git a/installer/OSX/Installer/Resources/welcome.html b/installer/OSX/Installer/Resources/welcome.html deleted file mode 100755 index dcea4723b3..0000000000 --- a/installer/OSX/Installer/Resources/welcome.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - -
-

This will install Synthesis: An Autodesk Application 5.0.0.0 ALPHA on your computer. You will be guided through the steps necessary to install this software.

-

Click “Continue" to continue the setup

-
- - diff --git a/installer/OSX/README.md b/installer/OSX/README.md deleted file mode 100644 index 846f17c8b2..0000000000 --- a/installer/OSX/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# OSX packager - -## Build Steps : - -1. Get signed Synthesis.app - -2. Zip Synthesis.app - -3. Copy Synthesis.zip to file system ` cp [Synthesis.zip] [synthesis/installer/OSX/App/payload/Contents] ` - - - Remove the Synthesis.zip placeholder ` rm [synthesis/installer/OSX/App/payload/Contents/README.md] ` - -3. Add data files to ` synthesis/installer/OSX/Assets/payload/Contents/Synthesis ` - - - Remove the data file placeholder ` rm [synthesis/installer/OSX/Assets/payload/Contents/Synthesis/README.md] ` - -4. Add unzipped exporter files to ` synthesis/installer/OSX/Exporter/payload/Contents/SynthesisFusionGltfExporter ` - - - Remove the exporter file placeholder ` rm [synthesis/installer/OSX/Exporter/payload/Contents/SynthesisFusionGltfExporter/README.md] ` - -5. Change directories to the OSX installer directory ` cd [synthesis/installer/OSX] ` - -6. Run the pkginstall script ` ./pkginstall ` - -### Optional Build Steps - -Update the license, welcome and conclusion installer menus located in ` [synthesis/installer/OSX/Installer/Resources] ` - -## Package - -Publish the newly created Synthesis.pkg - -## Important Note - -**Do not** rename or move files - diff --git a/installer/OSX/pkginstall b/installer/OSX/pkginstall deleted file mode 100644 index 359e862088..0000000000 --- a/installer/OSX/pkginstall +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -#Priviledges -chmod -R 777 ./Assets/payload/Contents/Synthesis -#Build app -pkgbuild --install-location ~/.config/Autodesk/SynthesisAppInstall --root ./App/payload/ --scripts ./App/scripts/ --identifier org.autodesk.synthesis.app ./Installer/App.pkg -#Build data files -pkgbuild --install-location ~/.config/Autodesk/SynthesisAssetsInstall --root ./Assets/payload/ --scripts ./Assets/scripts/ --identifier org.autodesk.synthesis.assets ./Installer/Assets.pkg -#Build exporter -pkgbuild --install-location ~/.config/Autodesk/SynthesisExporterInstall --root ./Exporter/payload/ --scripts ./Exporter/scripts/ --identifier org.autodesk.synthesis.exporter ./Installer/Exporter.pkg -#Build installer -cd Installer -productbuild --distribution Distribution.xml --resources Resources/ ../Synthesis.pkg diff --git a/installer/README.md b/installer/README.md new file mode 100644 index 0000000000..0b082ada39 --- /dev/null +++ b/installer/README.md @@ -0,0 +1,31 @@ +# Installing the Synthesis Fusion Exporter + +## Manual Install + +- Navigate to [`synthesis.autodesk.com/download`](https://synthesis.autodesk.com/download.html). +- Find the Exporter source code zip download. + - Note that the source code is platform agnostic, it will work for **both** `Windows` and `Mac`. +- Once the source code for the Exporter is installed unzip the folder. +- Next, if not done already, install `Autodesk Fusion`. +- Once Fusion is open navigate to the `Utilities Toolbar`. +![image_caption](../tutorials/img/fusion/fusion-empty.png) +- Click on `Scripts and Add-ins` from the toolbar. +![image_caption](../tutorials/img/fusion/fusion-addins-highlight.png) +- Navigate to `Add-ins` and select the green plus icon. +![image_caption](../tutorials/img/fusion/fusion-addins-panel.png) +- Now navigate to wherever you extracted the original `.zip` source code file you downloaded. + - Make sure to select the folder that contains the `Synthesis.py` file, this is the entry point to the Exporter. +![image_caption](../tutorials/img/fusion/fusion-add-addin.png) +- Once the extension is added you should be able to see it under `My Add-Ins`. +- Select `Synthesis` from the `My Add-Ins` drop down and click `Run` in the bottom right. +![image_caption](../tutorials/img/fusion/fusion-addin-synthesis.png) +- The first time you run the extension it may prompt you to restart Fusion, this is totally normal. +- Once you restart Fusion the extension will run on startup, you will be able to find it on the right side of the toolbar +under the `Utilities` tab. +![image_caption](../tutorials/img/fusion/fusion-utilities-with-synthesis.png) + +Thanks for installing the Synthesis Fusion Exporter! For any additional help visit our [Synthesis Community Discord Server](https://www.discord.gg/hHcF9AVgZA) where you can talk directly to our developers. + +## Using an Installer + +Our automatic installer is still in development, visit the [Synthesis Discord Server](https://www.discord.gg/hHcF9AVgZA) for updates and any manual installing help. diff --git a/installer/Windows/.gitignore b/installer/Windows/.gitignore deleted file mode 100644 index 65fac130c8..0000000000 --- a/installer/Windows/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -Robots/ -Fields/ -Exporter/ -Themes/ -MixAndMatch/ -Synthesis/ - -Robots.zip -Fields.zip -Themes.zip -MixAndMatch.zip diff --git a/installer/Windows/MainInstaller.nsi b/installer/Windows/MainInstaller.nsi deleted file mode 100644 index 7cf07b658e..0000000000 --- a/installer/Windows/MainInstaller.nsi +++ /dev/null @@ -1,332 +0,0 @@ -!include MUI2.nsh -!include x64.nsh -!define PRODUCT_VERSION "6.0.0" - -Name "Synthesis" - -;Icon "W16_SYN_launch.ico" -Icon "synthesis-logo-64x64.ico" - -Caption "Synthesis ${PRODUCT_VERSION} Setup" - -OutFile "SynthesisWin${PRODUCT_VERSION}.exe" - -InstallDir $PROGRAMFILES64\Autodesk\Synthesis - -InstallDirRegKey HKLM "Software\Synthesis" "Install_Dir" - -RequestExecutionLevel admin - - Section - ${If} ${RunningX64} - goto install_stuff - ${Else} - MessageBox MB_OK "ERROR: This install requires a 64 bit system." - Quit - ${EndIf} - install_stuff: - SectionEnd - -;-------------------------------- - -;Interface Settings - !define MUI_WELCOMEFINISHPAGE_BITMAP "W21_SYN_sidebar.bmp" - !define MUI_UNWELCOMEFINISHPAGE_BITMAP "W21_SYN_sidebar.bmp" - !define MUI_ICON "synthesis-logo-64x64.ico" - !define MUI_UNICON "synthesis-logo-64x64.ico" - !define MUI_HEADERIMAGE - !define MUI_HEADERIMAGE_BITMAP "orange-r.bmp" - !define MUI_HEADERIMAGE_RIGHT - !define MUI_ABORTWARNING - !define MUI_FINISHPAGE_TEXT 'Synthesis has been successfully installed on your system. $\r$\n $\r$\nIn order to improve this product and understand how it is used, we collect non-personal product usage information. This usage information may consist of custom events like Replay Mode, Driver Practice Mode, etc. $\r$\nThis information is not used to identify or contact you. $\r$\nYou can turn data collection off from the Control Panel within the simulator. $\r$\n $\r$\nBy clicking Finish, you agree that you have read the terms of service agreement and data collection statement above.' - !define MUI_FINISHPAGE_LINK "Synthesis Discord" - !define MUI_FINISHPAGE_LINK_LOCATION "https://www.discord.gg/hHcF9AVgZA" - -;-------------------------------- - - ; Installer GUI Pages - !insertmacro MUI_PAGE_WELCOME - !insertmacro MUI_PAGE_LICENSE "..\..\LICENSE.txt" - !insertmacro MUI_PAGE_COMPONENTS - !insertmacro MUI_PAGE_INSTFILES - !insertmacro MUI_PAGE_FINISH - - ; Uninstaller GUI Pages - !insertmacro MUI_UNPAGE_WELCOME - !insertmacro MUI_UNPAGE_CONFIRM - !insertmacro MUI_UNPAGE_INSTFILES - !insertmacro MUI_UNPAGE_FINISH - -;-------------------------------- - - ; Default Language - !insertmacro MUI_LANGUAGE "English" - -Section - -IfFileExists "$APPDATA\Autodesk\Synthesis" +1 +28 - MessageBox MB_YESNO "You appear to have Synthesis installed; would you like to reinstall it?" IDYES true IDNO false - true: - DeleteRegKey HKLM SOFTWARE\Synthesis - - ; Remove fusion plugins - RMDir /r "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\FusionRobotExporter" - RMDir /r "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis" - RMDir /r "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\FusionExporter" - RMDir /r "$APPDATA\Autodesk\ApplicationPlugins\FusionRobotExporter.bundle" - RMDir /r "$APPDATA\Autodesk\ApplicationPlugins\FusionSynth.bundle" - - ; Remove inventor plugins - Delete "$APPDATA\Autodesk\Inventor 2021\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2020\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2019\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2018\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2017\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete "$APPDATA\Autodesk\ApplicationPlugins\Autodesk.InventorRobotExporter.Inventor.addin" - RMDir /r "$APPDATA\Autodesk\ApplicationPlugins\InventorRobotExporter" - - ; Remove deprecated bxd inventor plugins - Delete "$APPDATA\Autodesk\Inventor 2020\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2019\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2019\Addins\autodesk.BxDFieldExporter.inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2018\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2018\Addins\autodesk.BxDFieldExporter.inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2017\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete "$APPDATA\Autodesk\Inventor 2017\Addins\autodesk.BxDFieldExporter.inventor.addin" - Delete "$APPDATA\Autodesk\ApplicationPlugins\Autodesk.BxDRobotExporter.Inventor.addin" - RMDir /r "$APPDATA\Autodesk\ApplicationPlugins\BxDRobotExporter" - RMDIR /r $APPDATA\RobotViewer - - ; Remove excess shortcuts - Delete "$SMPROGRAMS\Synthesis.lnk" - Delete "$DESKTOP\Synthesis.lnk" - Delete "$SMPROGRAMS\BXD Synthesis.lnk" - Delete "$DESKTOP\BXD Synthesis.lnk" - Delete "$SMPROGRAMS\Autodesk Synthesis.lnk" - Delete "$DESKTOP\Autodesk Synthesis.lnk" - Delete "$DESKTOP\FieldExporter.lnk" - - ; Remove obsolete directories - RMDir /r $INSTDIR - RMDir /r $APPDATA\Synthesis - RMDir /r $APPDATA\BXD_Aardvark - RMDir /r $PROGRAMFILES\Autodesk\Synthesis - - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" - DeleteRegKey HKCU "SOFTWARE\Autodesk\Synthesis" - ;DeleteRegKey HKCU "SOFTWARE\Autodesk\BXD Synthesis" - - Goto next - - false: - Quit - - next: - -# default section end -SectionEnd - -Section "Synthesis (required)" Synthesis - - SectionIn RO - - ; Set output path to the installation directory. - SetOutPath $INSTDIR - - File /r "Synthesis\*" - - CreateShortCut "$SMPROGRAMS\Synthesis.lnk" "$INSTDIR\Synthesis.exe" - CreateShortCut "$DESKTOP\Synthesis.lnk" "$INSTDIR\Synthesis.exe" - - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" \ - "DisplayName" "Autodesk Synthesis" - - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" \ - "DisplayIcon" "$INSTDIR\uninstall.exe" - - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" \ - "Publisher" "Autodesk" - - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" \ - "URLInfoAbout" "synthesis.autodesk.com/tutorials" - - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" \ - "DisplayVersion" "${PRODUCT_VERSION}" - - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" \ - "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" - - - WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synthesis" "NoModify" 1 - WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synthesis" "NoRepair" 1 - WriteUninstaller "uninstall.exe" - -SectionEnd - -/* -Section "Inventor Exporter Plugin" iExporter - - ; Set extraction path to Inventor plugin directory - SetOutPath $INSTDIR\Exporter - File /r "InventorExporter\*" - - SetOutPath $APPDATA\Autodesk\ApplicationPlugins - File /r "InventorExporter\Autodesk.InventorRobotExporter.Inventor.addin" - -SectionEnd -*/ - -Section "Fusion Exporter Plugin" fExporter - - ; Set extraction path to Fusion plugin directories - SetOutPath "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis" - File /r "Exporter\*" - ; File /r "..\..\exporter\SynthesisFusionAddin\*" - - ; SetOutPath "$APPDATA\Autodesk\ApplicationPlugins\FusionRobotExporter.bundle\Contents\" - ; File /r "FusionExporter\FusionRobotExporter.dll" - -SectionEnd - -Section "Robots and Fields" RobotFiles - - ; Set extraction path for preloaded robot files - SetOutPath $APPDATA\Autodesk\Synthesis\Mira - File /r "Robots\*" - - SetOutPath $APPDATA\Autodesk\Synthesis\Mira\Fields - File /r "Fields\*" - -SectionEnd - -Section "PartBuilder Samples" PartBuilder - - ; Set extraction path for preloaded robot files - SetOutPath $APPDATA\Autodesk\Synthesis\MixAndMatch - File /r "MixAndMatch\*" - -SectionEnd - -Section "Themes" Themes - - ; Set extraction path for preloaded robot files - SetOutPath $APPDATA\Autodesk\Synthesis\Themes - File /r "Themes\*" - -SectionEnd - -/* -Section "Code Emulator" Emulator - - ; INetC.dll must be installed to proper NSIS Plugins x86 directories - inetc::get "https://qemu.weilnetz.de/w64/2019/qemu-w64-setup-20190724.exe" "$PLUGINSDIR\qemu-w64-setup-20190724.exe" - Pop $R0 ;Get the return value - - ${If} $R0 == "OK" ;Return value should be "OK" - SetOutPath $APPDATA\Autodesk\Synthesis\Emulator - File /r "Emulator\*" - HideWindow - ExecWait '"$PLUGINSDIR\qemu-w64-setup-20190724.exe" /SILENT' - ShowWindow hwnd show_state - ${Else} - MessageBox MB_ICONSTOP "Error: $R0" ;Show cancel/error message - ${EndIf} - -SectionEnd -*/ - -;-------------------------------- -;Component Descriptions - - LangString DESC_Synthesis ${LANG_ENGLISH} "The Simulator Engine is the real-time physics environment which simulates the robots and fields." - ; LangString DESC_iExporter ${LANG_ENGLISH} "The Inventor Exporter Plugin is an Inventor addin used to export Autodesk Inventor Assemblies directly into the simulator" - LangString DESC_fExporter ${LANG_ENGLISH} "The Fusion360 Exporter Plugin is a Fusion addin used to export Autodesk Fusion Assemblies directly into the simulator" - LangString DESC_RobotFiles ${LANG_ENGLISH} "A library of sample robots and fields pre-loaded into the simulator" - LangString DESC_PartBuilder ${LANG_ENGLISH} "A library of sample parts to use in Robot Builder" - LangString DESC_Themes ${LANG_ENGLISH} "Preinstalled themes" - ; LangString DESC_Emulator ${LANG_ENGLISH} "The Robot Code Emulator allows you to emulate your C++ & JAVA robot code in the simulator" - - !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN - !insertmacro MUI_DESCRIPTION_TEXT ${Synthesis} $(DESC_Synthesis) - ; !insertmacro MUI_DESCRIPTION_TEXT ${iExporter} $(DESC_iExporter) - !insertmacro MUI_DESCRIPTION_TEXT ${fExporter} $(DESC_fExporter) - !insertmacro MUI_DESCRIPTION_TEXT ${RobotFiles} $(DESC_RobotFiles) - !insertmacro MUI_DESCRIPTION_TEXT ${PartBuilder} $(DESC_PartBuilder) - !insertmacro MUI_DESCRIPTION_TEXT ${Themes} $(DESC_Themes) - ; !insertmacro MUI_DESCRIPTION_TEXT ${Emulator} $(DESC_Emulator) - !insertmacro MUI_FUNCTION_DESCRIPTION_END - -;-------------------------------- - -Section "Uninstall" - - MessageBox MB_YESNO "Would you like to remove your robot files?" IDNO NawFam - RMDir /r /REBOOTOK $APPDATA\Synthesis - RMDir /r /REBOOTOK $APPDATA\Autodesk\Synthesis - - NawFam: - ; Remove registry keys - DeleteRegKey HKLM SOFTWARE\Synthesis - DeleteRegKey HKCU SOFTWARE\Autodesk\Synthesis - DeleteRegKey HKCU "SOFTWARE\Autodesk\BXD Synthesis" - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Autodesk Synthesis" - - ; Remove installation directories - RMDir /r /REBOOTOK $INSTDIR - RMDir /r /REBOOTOK $PROGRAMFILES\Autodesk\Synthesis - RMDir /r /REBOOTOK $APPDATA\BXD_Aardvark - RMDir /r /REBOOTOK $APPDATA\SynthesisTEMP - - ; Remove fusion plugins - RMDir /r "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\FusionRobotExporter" - RMDir /r "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis" - RMDir /r "$APPDATA\Autodesk\Autodesk Fusion 360\API\AddIns\FusionExporter" - RMDir /r "$APPDATA\Autodesk\ApplicationPlugins\FusionRobotExporter.bundle" - RMDir /r "$APPDATA\Autodesk\ApplicationPlugins\FusionSynth.bundle" - - ; Remove inventor plugins - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2022\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2021\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2020\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2019\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2018\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2017\Addins\Autodesk.InventorRobotExporter.Inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\ApplicationPlugins\Autodesk.InventorRobotExporter.Inventor.addin" - - ; Remove deprecated bxd inventor plugins - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2020\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2019\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2019\Addins\autodesk.BxDFieldExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2018\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2018\Addins\autodesk.BxDFieldExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2017\Addins\autodesk.BxDRobotExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\Inventor 2017\Addins\autodesk.BxDFieldExporter.inventor.addin" - Delete /REBOOTOK "$APPDATA\Autodesk\ApplicationPlugins\Autodesk.BxDRobotExporter.Inventor.addin" - RMDir /REBOOTOK "$APPDATA\Autodesk\ApplicationPlugins\BxDRobotExporter" - - ; Remove excess shortcuts - Delete "$SMPROGRAMS\Synthesis.lnk" - Delete "$DESKTOP\Synthesis.lnk" - Delete "$SMPROGRAMS\BXD Synthesis.lnk" - Delete "$DESKTOP\BXD Synthesis.lnk" - Delete "$SMPROGRAMS\Autodesk Synthesis.lnk" - Delete "$DESKTOP\Autodesk Synthesis.lnk" - Delete "$DESKTOP\FieldExporter.lnk" - - /* - ; Execute QEMU uninstaller - IfFileExists "$PROGRAMFILES64\qemu" file_found uninstall_complete - - file_found: - MessageBox MB_YESNO "Would you like to uninstall QEMU as well?" IDNO uninstall_complete - Exec '"$PROGRAMFILES64\qemu\qemu-uninstall.exe"' - Quit - - uninstall_complete: - */ - -SectionEnd - -Function .OnInstSuccess - Exec "$INSTDIR\Synthesis.exe" -FunctionEnd diff --git a/installer/Windows/README.md b/installer/Windows/README.md deleted file mode 100644 index 21ea8340c9..0000000000 --- a/installer/Windows/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# logoNullsoft Scriptable Install System - -For installation on Windows Operating Systems, we use our own custom written NSIS installer in order to extract all the necessary files to their proper locations on the system. - -- [MainInstaller(x64)](https://github.com/Autodesk/synthesis/blob/prod/installer/Windows/MainInstaller.nsi) - (Used for the full installation of Synthesis, only compatible on 64 bit operating systems.) -- [EngInstaller(x86)](https://github.com/Autodesk/synthesis/blob/prod/installer/Windows/EngInstaller(x86).nsi) - (Used only for installation on 32 bit operating systems and extracts just the Unity Engine.) - -### Compiling NSIS: -In order to compile the NSIS configuration properly, you must compile all of the individual components of Synthesis pertaining to the particular script you are trying to compile. Then the compiled components must be stored in the same directory as the NSIS script, in order for them to be packaged during NSIS compilation. For details on this process, feel free to contact matthew.moradi@autodesk.com - -### NSIS FAQ: - -Q: Will I need admin privileges to run the installer? - -A: Yup. - -Q: If I download an updated Synthesis installer, will running it replace all of my custom robot export files? - -A: No, _reinstalling_ Synthesis will only replace all of the application components, but your custom robots will be saved. - -Q: Is it possible to accidently install multiple versions of Synthesis? - -A: It shouldn't be. The installer will always replace any existing Synthesis installations on your system. diff --git a/installer/Windows/W21_SYN_sidebar.bmp b/installer/Windows/W21_SYN_sidebar.bmp deleted file mode 100644 index 60c007ab76e40e022f2eb73aa36a4fdd86a7bbbd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 152310 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 diff --git a/installer/Windows/orange-install-nsis.ico b/installer/Windows/orange-install-nsis.ico deleted file mode 100644 index ef3975f56c9bd8c1a06cef83c67c535010a65ee8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25214 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 KKeFO?tN#O)lB@v$ diff --git a/installer/Windows/orange-r.bmp b/installer/Windows/orange-r.bmp deleted file mode 100644 index c74fbdd511d240983ed66bb3e37aec500df99f84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9744 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 j4Y6aeH~t00000NkvXXu0mjfPEVab diff --git a/installer/exporter/.gitignore b/installer/exporter/.gitignore deleted file mode 100644 index 290dfd77ff..0000000000 --- a/installer/exporter/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/Synthesis -/*.zip diff --git a/installer/exporter/README.md b/installer/exporter/README.md deleted file mode 100644 index 631eb3527b..0000000000 --- a/installer/exporter/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Synthesis Exporter Installer - -## Creating the installer -### Windows -1. Run `setup.bat`. This will copy the Synthesis exporter into the current directory. -2. Zip together the `install.bat` script and the `Synthesis` directory. - -### MacOS -1. Run `create.sh`. This will copy the Synthesis exporter into the current directory and create a zip file with the necessary files. - -## Using the installer -1. Download the zip file. -2. Unzip it anywhere (likely your Downloads folder). -3. Run the `install.bat` (`install.sh` for MacOS) script. diff --git a/installer/exporter/create.sh b/installer/exporter/create.sh deleted file mode 100755 index 22efb4ac4b..0000000000 --- a/installer/exporter/create.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -if test -d ./Synthesis; then - rm -rf ./Synthesis -fi - -cp -r ../../exporter/SynthesisFusionAddin/ ./Synthesis -echo "Copied over Synthesis exporter!" - -zip -r SynthesisExporter Synthesis/ install.sh -echo "Created zip installer!" diff --git a/installer/exporter/install.bat b/installer/exporter/install.bat deleted file mode 100644 index 293bac431a..0000000000 --- a/installer/exporter/install.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off - -if exist "%AppData%\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis\" ( - echo "Removing existing Synthesis exporter..." - rmdir "%AppData%\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis\" /Q/S -) - -echo "Copying to %AppData%\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis..." -xcopy Synthesis "%AppData%\Autodesk\Autodesk Fusion 360\API\AddIns\Synthesis\" /E -echo "Synthesis Exporter Successfully Installed!" diff --git a/installer/exporter/install.sh b/installer/exporter/install.sh deleted file mode 100755 index 4ab0801918..0000000000 --- a/installer/exporter/install.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -if test -d ~/Library/Application\ Support/Autodesk/Autodesk\ Fusion\ 360/API/AddIns/SynthesisFusionAddin; then - echo "Removing existing Synthesis exporter..." - rm -rf ~/Library/Application\ Support/Autodesk/Autodesk\ Fusion\ 360/API/AddIns/SynthesisFusionAddin -fi - -cp -r Synthesis/ ~/Library/Application\ Support/Autodesk/Autodesk\ Fusion\ 360/API/AddIns/SynthesisFusionAddin - -echo "Synthesis successfully copied!" diff --git a/installer/exporter/setup.bat b/installer/exporter/setup.bat deleted file mode 100644 index a6ec90bb68..0000000000 --- a/installer/exporter/setup.bat +++ /dev/null @@ -1,9 +0,0 @@ -@echo off - -if exist Synthesis\ ( - rmdir Synthesis /Q/S - echo Removed .\Synthesis -) - -xcopy ..\..\exporter\SynthesisFusionAddin Synthesis\ /E -echo Copied exporter into .\Synthesis diff --git a/tutorials/FusionExporter.md b/tutorials/FusionExporter.md index ed9d87e8ad..b2c1d3190e 100644 --- a/tutorials/FusionExporter.md +++ b/tutorials/FusionExporter.md @@ -15,11 +15,40 @@ The Synthesis Fusion 360 exporter is the tool used by both developers and users For information regarding the manual install process visit the [Synthesis Fusion 360 Exporter](https://github.com/Autodesk/synthesis/tree/prod/exporter) source code for more information. -### Getting Started - -After installing Synthesis, the exporter addin should automatically start up when you open Fusion 360 (given that it was selected during the Synthesis install process). Navigate to the Utilities tab and you should see a Synthesis button. - -![image_caption](img/fusion/exporter-button.png) +## Getting Started + +### Manual Install + +- Navigate to [`synthesis.autodesk.com/download`](https://synthesis.autodesk.com/download.html). +- Find the Exporter source code zip download. + - Note that the source code is platform agnostic, it will work for **both** `Windows` and `Mac`. +- Once the source code for the Exporter is installed unzip the folder. +- Next, if not done already, install `Autodesk Fusion`. +- Once Fusion is open navigate to the `Utilities Toolbar`. +![image_caption](img/fusion/fusion-empty.png) +- Click on `Scripts and Add-ins` from the toolbar. +![image_caption](img/fusion/fusion-addins-highlight.png) +- Navigate to `Add-ins` and select the green plus icon. +![image_caption](img/fusion/fusion-addins-panel.png) +- Now navigate to wherever you extracted the original `.zip` source code file you downloaded. + - Make sure to select the folder that contains the `Synthesis.py` file, this is the entry point to the Exporter. +![image_caption](img/fusion/fusion-add-addin.png) +- Once the extension is added you should be able to see it under `My Add-Ins`. +- Select `Synthesis` from the `My Add-Ins` drop down and click `Run` in the bottom right. +![image_caption](img/fusion/fusion-addin-synthesis.png) +- The first time you run the extension it may prompt you to restart Fusion, this is totally normal. +- Once you restart Fusion the extension will run on startup, you will be able to find it on the right side of the toolbar +under the `Utilities` tab. +![image_caption](img/fusion/fusion-utilities-with-synthesis.png) + +Thanks for installing the Synthesis Fusion Exporter! For any additional help visit our [Synthesis Community Discord Server](https://www.discord.gg/hHcF9AVgZA) where you can talk directly to our developers. + +### Using an Installer + +Our automatic installer is still in development, visit the [Synthesis Discord Server](https://www.discord.gg/hHcF9AVgZA) for updates and any manual installing help. + + +### Launching the Exporter After clicking the button, a panel will open up. This is the exporter. In this panel, you can provide us with most of the extra data we need to properly simulate your robot or field in Synthesis. diff --git a/tutorials/img/fusion/fusion-add-addin.png b/tutorials/img/fusion/fusion-add-addin.png new file mode 100644 index 0000000000000000000000000000000000000000..bd35ee0ce2616abf9b8869a8297a4b40945afb10 GIT binary patch literal 1788931 zcmeFZcTg1F);>y}%;WXU<_Ff)J(k`V+XgCxm0XUUQ!=gbg? zI1Df_-1a@^JKuNjt$IDbs#A6EAKz3>chBCvyLa!s*4pb?>*)zmS5+V;q$R|_z#xA4 zLhdyN1}P~92H|b|TR@4iaV`!9#%*b9Sy}a$va-zTE{+z~cIFruFGAw9@!r1ayO*XH z6D@6piIe?d5$En>%hdSK`tM>G-aoLMm7|#?A3y!5x`WA* zf%)D4*8J0Xzq4dXClUT!yt*~ z=wt#R4b*)PiesE-7~@-{zOW6IO_`KiT~cfZaav;Iwr4PEja5&!!yp>BC#{RT8YU0Y;NA zF{}iU33FMu=Kbys(2N93f9^RV`8bWSDsAi__cc&$m=veteh?>4>Ld1dJu*c^M-Lzn z>7pMNVO}obKd5xm?_2OuUOkO`yQ#gi8^FSrmOlbb?Xu)N<(v3cR44c8F=;XtA=v|F zt&h|9L2SYr#sVemVU&Dk592#utce7R2JzlLj@>o=NVD2T{Sj2}T81^};>+!dF z_K%VdKZl89j&n)HVN~55-Kzw+PBK3cN4HP8aO395|0>ibG27^CUb%C}i&moMu?;32{7g}gdlvx45A%84wuhpQywR!@7sZ>6+?9fjy175sj{BbOr}S0Y{^FH_?%CANtBmrv(V&r z5;a+41rL_2^}A+96^P-7n)aFoL7Lh1Xg<*K+p*x2L0b653}`oa@8libWhHA3zWArq zE>#m)dDdI(3{&p64<{oTVpmrt5OOsQK}}t~t9=1tfzWe0pNe0~o8ptAXJU}gNXo;! zmrJAmLOOy5T)hJv83HR*0cw^+^>?ziR3lY~FkZCs9#2grTg`Z4k6Pah4c=g5l*YHW zw?AB}kG_X-*4VzkbedXe(WVy6j{!@nBXiD7>BynN@>BZWo|=mn?oU$|bmELv(nfwB zh(U_=K!*OsEpp>i9jDZ=c8w?G1K6w?x4H0a+isQ7*kL!e89HGI`e!Y(h+^{u_AiUo z5oxQ5cwt_)iyzX+kdi%88&ZEVn~sVm=G9kl%ekX7-4m>9MTV z1G?zq7eW`vLPR0|w{=e26;W{Z6qM-}(MZ7x z-%?EmgF8b+iSyRZoN)2hjzp=_1Bbg4mgx=+yd)QcjJlXsug^uW;@(M@J@g`-{=o9= zkugW+2X)nH8Bt|XV&rR)6^73*#_#LiU060$eq=#zNt!?^Lbm&f?Ne%JPbVf>NVY=7 zL+{TIUedqD%>Pi8Ruw${tl`MqaVl{OaanP= zKV9Rd9v{4s%DYm($O_F3EhbcA)s+8Mp<$l)lOS%6!|}UD0qaCU(lhQ1o*b<=YOk2o zLEnFAFlfvaNK8B}pe#TXXloc1e4gMM|7t@%5jy^J;!c6V>)khOg?a^*<4<=gCpgEG z#~lkutVRU8t>kRht+__0i=7R#6k@Zj^Jy~r#`H7lRZhNan$kWO$SSwepY@ydn6=ie zz4xkLF|0VQSTna;yHK{!Nbk*@j<}3(lE=q0zI`4B<(}8U#j%Bz$}s;S;#9SVw-uf! z94c@zz7k*k`3c$Z&>if4(U8;N-Z143+8#w3@ZMja@t!a8NfX-G-PO{lI zmF#Nxd6`5!8e|-6S~eGN>Q}HW?aRW(l-~J z368jy#bXG55^NHDD>xx*P$4hekneJD%Q2ECvOaQ-;UIF9o}53|OZnvMlxmHtejZ_- zt!cg~cFz#aoak9xWvalEckBr_RgzL&|FZ6dV7ly%+s8`4+#u7$cMm!1haTRjlXnuc2V916ZI)COI)vVksYN*%T(6qg1y{OmJ z=%?%_Dy1nEPkOyY(KYIEBhw?qnNdINIy#L9 zSFF5VoEIGW-o1DC$ETndq zU{tkSaEB)MCYBZ<8SeW+nPMy8Z_w4Boh8|$FDqks9__Lb#n8QZ@tEMT`0HEUc;Q9@ zE#ecb)hvi^oI--JM=9ywL42O83=8*?pk#i{!#opdK{do%E32Tf; z1;YCE26s1nkF<}|&ArTv2DbW6f9l80ByYrOrcJQDBM``a9D<~iCsqnrJ`v%o zs7~`re8$nk>(rbM)6Jw`seNgrMnOY{iO0L)5P5hmHv?akNu8ChdSl9 zIo&_M#YJgKb@&0}Y<=5vDRpMuUrSFiYdG?F*S zk=a=NiKO$UE44db&}f`-a-G>!)5FnouqU3?H8R)HY>B8VcA6X8Czu;Xcb^yg1fwG> z7>qm#Jw5hAXC}1vwG5i)-XSY!tDe6eYh-gD-e^Yq(%J6Y)=qAZ$L(XISWsyPYID#v z>8*gt_u=oWgSqZ#GUhVy6f5lHO(8|hgxrK4iFgQq6s#8Z6=srJbuxp!ip`?zsfywj zSAE*DeEv@9Reu}7Xh=(JA{$e!s!g*m$M$%2%~nn1SZ3aEDuIOS4%dlY|I<=eg<;zJm7czg{y(_7A46iA5P5)w>)Q+)M~eO;7O{M%68P zKSXyIC+CYaIvhD5=7*ZEcT`tWhdp7759c|XOChaS0qyS#Kz8WwyDs};XCpVpsH|xP zPbOEURfj#5OApI08|Ao^vY7wCz_ z^zo%@#VeabM2v`!qeqK7WKN7!=y2I!v2pl73A6>CMtnXx+RN)y8MkxNj|W0b8`~o<@NORpve0ctGCYHN1R0Pk8@zZJ?>d?@}>!YcF#<9XV@z zV9bDfNbw7a@JsyF;Qv+i-?#i9P2c`^Q$aqy|69}lQT6|9s_AO(BI{@m+|*6#zbEXU z_x+!h|7mv-+2=}n;-ZT14A0)rJRh07v|oAwUho77h2F!WpD zn;$_F~Qir_1)kNucW0di)p9AD36H1yBR5t7F7q~;mTm_J`T|`wAKg!l&Lt`ISFN8ucfNjNGz6(G zAioUHtF~!O@mPg)_&r5U6sT?qq)is=yUd25h)*{5vmp!fWn~2MeIJ*OjL1d} z73@*{)UV}9#8lUbaeDb>R8PUR=BoSARqy1F)(#I9%^!8oZj61Kw3~R+NZf%6ziCFb z-{o7-btztSR%w^h;s@8~{YQTFbF$4|&>>I>D3!$T=J1*vn%8-<;``8$YB-@$a(qm1 zuQZ^ohPDrYBppy!m(9Y0a{f@F@@b&#pRh#3XA$lWvv+Bx`hGhGM;`}!sDefo@dW7!tU2IvIgMK`# zrjC+KBbjqJh)Mq-b|l!!mo$2&{%oPF$oj3ewl?fw-&Q$kN&_kgsa`&iwz;p$oFRu* zpiy+2n0RJ+IfMB2?Q@XsWCwO8OSCL85>M% zkCoYLU||Xg+s_IvSxvusBT4GCJITF!aDW`65wMt!WDvJI*&M8KSP;{zFbf$>78%yn z)h(*7o*+?v|3**o3F$5RO(Ei$+dq8@((`?(m|xRH!u{b|Q+w{qx-|HM zR5j1Q7zmvLeK;fc13^*3WWdIu5B0m*(*HCZ>||(FfbhF%i9LU$4{8sBLeG&2>7erq z&Z~j^gZ?MO!|7L7eZM#P>FMVezUzY@S73SPFqOfc{;R8tAt>HwA`tZCq_%`E>69 z5O%8-dWgdNy-_$%m0cn*PFn(zpzK;g$og7u{d_0lgy||A3T^hU8 zx^_S^xZ4%HOH4nYt|Ok$zQX?caehE@g~UUnuy3yKUrEuxjk;PLZKMn* zhvPF_CQndot@M?obkHnhC zhOOo0qdkYg!`Ua26sZR*^HXBnR4k+;Ln~3VZ$@!{&CS_@!C=T-9_ZQ{HJ&GBu2*As z)PE_X98n-U7#yaK3L;WIp}--P#=!g!ZQ!1mUF*Tr;KATJKNU@i+E&Y9M%3(7i-j8K zx@w`xdn_p_$?o!W`(mA@JaZP`8J$Ij&+hE+F?l*!tS9B|zX?TpLZ=|8TIk&7K;oJ} zV}1be2@J<^W*d0;Z^-(;eTwu2mg0kqbF@AWEEd z(mN?<|F9eF$4Sx|z%XB;P+)8j()FPF2EB9x`mGOY)EU{BHXj22tb+e`w=l5C+0>1U z97AtFP;?U*?wVd{ndaTiTU%Wnv7_OQ5&DQ!JqE*^<$Mi$BaSMuPj(G9|AjtRZOlte}}kR)WQ5sXWKLrY8R2lR&d ze6ICc57nz6WdewsRASqANu7ZI=%9GYF~|0u%VFJ|Io?>bp4qlq0FNyd;y)SuI$AoT zq-2PV=p5C0gNOQPbS~P59Z2@(KP1n8sm3E6={~G4RLkN^7m?tz%Jk}>pj)=Kw*65B z%mMzB=)Ux;kn5p?t4K9QQcK$2gZA71*;xqnKgM8pZlh{Fmq%rRE*~cInFa}oh@i^X z$??Dc8os$2p3`o<)TE=QuhQocdHg=-Kl<>bFR0YX0*o$mj4GfvNNDX-*UjGu$qx(D zoMO7xF&$VGqSi6DBkkn>V@8Bj`rT{XFHbVEvN{-{SI+4VGm4A%Q*p4MrwTWv*R?li zMr`i>Zjt{{3ELnTpPHI8y6Q5TPQSTGpWBi%i5 z{Xfw!<|jH>Gj@{=4J9)Vqni5p69-;k1^iaDT3){uB0#GJb=JNa{U;@v^t<#sFRy7p zZ}*VEZ0`S7=;vhkeyF*n8n$%5ia?9+q5sigjpdH+-`MM-GOCIK@0rFY@|iw|7uPOU zSdZl18-+uSMk~u;`IUo<^H5q85>xEteCeNfm_u){W{Ca2)3Aj4g*! z%9BMLX9IDG?2cBu;`_|tIZ?fq_1IWGxvTT_t{&UJSZ%GX%QyNHP}>Y!KCc^>FB3H& zVhVqhe-5&d6Vmj{@jR{*-&Uk=J?Q*B=xa0%*22Pq$NsF)?)G-g?v&$OD72Ny?2Tz` z^?+4*&7^R$kZpW*O^ue|xvnOBd%JvSNL8d|*CPwrKzxeO8@etncTO7ox@b~tA1m3i zJ2~M#(a`8yG$lFiQdl}XAlbvOvSyp=FqT(4yEhXH(=J?-7XO0~&qrVpDuTe5Zjde4 z&4F?}Cch`G5;_8}ivp6S$smgeS#d)&HTj9Qi~&fp8XaBZVu=pHeh@H8+yC;guN z;ek_XX%dM{@FwpQyZzbfi;a-9!~z&f4hkQEC@2JP&l1WHKebzIddjs}Ip(`@nMjED z;>K`Gj8L9HMXS`1;a=i{KPr+Yky)h@l@ z5{oB)&6^-1A||KR4_221CP7c$fq4?8jSf^ACtU|OhmDre1IRv{0<@XAAZfSfOhl=> zMKWMo8?!MZCe@eTJ*CP3iHg3v2HKSRV$0W}xEibrK?0UTTv>>nLj7G22o=w4PJe=Z zu9C^f-W4q!T~TIcyNJ}t!PPAwkua!-ZfBZVc-guGy3hUhp8EVgmQOOO1&q4t*?RN9 zP8)EOl-p+nECvqyk(Wk2kqnh@@>DlKn}Au`A5~x)?`&M1Z4F z12}bKf?p83Qwa#a?q-+k55tl(V|4JYyNbPlx?idPa2M6#UHEE8a``8v2ne}*ew@&1 zmskqD-mOPA!6b!tSN3LHJ>c^+38L?EtE-2q8XETYlhb+lmt4?Lodh{)*F@!1Vq)S3 z=5t0S{8qPZvat?Ya-E?xaa-6>iu;;vWlfDvZI1{)e}uZZS+>ItW75cZiMvs48k|S3 z!ENS4l$v9N=q~?|FDOAm8q&d2DAPSZ=w~icCn*{+$o~`?dowEpgOM?C8PvG_=n;9) zx%Bt?f^76(`~bN+KYDe$KY=%WM1;f0<7M3v1hwziI+m+a*~93|)#}G)nQJ4BkYgo{ z_PsjWb1Aa*=50tphfcg&20J=BLdF^w5j^P`lC7&eUq_P<#KpvpAEt_XmZ(kM)e47A zOB{8QOU*#6OpCTPB$s+8P9T!hI}Ht+rUN zb~7PwCp<>A>F@^;ZinyrO7OK31))~_+d8U}bTTIU*II0)nye)1{3JKGd$Yqosownq z4p=rMKVKn%S|!5Le0lQrj;rfvye|i=#htIRi}mY&B+(}O#rPX1JYC2kb$W@T{|kfa zB+b?NP>1QNWbcgUiJ|jhVi5wdP(jW%-@>UidyE&a_NjWY!D&9C6uhBmn>4vaPPZ$1Le6HFGp9`=sUr6jeEB6+!uh9GuiBccEKwqC zB7HM1coAiNan{t$-Wm(vf5aJI`t=EDiUIcz$c8uiHU}}6ii%1N7PMtpw&3$M(TJa1aG#jqOvJBIYdEefSnIN}ZS6w>= z@$(tkuA^J}c@~E@es7#0*=D;D)S! z_P<=!b-sFOqd@E zI>x!fBxMI1P9N6hiO)WzxM)qz<_zn?f?>g>8YGSqpY1B-o~3{-kN zr<+;LN56fX9G_lq@GzzIAi&YTQDk`%^Yb0c=Kz;mW-bm-bne^E(-tO+fmms_n6^Ag zqUw%{_4E$A3BW9bf50Twq%UrfvCZi>xaAicGz!8rh z#&K#N?^+CeH726s8910*Sg>!np-PB;Vt_doM#UQ`a^dS}b8x)T?*Rmn=%}d=37RQC zNP5EclL0W>+x#=Ic~NIXN>O)YZf*y&+{VdV|sAY^u{`jd-*_u}LJ}0c(cwC{vyp1H+V=KWbzox>kdX zC~>R!$mL7$5F0qsY&7ENeE(QXAryCN_pMQ#Q~PPR`@~e8d)kMSf@vwAhs{Iz>R+}D zklq`GA`^uw5o)GDJSFjQ{xep2)9Gek&-QckyzDDynuGF?xsfZcVNdYnk>Ok)-GPx9g)L_jh=LQ@sier{lEgB{X>KWA_sCB9DKkC2WTTw5 z+!N{j?JIow-70RKaky8D>!Th00^tDZ`YE-;&&`pt=Or=CEp+Y-UpPb@7f)aEyBP$7 zHwrCvPE!=X;Op-Owi`le2h&f!9);_nb|idG21R5A(0+^MrV8zzCXj~$f6zu|f9a1{ z4Z-QoB*^t25eJ0GVQrAh`X>^%bV=J9B0r{z`&1+FUzl}x(iAgri+SSX&o4X&Z9Q11 z7kTS66T@w6la53_i;FiF@g$cE$un{sQ*vJIa#N+0CZATq25MGhB){;JeMvu=As+vF zi-xMU$R48q(A3lV&Xr55FLPeyNdcXu!Dw*Ws@Bg`lC|VDha{5v;QO69=r~d7=eim< zx{Ng2``)$pVOteaVg5UHa(F}gp~(J8JHHLcot^uC>p;#kVez({eC{nhbgG_3MiE7GMH_2Ql7fzjyp(E zgBKdyr3hqzoPK~mmSMHQ8lk+*KEHZ~qwY&aY+Q?kSZV2=l?zxpuaytrK3rhf+mFD& z@Ol{Gj+L^r3ob(sVDJ;AWb?nd?qmita)_q&)%M#q4$ag7%G+04jQW}Lb7!KEo23xn z-Oy|0BUb;}nFn-WH5)6lBybrKe`NBK=~?Q!M*78swh{N+>#eM)OaDEu4X-Gb`R76~ z--;W>l6|k+it~rFwxv0YrUsS=K2wd)apTe@eJ70s#Fi8(S))dTvJP@h0uWtib7jWw z)h07-4UlKDqt1TEeQX`y(zQ-f1zy2kmk(bYc929VF1|jL`S#JJgo%|#)aTTyN_(X! zhG45f=~4KrV!*$gTj>ZU^86HkdJxh=6hj~Z5a~9>Ym%I8jaXl*n?%=9pbSH61)}~4 zOuukuP_dcK4^Hb}Sp>Uy-a(vZgfTXZ(%s>Q=axbj`n2Ng7)hbU_RR!Bakz z$XX?qclDpEc>ou=!*W4aL|~mU=&AF7u-)|55*Fzt?Qqo`xOWQbGNM&v=h=?XN!XGG zH(>ZlBJUy&dJ3fdYNdRA-_q(%UgyYRU)u4!UfP*IcN@F;>MPs*9GT(Ic)K9C_rM=% zD}wwG)lawl3#SgG$V7@fNPAb;T(r`N*roSJ-RaRDv^4#D z;2+?t#!NF(gnBbq>sXs$=+n2PK$QMA1d~FJKEWn1JQmfxA9Nua;2$VULNEFba8it2 z`|=kJOWWY*E!O4Is2Jk;#b{;b48ASHood^u?%4^gqPNET`|z}t{cqA6qEBVs8rc=R z+qpP_!9?N|nKLpxaI<gmI;BEtj8}+#EP8J)Fb`$zf z&aFa^W7EG()C3MEaOuQ2iqmwaEUBA3?|Io%IPY?G?kw(oY}V3HQVTe^k%VbX2jv9B z+P*DpC?+*5Oxy-eZ~sLzkk)8@9t*|F;HfEYX3svie%;7qsgcnM4%P$&raWFRI7)!S zQEs$r_#pGcH0M1#>*M)v&=Cwoo4juA7mD;x1j)wX*r(u6s2Aj+LBqEjFUp2gG<&)> zP+nfGUAS$H%wWmvZz2){cD9upBoa?5fPAKu`%V>|Q3ChZ*4_xm5oFE>3{<>?MFx$%@sUfytu2MM=9G`_+L0O0?~Fk4SnT*J$P zHd9GG~KS;;UB-1@7f;|a(N(|6j(z) z&8gkmxn)_MvzC#8SicyVp58jpV~hHHM&Ot|<(JQF5y1;x(x%L+09aLTP#a%Fmi)$e z^y{Kab$?2D+kLtH(EECW&RJk^a1htgt@O-K?JQk_Z30%_VL)?n$ejS_(* zg&SjN4d`M$)_AnwdMNR`tMvnN{4JDR5BC}6rxGPfR`jQ^$$sT2g>qZ99*fB$9dzB} zBSk-h9f=WS#F=J(x6&HJfu~*d+8stk-EVR@nJhQNguDMnlq8ODE6uaJS*3k}u9>0C z(3+^Eh0({_&ZcUTrWl@E24r6_(5Zr$EiX3+M;czWAM7AKI$H{(okecIpIV4M95{o}NhyHJ|T)O-=T{@O zi&{)nxG0CkrWuO2#r|gizPrGftW(8*tG(P3IJEYyO>2Qp!>MpFx@5Q@W3#RkbF?Ah zJH|vEJEk=Lj@G-P?vaIs)Y5Ov<`J(j3lyS5Mgm-uSu6GteJWc(ANGQZ*XC5$di2WK`Eseultf1#Pu4R=T&o) z<}WgQdMQ7zFa5wueG;-BS0rQK!zwk@75J6XsRW=J(o6b`ml!tF9>ABf8Oa_}*s)hQ zEw=>(UISRi=#+j>>~ht0Z+9Q>5SO}dIaAq*e|(9 zq%R(Q9D$8ol&&*!5JpcqhlNEZMj9N?jv>F2LAUA(Dds$j))_VoeJA{Sw8Y`>K39be z=cxxj{sY9oXMVzn$z%)XKZm&qK$^^l7bR<~R=#}}VX4Ct2>S9^8md9D5AmJk4Q|!w zGAHkCxOx0`k&1~PM0QoOaEXW7U;P+XW8NQMHdU9)+uGE#kitl_^uh&uDl70b>=eXL zG1b^z3j6wFdO@Qd#pCO*6u6A8v%kK650{B?HE(|P(0{{8K+`9W7!UYueZLwQVu^GNyR<1N350!^uR zdI59Jua5n3iSDgyKNj^nSZv01?2W|kk6qJn+8at0CnDq6Du&rSpq6~qGeYo<>cZbV=1jGAcx(v#%tXXxzfkFETrnBt$`P^pYtLU zhDrhaT-LB9NJC%JdFy_CTZuvARXv@@ZJBLjoiV_xoaqXqIw^F^y3T{_{EtOA1 ziu=U_co2p2&{${5`qJ>>_?Dp$=l1T69`q*5#_=cgUZ>34EKP&4xYOK4!=k^ zzb#xZb!|1a&NG1~2t^7puI6?rNz?07?U>gdZZ3G#mJAUqiySRUxpbx#9N*doRw+l@ zsUn2TO$X1i;dQbS^Kwtj))E`_NW&Zjt)A)(mV?4p<7ZR^n)|WC+-KVGgO5~cNRhhA zM6sLY@F3W&ZH4GPXs5H8(}fMhqEn?(ic>E2#5MhB>vL`D(wm3kha!K_Elom}rKY0g zMbZ0pSpJmw^osP^MeY-~XbL0D-a*Q7u=+&AchPBB0`9!rJimd@_TWn9huT>-Y;mRi z^s6RktblduaRtC-nR!Ot0fZH=>FQ!-3XB_PtsESL5jpP=CXN+qk4W)RLDp~H#M&>l zNOHwXsxY>edlZ>t1(;yYW_S(az`HgUDZ#mB?jndd4l4V`gVCp>EM5vHYWfu z-&~WxPS;zS;AYzfpbs8RJhuLLAcX&!v&`g{sVl zO+@}k%uBcoWkN_>PaPYR+bhwl7t}3`y{{^;m_k8hYzNrO3q~{Z`_&GUl5GM-7}r3y zD;>R^Ua~20nY>rHAWQ4+;3um{pK*HY;RW_{1XNtj|3FQsV@>vH!Vz;;FC>BG{#sDF zrayWg@O3Txk&wcv#D1n_vM0UIFR=Aj10P)5;_rY!Pc$PHwR+mP`_=5X?nfvx6?{rE zz4!Og!hj}!EH+A~{CDGZ%E`jqt<-pHwmERU{rP$UCQ=>y=<-=Ewg74V(SSwuRv>$@ zZ>S#QIDAKDxbHeoE!}}|Pc7c8Su{Y}MbBmFb$=8HU8hEvQTtH_b<3~wW z!}l~7mweCm_?jQfkqodu4xn3q;os8m^0f5cf{jSplW*TiYB|;zoqin(cDJCV8l6^5 zM)5v=Ywk`#tm#$tCe3%yxcoW~a+R;ZIlmtfUh*!Sr%<}vobqs+N>Ef=a7ad^#TBkX zJy#P6A+;Hhd4>50N&3(R`0ii~PZPw~u={WIC}U;1ou61)ZEcNa<%)a@jwyPhiuDhy z@s=qRaL&Faa`j33;_ zWW`p;s8v)`i@KrTk(D{mQFL{!lmt>B7lVDnaZ99w1F7Ou07NG(rtuZ_zEZ?}$)?8< zFU_oaC*yKse6-n$VG4*x&H@Rl*aZr%9+}HN478K#PVs4@$3Dlj+DnpnE}hq9@Sky< z9*4{Af$qO)QGIs{h)sB`s(POtM<5i#%X|MvKD)Fq(IifPRo9e*zkqzknHhuMZSiAvR+*i+M=HNSjr)SC# zVntgTgNI)NJ3BvpCnF@kX6pj6FBb3%#J)jC&pFIzeZMsW%jp)7L(k2UjBaIo@J zQ;%gvGgVa5eRlHwqFdtKht=EH2up~~ftEEM`?^dSkgbu-vRSoQ$Dm0xZ}aKvcDmuU zjO}%54uVfSThrX^=Q;r;KK%`gNedGbaYZI`HTD9yr-fgDU>o982a(v%-M6N#f)yZS zJ2_op-hXVcROj6B#ZQu_j|Kav2H*quMA=T2T%5=UG&}2*y$jeThI8bQ;aknQ4og}p zDwBqGXP0-$6L3>pF;b=00-6w8i`QUlHJps~ul@F9+Z?fhBH%J-2-ryj44tLv-ErV^Q(R ze#}({N~MY|riBci)X?LUQK2l?pg+^rG&nO*QHeuCL;C!45LV)JDfihDch8wX+_3rg zl(Ilf&0%tPpNi+Zf%AUoPvp_Qu9j}VXZb<)1b;R93~wZe3FCmna+lxZ=ymX*n+VGN zeSOlm;HRe}y#Tf1`W#4pZ1u$LEkzBG9G%_T3pa>YKMn;4MU^A?c5{8aY&W??TFm}he?tDoz5=h*J6x)llDEr=Zj^=vQjfL$Hn`z zAV%AAK8ee%td_`~g5Ebs0oixOi@GKL5Cjt&!H8Zxr5jzS5}6|&3FGZB$Yt-tsP$bI za(sYZ*WX%g*uIs?yZ^<#Z=u$1CWfSf$EvLN8UKr>c^s@AU;#b45C9S@2Q&@dyT!Fv z<;49--2^zNHE_`pBgPf`4c!|cczeM@WDI5fI%WChr4n=W?Gf#FCxB}Qj`7(q?8H&D z8(CT^l{XqM9eNt4dy5ZsiyXQwy6Y7j<3_X#*u5$!VE1fF_f24@0NA+prjs(Qh;pKj z=~MZl@eYVf8;iIO%T1%NQ78RLG@O97lOygsNE3bhqI|bG=72uJ`rA-Wh%$5ZRmrF< zdjsT0b$&~n6|3su-4@H(sk9alkj(jd6B!qxk7!b0|D8{Q8fmU+4*nJ6NY6>K6|QQh z_@g0Yf3!@FUq@F`e=ZYdLOC{R&?~T6Cdn`-Q{8;YgV;s+|BS-TzRCddVGm_zRN$(THU0{3v8da-9r$ zaC~pNj10$W{Id0=zjvCu?sG%-En1sNy33Gs$)+D7Eg;goQkS5a4Y23=L5cInxg zhIGSX-QvQ6WJM)P@8vy;uO;QIOJ1#jg?)2a1b@sCec2g8esR58Zfnl}UE9%Ad-crZ zB{_vv={wtMajpDE%R)46g)Qs(YdLFo@%nn7V`>N0GMmir??)r=% zzPZZvs17rB!Xjr?0QPJ&3UKeL`>4RY+df1~+zlRMWcU<~ixcYgOHT{Leqdi8E^iNlr z(nhiQ8oZPH>$m)<2GaV8>$hJ}wA|G?-(n#i$6DX1phg`|mYj zcJA}Rza7zxMn&w? zp1`{Xkl01B66c=wysk5XT}Gd+`%Hq2@nN_jLH0ShedStWK|nG~uiWILnqL{fgRkgG z+p7iMU|^ubE$+r_zT|S2_RAm6Mf6GAm!r@WDsMVb3QWcQ?caG~7?1QJjVW>FIBD5ojSlAOjKaPV{ zV6=@$yZRmQ%?IM$=O>iOBunzM!jp(f=neYS(&Mh#aWPg_ul3IgR~8JG^$EWFkK03s zW_0y^6|n>s52kH;ib+a^-I~@uP%EZnn2GHaDLM5f76;3xZT(O$*#78S*W~ob z*83yM?1N*>x?o*5S18M}0n}ECg1esn79=hZVSv*F&{SqV5+%$>j8^|9U8q+VTNF9;UiRD(Z25dYoD0aH^(s8i&ukckgzIpdEp&)E_`!Im8}Ozk z?EH>>cMtVytd1UtwA@NimcEV*xAn4`MuK8qv_&X|+^GV8=pS5SY>L6_EiGN*(RW*O z6^Xaqfca$oJqu$#5337{8xe2S+}^0+4J5M{BwLAalWQH99DIB)`(oMl&yUt{=T>ni zws&{?OBHYR>`J9$X*D)99J~KePLY=0{Ym^nYt^ZFTSDg!gV_2~MyftjJf-x~+#KCK z?x$Mz?4GxY?s1g?s8e)l5=d=;N3Pxykmjl+Z$^ET@@4<+?V*6=h#e51%UbL$$?%E* z6|=k_=X}YWf#Dj#DOKZabV*OEr@zXAF>BP`t;o!$HJZGQ$UTn!Bnilyecw6YHOLR= z*YSc4BVGb&6J?DNPpg~F!Q``FO1vNCL4cTgiTh1QO9&f-X9a<`(|jh<4kireXy3Pp zoL+y{`l4Zk?4SZHWMPE+)#*Dq`tQZ5r`I_L34TRXqoJncCom#Rdb zC@r0?v(L%b2LPe!w-s9#xT%dth%8fWRV*xq4!|f4tRB?cG#3Ea)?yy3!Fercf2K08 z%?1b^3Ii-nFcB`{8rK^(REL2q{yOoxyhROT$MFb#ZL|8%9 zQGw&`aF$r&E&7q8FXsIC+L-5JYR_KYin49?LtL0;`ES+rYGa}*9PI4EMJ{T;_Po6N ztjt?~8z69VQ9G8S*Dm|KacTQ`C~${%X~UI{r#t6D2Y01TV%4O!hqKC>@4*_oUkz3l zmv2$0NP&F-Zz8fIoJL^&`cN|~X4`g8A(H+G^Xd|UFAuAZOde_!olz)X%g$yYGJjh z@EEDRN7|~zi*msz9%@7J`8ys+fqLTv{kCE!PQ3XJ8MgTjMo#UbGV@wTxk4ZAFh+h+ z_nmCIB|?!FYo7Y{=x3+bhZS&40#@O}m)H(beRx}qLNv%Gl_eFCmN#s6U7>MjZ8(t! z>o2Ezem;>Bp=I^#ZEz-TJc__?NbaWHe6jjbY&ZcyLY8v8X3Oh!o)-_P`uO*Z32AOz zit%D*W!w-B_SUyf)=#guA$jQR{Zcbr=aG7>LquN6H)a#u5Y8>#fV3mJs{R?!ct{Va zROKvZUM~mEbIxslGM@QjdCWn=%}KsDPR{&K>QaVtPM+I;F#UGY!)C;53Hu@@HYuiN z#xOfc-7l&7SS8AL%sFA*FogWsX!#OAMB2Gv+=;Z*4LfPRH&r8mOA7n}#XtTFprk%J zMJBy|-ju$?bzfj{mqkMM)!k4z*7k%LdGqaS#?M0u!rgZVtCEWKuscChD1aP z48h$-Wn6`u`#r!+5N%^68|mPjnxA{&Q{q1N%UtQ8?R==dmNw{5`v(-SVA@ zPgy8^2oG$s&N3wER&a4oMCplc0=HhnI1LZ?QJ+r4-%sc7TBlk9_A>9`G)Y{?XedDV zwQmo^^_){MZo;2OVo6F$p8e!3r4ap%NQHr5;=g6!C-BW08X8=D#AUg|Y{$gJ#P(kI zurlE2*AjHCt$gdcocDN;9|Px z1hI#!5OOb;WR|yYh&sI_{=ZoJ>bR)8ZG9O)Kva+r0SOTXB$bv{KsuCelx}H>0R|BS z6r`n*?(Vi|7`jWkk%pmudwkFN-E;2eqW9c$-oJcx7-sf&@3q%@o@YI4eC#Q5oJx=( zd29Lehc{gXodaoqzM-(kwIKBJaraMg($dntgI-p1Qs?uJ0R34F#rEwKN`X1lo9&O)aD z>?cnb0d0hTee#Fc$S42Ovi{-4@D+@e)m0Yz`L>8Srh&3=lmFVH{voE^H~aNWmseKW zu3f(_KT5rH>t9;eKfO4cu*Hx)vdV5=x+`Ku^M%bHZ)AHMrme?P9N z*Sv%NqsKLg4}=)y#peI$hLOej6=Imw^5?Xp3D95Q|JT>~5By27>Q@99$WgNgp+91? zlkz|CrvLRs?$e=yfalqwXV>2TzPvx$1ML6ke$3R?2X+9FBV`t9`3(&X+jS#sppxfb=9`4#7GVXuzq=m6|_xBq{ zqhnp41MUO#u=`j45cmERS^nF~HT|+0h(0|&SiF4sa?=pR?e_)pzZe@DQ*jS5AjkXb z?Wiz`)W7vFe*Hh}<97;4Sy`>@Sl4g20Q4%j0CWHGe{y-?QY8QNY(MUPzJKYjeV-oc zbruqb>d7rFRR?`sM(oj9oB~aMX45WSz59@JmHp)QN)5j#1RnCb`EMPKk|O?)1w`Uz7#8)v^ZC8J zLa`kj94ftf$9htYLNm_*!k!~9qG;?)s46Fg5fdBh@@UZqr|&)ld)y3-4y$rL^zt}a zN97zEY0lc%#P>VZXgmXpmNDlXYhhQ%ml2BD4NXm;Ej|3pb3xQ;Z9Jt&Jzf|p=j^O z>LQ}mS6IfmV9CCN&G19U`26g&3V5s#P=yWrb>aXHhQu@mdcuQdzj>uUgCZB7(|0n{ zbFEVN(b7^I(s|ch{6a1E($q}`Kx(q#u^Z^z!=o}8%!=U)yMjmjjZHZ~RDxc_}g{`?95=Wlo4AA-IiHvk&y6e!@K zSFhsLsOe6<7PlE+pi4NIhR@%<^AhjM_!43`f}E5ol)o|PwveHl-kI${qNbFJ$Cfax ziJdg+0rk^TsZ=(#P92~XVA^kBa?*Nrr~vgBvJuZm zs~uOxW<MNE>w$PVog=mNFp8zmmp?R z9S)_F0oFc~_E6ZyZbNr`k+#3Wew$2<4$a4}bmc)ddu*qR=QGT!0UNoTmjg%b29@<3 z>^s#~$PjB|6>KE+*yfX8VWZQ13c`XLb7(Xm@|`W%oHdnOjyxuSAMXmx7~FlyhRtvz zz@xht@6-3TWbwc?9?u;ftI<-k@7z;8j2c=!KmgTUj-o$|yW(656yG6l zZ_KX(Q-cKWJ>7bx5GEKDjXEI zx%r(tEnDOQiRsi2k>^ON@7yY7)~&_!`~5YZ<%Z;rtO{QVoLdK`;O?ls36Lhc`2=h_ ziFIxUj@Dq0-IP%Hj;*4*CcnWztPrfa?s#GoC2mjS;>a2!I@FVsBKshwU%H^aIJP9% zRIy;qJJod1I*beVCsEw?vhmo7HE>F$g(RMzEH>xz^bku@-|zWS`sW_G3uDVTIchCy zF@C{>RERS_ycjCPNUHvFjbKP)W22Yg!MMHC+VF$+c{^Z<;{L)XO(qICJO=PBbyT-1 zTfxSLb&dN`G0LSZAMhUsJz9%Amc!F}-a2Y(Cwa@WTk(v?;|hT47kHd_F#Y*;y1jvb zINBl~6>y(36}q5IeEH+C+tl%-2@4C0(Yj}aap*rwC6~I(yAhnKvLh^v6BxqIou3|02kEWr9?fDsUi^6?$stj*mw`HZ$KJaxoa|aP~>dC*jRHkZH=ltZp<3dyMEsNB&tCBQ7gS79> zK6Bb!({Q)WJ^ivj%bpzAui`0mqel5XL2py(-7$x&6A8WtRWTr`K)CXh;t z_5;fghCdDZumj3K8;o*_u5h_f)raEbKb#;|xdw$l4C8Q4&3)f;dSImGYxenN9OwycDyFSd<^^V>5 zJ*+%aPHb@9%Z44)Ov%9>x*uKzHWFutczAfnI|inv43=bE14lnU<9T$0q=dtKOj-BA z+VQ{L*E{fu6RTm&z*;aI*4UeiTFD*U1Q-8D&yP(T$4NQ@;L#|BW%x${z$6sn-JZtNm zB;SwsJk1Cv8?Ztyv7OOdI4lmU8*S{q>okk5yb1vd&q$Z`vLU-PR-dBFw`|ymyD3(D zF0E+};jSyBEvqNm<`xARi4&leJZmx-i72MBXeg}fKT~pqNmAlBMYS5P)osEGmaR6! z+c3l7Qo^#!R4s~G591SQJLTW$zMZ8N3&&qu4v z8$~wmv*fBPM&nec!xC1lp>z@4p3N&vW{pGj`&?Aa`EzIuhk*^I0OtnYTdPwd1(g-j z0WW4eHWod)u>6?n)opbHu(SaE3Y)u2wPmH&JE@$RLI3>fA%= zF}x;k=_3qfMh4`vk+-Wu#od3trjnJz{^XPZ+t&aVCF(s+za(NpttQFlnTT~68x zuVpOay=04|EcZlCom+en3L?M;-f2N29)t7OO%r3ws&SGBT;gpt#O>O$pIY4mPipiU z+HhGu$!MZm=nQW6s(6V<0nJ_DiW;N>(%e1c;30 zoM<+sq>PMa6DnyBlTfiF*~ZM6P~4Mwy|Xk0_M`#i^O>E*b9d$0E<`@C`{;6(4*s7L z^9MkmQ@S1=6U-+?>J|i9e zFaXYaUKgR1RjU?7cKd7m;L_RB);pils{jeR_w$PK@|aL`?Tt#yfD|Ux$J<_o>zn5r zn=(KO=azWlDfH0jb*m>><>8(b3E2J0$tzR6y)U9zb&5A(n{MfiM_WX&1w8j@^mKzP z^#W9UV0WfljE7$>$*gNnQryxM`u-%zE|Xs`=UvH=H&XDE82kJ*Igb zmOdaj2Y?d^AtYC?VR9G!vG(rk>|(7fj}y^0kPW!o1y*jvC(wffax+8tV&v1DsoT*Z zQAenB>}Ho`(Hr|w?ewz{pZ;N0;}fI-)Fopr*J-qOm_hX zp?yDuX0PVw9#k2VSC%)B=2$H?1o89Vbsg* zR$^D_Dd-+}6+u=A2XJq0miypH-`a{OaQ_aF!7D-%wLe3G-M@jh34#Jp$XnNdYp?U; zgRcu+@fOh?{P0!Hf*q;Pd#$~onn^s{%|NC2t#V4{N0vi{I-BHzXLHnhr04UidQ$oM zBiqfwNB7#tJyf6PJ~81mvRuv$StM)9)>?MbY5oO9_R5FbkyES&rjE14Nv}rm*Yj)G zFb5Vj5nOG`b{4i_GrGMd4F)Q34|+Uz%R0^IMWXx<#vOI;(}rRZy;QDKWc2s)I5Y=k zQ}t<078>2PZage$8pYg}S&qab*)uF3HrTR^6UYQ=K>Al-?eXexd{JnzL}gx0{>xQ> z-=M{_UYz=6!bNPF&1w!;I0g+*2kT$eb8!(Y!_>(F52`hkPJ(#mPP=~z%FPFM z_?2qkN^qxx!wEPqpj^OEP3nvzXUd~Vhf8dpN9|c?duFpLUMB@7Z3Q3Gokd-Kydhb8 z&ojyJupihIjWi4G7kRG(7?*=i;}#|5{wfKpwy89G5(%c?cAgEay+NCoxw%h*(&gJO zXud(erat*57_!LOJl0Jp1GZnwBsr+P@BP{38JLh8_j{FJKBzZKsNL zA8vd9URhoLtL9NQOSZCeWsURgcDC61j#>8TR`iXs9abln2r>Pm?fG)M`L;^)?>p71 z5k*=uK}z^_C!t>%5rSx!Oyis$g{95}*bza)M7Bt;^4bn0|s>V5VZNQ%CoJh9DB z<8>cF;QTD|o)J#1)aoLUyL}4Bjhn%qC$r`HpCJHTc`JClR+2S) zW$VLylSVy=?K^CP11`sV1)dYT``a~inP=-2Q{_}=KmD>O4XjQ^5>GX>N{l`}Md!JgajwwMOq82ccDL0}G%JQ<>2C3Y)vToP*~_SjSN94H>KROd=d z7p(AImt9oUFsOdH@vg4+xY=lcLLqI$eqmVQw8O1^(s5*s&mvycO^u@Dotu)2JSjIr z86m@gOxY;q*QmpDqfMd4l+3})K@In4@%cXCNUPvuM!Z175PO6#CeF-|)Ssj4_BlUs zC|{lJRRr!8ZcDY#zBG(U-k9OpUecQ?)Hrl|V+Ye3COkDGBZkf;pU+(=hwlMY=%{n8 zkZ-5HS&qM|s)`*rk>D))NdUA{9NmpcIdj^W|fM{>$@TW#OV!D!_{>XlGSk&e~ z#NZ+V_Mx29y*h+x`aKqI7cY5Xpuc&>YE(lsN8ggKlU3a^ZZLDl#cTf!&CFV7-fRLPG7*_QoI?WH;%$o%Jj&V0UwB%3&Tf_l66)?Guf#kmD_LcNP26 zFCBT~vfj6sOM?RS)40cpb5lB2RaZFVhCZ7MWf#*>cdmQ70SESJnc% zM#kAfPM9l&KIyue=QSh)3^3=14TAT|)22w?lU`K?&3SY{r~9gKAg$VcXG35lI$|>$ zuPWgcQb8zgE!*0bG2X*iAxpjWl>aIhBUE}KTk89WO30XLt(o^CRf~6Wu^iEew}DY6 zx&IiseC6J4owu>AiZ?@6GRy475-#tV6QEsNxvoM$Kp=L0Cl^?6W@e`q3w^Y$gr3C* ztJa(y`|rAE#+IC)?Y0;A-(ghAHnbjnPsSAr4E2y|fi3PDbsM0vHt|?8J?Cm|1K5u? z%$ZqJDh%6n`x3}zHQotaBlLA=-$#u%s-O0+q!SOXD-HWC%ANf^r$|nxq#atZdsPUQ5)YiL-rD6pH;pgt{6BgVXEObMpkT_^kIET8v1e98QFQ z$XIvfC=Qz(L;v{no!8=6`|tg>mopM$Zsmb&UJ`Wo$=1eRKJ;Vc?qXN3HB4G(tNr;n zOSi6i7#_qWln;9ZYdQq|%jBmT;uFjOObZH8KwSe2cW49LEOc3GUGm>fpxiYljm=v zf#Rke7(PDkCwMp?t`xBMmEQ2{jcojmN?vFet9Xviq7aSX>1?oYz}Y>so;5c5c!$t_ z@cL9a;?a6$9p~ui=wkB$MilM+X%^f05r`n-o(#mG)vH+i@oBS9`9!7d>_?+boJfHr zV9fBW!dj}C2p(noZd($9C=t(Y*qHDEF9j#k>_%9I1!Z)NZ#iEsTtdljsm-UTOcQdghQ|6S#lS}f6L^1xa_R7ek>!DownYt$ zQ<&?%76*-ZvgsgmCq`dA%q{E@6M@wz_snT^pR7R)ugE#tz_Mg`C~)u)T5?t)#KB;S z`T2-L@_RA{V7ShX7GozY^!DD|V?m}!or_LK>3M_z(Lk&bI@_^$bwbggtmm1AsLSl$ zO?LOgmpyLs0z?GZ-@a-j;awa|Fm8g;i1*C~(_bs+Qyv)(cL^Gw7fIc0IHxk(AEsHH z`OrsJTMw$*4|}_50U>uK0-%IRK5Q>1T^6H5^u4XJ*gj7<4(WVWjU|a=XJ_}$A0h@` zkDPbyFtSq->%Lb>U+88Y4K;mSjuP&%k& zpz8+V^-8N@T731&m(SsoBGQ_VCnU!wUw+9`>-CT@I0idoGy}CWv4*7VeM|wd--F(BokT0UJhf(FPjh=O$?ExNrDaB;NvuqH_tvMSIIPZx40^QTnO6=O`t9mT4J7>iVCVno@#Ne{*xlA!g6AC+||$x^$9pzALFdLU~e=*4s~Hd)>A z{|%K@MACf;G;ff&IgeL6S}c#2eWI4QA4oV{0PfaH zL%T7{ljX);AI%b`Tu2V(h)je%wjEGEhaFT`445l(L!lptt=Y$GEh&%=g*3pfa1gfF|zr&obmG7GH!pO6n;B78_|>ql`?AEU+^p|Om+-kezr|qP8FCeM9dEKP zers&^2+23ebz}`i2wO1N)J{J3#iL@4`J7n_2u|tatA(Byqd?xcuL!YCBl-gp{4|;= zWTE)(ga;Op@;kJ6uM}wA3P#dtaWH~-XRt~6oh(1WHo=IA1kW>UMt;fk&3rOTrC;35o+CUNP&Xw1J?&*KO@exEN)Z}z^+QNQeZxcC-& zOH(z?|pr_@!8pzFi0kJ&8P13gxERW|JM6lbp(yV+^NNq{{} zQ46Z3?U_Kf7!A2KA?tBoj%ROmHc$HJdO@il4m1jqpaWVI>ODjv@R*o_j#j;AoeQl> zD5_Wgkc`V5BgZ__0%qcdqzMti?Ke?J+C43NalAavt*BG6=P(A!i8eq>%W*8mJ11bc z)oTHt4MM2wht2xqE?T1@AszcpuI;@cy>=k0NpnBiPDXqL;gJ?_ES5=M^+_*IFtNWW zPM08rI9|UQ)iJ*+tE-!EbdGe2zP{wrL~1fT^1_pE)*Zr?jP4p)YKjjlMHP2}Ww;cT zTro!AKHZ)bnELFbRc1~{!FN-5uv2dPl3;p*pqup9CtD!`nu#j&b8{cXhGM2Q3${Q2 z@Jq$pukG*@1$z@JBfOehA`7visO3vQZ;Eez03aNV665#wPlx-E?xhmJl+SHQR1W9rD%XBKhk%b*bMl%v@fXtB+M?#l^)j;{azL#+EKtmvoH9_cYyI>(}Ue z_aN&zrBQGAnk4rSF_Ect(Htf*NpmSdeA3mRr9L$MMYUD#yY)xnUq;J`*A=c!4^l~X zC7-JwtPIGk^k>a#uW{tD?JnVA!|t`*gqda7Ox54fds4hN;nCFY7!@6TFM*aZVU6AM zbT;Yy1BHFZg9QD9X`U5zdc4AXB-Ksim&mhWi^ z{N$9|N~-I+Oh@`^gspe)U0FJ^u-z>NL85oo@9yyr`J7pQW{8HWre}yqQt8|X+r`iQ z6D9~Fb6i;u0C4Ej;^6l+$pb7b#VZvbDEg2>4_ zx8vOxAY0xBE@9CjlyQh{CY5YjKK5^PQQ9*u%hatVv7To;iTS!jy^CK!VwH<1=i@MH zBZ@|)(mNblyO^K&H*5Q72@BiTGA0s(ekMC6xNl4C%*5{>07#H+`+Epb1*rmW+dC+{ zpc{T=w`v%KT74(yDcC`)6n(s*GsRCOLhUK{olfHkFq&qCLawF3&K8_L?&9KN5F99P zaP`ljfsqIKrlDuwzKLnQQMmX9z<-O5b`3iCv!n1(4vw|YZIW1!N2N{XxiN$`+dSf`{2Zqz|KhJdp5s~ zm?TnkK_BGQIDGYZ_~BUXooOb8g~0Mb2`*j>g?UZ#pat_rbY0iIZ0SbFtX7=fWRD}S zRq0|yg8)JSo<&({~vS6Bhp`}r=u}ok%$$d7(CCsO)(z);Q zBxrN3w2BUyr3l~$Q=WQYp3VGvdVOU|D;AB$%zU+0?W-b0QF>aM|Cyi2(^mA|w0jfs z!Jjoa4|VjC1B`n}+IEFmchgdnk++tG#$g8&lTdz#CH!62SDSSQVg-V7>`8aqP1ysk znAObnJk@hS#@dYjRG|J4A5U8`)vM9lr`6K^Nwn_x-g_LN8};Qo7P}BeYm65O0Rflr z69JB)o358SuejmzXR&4{j&p=5D1ZPpV8Q)XFAU3+08NXt@NCj;lbD}IE`m`dRW(l& z`ojx~F_etpq=fB0P4q!8p_wPc*5SZD0F^y z&>&aXV1HN6J1OPWZP;$lLe&6}Ue)G^(&4{4Uav5k`R1Q~rlcTfH2Tx}WYGtisRXjP zxd=7gd)p(!;E0HaT{g9|fEgbER9D8_eEJ5NB?L`Q{^dfn339W%#RW#XD13etytKnE z_e{6wg?Je6C*#f-aqDL3-)+=7ktAA3h%9B2gct3N+;83c{ z2bkS{YiX;AfV`1n6Fpt^W5mHX^R6RT-?eKTA&-;dIk#yKOuG*y5qnB^x>snTRI)zM z%2{L(X=Ly+?M1tM0V%+hxRjej8F$LJh#9(~nkz7!?a%ZyowX>)${v&YSr9Jo&pfKC zl)~V)nPjGN{|QIjSqkI?a+N&|)j^Zp=FebMk^6N~3CVjayfPdvUJ4Qh0<*Hz-RW}V z4}oh6?p`Al@2B1a{cbip*NU>q?`Jp|4&N6Oxx!J%+}$Vduz9`TJI^vG00^wDk@I35 zmIxTzK!@MGuf!D4&o3_)hQr(5YnCvFmOx@we83lVIWEWvf(9E|NRPE*I`rpSLZCfmh|58wu_7W8o{|a*JZvJ%95fRO04BZ71*2~`-Pf{D z;JkALOdICGFd`yYQ!Wl}!mAmap84PPVZNn8gLzB~OS_GgTLA?c>y1^AQzwI8qR{^h zhwwXeq?{hOnmYBC>uB+7 ze%w6f8<26W6dhKc^IE0ZK%;>}t9)aN;{K?d@WJL7lOBTiqve#`slH?N0ppoqic^H) z!`dfcV&BrGL7W6nY+O4#JMX>N`S18(w`Sa`xpB^BWjo4{Gv>p7eF)mS{ylIN2sw>5 z#;YbbXra&;68By4$mD>tyUFJthm+5LE(?44J?ohTc~Zo|hitV1Ru--D-7c({;UWV+ zm*?%#A(^(bO}58)dPxQhUBv*n>S>nNo5|Ly$TySL8}D5eCsgGP3@QzcYS2X?i6&wn z8`yaWv2TO+grX6T_lc%EzpCf;{X26WR9Rj;MRKp38hhF-VyRvCl!K;D32=EP1^FB*zIR zg00(K{F{gG=4ODKeJeWWoid?72d$0jRn{JpXdvw3yz6b+i>8IDn2rt_&aI4_-;>h= z@ohy;$KuBa23P8cQrPUWbn-OI+*go@VGPJwyW=jV&QOD==edfpZsHq3Fy_IyKST(I zluR(;3;?v@=$Zl{%aZNyggDef7!W$VLcu^o0?<(=CJgtwPI)|AOSQhd<8hsmeRy#s z!dd(l;pfo}00eMaOftWVOx99KJgVCtwSYc6a9NBuNl;+XsaD<{c|jW}ce4;c6w&NP z0WME?^&7ni_$7*i&#VB;-YUS;Z{i%nASERQMMDBy@uvSma<6X_S3N*l2{w|p&$h}x z&6u6?7a;7}YSxMi6L5|zZgd{$xa}Cu-vq_|^P6(L=L1oRZ`PN`-wV+Tr%2c=CKsm1 z4oXF2n@K&lnoTd3qAt|`>5d$24FolpJC*Ye*@A0f4AqD9Pu~SUybvY&5KFj8PgYFvR<*p5UWja z5Z?-ejbg3yb1xhd0(gVy_9eQH947L2y`6FX?gj8Est}+8cLC&1a{4*z?hvmGD9bv6 zyU|{Ig!I+qIp&9gs9O4E*X;dVr<6qd+Z%Vaby9@hKQRRcv^Ts{NiV(yld17RxE=FL zOB}s7l-MIrT?llSHc6&Gy#9fHNtPs_&LHtUi< zAHxIcmwtjN7wus3Qs)YgjI_l&PcDa1i%8J*B)PC4i+g_l_}W=|3Y@Mq*vI`A%Bz ziY6#!vce)Gp)uIUrG4^DrSEkLoi}xRNIZ8ReyTr7J?$2Dnu|eUrS(KNGnZWYLewmi zF(y1gGT||w@jBqYjra20zC@v@f~9u`fTh~_x5|IpxFSsnS5eva!T7QmP<*CZ z)szdh2XJuAwv$eZjXNEKrtR{*Mh$-z@u$UIe$KN9a zSqQ9r&1XLv1ciX~h)cQbnO346b+Nu&kfd+zz4}npa zsC&Yu&V77|#f9(c?2>x?N)5hra^J|j8*;thX-qe%K77Ae5f-d`Zu!Ce*WRrK_UMyQ zypX-IQ6D~%AA`811;vH3dP!MNQ=T+Ny@TxONeH5B_G)u>zefeKR=4nbGRQ75+|kg` zm=x`y3MOV${@F#s^+`B?cb;ZZ=9&0_Fwx+$Br9-@yzauVnCMxbxxO*cKG#EEzik-X zb}#}3UQmC73KE{$1>NJ^ey@Q3G!B^G64{!OT^wf#uH2T!!2pBhc&Y$m^2J{TO?e)VxPdb?GLP)Iksa|i>+8W(Wica}nXJpdSni+N=6 zY%A8f`rh>Z87|0%9`#`Km4ny^E)h+U+hRQ7Nx2Y6{<@=Bouffx|$W0NrH;0_kf57fo>Eq{|MWOpY9Qt5u1j9iwI&P58x){p5`%Q-xGA2fbpYsW$2B8n zB#|jqgUtwAP4PA)?@AT3u&|!$X}vbEEg~(>&r9-Ot%wnm2g z@L1JI<6ofsBU;vQNi6dkD?}%}saig>ULsqV`%v)NdPJI&z)+t1`RT!<8cVCk@qsT_ zF(-c9DSy0)13;Lclq%BF0aqynhEZG19Xz_w*w|IUaG(wS*z=?o_v6nS-Be+-&B1sv zVo3m->CI7N<7jJ>VXLD>zkEuQ;jFdV_arlVxsR+hHY_Oe#@#0`?2v;xQIT8R;U0(8Vm4qF_7IGrWe#B%{4%}k(!p)ihBNDPGq#6Kmi-$hysv? z7vsCy_X8^BdgZ;C(#D6$Vp1Rh5=sPG&K=0J7cY`c zAuceqWq=w+;R&B~*Cy=(P#{1k!$AqOuG~}8wtXW-ou8tI8h&Way)y`4)7&LfMom~N7H z`VV)PD^|CgARtS;6-!B=5?I};5v#aJGw$k#5!e3uXGMuBD^KvTxt`}~nF1BRY`xijn{OKiRHIiB=ciNWtw4O=`Ib^C zUG@enX;Tlhn6f!fI7Vp*0D5r8?n6ls^hE;zRAgzap7@hA|B?K`tD$$9rF^CXr^3 zy`{pSQXq?u%L$A-1K3ZU6c%TQC?F34a}mESgsISYTG1_Sz;qTuLK1^TE{1ZzJ34Xa zD*Ux;62jNeM?ynaVqa;7p}GG&!$Qee0P|M`(l%KzzJuiFZSx6~9AHK~knZl?6h#U- zbJ%B063KbS4T2j)=G2#Q9UAqo{hR`7V*ek#v=BastEy_Gsohma>{&sKk8*0`4_XF+Qt$EW$^-K{%d`tMbBdQDsac(G`VUOWptX%6Bi4bY2z zfVc6GIJNt%hD%5_dyaaLVpYhCMG881_az#$wO5+ z{h#3VA1AV9DiHyc9*56CF>TYae}07UOa+&`IE6Q&8knfHfr6leoeO0)hHy3hojgGZ z6HuN>5=jVp&SA~Okzu=DDf(8Rt28&!@dR`V7|wX25CC8V`}oTZt5nwlhAzFjRy_sg z61GgcUi+v3ln9oLtQlNKaOe%*m(yNSG~FPAXMrHIyb3I=Xky-J6J8-w>Fw>E1KDO2 zP&UMTTL7Ao1^!Q<#F;*fv+oK42tUyP#_EiiKDYuGT(KRy%4*JvKAjb%z^_Xcu)q?u zKRtdUk~3>J;u}9w?6C-cv1fHW$Wmb$i@mG`_dT5q2KnHzXN>rWIUKsz zDga2XWVLVQ*7XA7mK&}S=TU=uzJBW8MWirt{Ger;xRtfD}wa!u!G@f&J>9ni0_vk901m*U;mZ0x|lP{O);89GvF zo00`eMzxqIx08y4#D>`*e#0MM-WV4Fgij1&Elo>+z2t$K?7kzBv1gP9T8N|pv^573 z*naW+KzfAjqY!k$N;=SYT~mN!r|`)H4*|eMF;l&9iH7BU`o*ojtsMl*VM8tR}NaapzA)q z+=-5A8}OZ+L$N4Q7@UPD<5VI4Dj*_YY&buwS=tf3c7G^&k2A|#2)*ydyp=|cQ(i~h zLnYuuVH1kAyEdY2tmSB4CaMLh(Cbk_ckiJFU;ItvRGi3qgpBkDLNb-`Z{K`Ga_$y0kMuQQ zAlffs#z>{5ic_tTL}Y!@)&IoG%(J&ULw;XhM5^X?*}5o&L8_Bw9?gKRhBdt+0bG(o zp;pf(gin6-5r77k4lVpXcRooKGSxfs8m!bC1k33j^aD+PApxkObxogBy-3MMtV4WR@j#fGf}98)#UHa(Zl^~LpkO2m(y zI=O~E_NZm6=9!oGMZGIE)#K9m3@qa3Ktu@#VzaSM**ef<^)W<>^WI}y0QGAb0xSB3 zG@-F3l!$tw#<`+oQbF$ePk``C0Km;7(lyrtV6MeMY|W-!4?ez zsw5IHPp=%n1KdX|Se_^E;LvCbj+y*pOYoypt z6|}UpA5$-ZbRi0WVO1-ULSIkz$85wC2h6h8NTqaG+B6Ga6}}!Y$wBpB&CSna=A!g> zQ94c<>Nu_F+(h)aTu*(V5rvB-sS~}lgh&A`uv*`Xjnv9>$E$^N^9yvhVsmxs7;~*k zw_7RrrOZJmb4>p|HplFApSp&Z+}#-WZpcs9kOj_ zc6mC`23&Ce9Gy}tJALXXa4^0=4bU$>(=VV&I2dGUY9Dq3=D&`8kHDuTSR3e%d z6Qm71J4~v%9{@=;DC>um(c6A?FyRc#mW$_8X$qpS0Ptl%X({JUXb*Ujb;s^MKVzkV z2KLEy&PR_x3^RRQG2s-;tXn%=ns+?-8TXlzQr&SeA>Xq$u$N7|&-aXkU?%z?2ZH|a zuBS~C*E1&*bScM^Lg1P#}i#XplCJV_BAm@a!H8m#-o%c@7JL27au*)x%3EA zl3I}u=x%(L>^Wfdy8zXCosg){#F<$>V4If;z}L^&;r{a|aW&9p_L#ik%M%AGJ;4OwvE=ko! zM$u52k3eB_q)X)7=ptt~h05OnFm4+9rdU(p=(*w6D03?xJ}=zo-1fBIJuaMH7A z^@uFNkd`I*d{x6W^BPk+R4V5(v0$t4;{oOoS3y6V1ac!dtS8B^v#5-?n}iHdeQTD= zLR^KgMlZCdX0cB|fdqev;V?%^5s`M(!!IbDRN+@*)?JND(jPkIk;e)k{|~;`$S>!S zv=YNw8?k-tEd6zNJ2b}qCC1SQH(Nl75@K17G8Gp{qi6BY zxk&L>E48G@6Fc|>tdHRe)AxA6CV`}xku(AIxk}1CA&)^_auHLfJxurdC`krU9G77hsWcH3qOdeney z?x$<)Fr))vWx=)GjR~#%dgO|i)iSP748!O8OY^y@Y&`KGR2rv#0QMz(YAzXj!`78Q zE^pSxcR$tGP0M`r`ttWZk;~-`zuPS$?m!cqvlYJ6cPlg6ypk-=7Dddk_Z<}WLPtQ!Bpy~`V&X>WUBiGp#jt^n2u25ce8G7t@Ta}B;fd-C`gCVeat;JYj#>)yv zh3+(tKh?Fu^GV9scXK85BOu(ONA@aAR6EkC>$?|`;T4AjJt+dH_)e}CA(oG{6MkxR zCQJG07?4nr(uqra=3OiNnhNxO$*tD|7+12`K7i$r0}hys$S*$C)g$GZP8ui+mWwWq#RCR|7S9s>0LUW4=`{(x zJ`~c@*V~(FK9IdI^07%VEj|6R`N9XybSQ9l=RgSWHF7Sy0U+`0W^+PfqD?-gT<;mU zbUrRN#Au5ILKV7i8NuqarRG)#a_D0UPZgO+kGBn5M`c?N+V2!L@0_Z0o;PPwDG{T(81}$# z9jOy9N4Tn7KiN*}&t1ZyJ^=Am(;K+Pe?uD+*p#r`I>~ZJsyy2n=cjv+XWnhB4*$*4 zfnV&UM4do@&3jCov#i(}Z!mzj{~G)N2BG@)s^-yBd(~&vW(SwVK80|DSgyAtLR_St zUx4=bik-c^LAjEX&#yNZ5x4>fMM}gK*u~yf)RkTwAtHhb1aJohcKBmDkW%Vki47Y0 z(^8E{tz37}4Yg?6xB~-)!9P$o|3guqBny65l54bpjRSxn&vYR%V$)$7!cw~RCgJdB zfMM|O&5f!46MuY+~V`pHOIs$b}{JTz;X>s45xZAvh*e$H(=ea_Du9w3Jkc512uBb8c?# zoBQG#{;y?#zXA==A_DQ(kQbX^i5x<>LqsCRh35*hv#6VCEdoJno^PauV1KJ>Oz41ZC(!#hF09wQ=YzP&QHHc2yPQpe~P z<$lMJG2`tCw`lnq;Xmx*l{>C;w{Kp2{J{qnVFs5y*ih=aHfzAQRhsofVj^2$WL`*t zr#4i>?I5c5^CcE#SthplQA(C(iU~ymLPGTZx1BU$BKm;g!35@PZ#yYRaR2%xwv6Dd zcm*FgfVaBf$--Y?4~s!u+D#-U?jU6BIxl}WeIs|lYybWx*vw!-39vEh+0}v1?H22Y zuvmI~k~7vRwnMpMd%L^66XABPcG9aUsS~ym(p7V*Z%Z{op8(#19%cT(*3#b2B*ky} z*HVf^lYqStE&S&8MJs!eL)Sk9QG7!1alpBSl92H~x&+U}Srh)i(vUZ~c2x3IHGhn; zD&ZUCN4TLvvKLgMKQe`EVCN)@Xqmq&m-E)w|Vpdg!w0^)JnC#{KYxSNI zflu~Ze&Fv5@{cC2|MYjfKEHM!Ty)89K@tCO40rBTC|0g+gWRCyFqGff#P!Uu6~1=6 zwM*!hXJTk5bJo52)F%T03BnPn&rdabmwswK>k1R2rj!BTG%d(FD?z@CzBMe2{9Cl@ z0B?`aVg9$^^?zEyaI;@)mn5s#=m3hH>t^t|IYTLwzWtHBeAgXV((T)SEyw?(?W^OW z-nRB(BqbFQL1F{}X+%ImU=S2(=?;}{MLNeqMd|MD?ixZty1S8vp;Kbux5sntecyZE zbI$ua{_gKTKd8)n_jm8L*Lv3TJS!3XSu*eU|CjUj-(JiAd~;wwf=}C$M=#i{8kCXo z!R4!5`nu>zyUyzjsNZ5PlnEWy@b7?E`7ew4r>p!2f6-Qhfe*g#<3+Bg#Nf9OYr%mo zRr(8HR-$wnwib`0Pv-IW>mfA}m?C>(s)kE`e>-)0R63Znm=nb_qwEZWEQ z+sS)M4~}94cQM(Y9mN0rQ4=}{0$?Ei8lTZGVvxdS0?2`l#0@tKL2mT#gxi70L7I1M z|Ie@7KUk-KIqC4a^q;G?by%|_XaTY;0thhl4SoX#J_FdDz^SkP1|#~8)2%3gGBYm)2yqkt=8^sX=dl3NuCA^w zuCK4Jwan%5=hU@d*nl8OX>Dx{0)6ecR~Sgm-wx_8oys308+Yy$LxF!cp+jWe zUzUx3vG)J-V1j=P(x)3Y#4iJJ&&7L9l`-bOzES^R)ow}rT$O;xNG))q8N92K*ZB2) z`Ugj)-T-<7v;yi?PQ&l~IT!3#UISS2Cx9<&L~=`l`O|-N#b>wz0D^-LP-biZr=KAs z{8`C{j@5KkBKD$pF6bt$%Ef0CY4kmN5h@ z*CJprpaqoMA#557XmQK#Qo&c^V31)aG^0s@Xp!hqFyITzNAU&%P+JBttzOvxgGbt4 zu`xh!ba*{f%;vZ`#Su5p{&scT(Mseb65LsUQxO1CwDAY--y*%ok@ETZXpG*%!lK8L zRajUDUBm*;Q_^rYM^uL?aCe%GA4ms{aM0c7#K*@k%jz~QK(=XR%7n5ec|k&BVyH)p zjD5A#k{=RD%gPd{a?J1`hR_tpf+|@u6DGJ-+WpyXV0J0tXb#ju|%Kt z3_9awv*+6Hrd*J;5B%&5)lcG!XZ8JM!`N>h8l#9;Jcg$FQTaOKo*5_GK=W-3>{OGc z;{j%ZBYY)HO1yp@Rg|*ScV0#G@>UnJe}n6>NVQ!4T}0WQ5MlYUL)x&8Qqw3ds@z~R z_yPHe+kNDZ>E>8#Y)b~J=3;=>j+WaRBu)#!j}1?*t)5Vs>4ZJdfc6|poiD*sWkNgX zVjZuVqU^lb%!HecaxbmwC)Jh9?C6zF=RY1!KRbMNCR$wQwCB8LOM}`wcaj-aNn$Gy z%ZCk_F1R=;i*ZP^-lRB-t#L4jTWb}JU@ulblFW<>x@CE1_olGeDOS{&YcOkTQgmKh z%l<~S!rPmfx?rfC?RY$Mbumeq0q1RTDs`EiAtK)If+61P#*0^577<=F&l0R(b7aRw z-CBtgd$8Z+Ro5`q$6&cCXk$8xTp90>KM_H71;^sR4n561_*U;mOZ?SKo`AEK8wPuh~)UXA;H_)w) zdKH=l^Z+suBWS5`lqgV81J%R}!0o{yTyz5+fdwD|4*5vV)&^7sWOi;#UqG8$LGk6z&Ud7xT(I&dS`i^b|E1r% z-qJkWz+}RfhNxr>V02DXf{FO}3PLM4D*@|B-mcbOF9Ruqi~|_)izadnfxEj3$nDYE zgXLa{yt*^j%_>8(F^$PhAhUj@c8N$7IB({odA9>Bt3P5sFFVQAS%!?@|A>!9t2(=N z67)gJ@_3_Sj(C$5;kQpUczI(#1AgXeJM@Md-u8Pt;xMmh-KUBUiP8U0^14emCKIU>%}5q=Iw*c7@@AXeX3e_pnrw|w`~%3N=X{cBv3tEj0}5zjAg@Wq1{ z%rva%Bs>gIRh?FZSRaqimQgi8!iDZ-%K~ss&NgO$|L~2z&i4>IFUyL`@S5|p6UO5_ zoD;;xy*%LA>^szNgwya*e;OS5U;r^;XMfqjBk0({%0E1L>Qg>=w<=Bc@c1oR1>uQn zdQF{4e5R8vlJ&)8VQA{0TePvt?F+kJb<5nN3(~|rHf<)GwRFOnQG}KU!<$3dCYvEn z>NFFkM2e*6`4nfN>>bsC&z_btCyFN==%YkrZB99VI4qeZ^~}FtBwq+5%a&I1*q*jc zd`L~8ewfc3dM4MyZQi8sVt7Uy98<5mKB7p5gYFcWkkN>^^`%HZ@Jq_YCw=&tYJn)N-v}Q#gWvr?^z!BmVBkh? zhWMRL6`Lwn0=BN)c-M@C2r*5Az~f#++v}fb%|zPixn}O&9jl6q|3JnZbz+==Ft0xc zY`P6S@>_84UD2i7A4_i#w0!W;Df@6P1Dk`?54zgrHr9D=2(Em6#MsS}p2n+XBS;Zd zSP@YT;QxgBo|#r0wg<(@E;%3<fl)cm+qu<6OD|=Hw=U5HCFkv&&2-!}3pnr3@YUD0-yPTDjK?kX zlG%BBt=SUmrJegNEz(l!iGZ3C&4faWs05G{%y5`M9pA*1Y%SpLUG~s;jnda};6u+7 zM|=p{qZ;y3+`Fr&tQ>^4fe7Q&V+Ga+e9SAL&&_u(av5>wVY>pTR|WVkDoU}z>#r#C zy!#%PLf91)6#}h1!VYrU5_<+bWE5!9ej!!4r3c6A&X~Zaa=wqwNZMX``?=;O8)#%I zH(^y+)E9G5jikdfnrWP3^o(Ai0OjM!!}h@{D%I6^D2$8{tx{kzQv)tlbYfGcS9o}fRP#%OBaV3!<=tcg^U^kntpYFitYSC% z)BTG-Ms~Hf-cGKwRQ?34n<9)EiA32;$gWDt)w|eOB}ctn0DJ;;BNovOL~p4{!>>5t zz(WQ9+K^lRRB$O))wH=`8dGqFO1j@FWwHw2%(1%8 z(2yUa3c%<)^%qF6veoBbjN|HL0-FakU13}+UQvE=2TWde3C9M0ii_2Y){VE7V&;0W z6oA?+wmcNX&UQf3yTUvAUSw+aptqWViJo)j^i06Z-pIt{BhFX!&@PjBw2jHQO?9p$ zXh@=RFKr+i>6+I|Y1R&_%F?AJ_1(80^zQ-B363#l`H~p}-d-Q3q2;06>#hE$&yxfU zg0LX=Wz+#Em)cmJz(6*qj>!c*=ceA!`A@E1sJm%-J?F+ll{Q&9?QOx1D*SFpQAcGy z%AuSjr?ae4;e}W=TrY;fNAe*CT=zPvIeQvcJL=c1r=pqb)`TKDzE(~$5ID7;(g>O; zRi&ergs&>J3??W8+9RXZ$5r2RPh2`RC081yhDx?Oxy_SJ_7flCy7N@ZTW4cOOgk&PGx zdWc2SB>|w?c_76PdY{!3&U2q{MEA@H6MgA_0qA;Hfk6EF4{&!ayt{70T}*W^w?V4x z&2ub2EH-qOy1b zFb?$b?+%BS7fruGr~@vFMV=th=`c)?=85RzUSzLdNA7eu+Oy|zsdsx!!$+G}9vAq( ze96EQ=>9wjkJE8eENS1f2mL%WGkl4j#RdXDoO0{FY1R8p-(`WkS7}~?ynNWLAYu3M zdU5R!$oDBOSy#^3;oNAqkK-t8GP~;`)K+eKHYbM2Wv@^2F#@*@4U3q}(DavF*3?*- zA>bP@^-6hq)*j_qytvLmBKjlYuF|LSVlRb}IYzj5o!m;daGh8em>YUWq#1PYBY|(O z3e{a3_G6s}t?ACgPM0^|&xY_0^=Bu-6{=BrW1i;9Jn}QfzD&}6oAFF-f}q1BcbQo} zO7a68JP1JXFR%DIF1D=-0byE0&I|U+^w|#uq3g#WBmXiIG&ZMtw zOs%hK9b&UR(!PSz=0z<^Q(-AJ_{pA!rnKE z9JB^;2as7K17GqbbmJ7i`)zwz3)>?0!_b8BwCFJ}n#gEnA;@=K+p(`^jHI`2-3kC{ zSmZoqdaPrV(}3e-`Z!4KgnXG8uW=iCOH*?LKH3rLaM~DH6hCrax@dGnAi`+mS}iv%rjK)sEeX9LnKQQ-a$;|v6;l;t)4XC zW-J$PZ73UFC?jExv3rHi8TY30Y+P*}!hoi#4G>kVJbjj(o`arVY8-3^>%D#<>nufs?N4f9bmYQLRuveIGV=hK7>v6|&(PK+bfi`EY?(hp~1& zPV$O4ul1O!P31z^eR$Ua2AH$TprPjw8aaqzPE^mCWwx~k1d1kE0#XB|Coli838<`$ zFO-g#S~3G|h?ml&iQX0LHNk$pH&s(53MBwHG^o|Jso8!w`4&IN9w&NCvvlOw-bC5M3OaG1Ph4_?&2M!1Cc{E?N$)r3PmH0x4WDpm@J$a+8*m3jvM!Na#w^ zWt9rVlr+?^TYF7(y|P!Ns?uQWIB`R-p(}N_%7LtB&C#o&aRP@CcU(VwToj01B!F`= z6kXQVJ$YGK45qd|OatZz6#CAI9kIe1duMKd)B00nJ7}eocv^G)hx{A+dqVVx4IMIp zSWvzsO)*IMg#bGpt(Qgz8u~5|zz$d~_o?$Mukc_WfD+I%fs5`3GilFsBJgFo`{6aO z)~7Qj$04U(k7634!}51Fruo*fj`m!=u3fVSW5fq;s6-#yAav5t#Nq~6F`adaT?dhz z9>47p@3?*a{<2n*@gf8TZ2hA2&f{^`zH+4~C(2*RwV|MSi63W(QH0yreU!kA2PM4^ zyAZ(}@mOkZFbSTs6l3qF3(^LYMysVo;0JMAT(L?{s&iwcFxlQAaRuD}k%12#-r_VL ztn@bz6vE|fe&#=K`~7eRE$kBg_C!5Tf86&l7PrFsQaM#3)%ysVz@X$$#V3Q7qpD@8 z1_HI-2R{sJUqoJ8I2V`e`Vj@u>cFKOlrPRlB;RzV3Uei(Fm^l|i8A}TAtg04SpQgu zH$vg{Y6Z`Ojt!^7kzw@I2NFvdWQ^!{6nDgi`)Xih^lmVpCXm_L!TD)5IEa@@y z;Eu^JC)=#q^^=R>ZisP1*sSf7-h#tGTSpfVwWqAt-{8Ck+o7kcO^VLrCYmN&vkQcq zJgsam-yFc2^fz6tE$Dp>7haQyayN7VL}!QT9lm<_Lf|F5e@2=Km9?`8{pd4Ro7X|8 za{I1vQ8Ne}ZGFJ2ij#!F1F+*qgz(zWS-2ax-Hd*->Re&jcu(K-&1 zfVfvt$M-TGgq)YxO`o6W5goYc1vBOz+*}s`>Z=Z?o>Bh(?HizN(85f*ta?{X;gRCD zU<02D*~02GR+n&(>V5A$l)KOT;>j6TW3WYP3j?!y@SNr8{xs3l=ZJsD-TnY#{vZDY zu1xl53oHE!Aj>u7TsS%0yep1Lbp;?}mXNE@%IqEAz$nQdJ$l3q-=XqO#cZUyb?U@P zrMcH}r@IAccbAMa)N`Zo)d#&bbgT-pyDU*a;ytQ{DF$ZMKY3U`6dDyW14dl%?tdZj zCb$}KMM0PgEX`8RD*S5jUd(cKm^&4P7 z^a*tZBG3J*kO>aqHRy4(7B9ELg?V7n;S5aJI`?#J5sZMEToierczLVt9Hof%jws#$ zE`C?fJtN2;oGuWG5~|7r{~K;W#shF^B{O_LibzT64V`oYcH5%7X5yiw{M1gq7fQCM zxY$;g^bxd`LH8ALC-%W)EiQMi0NzRaTs3j)9g^2u~2!et8)SMfFEZ;sDfh(+zvM&vF{ywjWDV3RtSlG4@AxB;1X)E3+OV^8i+Nr znwJ+Vz|0eKNR&g0l@2FjPnj$reHX|y0?~pVlTg2)Y`E#pr54!=Y2ua~A`rklolI40J{zwDEK@dcmxigq5p?l*rrt|+UEm^BI(Uq%uIgN;`M zSgO-06C6PvAg$|7!3Eb-JfNhKOa)E@SBLip+dySUw>XWBvK6F}uLV9Dy@<#@C3%%*3$F{(n&x43p33vzJkv#ay9j>~3)@oc;Tg+{*e&WQ>D zziroc5P*|w)Ax3tqbV66Zaq?z4_S>9>*@mqSdE9P{1W{ggeaS0-nmWqvdb2i>D>${ z;M3liC*2Bv@>j&+f9bmZS2qhja?#8R_KNvhz0-2Rtvzw6E#RH}+HB?nj=TAm7MSB4 zStjnQG{PZIZ`Xo%<;s8;8@Gp{T6hjipaL1wGqda!1mW{nXt{u876UXq-A zyjp3p*Pqm-xi(DhfH1e6xJmj01fO|8%jcigBdRHDqKrw052wex_}BrQ$v@%bI6U;D zW&w26(m_Q0cU_IDK-#ZBz$*CZ0Wt*dzG6pRVC$8a$_uSy1F?hI5BC{XOTgEG)?qZqAyJDsCD4*+=z zMI;Qs{e9=8d!jkXdC1L&98qTXm)18Qln+-=wU=^S#`{6gY$H1Gvm*cih062_kc;2= zXD-eUuLs&m27cHPa9{`o?9!-{V*Sm0#6cAP6mcjlp<IOiNBNhbE_fB02nolB?Q>W+;v{Og}97oSf^xoHJ;j4djd7?iGIYtBdS`S zzay#ygT2`QDE5Z`Td|kZ)$tQppb+_YVBt{fD_Ouf@~w{xa0kO1j63JQV6qWlj%0|3 z)t?KT9$q)oKY6}XqgV9p*)01rA=U6RdB}+%^7fy*XZ=yS|NGB+$++>cKx%REWy*Im za(Su(K$Hx=2k5qDkE4hnzC)ts6mSVGSTMfTbnPToxFwMF{pjtkb2|d!lLa8<;UNU& z&mDLd+G^|jSD$EOs(^g2kE}vZOL4L+dX@6|wp3*+Fzp&3K@(-gK%P&@WAuKxQWT_$ z^XQ8hHIx7265dOM`Q~0#@1+60Wl_zZNTQk{B@ZBNmfsW?e7y@Ow-zhFoEHCJ7+nMm zHwwF23ram3{yeF#;V1=M)>6^RN5`_3Zh#r7qVLL8w9J9m?6EHq{oOm9=PzjEqh}<- zJC!*gAVfX`ckkl@Cez+&D>OPDZCk5_?yEif_9+lpP)c&Zy=ncJ+I!znqBS#8hajYK|(E;zn z&wzKfcq_{u+?=(xO{iQYRk~M`vZv+0WoIY=XoZ5JPzF%Q(qb>I#V}IAuJh8Wad(#$9L4+3-4wGcf>i-TsU5X@Aaoo4Q$}L zsCBxXhWHOuC9dVJ3k#k7{SsEKfJyQ{ar^>v2i2u2RwoRQ4w9sFG{!nXItnlY%Kn{KooNTsLjDxV) z2?Em!GFyxY92Lay{ewTZTdF56(}7I@z*Uk9xtZBZaZEZ)SWb4Pn2K1(}d^TdMa z*^UXzQ*8lYT^0<6HUfw$CfOp|C2%py0<8IRB|7{q5iX*Ame3UeQ=%l$GZ_tJC^2Pp z+AJMZ742IamioQ)0E(HhCee6M!vuoR7`geGHW;TDSgN%EPhO)Sst_&%eKfwfYg^s@ z?m>ruf4BVBlS}f2tY3cHU;<>3XtH(~fZJb1@1LbLF@5P-X$@v}lVT(}bRAf~5Q_Gr zx!KX<;My67b{LErKUjCj`(OwHO6nfgtI2=CuYzCv6Tbqw;G*<+Pi;^l+XOYHq^J8b zSylprk5rJ;RcXuIfupB8F8B#=aH9a!8NIy8aGrGd5j}pY=~#F*r?_iJy|7spg2^R4 zAtwM3t!e58q2-*-#S@dw1~h_I=A}owraxQv`8V_-YC`&)W1uhJK#Y>#D&m|og3B0g z;It&;LsIVSc6+CS^~Lz}P{gid5(-ymzFefz#Ic0AwVn|(h;Ug3aH4^q$FcQTG;{9M zcusH{wO($FyS<~ea66d7`3E$a|K(>PuV4W3zv=w2hP|azRsb^D z4($U8`%Q)d@~=NDg84KW0DpQeduYAJ<$E7+IUbC5zbk)g7z|lzga(AcA*Xb zD1_>|X6~IE)`Hq!9PKaKKje*hLGpZLcCo6+fsb45!gyR-wHrMOz8oskWn^{|r!dosplDymu#n!9bPpbqot9k*AG#E1;7Z`5Cxx zEQz3*`#&{~s4B#i^)wo^&|mSysWJ%L(-q3sxqEn}{DgpMf&Z4^$U)V_K?AsEDO2(t zcwKry^T6WlM0!aUT>wr;03%ImhXJLXwc>6Ir;DLL(BGOGu5S_4&I^WMq1!OFG;TFF zNw^TNrqP9l0D(i2)SwBoy#VT@3sGYyUS`t$RXOPVXxRi7P;jYZYM57g9ma*=51b+DDVloFk&d&N%(Q5EftjDI|IX~=RQ;|5wqCEY^$Gg74( zt}naj0@4RQ>-loBf5)R;z}Mi0zw`3hUA5<#HA@5S@Qul8`JPB-L4SOH&|y1quZ;#o z<^Z%73N3a922p*$RCx1}@ax^v*GDB>Rp?7=v5F3q0wrd>U#zaLRqgPvMO?dU6GO>S z`+KIn>C~dsB)95e3>0=IB?hMSzH4h%N6lhqPd0?Lw?S4Gj=uFuL`=27J_rzBuvDUX~JTR9kpEHq02svZ{eCyjw*k(M{fG)z-;SL>843E`%n!YsJ1$s9Z z64lKQQQpr0XCw$nK%*+|iuA6@W6~sbmE!BN;HO+ep-cJ^sw;W2PKM07}7QY2)m-q z(cAKtr1~lW8bY0>h6^p_(5;@R2TfBoRfhjK9A0tW(e3FvzPZ&$gcb6rMOCJ_!veT#!v5|X+muX*v!n!>C=VX$+#vGit4GWCtt zAdXfl7j~~Dm$lKZn8@A8K;DDo-RS7AqRsUFAC3kHPTkdlPdBr^88%6&Ewz%Qg*{Re z4|6XXXBbA5%JP>w?;mG=eX(#v<|A8H?Y88kR-cCfzsJ@)lZEoZ^1uCJ){FR7nDx4- z;9lm~Ojf*Cs@&qb%9R-Z7=gph{i2#QJuX_k5XN&+1Z&YFyR)5PDx=XVei1SDZjSiQ zJmNf^={2J9%|JC59&5=NeKt+A?#A!rl}VmVXV0^#tp($S%3ud$|%jMXDeVLnWUb8e``;%M$FY|;ovd10`xFfRhDv)aP{G)-Uk*R5zy4s)FdI}P0(=Rq65wr0BE35 z*3`Uq&v|e~TV&q@G^AZ4SJ!F)ScmND2S?GR;Lcu4Aj3ixGOu58jL61HK7!cJLdy)1 zXgsh7khqL^YU*qW0+#}KJe(8!8gqT8eJ&x9Gy|PJ^ zvBfi()3XgfqFdX*8#6)atutM#5_1%QS`cq!zu#q$R`al7V(L% zFMd{f!efC)1mj*;Ykx+QtZljxKfo*Dign*Wtan3@-k-k2T`~XeVCxyUgJh2N8N|fI zGTl%;bDs}OsgRy}gQ**(0}e+6DvH=*{sSVh$2rQwJ3#23QdXvZo-Yxqktp}ZljXx6 zkto}R-L-J1mX93Yy`&z{ic9;+>L2IJP-?`GSF#G&YIwu!aBnk-9;uh|Ivsr#+BpP2 z`6Mym%Qc>_dDT5r^5OXtS;eNkDhAe_dH?1RS#ig`f_M6C34c*L{dvZcB3XZcfjQXP zvw4W@s>mw7b^(Hyq7FmU4j|F0EV0}t#hA5$>BhSW(OEW z$IOrwNepLq^=B~zxGHD3J~dlT5Ms+D&RT6yF2Kz0W1P~XqM@t?K*BZFI&pT9`+t{5o~Y>VU)BjS3sy1IisF7&OF9wT^}$|1E?k(@SG2e=Sh)d!>H+Hrf#*qi3?&|} z;-3smV;#cW!Q4W>!muVsyf<;t;6Q1%wYR%Xey)rgSn38}fRgYd&+s!v@(gRV^cyYd z-g7W|tL4C!_XUfyVg?c^>ft&fq~c{ed;)SXE>NQdy(SiVgxm5)?TcI+c92Wm8nTiQ zVdG{I;G%f@1euvr$)Ajkr;B!J21c)~CFcD`V78Bl8kv2p;5(3-DdA!_v0p2?@35hs zXtlcg1Xu|+@TKL=yt+l^VeUU_Thr_cd{&SrclJlqHarGu=L8=*j4Yn9$dG9o1;dt+ z%tInl%Xt#*KAiqBqJZA90DR4QF{N84EHjWCqi!Dd?Ax{K&nEMuT?aJZJ_BAXBFB6g z_p#}HKsnG}*i4&!j-%9Yk#L3laqQ+MszRFfN6G_7M@KO+DfOjxBHZwn@OS&}Hlk+B zA<6NRwD*1(DA2LmGr3=SNTK^g-pcBr?S|qY4h70xJF_ENmpbO$D}OVjdFb7FE~PHz z?d_E{3-j$CZaG{}$pp{o_ID=I91+erep8Prg zF(xEz4z4&L<00i_S}N}nr=!|a&vGs{8Rt=G}1_8`BT7SEp-s%mKXy5?pS5WIsk2CSVd)LSr0 z0fmzzfxtRK@@-1SNhM%nA=9$!L|EC_j7JKdII2AMqJ~(j2h@+HaZXkmyuGNpSZ(Ak z?e~eL?c+1ck2mRiERJj}R)V6g#*;J6geF$nP+AN6(*I7mi+4 zXMF&b^B&f^hI{AFL*3u1eRmS>) z@t;^Guyg{dP&PJM-OkQV z&-iZB_8WcwpLQ-L4YX%qV7?qj2V;4}ihwPYo|LH`$^Pl}UpQy}Ees7O)w_M+G6g2& z_0B~9GT~(?4EkDuN&a8o1WrNyR%$fvR-WYJtS9C_zj4;E1Si7h{RR{m+Sq%vA9$){*(PS)nQuB6YggXDq0_h`t z5G{8ct^RGoO@I)1Ze>$x`9iRa@2$U5{mbM3-*m+>u}(efDzvq=(ZTsDkr4m*7nxMN zH~3pZ1apSJY3}~{<`Uwu@JN@Le~6dqfYltuPp|y@Zw;S-6dJ%yc^x{#4!(MfgQ&o- zeCI#f{l~KPrEZ5db?pm9#^*1s%HD>`T*4!pak%=ef$$FkCkzLV(4b22f_7jn)0%*- z>apXuxIg-}fAO>WLI~^K`oWQr<#Ht6%7|4s`#$~FTd+5{*dzYPFQA1GJo05>e=NM4 zix2*$ppHikT-7CDKs!P|pTqjEH{#E)%mV|vu~C5@6Y{}D>CZh)|M_zN+h&2wk+-z9 zUDi-h37)31GH3YTub}|&V0bqjFXNLw>hSIRd%MD@$UD2Rus3cGL-w;@XQcZ|4}Ut| zmXLj0IeJ!W)EG#sQbWwy_KYuy^*)1D+otF?Z)3==8=eQ z*pEzP4l`lAS&xL?;Jo;|G6n{LDAaa?_z5R7Cuf8~Z*$4~JC&lbI|WVnHhq*QIE_K8@OW|uPe13-#_?SuWHLyqkMT=tjuxwxxB1lWv02RQdwM# zR%muJg&b?&Tx+Ue+WTkUV!WDTE)w7TSP!cZ1*S9-Xg^FN0Oj?Kjn+7a8#CUM{me4iQ0F+(8^vTrW@ss@4 z%^lDjmi2t*V>4R#^lMv02$qnV+O)avYHgkzT<2{e`x>O0o*Z_Xc^Vo^GrzIK*jSSzqnm(p5lJ`d7NC+u6LeZq;NR^CN)i6OrPW~WQyB_dj$z@Udt81t{iVbD+5Vegm)P$;XL@1h8LzlX>kJzk z&JCt6*Lg>N<$ZhU(mVFRk5=r+!;{p+T={!Lg^Pd(LLRP5&xzEe{DLj#?R~N1gXu`c z^Jo4|;GW$(1`~t!HM{acgTt`S;vSWp4ekaJu<)G7I-W@S+b4t^x``Aml!~o)_}n3|iI+(|HEL%F5181t{CvmWO&X z0!-q3$8jC+4i?_tv{n^pp`o|hF@~zoz{igll~#0Ds8#Va_4HyR+*}3=0g{|Aw{&CA z#lV+>Yr~!frPKh%4)j;)h^F_q#rDNsf(9seyi?5Z=Ls>+29DQ_8qvkG|PwVAtEQW`* zM=*2;p6;w}yv1#}{QDloegwj)e#qYwt3p+<`!1Fp9s0NCDD0~ND-^z@Zs|91=pCljXLI8q3u)VH#DYC$e?zBUD*bpT8gsJ zDvBx~SjJ2t0O!P>yOlPy7)iAZg)vL3+ zOJSQk0TC3be=m(~QN`S!e2Ko# z_hXMm5I0pOl)=Tr*Cd}Rgmo-$$c^ZVxkhWKa0(;2WH;*|>Fr%vwMHkreLCK20l`{( zTw15VtRG)@GP!94#AglPNa#d$otQ{C-}5M$y~TD1ps&$vn*8j>!qm9wWJ*4Of~SOL zFw9HuW4lrT7{wTfAR6e=u%WHj$#{cHx+38K2AqJi0tOJe)Pw{mN27u}O-kx_$^tS7 zR-e~p)X>T7(6}u{cb=YdNHBxM4kS^2MJ%ZCV8n{1{J52qEjVykpN*3(<;@K!2)WaF zSP(3Tr)R!YS>hXn{pjp4h{~_{9#Mk>*&i!he|FLZ|`-zp-XJ!7RLTJ2hf3R700C+ zAQ_u<0-OOEbW7}qyC`ML4TwUGpPY9+@G_mE5harKJl(KCgu9*XHU%RFU#WZ}M=t2- zRPFtkT!*LoASscboPjKRR&iW-T#A)zs9IMB|?l%~@E?11@W*E)u`rqNThoD_)oF zR=w3iahQ{K17v+@>BF&-MEZW?nwR@gdy(g!nF!r_`0;6GZZ1(CYq3xqr)B|EU~}&S zoC?Yfrj9EuAu0s6A`Q!n$qaBE%LP?dnPE_r>5C;WC20#c@eYMq7Ri^eUGu}Hv_0vxt zme&?OPz~_zUJ5_$dqHVGFBoK?Z>`SE=V?EwU~Xv}oP2OSa^PTPtmXI9K@Kk6UpXD1 zc--&k%M)jundfp%UzHT#yH{VrMRp$xgsV$X!#wVjW`%p_jwRscQ&m@Q0~0R3I3^0M zdl-q(cqR~`37nPoOT;B#6_%Z2ojuNGaK(BWZl~QC92~qYoIwkCaxWqrtSfUY<=*0i z7#T}Da>Z&@=M;TbuMfX?c}vH5`rZu2k>QR}akb4H7y*D4o_YNg@O}BfV&j8^Mpy!z zk4C5Y5PBdc8HP1~`&_g><<jEdt^>3agpeI=3H4cIV_W|%XQlEzyURd-ql}9z|~+P@4J6iOxbXSGf0-6b(%g_ zA4kAS{+)Cm+=yL~9trfC?`wG1M}4a&`;snJEFZ7G8K>Av_6;ZQ@zI{w=ic^cp$m$5 z=lOM3eY?4O@S}xkyq9~c6UE=R6>SHQV$+kE&4G0}xQ46WOI6tRot34Jg~J54Po;!X zpH5-nr6h5t4~Toi&SUWVZ;Y&Y%(pBnhSF%LFf;>U5i!Z*2FxaWMbn&IKva(VtDg-!u#5AYU`(H&a#zq|a zj@#CWbh;^6ISuQ(b5O;b@RRt6>?W#j2ooYX1>-2SF(EXJ$YL`yTV0p)L?|KPWm`n$j?zHN@QrqmRtax#BZ7qke!gt<0b~EK1BNzIHtC3X z(#+YX4hC!pFMx?r=zHNIZ5ac2W9*-yCo7M#16c=+ZI?T+SnwsALPnegoIyy*F8Y z^4UewifzQ?@yjXH^I}>Z50SmCczdSR?TVl+W<`ECch^cM#jA?#aYu9U#P^Q&b|+(9 zQB$dvuI|Xaiy~)U`2(xlLt&gEwN`cYWa74GFEZm4m$;bYyoASWAy{Y+1wmkSur~5w zY;^*Ry_$H@&H>Js{5^N6G>HN&Dh}98M)`D@^blFVZf^Pnak0?(p|M%PeY~;U^zV7J z!s=(D*4pJ+08)DR6bXS~SuJ!{v~`|fV?qw%wCnb1wcM6s`zo!Y30pfu>5-;TlFr!J zB+nPDY^le>)0g(Uqv7|~_SQoDuZxA1=31uKBJ|4~Y<$OJ5|1QOh03*=t|$bcWDmj< z1$%{cy5q$RYyq{7wluEg?f(A0YcH4u&7iU0ewxAvue&4X<7rQ7a)fGLihjUCX>feM zI*<8<7fa?umQiaEV^tR5xus^ci4GR;_TNoCSYJwM?`Jux6y&M3h`2KU_MC`flXy(% zUQu3|_?WOrP;4-pTE9Y2VlWBvxji{#XO3AcAnMqJrQj8=g#|JtIaBheq`wwZq2sIV^A=WC6a5Rf_dC(4xXQG)g=B&k7SFZ=|KqP0I>@<6 z|9RAI{eIM745zP+^r%-Hca*?PW!+L|sLhfne`U+t$T9IgmI-0>4YH3+Z>$x(Vb`Sc z@4RM&)o<>%gbknFB(hYu2;K?0PkARvZbCgm#Q-Os{Be=_31_CQ)!-oEKU2CSpUd~E zEwWFyI~``bx&jlQ9Rw0Zmp|Gaa+odl+x(Jz7+##NxZ3`Gq$rncvs_*R5E3qLe+GGb z_Xp*_hgA{c{Okbj7u44G2dhjPqO<5h>r_#!V_9gwzUc*#d-b}w7dRyAKMX5*VdGJw z)*L;k#LSil<~-UUiUp{dwJkr{McwG<;amaD_EQ8pdr8d0{43j2sql4!AB&nI@pHuY z#2vqpA@@(;G5OPOYFu6R-sx6h-E$Z65=1BDp8u7QgF|NSVdPy82d2W`v(=?*2cK9+ zgpP!MOiav{9m{n)@RE;ZrIWk3>HN?e#`0^G_Sx2@c~$>gC62 zP&?wGPZ{nQWmyr)o!2*D3I8qeyGUsFGx6K1PZcFymH$1g9#?bKmFRw>cJZ4J*YHuR zPOem0FbF~tnuw-fzYMcGxu(wjwxuPMO+Iz#n5 zql2E?5SwSkA&lia=HgSTM?X`1JW_6rAk;H-ywsNW_NK`?7+z^a_P@mA&-EuH6<7g& z(7g^0$|i4duN-V=mYdM!syo+nvPmYSCEbrZ85{0+D$-Sg0dJ0TWz6v?G+B({zf-}g zH=aRr8A@toN>7Zr>k0B0YE9{@+g4NTI-c7E2!{tsffc!4FO9x{+e}eqxGyPM*<*h& zNZ4Y)+iWPTjAN&9h*|RU;7pgJ?DvBEO5ZW_M8vfbe2umaJ?b|gxkS0M#X?WBpBObf zic}Mk^~Wb&mgjc&NW;ZjJKdAd^f(b*+0lew|3~D*{xbT7@mO?t=zE|;DN*0QR;>lI z!eL(Y+5G&P!)_l!)cyP0)x5o_w9ALzr-Mmrv6?JmB1k(#2TL9LEm?uZ`o%M8R0S&= zOm7{Wm{>d1q6u2N76Ym=;W61*ygeUST1^)Z2RM`j7Z`sY${I{aTFmWDo0`v72BPneyYAoOJj@>-VL0pyCCWVR=gki;5B8eH4B) zZhQesXmDaN{lKfYKYGDZnwR-8hPqWjNot65FmPZlaI&dsrI^Od0<`z~#l*6aW6dsn=h>7A^6(#driDrc`b~Zy85fiL_cp-2x*HZFr z#o6*F^A`i2E9cdUbdN&}6l$jVeWbm;1K-<%Ov_y%p#M6KQ>h0ZrN_oM#$>H0e@Fah^aZm`sQgyBeUIXdE*Y$wF zSlLJfF|u+2B))F%;WzQMO9Tw+iFKt7W0#AW~E{6*heDjkP(UL)3ni2 zS~4^WF@oLjDH*HdXZjLzwQiV=@9yNy+A@Ahea%~={7%$m>Ewl$>qSDWn`YMJ<>Yp} z$M3E;8o#9xbkQvIcw3`+m?YXb`|)Y(^`I!2k6#+Gc^<*bDzUs-0YS&cLRUI0yhbNJ zBUkTwq?oY5#8DFuN>)7`wFXN^{V)pRS}?Z_3^(wSdgn*YWl*(j+PAUYMnY%6A{;%Q zYUW&eCG0bvx(x(t;FVt*j`?(@{5$fc_SfNbY;P+^8(yFUzc-%aUL%2EeUfwsdvDf0 z>B=88%>IZ=@cYkRi=(?>W{R%fsY~-*3B>P$Wv}Df;~VLteS8;SicXBCUpS9Dyp5*_M@={LdANVU9P`t>@+6x2S4 z8J>OU`x2qC*L+e^*%j+Nefi$Dv|LKHo{1Mo&z={}j-;#yE2eDRt?O;TWQ9S=eAYQQ zW=roIv2xZtzIZS>+$*oU*Gg}GScaUzAcqFDiDTnsK;fr3?phU__w^iXF(L7q!_7eU zYyABS>`0Z?pT>G+3_JWn^R|QE!NW{52HVW9V8~S~A|ftKnpkm6LO2|IW+ho*9Y= z27d=Ye^MTiF!cpZ8}zuSfq{YL!ZFXr_m`BFloGw(k$$C$ij+Ad}KuC!8z1Z_y+Z zod^dI8>PAP%yDLa)rxh<-6-40J#+qo|3po4>b20eJ=2=Egh>)M-kTi9`J0#!zk3|d z=|WskX-J2s2c{br=0tmVDR>XRue^{}+ob}d=+m600-qu`4fcaSNT`4iuDN;M6h)G&nNl5PC6Olf?4zQ%fF^8Z9$JX^FFZlyH5#DdK{pBMc&Plj) znPK&h8t;N;mMF6f+TQ2?K5GtG`3DSup+3XXnrTx2Vsz9h@Q!g$S|}9yfcX5R?wk#%-Cv-2sAx@#uKRYAtBZuhYqT%SQ=vLXHY<(_pu0qS zTe+&u7iK90rN_Bily{DejTwQFdplqv`{*%!N%{D354PA*tK$6QY>C%Y_s0{y-~+{K zZHA($+PbRopdAV8@%07(w^h=w3(e*Uusc=qZcH% zdNmsFGN-MaJWyDO6(3;{S6!um+~(-T4I10fCks*c-~n-D=QT=PUg>!vR*1;aeD)D8ZE*BG08&Bsn}de*S+*d+Vqw z)b3qWmLMevBA_%XARwS1or*{|EV=}wn?X^CX>J8|28#K6lcyMoHe1Bh}d?b|q> zu&omu)XtP_Y(CI$J)x$3!4SEGn_4plmdqrKvi%uqQb{c4f)s_lc>6SLyp0MSB0`RT zFcF-8iHY<&QRD#9(P-RUm(+RvSk7l;guIjUchlWge{Z{n2Wq&lL2nzC|ADw;^Zt8xPo10y1f3!_s z$ge2!>SDm^LM$qW8FwYL??9}_1&t0mIWS)@vo7ZHj<$=2Z*CPu_y*$@J{w7%xBVSF zme6JWi4S_-wVL5pN{R#tc^Azx4+cl$^sTt+KBv$FAe+f1)=*WYLwG2EA;~yR0IU?NoSY6Lq0l{VjQI-<-?r6O}1V05Bv5qB<}N` zr=Kh5J?U6sqRKa7=9yh7Uo3_cd4VR@iO1>Ka1sP6=2h^y3*J6>=OG|)HB5L<%frLG zdaSz9a09KIfeu~Z`FSm(b+P3WG&14jZI)~?@Y@uxC`g5^^ZlX=zBY-k%D=2n)};>M zrq7+$1W#yIO-yA0UgE?vLbCeCI9}eXxdJsRHYUrZ+q)7!{{7w#c7zCcJrr=IY$KlC zMV8__*W&~>B=33QQm_gwAoN36+}Z6;;m_zM!^7H8$s8zlHan8z_Z88aAlv9)hP=17 zjAi4;6nrKUuU2T9pXQX_Alx>?!^4Mv{d$p`*KY!X)obD$*P@xtXE-z%50%I8B7jHJ zs5Yr_EkZr)jx_%gVIR2)E~8-}Vs_&W5@A$_rWuzfWdY zX(uM`BW!)b`{6M2r=6*IqT}|&q0gC5@A-sYF`NRTae8epTOUkQ^^e`O>Jp2ExE0qM;}tf@Pmy#9zbYlwpGi@y`r zmBe+vKU1X|Lu}m2M$fhJo|f~lF+Dk5-tG9TF}=Q2Tr8(UOw&pQjIMYL0s{F*Wo)9` zK}6lX{h?YKc6kcn87Hi|C*%Xay4@diVBq*>YJ4dMulkpRypd1;8K18PvnqeTA23yB zsj{i+VLeLW<6~pp2;151iNXhax@&#W*4OX})OA7b`N*)aE}+&SQYSR~2r$l%2{oDH zw-*zQ`=gtfd_}e)vOP?v0P=j0N1rhRh)8BWHAl;fm{qqHYrvdzH_78!A zZo_*;Ile*BvLEE_kxsq>SNzkmr~!sQYnSo4tVJWk4>t2Km+c#)DEtpYk;%;%X~w7L zdT?M?bZRtGqbTIzfrbQ`WenhBdNtv=;u^5kbps-dd**LW*k_H{tz8rCXF4%Pt#T>Y z;Ou=$jTeemLdXtn#!BS&#DK|-D6LX$CVButV~+w8#CEkxs5kzqsGIrxoI&lxULIA> z7i&RrpQ_%|(3BsIXSbfEL2`OOTg6wTm-!=4{7#V3Rtk;6S;lu6@CFhyW8Hua$@XpO z*PF!VsI1$!S%V!jGwL8eFG6K!18kh#@d*>|b`5iuTbix9(C^{#^g_>agaVhJ@a!^_ z=Y1MOGYN4f;7+` z=>aYt(^}hpj)PmIjB8}H$9`uWU{hRwPy3%0&b=?-r$fut3u=bFR#tjTL7HW6W8+o8 z3VY*uAh>vLoe%b(9w4Lf9Fhf7_5YU_K-aSp+s$EZ4TJ{=NJQmqJ~zbUCCznuH-Em4 z+TfC3q1gF*;md<9ra9qU`l*sZwyy<=e6BSw!5O*A_l_rpP$Ko;{t0IH|EYgUZ5w8i z#Q!}6{=R-N7CHXSpkmWXUnfa$syA~#eVrboT(ZKjd^*S4J!mF*h zBke=?1i9o$X~v)EwDAd|fABX1O0$J_2_D~#*_@zEU_0^ynA%QRJ*LeD42-Z>)Ea(Z z@WOwOiSxqLEPQgDHQWSt!_?gq`f-~kI=OorA3kE_h4T_|U@GSl{D2LJuoO6sl4&33 z)Yp?w8GzI-0+^7AN=h!2O6=0uy3(CYPjK~Fk&Xmf|AR+Jp!BgY-A$_l$M31x{BgX| z{DB{wl>zkJ10lkix5MIxlb7@>KT}K6wmm|_$aGJH1Fe6}*Ov@5cZ!_@8CDJOzb@=t zU0rQ<&AzMx9jB{Er}gu`A~IT;Ng^2PzDQ-q$uvh8%bX5eziz{$NDA&4-ZxL{w+P%88r*P-kMLL=(vUzpYIaolQ+VV;e?Hc z_$n4K?;tP(0%WOdJEgF<4_2_J2FNr*^-2xE08;1l#F$*Fw(xbQ*;L^TS@dgN?>Jz# zbRoL($+aeEdS_q;HD+1N79eU>QTCl&My`9%?uvd3A)~ivJ@?~tr)Q}(#&69rq^qDM zbSHLt^okx$L6K13+84rnJZ8RmPxhs|t)$e~LusX1{R-QkwFdiD9?>rq$8ic{NUtoiK1@VqtGsMK1fznDaQa$m*_?Y5TC$8+jF?4;7_uoWCQ;%k2LSsE~2R z`OD22d)*z>+^r=b33GPd3L~#B91J=#o}~ihLxeRSBf-=;9}SIg0}!%)FlQ7dg@w7L zB&ZaFJOx?OTR~L#Xg#owm2ZIOTlnSB*luhpCpvA>Ixcvker_+U-Q$w=ME1L%Y<8A- zdrq#Q1!%PeDOp(}d-bQ9Pk@5|D_5LV#}n%zAwsGWVvUn=HH&cIoy}LU+wc)qcL~(2 zL`svM3NP?94Kj4YHx2o}U*T~=N332V<~dF_uyJ1FQ&<8*n@Notzv1{YCN>(+knR3( zlRSV>>^E0)?d`V?WK(PSvD6EH9pz?Bre-!=8po6I*B8**miJ9(iVWOd(d+B7+$L!{Jp~|1t`8f z+hqY0-VO&8)T;YJW@3*Hxoesr@?n0EF`Wp-GzRj9(ME~+u%e|v?U<~B7iyhj6VhSx zO80B_Zg91T!h&`A{{hs1(+S{4(lS=88})}s?gpP4*~AY3N6XQ9G!R4R&y$D5ss)8+ z+^#tRK<3ygbK9+tBj&P6ZwNr(kw=6Q9=91#N1}oa;i}+Z;nn zPwxw}mB40IC(G82j`0AStivhl;!^%v6cs&d!Q+ckwcas72rC?3^HVo3bA~V|F)33r zY6)RH1+39$-$jEzLY+FY0er|-FsbRemHa;_fDN{JWbP^VxLaOli2hg|J-0Mn|1H1O zgC~p+8jCfdE>%J$Hb?2Eeb2Ai- zO|Fw(h829=^l!SAU(O%cT)mhAywo^1Z%)4dtV<8mfWN4J-k(wqc)))pbNlidO0po0R#l&Z;M>2`)t5DS1_o#pifiG_6cvP)#yh|$}5dOqKLrb{f zvS3AZB8Uj^zb?AVTafxnEnLSBrE>2^V_?bf8*h{)Bwan&ZZNvu7;@qpXM!Mq?EY2i zQUoNq4vSugD?g+^z4vGW4rPpu;PZX#1x{th6Pkg6)HBml)tH}qz3kvC= zbOQ$ddFbT9zbNH~S3>=^+c2;!RYtV(f(a;!42PaRF?0PO70tXF@R?E`84dXrsI=-A z?`Sm4X^FU6Ft7~6y(M$o_X9`^R1e3F(l)+Zba`Jut2Z|lhgadElNz-8ILm86 zbtyc-CmU3+l`5HGtp0~ZlGAQP{9g_B-oI+FDu2W>+szrg{3xs{cMyl{Nw)T>f=sFN zkm;M@Q?U zpX|Lz<0B?xpuG41>Y@|>6j(KnXK8gPkrFE9DdWxz0Jc;*=!OG66#x<^0a?n3=o-ZS z`*{@@PZ?j^3MfLc3;;`0-F8T*8SW=x`m5-z4_ekMPs1-;I60qjPJp+LVJ_-biG)LZ zbWl`As_cg7IhZgJwAA6jL5lczPnrgw0{dZGbMpca(;Y_i#c~msp^Sgdv94MTK)zZrC8Tk=Rinqa&U;-o9ENlj>OYDFIaFzd+7!P|$p`B^I4~rdOkS{?P z7ZeQE@LvKC*X}mS*fuj1l;byNdCo_yP_$9C@p?5Aoqf^~uzQHIz_;&#t-HoT_EdX- z_cX2!boixdJWCzDcsnh;^9x%w9a5H0ecyjbafy!B$EYM0EVEs*oO*UJ3>7=QD<|SB zZ5i1ixd&#L>0I$NlUZ Uc?qo?z0ZjCq@dlTWv9pl~aarM|CSJ8yri{DeoS~D@% zwpX8ti={PPsPW(n#&$kaBO-<={;<#_$vXtbG=n2UHx?;S7qFU_08{Ov+eTz|yM?wi zs-G8Gd;EOA?S*o%L+*GcP{meQ>=g_2`&uzyFFPZh^ofj8hN<;{P9ECU!inl8!uu6kf#An&R6mf$v^ z;g0gXq2J2jh4lo_2*8eSSDDt!Kek|DU|_7M&0j?DLCa;eKu(yA9OG z@N6#p+@uPyy+l)kjVH{P@3jmBSv7K=daj05PBV_lk<;^c#s))ATskRP;V64$K&?yM zqj;D2Wugm$W| z@QT?-Xx>hqJGEvzV@ceyLuA`InrEKUU6zjaNe^Cj3G6ly^|{OY`kplY{#Q z_t4HdW20@Jt96W;jY(L)emBo9(`63GBkJ~9X9#ejY^ zIvxQpwD;J4pu~2yfXf@gQ~GJ{@R2LHzl6^?ZRZ~X?UB!zI|!%u!!Ybxp+roFsY-@m zca4ka`RzfRI~TmoTvdFsP}S2eFXDxkC1vsn&;!72W^W%fs4&e&ap}1OogZZLHP(@2 zxim7=%vE^?(457_Q}nN5Gq#4=2?H=1Xz<*N_D1GK*XLvp!MRt~TJs4YCxBtkkP19t z6{4$AcaU%aR_Z8(vc){%s3+htzaOcj<*ImdQrYOYl}2362Nw=i4&16bIy_uy!;=mx zbc5%n+-zWxek`Xilc3A|dDG{rT>_)GXLYe_$M1&XPT1WAqS&Jg-Lzbr`upU25j#7- zZAg>^QMD<9JDK2czV2>fnrEj@-M9?DhAV_LEgUrXj4wo{>OBSG@RX|3bENd5@cmgv zF|Z}ti%Rn)iSh>Hr!YbqNw4r5Bh3U|FjW zM=0MJN+o{0Qs;OC4lzNOL==NHwVH=H)M9&lEL62F-}LY!{A-gGuC|U+bLG_?J!LZI z8yYm{R7*8x(iKDb^$cgszOf4kC~o}EJR`}UCRvqVU`y8(oNEZqX=~Y{^~f~0gpCtv zB7$_(H;mtnk_-?z`qwD`J2B(``TsqB1liI#F38BB&SiR+#PTjjZ5N!@Givfd(Pw2Y zAg1+?eYv^0tH58nl8aYwTTl$}YjSo78_&G-5ehp86yf1wXG&wud(D$V1KP~gSW}z_se`w zk;0tCo}$G3duIhKFMK`RC-;Vw_HNK{WIFTSabWZO7H~bwSdJRun#%rS^G8I|U^;x* zix4Gv|94Tsn?$t?I0C0?1H^#!zyNcM=~o&$ce;0kNjCy9ExvQwtcfTphCd>l9(lrY zzb$4nc*3)~Uw5{0XRcl?!R1gin%mxdW$|SDO7H>ibHrsZEaKj0r>74xD(!?K_P6W) z9>DmHPJy%lsaevbL60}RR+f%#X30+Av3$uoYsfCRdz#Mgl_of8KGmhZ;SQ0M*R(V1 zRaF5rj4Z1SK#l4}XR1mo8MhGkXsV!#e91q@6hOl?UW)}jrTe0Y%WE_BS`--QMAR=@ z767wYHH;?J8^05tkkq1Il5_fF=0JG%UB<#X)#Z?2J6pJ_CcemX zFyUIvYCcP-pysswn0|Gf*E;re+UKTfz_(KNF0Vk+@}cYMn!Q~c=~FQ8*D2jw{XbX9 z|5h%U#Euj>rZWQ+wI%}y-nGRmzk*;Ifd$avBEhpXaxr{+q76qpRYoHjDC>CO`-)#1 z!(&Uu0#jdD<-8kr)fNau8+v=Eo^*RSU3?YT;i8+f1E=NlpQPZEE%vvMrdCTGS-@`=M(%p2v9%k z1m$7W3A;PXC2O)&zcG+TU;h>dj;e!{0!`S8m)o{Wa3j7-iEYH4$PaMWdHOoL3GhVz3WK;$6$t`{Z)7A2|)Ztn8+A(cgmozf#U++Kzq);%&DS+{1W9HG zp%0?4v6=?JGM<#Pum~ToZ+tgie~fzIly@7vs@MH%Q80~y{dc8unOUiK@Q*qp1OM8W zzxfLogQbo6%)-ja*}D2`phjj}qx{CxEtJ(428Ke(5>d<&AEX&Wc1S7$;1jIt*+F z;G_DveGHF7G1Ihinjz$mmr^fq4JLZIUEB~=$hBxOSI1fPlAm7-Fx#kp&`6J8%1Ha( zwllfms^9({;dp-Ql?}3GN$K=FwRHfDA)#<|QaOBzq%HLZm+FFxuXs{uvqBwG|3HFB z5HJ%)?xBVD{213lf&dku^_Zg4W=2$yuGEh2dfPTYv=tg4P6gn}Yg~UOWzGNq|7!Rt zRH}{-V1C7-;A1}4ybXh!hDea)?RXB?T4}Sy4l(r@ z*e^(+SzqqHJB{iG4?9s7AAfI8&$|b2rE_mR9}y7vUpPx#GwJ@B(A*ct+?2iOT%PSOScshPX_ZIRra3BQ*1;=@&RtgR4s6>_-G}1pUQS*n z;U0$L35#2)6cpT&$MPJ`Oi3N6Qj!mOzrTyDKnf@U!{+5e<%`X|!Qm}r5ZshqmLdIX zt3=%W*Pq0|t=xi#5XN5XFl$qzY-<_+UwwS=X1pLs^1np22!b#f3 zOfH32O~4lI+5E+L<#T#^`aDC!^~)lB*-vEdS5~ACi@?w2g-N4wSbRJK@CGl%ek>#Y zESQxUJmDZd=*x`bdi){47;+{xeTdDg-Xii>H5kjVhQQWN3F84bVW*-wg?nho^rX-6 zqJML-kmdqNR*PF%h#L;@y`&(j5{tU>}8- z*V_F<3enQqw?M~1Yh-pST0;e@oU9TN`>Y3AvC%eLM23^&PbZ-?8*0=uq2nI_38<5u zzu6fW!sA3nU;n%c;{_9nTN8hY2Cc4|3Nh@tjJ5)x4_$xWR}o zZOB#n&5ux;BwUw|UaxD_3?~aYa)~sVX_OYkLAr0Jyh0T@MQeEY9H%j`mElv+&8_>$ zs4HtL;yP}Ak5&lOZZmr;Gz~awf*2D!G4Ab#R`h=|GP4Mil5S$=W*5)s&x$aDeiyI{ zQXAht=@ulf5EVB##qrbwdeOqu{}Hfbg~`YoTO-D}KCqm8svqejjoc@;^F8>A_SNkB zLSN9)2dXNR_Ww+F3%G$O3iB`}U0vN%i^`0wazr!sk9nN;3E03|gSfqW>w+|+KR%6( zASAN6?m_%F4o_GI$+eZUJY030gm9uTZ3!} znU3|~TT~%)YJk6n_A=H=>9sDqAV0X*BGCno|v<;wc+A~SP!Z8!LS#6xxD z5j@SwpO^aF;4bXNtIy3^IB#1o)dyxykxAN7e2vC6r-O@nX0W+plw#-)oe5>5>?T(X z{`T0ZGX=z?=f)%^8v}r6!5@iWZ&3Oh1K$qIh!3*jZc-87 zh!tt|X>Nk6dOwIA6-YVmu_ubnW(ah7VPHLm%28EM9(b|Y+Len7>gHqP$z0|>W_d4F zV_EVja3s z0h66awe{ow%sb>&6^gBgfVg)Rke!6zO~ zK2bFaJW+bWg@}75bA0bltEdYE5^+B@5^RaW9W|9f@Q~G~>$7#sTnyOeFEd>^KC9395-?mIIJF2g z`e;kcLo`4!p|f}QM}A-3t@|gh#!1c{;F?%-F*q2b2Z);PyH5NU2UFzYv4xTt!jJs9 zd*k~kv8&8nG!x6rEy7FUn4=pfuF6Pmr3R#`;iYQu1_eWb?yiScD)G_I#8UN+nT*WC zHSV+<%eR=ST-@muw1bnE(I}qSqw~GD2?fAH)uR~bEZ{T}Hx)zw+B}h`s6>)yj_Dn8D46Iwhbu~ylb=*aK*!;`X^hAh z@_*|IT(iO?P;D7coxINkC;s-W zF7nBWF6)q^kky|qdDis|?9-V}payeW1%3JJluf2 zfm%~*U{OP5?&i!iR+tM_;=OPQ|>xq%Ud>2{MFu^~Zl4kKhbRYDnQ%oE3sGY^= z0Zh;`KJ(B2wwYX14iiOI(C$W_U_=%O`j66>6oX!_%+V$CS ze;y>Q3;-vbOyd6A2MGjxw2e(oZ7E5IRsK2C?;0|}o(_^j{!Tkjxu-g}v$-_EOkb;Z z5Nlo|3RrgOk%@{G(377Jcvh>`nt3t#jF`fO?Ij0o^!4?vTd~+}>2tW?G1zOf&<B5)bsB{?@Lqry&jicBIhe`t0ou$zsb| zMP&2U9S@5)Q? z&z0`9`5L=U(ex8F=mW3-aadbf%e`amcxU%W>8toH8k|=$QSnUd(wFYmGhKzq+ZV0m zHxwXX2&+FAX!QXqP)i~Aru6cZswN|LuJL`a1;?3ZQ4H$E;jj&H>y+-qH-w;vqP!*Z zHuD3tqAZ}+Gz;!VbTfKBy4THFe$LI^R%wz+!j&d-!{Ml>ifuYX6OcAL&FIyNk*;m{ zcIJ}gwva9Q@Z$oDMPafa)ikNrZ-EtTuDn~tu^sFytjfhAhziW5fiVT_oAZt!OlI(i z?mdO|?{k;tflgqpBUE6w0yM8bFRJ$DNBR{=SG2KkUb$(ICGt3)3d>byiz+w>z|Px> zv>D|Yql;_o=T#d&_@F6#d-60>%h3bhcqzhp-r@(W7IJFi3aYkq<$p%R0&mM7Tf`zX zT|vB1k0!@XzVv{X8dfp1TU}P2tCuGZ>LHX6S2IE+2(NYN!~cto^1uHQ&$rK#E|RPB zunosgHLfe(Oo7vWd3rN21odgyBTa_YihWzznYilwawBh z1)T-2)VL%IeIAK-tDLEKDI_T=Z zO3uWhxF>qGxT4g=(PFpEq%mhs!hzhcvy&D)GY#VPky)qtgEDt)_)kCtRc}u?{TE3& zN+dw#RVm7w`T+Bn|ALQ9<>lqoToTQ(_;-xoAH3svb>&jMIYRd+o7#lo+FI#fth79{y9)cS%{s-u1W99iR`A=P&*U@Qoqj zzF2mqVyqE`i$DXm9%rTr5j$wmH=@^~B{P$>v!xRgRI!zX5um4Gb2^ogSZyh1n;mqM zT2yXY7B$>Mvn{U_+dc`bDG;Pl0HilQp7hQ_Ivd15r3bW$S$5vuXfC@=_Iic>M51hu zJpmNH`szP3G}5*N+)1CN>OW4177N+R*=v6VE`NQW{sNyuc4fhx3b2|4UT3?$l)}10 zf1c~(5vOZhq3PAHl&rG7j21);7G>WCw-KC@9&qnWjgZHTWKg5_vt7K;)mfA;>$LbR|;Jd;48^IYQMms3*@(aWq}d+f$Qrad6$#mmC;h1EV?9vyv@E>)a+?}-zu)YhB#2DG1% z0Oa^)k|y56Bl9BxZ?*o#8{R$OK}Pu@_rTKcF=_(@Vk6~Lrm09%Z|~P&ZhjLRaH^O( zb{6Uc_Z#zyW_m$^qtRk-HtFS_!>k&ag zPP6oypZNGYg;}Hj5qw!5q|DroH8mEuMA9wa#;>k!omWfm9QN{mT3+|O{5to1&vz8} z@xtRfjuX?xy267iAlW_;>|(DCALq{Ou-KCqa?%*QFBkbSXI2e?=sO!1Ed+&Ij#mw4 z@49t~^ajzX{ZLkv>_e891v&)M(&CGjwfRsOt`$V3MNUSS;Nb zdLPxF#8w^Nf5iCRJ;^p^i+um;DxCev8m>G15H4Qk;o*zw0t6Qhpu8!$m;JX#G9R=p zxXjkT&W=ZA|7j^;${a28?v+c~RB}(+=9@gPk$qYRD6X~NF z=D0v@0XH{3goXRAhJwmBmbvPC`2BS6pOR@0vpt2V&^~ck4ZDcW zZr)kk0b}CnIglZGtC`1d?&_c0V4&@oJjFo!Iuupo16DR&_j8RE-X|>E>op zrGM~i*DU=2MUK*tH2kOP->l*EeLBJJnUI=FK%P`bK!b$p~#SiTG#?^}TH zi{x)7VcZ8CAlI9sb8P|C_cheXeUU3q1^PAb8_%P)Q(w1xnzRJFQy%^~508oEeDONq zEmORVS73Vj1%Vv)%g9%#`JwIzXv7m6(PDoisUzjuS#WiefDzBE?(yvCg+u2+Utb$=>HnVb?J)GGO0j#) zdW4P{GBU{X<0&(nhC0tzE$Li2w+a_!#~YGXOPUZW8G#Jo{_4zAj(e4)cGXhuz>X#g z#DMcqbJgC7At-s9FEchJB8 zi!TA|hl3CZh+OGOU|=JHKyU7_a>j<08~Y)L>cl>~+S?Jnwy-NVi>%mK@&up&%N7Mcua!xc;}aX5p11NW6>2uP`8~vWwfY@C4nkvog9TC^ z6XWr%AxXQX>q3*~kVucUk@BDN@T7u3?8norH%Vm`X9=*GtQ(-mCmgTWY zxTM?a=2a&OtvdD#fw-kZuWb^APVYY8NxGCPK}XwRpvCMF)%-X-A{X+5p(F3ouBf0O z`$PVSS4@m*`}5zmlDTyx(o?JW;bOMW6s^j0Po^V-+F-QJ4@5h&hw;`)g$rmYC?4I( zMT6|nOKz+tdw5W;-Tpca5kAIxVjyYt>6$nfX5mGY>rLe88(f%12W8`gcWvqWEc%j(J&A&-Qpp@V5FZVtENFoz{U zV2-xF;Z6enrEHcrykXI$kT|z?-*x^BvoSZHP;Uk)8(t9Wv>2-;KN9e~0e_l5)aeZY zO$Y>1vhffJ>D(E#Hy~TLKxQy^a^eEP%s}#NN1304L39FOT%b;k!+j%R(_qwoW0aug zor{JB6c_DBB#sxE?3NQsN7~{xO(C(LKC^Ca(B;3?2*i}f<|D^*O3WAuU7rGMo*_A8 zWIYmny|m`ly!IH@`H!s^aG8&WF+z4y*fp?SZ;*p6tm@=V#O@zSWYHkicIP=MF4mdV zE*HgoV*;%h%X0#I`mqjl9pmYyxdWPMhw_|h4oz= z`1CTG7|Xp~W+y~K%5LjojXi)l;_u|Ewq|s=GS>nh*{ajM=A;>kx+3@WD#dc;e3gg- z_XmljiJ$AFlF=*M^DP7Q6x37#Jek5%=J(hh;W&Wp^RUxT63-Lus|%2zU5vN+;oU_M z^}W5soY47M^0chqEcj}sr7BEE$q4r(%v`vU(u+e!o#`c$^-{n61WKRUI325gEf}G0uYd3kvi;06^L43Hi7{>uHKB|nA%fYc6zt|=|U+u(7* znlyqh)a;+Ve?H#(*n^embr4eCui$sAd8b(G&ZLYV=FbrCbrP(VMovH=%Xg?mZI|Mu z)Ec(#Sst(3b*5IM8wY$;zWjB~!F+`oF(PL0rB=g?gJTf3&t1SZ^gzRYo?E(fr3b36 z8V&6}IUflKRvXv+!B>rS%b<$lEJe4o8MvQhI*e{-tHiy?nA;xWb@%mp7BOrPe11s4 z&S!)RfqeX(7?I#VmfO&>PTwF<8^k+Q>+(SEBq9mh*pybsqki&kCrfFAqr5 z0&g{pQH?DoGGGZ09~9|y%{B6k*q*dtl<%NriZ|9HWMibgvEW%&)sbLuG%Mpo$AGULgY;C`r17? z)(J)E!cq%c)Kof!Im*msJK+nbO`*J9EEnzq$(h5z4<7_IbKm(e(T9wc64t2p74gbk zU-U7IRIbI5XuSO<%Ag>y(C(i1VaWTT?aK|XzT;oMG-nn1`miSz1cib+!8~z}t9P!{ zw>nr0uU3d9w77G7WXqk2)yu~&E(SG8U!8}287!xmwm4IEQU&ka*0dbhPHsYc(PRXg zj!DQw|J`u!foux!GuYg{*F*0=u0D`SRgumRg7`iE`@~%kO`$Gv0qh?noN^8BCf!U| zT66;0sR~mch&u`f=c0)tO!!Ceu)Vss?zwa}9sq%Oij1AItEbn0;heW@m}p+Qs9h)K z|L);Y*w|S1`8<$YJ5k%q!mW9!ZA+86?^$w@D46WSWbK~JR#`m*ykU*PuBY0Enf%w? zx*nSQYlVcs!mzn?fG@z`-`}ZnfODPXKGB=31TAKbzuk86B?OZCA@Ci*3d<+<=NlgK zN1A;}g>ji;*P@T7>BcjEwcpMDFcF9Ca6Rh2Q8r$v6&$JM#<_`t{NCl+vu9X|4A+mg z!{GLv*TDk0J#s59SKbc|MSP||`Dk>bRppqJeQ%1uKJV(Y`0~t$G|>3KW`9InT(pcw{i%Sgd~Prn1up@1@r%<^CQqpG@0c?lKdjF)g!h zx7gpxVkPp{_N&pmY}@t=*xC8S>}syhs_ua-EUTjGlWa{%JKqT&@nK|Sa$UwfGieT7 z6q-03F&G}1Hv}?d_Bqr15<=CkxG~(oG%S}w--DBt#@bJ3I(KvT5QEurFYrrIZ4P$i z%Zp-7%gWwtue-+dVrA}^(CDhOHYAP;txTuIB}W~^2H;MCgk&?n)ww19z)%ji6(4k1 zfghGE%>Qb%yMz853#k~rD0|i-oS~0TH_C9L546}1wFMHv$Pb17ECFqAygvZw)j}rj z?;E#L`_1imNVrGlw_eowl@64et^B9P{!l+)hYZm68eh&N6_C2OaPsg0LBX1s`wSq5EBKw+)AIUftjqV zuM4&zefz;0>3au=yjvVLQ~QKUzPRe{Z5K9n07Bpv%JxtyDln)~A7F}R%=^Xv)27#w z0LfGI2Q?+PGe{H@u5;p}3rC7hmGovdHH*P5=uVY-(p+n&L@EEI$(7Aoi~I!>Q)e8f zO{kPW)956Ld>C45iGJc@?mk<}2vTcEOh{1P($B8z9o}EeUyJX`FtI)LU(3sRn2v3_ zIg4+`ZQl|ij6KqE8w&Mr^^C5Z50oOse0}iwz~Lyra?!f_d+L-~drvrr9QwG+$+>l$ z-4wZuqDEjziM31B*|RFeC?oko`OPZZkQWr%su_-CU=mZUWkp3oswCPra90lEWrc~D z&0?Q~%_meQkCixnwdOXFtu;VZ&7AzbV>MaCE;~aRr&>D}cd?uo%_T+yzfp`$^tYGF zYF(2mKF6YsD>=uU)(^nStg{f*q%E>KHRl+!Nj^F|=-RYTYFvmoLaZLKdj0beEh@_& zf?IBidQ~a0`}4o@u|0DA6846hdiM3Ba`S~a6#k94Ro;fDAqMJ^)azM{N$IsZL}r{%oJQ&`p^IV7EX+w-3;GfBSc^uRR3TbED%V;O>s>Fa3jd$q7i%TnO*J4a4KJ0XF{H?p+1SgT|*67yO)@4d{$jGByWZ)!a zD2IvGm6esB->hqM>xGS)@a|_(^oMOpVd6s!f9#%cJ`|uh+@D6x;yuLb%q>YzV}^f*A3{MG)I6-;n{^ z7%u1D5i`}^pL@s)Ev$8;+lzx?*4S@jyjC_hf77^6ged7hk=l*(H@VklKnUAEba5XEyeN6a zPB>{~bm3B}YWDK6eDR!k^ffY2qS;QGbEvI{! zWkcI~#yhqVGlEo@SW|cn()0eb-)t0Q60sG7cK?CmrAyMTT^F2Txm^)i?vsqs>Ote~ zT#?*-WVvDdj5lfe)#12Sv?STASu^sUq2@kMO9g4}^IAs{fIwigd)VZ*uYw-)pv8j+ zu|&Uju+iN;PqiF_iYlE07>*P<_Cj6Cjmsb6Zv>^7P5&NK(n?M#!C=h9tQ@F&A{7ay zl?-{dqh~0{AE!P37Uep3e3g+20;rL!Z6(9YNmeXNXkxK^p zB$~x;q=%&?gCG0~9coFe;IBfn}XznV%Y~WdihsK#^M$ZUx4v#=RH8;48_^zQiKAu z?>D<*Zm1P16U&{ik@uA2UxqBw?vnML0|*zJU}C;LqvJI4)2l{@_4UG+P*ZpJRns81 z5uvh$OONR=d1as^WwT8QW+NQdKCI3%v zO?rgnx!CHX`+K2z%4C?Pa1 zvz|)-9)q3M2z>ii4uhkoKVE*M_`Man(W{%AqTUV zPGxmivpbfZB34HzE2?&1loeNfz>PK4UKEVw(7KdTx7236|NFrk-3|#;hAD4!y#D43 ztk~ZNG`H<-0gRvJ{q7;CryE&z1)2>@;(dviXvX353?rF3JHLuwynLy!n%n4HmboCy zSG>gGYmFbR?u*=ag=bh#@{#%VgHK=<)I6=3fjvIbc`|zJt3XAet@IAky)q-lkpx9Y z%j!f=;Acd|u4UL8yVP%ngyo+T0R5jKxn958bSZXhxa9Gg6bwhlQ<(!7W22h4hWkkU}kQa##elg z3M|+mPm_TM(Hq%nodh-N*SI(v!|4Ic3goHv2{)TqGw?BQUo;DJWpDy_IYjnuZSjnWWZij>Fo@MbG>tP=H*(v>i&P32|6x%fciT%#&vsqvH zkeHa9uXov=(r^>okhwz|uS!rR=x~E}r|lP3Begm}2t*K}rVD;yV+jsxxfBN4n`{~u zYkBmjbDBY?>g-Kn`UaKT4&7Pj(}{2givW3aU%h8knTlmO+J3fi`p%0vbe*E{a#pi% z7xVE`6g`(t=R5mPZ@);eD%oKiXykE`{*q0)nHo=it`sByQ{8BRJbm%^x^UeXiVhqS24CU5Q3dvHXlp>E`99s;cdSkzAikUYKXHU$3_S|R{p2=O4mdBMP- zNNh3Qo_2$n-l*|Er9A(B=f5@87+z;K8;3?PCG|38k$5jGYUwIe^7k} za2F6xZ#?)LU?={W-Rxi&c>Ww(?#_8 zTV9?k1mX`P-4T(@ZWWzfPl`9;R39^D*P^0Jx>Yc(H?h~?(^Ro?;YIRP(o*%sr;k|9 zBzJ2n?j!+ZAtzuyp(V3xAgGhs|AMKoDMg5B0eI@Pw5%rxf{PePzV)FZXKE)d6{IxDe+=AI!y;Taj9^v>(|8O}~c|GS~ zjBj14mJGmOt8Uw)AaWVi2ExpEZX8sSVvBl$$ZxSxe&O?|YAGH~9tIPOoxCB})E|&M zsj=^Up!v^R9ilsWKBv@{pLd^rLGk^0ez$R%GPo=Za*uv^?Jd(WXb4mVFX-qnSZmT- z@d*e%UY#A(eEX+J-WC5-B<+tEe253q_KD|Dr`Y4F&9{caCRQf?AL8CRs;af^7pA*Q zx+O$f=~x1i3MkTDQc}_&EHDT`x*H^=r6omLNh#?@x*OIy_j1R3&KcwR-e>zh-#CBl zJ=lBbVy-!_d0+91YvhPI#IfTfB8OfpjHPo%8g|CwzB3&H$PU$&=e#31~P z=YusZuuKfoIxXjgXt(!k>b?3zeM5EE8P7p{>v>xEQ6Vq^+dQ-RN}uQj<=kc4By{7Hxkk5kNu?MdUJc zwCGYz-7E@))?Nnhph?P3qFzdBUtpo-2|SNv-Z`v00UAEeFV{AC^Pl9rvLBpUS!Jbh z@|!=mT~HsT*pYToUR++JTxQYxe&-+0>Wp|QQUkqXgQ50y-PNo!jH=gPcRTi~$nJX{ zz6JdQH8o))a*>~WBO{e4e%~;R?Qx&`<70unUe41jK9`LBRj~g&#IV7Ay(;=4 zq?IO8cAK^N#koLZv#VgQpbwKQHr50kXScG)!navCq~^V12zJg!T9f_5!)7c{`s!rpv-Q6IXolfJB-lP?rcHJTCx5W zLw;4&6tyKEh65W4?I*jwDT-803QNXPvW)M{mf~kP(k2F&i+%9oxz}%~`G&!&Z>0Xl z>MFB%iA}ahMX5Z%*54|}DTY1wbx6kWk(V#7#1p^nL_=#+FB8DCG5k8iucP_PqY%U z9pUtp!YlK6taPV!pm(If>fvB?nmS0UD>t@t?LC3}?WrV@Hf6xDcf@3wjzBLWW7QG) zyYcr8ZCPJe-^Ql!LP+u@FNrLpIhg6na!9?3gj6#An(4|=$+94~0Iv!A?(y5yOc169 zx6MF+VEOD&%a(akJ@yhHSYV~Pc!V{I#~DnF!H03ZIw?o6?6?!;ee|%)Ac$Hzd0_%J zl=H(=udL~WTIy==i#Ku*9@R^J^UjgOLl36KTg@n3sF(yJ;&Yd{8F+qw#NQb;GIBk= zumkP4V;}wag~{Agt|bN!Ic5}jiB^ZorSvTAr5sgR*LY^ia;F8E7|Cc|2;>C2w6L&~ zB?5-K>BRT=d!>nsMWz*uBDH=_)`@dTvX$UajK%RTkM`Y-t?j-24Rfc}By-@8_I?@F zKSS8D(@l8#r3%j{b!^ne5Mma2M#+cR9t9~>y{$*@I~Yu7E{^ri5P*i~YLb=Ha+Jd2 zvGCmICXa8Bg#I*5{jr7k_3sF(63{X+v$IQyr|CP>a^$#TGn%Y2RF*z~5X-Z=b1ao65ksZxSrpp|=(y@N ztNIpsdD$@?=%F>Sv6&yWuyf`04O+&02Kx@X7KByS zOUz$GgaLa5STOx2o{+hMmP5Mhx4-1Ik5Ue!0*mJol`I z8HJgCuVL=G)W{b&488(>U2Ai2*s1zZJGX(r(R=I90xyyG1%5_&_`yUfuCE`3q-AVT zL%ZlaI9{>xzku~2D7qp`VS-1hKU>as#EUfL)dbl6uJ*g|7kj3E5^R?d=p(yt~GvDT?U1-7fI?Yyw;6Jc$!>wWV9}JsDMtJ z;ST-U>G>=6J_UL&G)-Ft>m@`)ZX{-V_86R>d^U+8hbSCan zTk=kAkuuf!k8=0j^KSbYRFLS&Qs1sMyV53lUCvlfl}ZW>YdE(eV`loC?$m93C~{iQ zO|UhM0NWoIr(Fg{2X;a7UW$|@y#LS*Kks3aYVOkTx;g+1=B2TjLEwQx|CxWTcxoc- ze(o??1xtyW&qQ)vgXb68#U+jWG0eL@HK8j44pgxrEK}!mSz=PTKN3_rJnZ%kzpHKH z5)%*#bknJEN7X@hm8k(8rgy-6gB5%3motCxY&&t(-P9l$o z?v>!vp8O2wOqdF>_YvX<2+*SyE9OS?QWST=S!E0c*QDHlJEBJFsFrLnj5D^5;%R+eX6@gfR(W92j9q~pm zXZSf$!SXG<_!x!tj2WDDRYaXrnbNc8=S%G;rwTXS{m9A5>wp8&Bi+I@Atr8~y zJG}SUot^a8ZF86?M+QLUXRu|;S4MU>*8BTIVJ2bW+RsfE`{icvI~a|D6}A5UXFUc` zVC9f9NXFK#d)>nH04=W{S2R~A)mIc&Oe4I!ORSrw$mMF;4<;st$K zpy#kR_>0Ga-n8OM5`4;?x7CxwGc-T8N*d8$q zH<+3Ctao?syaos`eW|er0fo`W_vOLFsn8)yk+b;#k!_EBvyBm&!7N$Koc#Qy5e>Eb zDMI#qU=|8IZ}2w9>|66Pgw0{o3RPGETT-{dIYf;LS0X|dgad#PifVAJE80A>6v!fo z$@#+zhN?R~{>QBLqSkr7nW<$vKK-W|ym+}C=Lk4edPC*D+zJg9OUH7vu%yTOVW>&9* zNsb+e!U4V@Qbig6TZBh{wCDu?7i+d$yu4*D*Pl|JRu$PIpix8ZJOz{xW{^f7;F%ly z9&6g)_5ltFn=>h-9fB0+y_)%-_}of3e@YF01{ zW}2zbdpYTGK;4#jy)DyNu}*3^(yYeV>hgW99gv8s926GbqIPBRRkpgE0(AB>V8WrA zcRM(E;$j8MOc;91SUTv!Jg4ky7vlA;g!EKRH_E4gMpfX&SDLwO%&&JH7*D;G( zXlS&Cj~2yFog)qwWwHd@OE8ywsR4H=!_|grWrcMIjErrYs43yo ze>U@&>DL?C`H>ZN7;qWhEz_b!@HMtv8N2yf;?%irwBez*G7e6h=cBjD8YT0M9jIGW zR6qI8aYkV!CF*xtEQ5bV(4qc1nom60xJiFiN5So~L1q;D!%qdr5ak%Wo;p4!Jm`o~Eh5n!GI*NGgDAN@T&p8+>5l*jOs^}PV6;c`74 zV!Vk)FF^`kqv8%!1Vy$dhF!lT@smW)a6~q;@GQnPm|rol)U-(7s@T5-1l#>dyR!R| z`@qo&n?*h>*!z=~;^5nB7odors9X~^Pj73J2{!#yODgNSFMYXH+0;~_iH&`=mM~Cm zF{S*BXDTpF)93iQhN@)nWsP;xDb~}&Uj|8s$ufUEKZ|fjc zgBc~MFofKOqolO6|KRye>PMKwgg?dhnJrwmD8;cJDT@BOfxI}uX3_2O8NGYou2mjA z(jo|AAxE!Ii%d@@e`GXTP0qnHgOX50p)2}BWUWkgs8Cx~&PJCh8l<*B6_*=Ci9&VU zPyL#|Kgpl1(091ymsrja`!vk>)aKF6aH!Y&IiCOtF!}l)6-?#ZGYz$>Vb*2~#UQuc z-uQY)iIEsB+A=kSX%#F#DY=hE@|l0k>#b)i5CSp=rfIn%;^VW>{~jMhr=+sG2=^eW zTQ|m48bRAOQEitX0KsYIY6^)hb5}^%o8=J~rxBj@WEmb#UMtO?b=#M$HG`*0L#4Dz z^j0`+^{Zd`GM-TNChms`_CxYj(ePr%WM5uv0EtlVJoG z@v}KIV60?1Z^V^~8C)rex<48F92FNzc05j-TA5A8b388fJzOE8S0;}|sO9^exQ(Mbmfp zpyqBzP4K1mZoH9)eo>vL+zKwuJTscF@*Rx>>84Igx&*(w4BaZCd#oWtx>ew&W$xHP z0Gc&hyzR*wCT$~3-F=R;c1~QSwGXB3b^uC59}vEqF+``}_6^UK<~l<+DC1-dt#Q`} zR5#6Xo)3R(SO5l=G5!-h6F}pNveoy)l>-bctJkZ|aac|8QF?^><8SM}1aev%^X}y2 zLFwokdAkwbH}2j4&I zjDtPS!{d0@F#i&l&!iQ}#pc``3_?Rn$C1rp$MuxEMwQzZ1P5&9ue!^GNhBL z!LV1dDv}pj(|1P;xH-pAI0Z3(A#FdcNIJ-hY8>tl3F4}kj0tvjs3=0vP?h66!>_0k z#Fw$i(x2mO**1;Pf^&tiwm1q0De;Q=9fN~t3H>Y}HDC5}_?$;G%wK8x%b=kNe19I5 zo{SzOc^|GMY2c6#D=IC4SU%*GgFpq0s$KB@h@A5Pwp*!)ZZ6%L^XgI>8n*l#xJgCDW1kpm764nC=SG~(Zgh?9ok*Ye;2 zV(HCR=cl9bHbyIE(ibI7VfWyUQv0GbVy@ON1P)?wr{)M_DZRIURAPcM;|tg(Tn@a8 z$G<9#Spa`lX+V#XfSGyv0q~4Tlj@ciXzRxofMaVLa1%?b0?E#}=wu*>-W!WX=-J>y z=1^N=cPA0r3$Y1l_i9-e@U{}^j zdDQx6mJ+7udwhByk}3KLxX)#mmlHA56BAmEIuk{F5{$S2u(*1LLU~bFb6FcM&B)G? z?d(IlQGyzx)t@Ho9UJKv_b~4I#5os`poJ!qVUp~8;6Q4WA{hS^jaQ#=54OC&NZ)2Pb4L@aGE3vcWdpIt2uwcJ_koioFdscrB z)$hz(@WZVlnYRg1Nde86Zl9HwE`fsPy4p(-Tjx$>+&EPa4`XD;PHQKwH%p(x;gq;g z!f5sY@Sl{}JatD^1N{|5hVXZLl_<3K&YnE?Mol3FCcih7Y;0Umf9j{2l&NU9Rs5H} zeSG-67^l{aF4dn5(Ok=K@;Nm-+4@0r z56cav^&n2q%nc0}hF zgXnDHHr((C=giPHJ9fD7tk6cWkINscS*NGRk-z$8M?q1^XMZ7r>ANDN1fg*i{6-GP z64S@)>=R99=yFe5$1QI$1(4lbjQrr|udD$*k>6l9L%XZ|Buncx`G|4#_w^K3fs3Pi zDW+c{$MYRu`5Z9@IAMc2UZ>WUu-Q=WHC@Y=rd^Tk$dl2Z7Q+Iul=50)lWq=gP=hG4 z$6Ln~m8yeM-mHAlcWdIFN3=H^XN$X^<_RUK-#*?hbYG~?P8>1kADuKuBH1pRFV=# zwrOi=W!KeFYWjt#E`8=7mJkpSP*+#?|Mu;hVhk2ndR*5%9dgroHc~zml!vMus@R~} zmvrv!feMKiO(!;1p)?ekpHv3H!@eBZ$2feEm8~2UdhbQUmf%}YV!1h%bu{I8{TBOm zNUqUd*v?E;H5|E{W{_e19Q}A_|0SAYrVUzgW4|@fep&4#gj%Vt7t=gS*J5zs>57@# z?%>)~ezbzKdww4|AnzS|C);mgCKk*lzZ#-K!OKf--dQ%yM#(Jm}Nkpz3#6Nrl`azhcv^cH{amn&C1) z|D@QM@QHSo*;k4d(mU_PPYju>Oi=Ho zKP2!=pj!V{fc98|Su+X2gpMAKa{_=a+ka9<{bsWe{noz^V8z?1dhG1~C*O?LDkL-g zh1W-ho5m@D8Z0$X(9R0YqD@FIJxo2fA+h2g+84vqC=}x1#@2>&tg- z%u#;`Kp@dX@ z61x(-25s{A@0Fjy`Aqn&r?JbSophptRgB2tsJKEnp>ltgib^uAy_#%+vu z;>afhbu3na6s)bzCZFX^qyCieF)Cr{{Ni-%iBX}WwJmiF{^qe8S&a5fh~^nkZMpS! znW)luvD!$0a0;*Bg{3jtn8hhB<8yV$nz-{!6!!2V?kDPTsRV8{zxV zenN;!@v1Wo>C$RCN>CP;WL~7)(N9=#@5rEpww|alzIZyr0iKXNF+T(sq^dy_>Lg9pKi_gR;(0Nad-F-$ zzxgf!30`|J;=m~Zx)L7w^;h6NyuJPXX22YlQ|fi+ORFeS6Y009h63#tm+Z1!Y>spj z(lNF6x|M_2PPDK4-!lFE%Cxbxy2*PsQYQcAxd(1vu_^=?3oL*f@qu2A%tkhG|O4I^MMW10wIrv{~-`w|KV!UmD_u#LNiwFa`RItsz zBl9!0K5eg3~9wyLU1wLXz70fa0X7wOFaZ*bslEX?13jW!X%jF!MUGyzi@ zj#SdaKN(=P*U{p-jY5lQx?pDVR~ii9!*WU^1_$8H->ZMX-wJpNLFkNYfP0Nt?G)Yr z<4}13irYWZkn=gnLgfOFND37Emx}2LjN}ykuQ_C#lt|ufXRzWY<&2uZ9}EXtR<|*? zIulXwVEmnHuwEgJQ<=7!ssDAC{`tMJX8W5uiUiO%g~!x5WT3VsKlC&BCN%F4uQI8Na>>AI8Q1X3fgLvr}~gKq8NCB- zxhWtx5C!HzF;lcrpT@V+0EGPA!k(X0k6_m~&F1}_>m0g1m;CH(nc4)$y3SHy`&V@N z!7H%$YDLoFLB_!`VQi_RSX8`9`d1Qd19O*#Fb}Fz*y;vk<(U$!&zZI==fu=7MH-n) zGNCVZ`=Mq!%fZ#jsl@(`S@F_TOrNr7Q7ACIzHkKYfgBth{>5)B@tJoW4z_}iL`~Aw znpYO?Vn_N{W9e9?5pP_o)E%Z{GxX|-3^rtWgre)0^*Ul6&_pL`-%L-^Sh1e%&}-Yp zoBigg`nxYUkueI;4*q(l_*37#(QomSZ1?}55J5i~%uC_L_mIT82dzEW8#qf$?$uij zq{qwcw}3^jXQwUjlCNwSb+{C_v21Cj0A|vyu(a4Owt4&Nd;mJO27`Hl9T$jK~2vqk0~XYF9t%mpq+*1v)^rpeVw=u6H!!jE!Yo zfaffcjoN&g{KYLBAEQwe_Y&-cKsty#SYuO74F5Z;4uiBWS zjCz+f?=by`l%j7x?*g4r)i_X!JTfxc@ogA#;ji-s@H7p+RUA@?1Jsz{BTqdTlvO!5 zQVzj4jo}AeSEh?e`}U>25oW2rU}4ZAn6iGCKWaXu*_1Z3X0}T*kC3DQTDO<6g@vl^ zT>U@`m-Y>ec;12`@k~sPhp)q@bv6LzeRCPOTt*D_0LT9w7zN5MQnJghgYGtiqKnUO zwhMShP_t*e_*k2eJ5VwvEgNuiQED~G72Ga!z&w&e z>$~c+K(2rb-rT_1CZElrg8!Fu?vhL;ceqD%cUa zaMiiqynG(E`W~L4gQ1))J@$t1VbEyECHRN&QdtQ!Tcqj^B@tFX`~ke1mFB%oADq(}4Tt^WlJXL;9sL>JAq>>n)HiXI5fM~^oxC-GrzB&O53I}ZfB<*U_hBd4 z^hRIkp-;Y$vhp=ylWq;M((SMX$i-=1Oq8{I9F@K#fw;RR816J7SoVBw8&4d0RWjH= zUHdM)0B~j^Shm;Y16YrdEs*{&M}Cobex6dWfTcEN%i3W~)Xdrg7zZtY*Uz^pr|~jl zT^caYKC)bZ|loA&qk`Ms;{=Lu(1^D0b@uH;o+k9 z6Y`prK=Mevd#4Sc>%-ZC0v>M{OkJF{HZ!uELZ3iPz;a2?{f-+n78TA*@-z9CrBR|? z<7rUX`KL5reE35VL3tVe6L1PTAW_;CU2i9xf`M;R8Pe_PS;O7W*_X z3)ZKmJ8#kDVb$$>>e|-$uw}~sxAl}(ND$l+-`xxh=_0NUOt;`)Gea{?D3<9l;WGo2QLK94-`>Y zO&+3z7+A?2z({r)t=fV=6NFRQAQDEQ8)LDx#sje^fAVc;C=J=CKHy%Jtz@Cjd zU^6CRy26_HsF*XT%)b?Qycgj&y;%aJPA2)p+5XEV=O5MIr}jF<4P2zvaz6W)AFkdW zx3RJD1UtC~b5(BC7q3y15@F@0wb#MHp)=gzDt*2jbKDt0X%n)4+!}zoxw7fIU_Z~( zS!(7qnrfImHJ6YUEPmVY)5sDx3MazDR*2`s(>pD3e|vy2Cp#MjxM$&52)pxd5wR~B zbT&B8UJum5>t-CTfCW$@;C5Tg5|4k-#t;z$If>xp3ag7XA>;q@OTgEUHJ2+#@WTxK5D#G8t#Vs;q)0ZrOmG}wC zBb4(J`F=~-vD<+kt5>hjTIRi1FB~^F)zeY^ScYTJ6k8vok<|AGFJE_ji^=NuS$Iu& z;y_t2BxhoYusS#a(w0o9`2o8Kdv?-+{>#QWud<+rpL$NjUlrc?T~#CmWuKifg|Xua z1oENE@+(C*f%3;!r7aQcOUCxAiYPtzOh6?u`BT&bmi?xi6e&J(fb{jc z+NAX{Sl`tNG+y=}@y;h=2`@_~7)WMdv-6>$q16E`lsmP+uZM>GtM89AiOBtAs=3d2 zitT4`Ko}Z7e6zL4gF$bW$ll)oKgYrPiL}8$%n<3dwp}|nX7qWLos@lopH}w1QkU`d zH8vK)BT{b~viL1S+E_65&?znMqA>2ls(_Xg;2t9{(Qz4}i);@P{lRwAF4B2WB8)OK z0RQ61!{XxG1Cn#3T*oSNA3H8Sfi$QiXnMg)^bkeMnOtmmch zfn8SR=x7%BtevHcrN9gWK?J#wh=zhtweC6eVojwALNj~F7_}LuIBWb(f@tJHbFrWa~_^%a@hT4i1^Dv6O}1o?v9FwK~*x89^X;I~KM2 zqHlx!DN!$|U&*)FK8QjvXSR?y8;*JvdO0~c0YM-|O0T+eRd!pJKvZt`jAx|g<*C$U3gTzaz%7*gZW2s<2kOf6ZtKke`9J3i~!WR1c= zeXQWv57?;fA2f?xM=jZH=ja~2XJTaZRd((yX7Zn{*M( z2~U8SGA=>d@08Xp;`m-7OjYoOr|0QLTpT(*%rRKOA_`j2N8~TPqZ8M{CGURY0mo%! zWL%IdUIxxUl>`~_@p&HT1W&haJEPRAdIa+{%VEh@GWRYT^A=NQJ~JgV3PTsY&$H`bDQ&Hd z1gZO>=aw$CnEXL)c#4c~f2FleYA6bdN&-&@SOo6c_in!hdu~EZk-10W)~@Nt8Y1FP zU|R|#_9ly46riarpG!O*3sXR;V2$i{lV{!KUHInH#+|yC)l8Fo66vAs6-4gQcXxa~ zCq`AEwoM9=piEIs(0;dF3@9KekBg7L*SyM^^Ev^gcE)SO&+~I!(rA5$e1720>Fsg4i&n8>P{$m zL^SKUgbz$As1JsY0IZFIgVhKQnK}TFRy)1iD(5zRXIeq|^=(vkncxZ$G7#E$LTK2a zz)5aW@fs{>=^2?(1Q;${hSMMCqY{X;&TS`N-W)c8+*<3? zy=Vh=V6MhD%x(CSDVA)OvY8l7E?(u-gB=w%{MgVoQZ}_Ya}BAjgsS2(#Q76gMoAnR z&>SotMk`8U_6-UE+692N54og=odV)T(tP@Rn}JTewZ?a=hy`I0$mt-K*cch9l&L6q z3vJ6En$w@Zb|HD2mHjP+PRtdcfRoI5Oxe_(AfpkXZiBj(^6uTcM7ae3R?;5G zjiSWV%gxZsg;MWifir0^au~q z+Ygfzu4wS_x}OLewUgLotU8XAfV2%02f! zWBOp0cd_4ks^?oNPYl~xUgz)mF2G?};iu%Zkop=HQdx3m9pw*{z-?#vil zJ_y_>9^{qfwySEp+vQAD@=1`6BpCBoulK1_8v+S~uSgaEv-wbg-qP^{9<#QuFTBCB z0io8LX1ekmDq#j46PnWq+ag+*Z~XXeK}$nq$jCkUH~ZHX_AGTsf^YoQ^MM!(WP8D} z8#=oim~~adn0;G7+GBfC6P`XAE2L60N0r?6()sVH^*>E{$?}jb&u6_Rwf!s4?{eqz znSJfrIpEKu#0f3%CwmzVYz#$^`KifKtMg`bi5p3;Gc`AK2|H@nY!8y`Y24D!M7z%crtqjT%j&gv_Nut2)RxRUFFpz&G z5~)pNH64f+&hCC33c0$7IPF@Rg{>Ndxi6cas;5!Hn?XN#{4x|NfIqp^gC+b%h!Ex8 zDxkyy1=;b|GCO4{*o>J9j9`-$L0rMg&+XWIG~6SNKe-mNzooPdSr`R0U`aoiYWBWq zOO9O}Eb`}xa9H2YyN@ZQTOYVUyGOm$6`^qd{(Z=MJi%sJeypepHW_qgfrE{rvJHvG zOJne{W>;6^EilclUu>VJ-za_QccekV{nj)+7&AE7zj*wU>w5MZopwb&ARmvt|C)f_ z@66N_S2zUu8_=*45NBa`-TZ_&r2@Z%fs3?J((iq-(*#R!3kb$~vq>_;`-R(4;|ro` z8xa$f!fuc8k4yjh)G#I~sqo#E$Idi%5~|!2B|q6Hw{5TtNV4rI+Y3gc*~2Z+X8W_J zd&*!of#bgd{Db+HA{wfJj>Z}jFO(6479XI<*pF6R-jB6*eKXZdO+9%ptC82ch33Kz zV{VQ@ZeDd(mr#6*?^~+qcjMT|>*#Hdz(}rs|9?6u`1{ZEGZA?p2PBTX3;||zI`RJJ z>iTGC?X4C-$u()BkJp|7Hk8>TxqRiWH34+HI#66XU##b2k`wW*yZs!zN7E8a>0bvJ zeG6dE-#A7M^DPtiu{$OYBzm+Lfu>xRhDfCj85JYZ1V4P1Aws1N3|hug5eNT=d>9THwM`Iye1u$kx}O5IP-~I zc-jdy9GHl_>l62@+dcwvvi`pF95tw8W%9-`!0^5Qs;z6ezOrPCs_S$9grq1wc+yO-2&Cs+tAQ`;5%Z+k6ceN z-t6mxP(?ph_h^_7yUekOQP8o8iU*~f} zVKb{D1leifGXJdC^wp@pK!5`_omZQ?A*c!3=uNMfRduJ&zOtOLVk+K9s z3A`;=0`xvx{JwtRB!3Tu!`O7>x~#FVs;bTr4{JO+)JY>Wxt?>Z($LSnEdfKpW73ePDem74TdB4j{Wm9=MK z+yklo%Zn$zAm6+PgLRS9X_x9pa4;Es*Vxzys6l21%%&Zg268B1%=U=47O{vIs9c9_ zdg>{AsjYg4fMBNSvVs!U8@_6m$kQ1H;_dFeO*$}wa{N|aXVlJk<5yL*NbmiV>1gYR zpyJQ7X=TLB-HL^g@h%jUvQBfpr#4t-FSrAzNZD28_>cn>CzUSZDXrmi@N0%?huVHM zcX!FfV3wo$U1<3SzP({~E+@SlR6!$tyM2LwL)zrq<4hDe1rbOoqqsJa8Lo`q0}SjjG*Fc2hTM7RWQ+8gP=;7iiLEw3>>drC{h|7 z4J8U$=-I@)7lv|%)OOW(6ikO@Rpv`emiwU>+lbg92*8ep;9>s}JIqhm?1rYN4Y*&7 zl;{hC_w>w1oKz~-Z4fNLtIkYmKP7*5pei_W2!ch?h}%YrFpm366hs1B$3AUn;!;KA zFx?4{{e{SGVDt&Owm|?uIyUAqxb#{x(`=O^sYZQ&~vJvY8x@XO2M5 zAtrxBin@jd>W>-2u+t7Aov<|q-=jxOp{t0RR>sVL7bDOr%`JY`^m`5rtcY9!X^B7K zUPQBacgpi%e}BsUQiFODgwd`6PXcUEKWxO(!^ijrD4U!2Q4JAG!!P2UDqgDmiVH_% zWK))Fu#bqG`uj+s1{1@Ifb4>42k?KJFecmmsx zMhYgdtpFcSRxRr)mDcxY2ml;|9&LA@#41J)09TRx`#j#q8*idm6l{rKfx#(j>9=|k zkL!!$=h(!IKc{NTqzI2QAI2u&XZ>Xqrdo&xlIp892yG*>>`2zu?t-B}4Vi)!9k;l!j&>SC+SqJermq(_0fFV*Hu8EC}pVhlr2C7Kn&@zEI9TwY0P`*W@4Z{!PX_T6qXWqC@e1J6b!KtN2BxH3|v{995sQ zPe*h;Y+QAJ@GESLkpt+&-K}MJdwZ3Gnc}wEOVbVO76NG)F@7vIE(t$HqA#|eb`VAw zFmFv(?m=QDy&B0@Kop%)*Dl?WwZ0k)Ic~%!&N*MKB1YOeWCih03`z`_cpJu>ocRm^!K z7z$Z95RP%5|4L7*TRK=+-D2L~68Q^COUHI4f%yzVx5ex*sF;pQY}FYI$_#!MlTkPM z%+ni!YCvj`Kae$gIqiAdSed1m+gPGLDdLlN%7AjP$O4*yOW$2@dr~D_@UjQ9G9Y^5U~c51QbClamP9XsiOUS(3a7T91Bah! zHNiBhsUnX;UxpB+H|C$5K~Hwj`b$4HGr&*#Ez-7py5$+cBf(Z_(<6&~ENokSi@CI* z(XVcVnMa)ry}d-ycw}S1b`*L)>v&D(zFyH7I+hIMmaYy8E7N6jnc$)HK$b^i(zoY= zNxrjdjZb%U(+^YEyfS>O*y>NuQ+r}mLSvhzXJ8pwTj5-a{$SZB8Ws^F6*l;mx3H8!Kb`EpSQBTNAr^ik+_-wT%qCc3F1~aMvAX(2y=$x6 z{;?qxNr@WoY*>Z2qv>>(rsb@I_n_hP>F%5~y#5ZL%YQnIm4YjyqCHL-Hd(u2krz zIO;k820hxI5@wJ{q;F^&i5eLAvd@WMP!HS(m&#xpWg8z~?q)@!iD_jD zUK{bizBv>j1%T13{6$kNY$i4jDT2(F2@pfdy9K)~26Bn~$9k=EJ-;Ji#Bw*0zLCtepYe9p?hx0~e#*{O1 z2Xv5bIWRxiVTSbloNb59%r>0r?e)Pwh%MJJ;9;Qg*dDA!?Mj`|1$!?~P&x>06ELuH zRNu_vU}ELmYqarqzF<5B(0MYH{HxGW;?8Xtkju!G88sao z0sy_3r#sWnA0j4&pAB#$J7)Z7C5`pg{b9n0)14XdVQty(#_(g~uA2!4!-J(f6(uZ~ zc&wR^*8;)z^CTeU?hKMT%DN&rvT3S73Ro@Gpl(ko)lMF6Eq)&%B^HQB6;0SXTV^q#JK%yt|fQ9neT9)3bC z@%TzKrshVO?!^=v{xDCPs_07WSxU=!3I{%+;-yLkGDBUYz$;b#=iow8cf?w|re4%+PxvrYCzSalkMdk}_Z`_!~)=z;mZ|e8u!d|+} z9}}(cKqD6XG=#d}0gqlngt{mn#=lZibR>d|l1sh9>oGch?_BapG^7j1HE-@~N5+pJ zYQ{*kP$=f;_=8tEyTHZzYEHps4rmf-jL&N5AtMW83{(4{Q(ps@6))+u@Rq^5QyA{Q?7 zO*r-i*R^yenS5e+6Bwjy!usM~ZVtC}J-&QQG_x^UbU4`fVHakOFpYMecmKI?RAeFgT@P?8FP^-dkJ`pL6ux z)L)X^^-`M#F!edzX2f z;?8F}zc;(qUTQZV#8voSo;1=Oe;qegW8j01H_U26T@rM6yOB|`3rEg@hg2w-B;Bz- zjmfxTZ9YufUA_b;%A^Q@ISyBbmuMvRpu%~Zt^jRc8~0ux&ON7FSNWPh7((VACByo7 zPtN@N7eZ-TKmajp7MILWM&+Xx&l7aq?cOwg(UgDheB~+(0TeSEHr(kJ#eb~={X%9u zcl=Hxi3}sE`DuSDRhef8*T#*WaT*_3&e|7Z1==Wv42<=Bp>SH!kDtW|+{|sP@8LZ3 z0u8CL_WcU!heqC)07cx4Gnm+t z3%iAMg}?Bl=TZM8Wy1#pYZRQ+HP_b+KAiyZ!Ro!s4ac*omg^}+0)Xd+0Q25v*v_Zp z#rXoVx6wun9ffD9w@QUi62q0cgtXMImwN5B*PU&UMt$ZkXJ35pl7{Vzz^@KfJoFvp zGPzm^sMn+W(8S^(Ru@eMS4n9w3dE#fqFAHig$DZfITiRm1N_kq#>n2h%-kbkSsYen zC=d}0XO183W{o$WRP$T*#$GfW9>-tNTbDD_fRjo+ApA=F{Y|)^=tzdoO03!q#%pc^ zUELpxa8%_0kM%jrb$I3D0?76Hgn7g}?WPUQbGYaoGSwf54D1IlA^kT}@1N;;n>|vB zRJU7-!+6Rs-<1uKw||DA(21I4zLywA+FZ+kN(nhkCkmT_5i-AN+v^u5-U`))@B61n z%b*InwOw9zc6R1qP3*ZLkA_GafbI_&U%P5AkhKqw=I!iVeM4zjL@<#+1H0#c;+qhND9X%&o4TrW9Md2j`lq(tmE+CvF5uM3eBG>!{D6~>^Sad5+TYHjQT{?M zGauLWXpYKoVQo1}Lg6t)SJ2k+O>5gm?YqiLB?*WNqf`skD?|Z z9_tJ|9oulGfAAY!9Rk`w75R}ya@2iC%a#sNB2^WF8q&UZYMsmDTKxLv#t{C|)z(mg z@fuvab+c6iY#u{A4{I)DkUQgwy|Mqk{Qv$3Iu>aBK$v;^unu#fXw@kP{7$ z@+VnB))(r93lGXB(gddYb6t#R<{2aK4X2FF=vaS!CpxzI-7tdp9%lb$A&0miND3Ai z&Kr(rQ#B<*DU-`+xBkfk`PbiQ(kL@Kn;A#keXF3PwAA6UQWW*yEieD|hazqT8wv{A zW>s~ za)aa9Tys-RUS8hyz*g9Q;`{qUihhMD0{^PMJlVF3!_k!a2VeAGAFWj_tpIO$Ukwn}Uv0M!UWbB39W>)P)|04$;nD3Il+0wVqQqR2ipZLyL zuBN{(aN)#G>_2gVKlf3<{bPb;c1hzS{mYl(-+$l`?Hk~fA1%u~`3LpTzx?=s2bncB zLO7SFyJaQC#c2PTBg6GkfCMBH>{71*7Av9d_bUHMivQP_%BtS|7|k{%8?2Y}(`eXc z`SZwd^$C#48npFI1*WuMs#!3)p+WpVcyJszvE+<=_LEg+UJ-v^3pbG`@TfX`4~iS4 z0j9tI_dot&uoz-*Z%+oaz;;}>Zc%effQ9GaX>vr+{rOP=AJNdTVmoq(e*YU(sUwQO z=20QHMx=l|*R8+qQ!G0;86?+`9DB48B{=7&h4p4^@+*i^4AADp8_{jJI(ZIhxG8plZ#1EW*|06{#0%RUOdpvR-GuTO)>NE({pCM$PI{du_% zFYwvDE|32Qm8l*Zc!ez;!vA{mv>(A8l4W!G{j0^I&4y%wIl)%l_MWR=ak zon2+|$7XF8$D4%K&o?gTKfihpY??8j@s@&_6vE_L*PxH#JHKooUI?IHFqAhH#vwMZ z0r|jT88{#)K|>tIAT1S0K<7PZ+Tj8g%ya^N8R29Lp!NX^FqdXYx;b_6nrA;q0CwLu zrMoo+jCok_jSOlX40ApKH58qBXBfi8ba}Ib^zQPLY3&OU+mV77)fETpjXgDmYuYxx z@xNjh;sFka!L+i{=HD)%zzl7;Zdc}{q^2;>H6JN}49y9U7U@Q2K;_>7kX&#nRL+w& z`G6jD-!LiTddrF*&~d<6MTVLz1LHIUY#Mh4I|%QHDN5l_!cU>_Q=Ka7NBe`Q^z3Ts zv1{^!fx7n)!^h^U7o8c=_lMs zj3qD+>nNmTj6^{N{BI)Ac7*n&2zLRRP3ZlD&--QGqeUBIp|}94elhJnWnTe$J_(cg zMDy^T&4S|MFaS|@Mc-6017E;P9Kg=e60MAMDdCrh8JM<92f228ET_g>&DDqwq1-q? z<8AK%Ou$wcVx*GLe3UEUIS(Xh!2l28X+CY*z2EzmQpCw56;N&XExIFhs%O0ozGtI+qMrx#dFKCIt>?+?SXZ|&aU3qXS_$i9bbxEC6Q1Y2T zY{>tNuM}JasRm)VDK)$Vw3&y8z*SxhF@^SA;WDNIViwz!TBbB`j~n>JvHvR9f06dpVO6f%_OM7n1O-u%ScnQJsR&3e zLF!t}q9CCnprlG8B_$0~3L;2%cX!8ce!AWFoZt0$&b@a({K2!gk9&RJ zdfypijxi?FkM{*lb`67v(0;Kx&5;)6T~N{ma=I`2aRbpZ-j}XT1Q+E~HLm-=5Bf&e z(Y>@Ky(s)S&@_Eq>oOSzJXPyOz0_z^*ysF%c6_OoMY2ibnAOyHS9}T z9)%Id5?yDWvIt#hyu9lWB{wknT`#+B!y~-(h#Ospb4TP&6gz9IUgGM6?3&zMg0GF< z!l_PUX%qpw7h%^x<;a&8!(bq0S;FonjmMZ?I9DLF5%X~olo)|PWTR3Y*4Z1?(Sv>Q ziv{h)9PpxwHVR9(3p)xOEWWQw!k-yHN{GfpOGi(NI0F=CVX%}yX=Jq8AjNt{2oE|1=wy9}VWhVmew9K-PX zoc#jK=FqsP`U9Qv|J#TOC-%I;XTr`_UX>ftc=%FyxKh*U42K#O^0y460!1T6o3M*< z5K%3+k=5{U&Yyr@lUHN`OWqP0pY+!}7&KlI^L@q3Vq4(PyJ?l8njQc|`pY_U4j2)j zyG>7-7$M7_f2NQ$5EclLo2uoS-rgA&-P12&&tc9vFYD0n=5RV1nLy6Lv=*5pMF5N7 z$2sfYhqHN=hlqmDH9=OZfQPi(Ty{xm9=q zC+{bi{Ww7}jM$aDI%Hz-$o^$cYU87kmkf@oY*JiV-l{07_hLKyn-1=jN0zUiH4wp7Q!h+xpc&3nf7q}wF4Wr%c!EI>- z7iUtF)e}u{ICe66Z!&ra^37A2(Vub$t>qXlA+pE9bwZq>b{%1l214Fv1}kq$NJ>^E z?5l#kvX==?$0h+_vNzMP$M$1H4;56X#rfS^Q?|ettpd)XPJy7J!Y5GbYUZV8yBc$e z-$z7O^q}Q=3KGkYK)6}1DZpd@(fVL{DK@55OKFgbKwZ;j(c&ewYU5`t3ZDj>`Rae1 zVg7FW)MaB-@bF)g5U}KK#D|;v0;fIqW7mMb23gN}x(V(5N~Huje-Ie4Ij_&Tm8Izc zfxtGJu*stH!C%U3}eMbQTC^Dh`_WkyYf8=^iWZ82rK5t!>uC zh_TfP?)z9F#MsiUl+xNzm|@LVfAS{P2eGXc(BG>e2Q4XJvoF5fhv-OAx--hT%$u;< z@+jSP^t{t{-(rBnRhk{02>Y3i!$?ThgH0t>vd^v@?pq4Us|2~vRBoJid_>b;%X}$F z?NJTsntT7T`JDv`;_ybM%lMSq8%!n)9k@ri%x< zrskp7W-mE?=;w&we{+Jd7YIl?Bj-$-Jr}Cue{~hzT6w#mXiw1)>G95``KT(;D7I%jJ*;id@_aqr2rjp2az$@P})u=)DLO? z#^T1CcqI4Ef7yU3>WEnR!PUCkKg-F~GePbNagC8FpwA0R-huP^I@BViR2-j}wrNx$ z?Ei2q(F%$6@$muEX)rbS+7IQ>qRH#E*8utPWiZ`6&al{z?+c6k(Gb_mkuLSGFD=&T>|Ib)tC)s`WJP2MD}jj=``C}8fCW}@QHpMUkskum z#8WDk#*W~wsHuFAIKOk;L?}8SA$N9{G)9-5>|1!mKOSL2lQI>vgaj3!u`x9*pLL6y zoVD<9LDY*U6fvh6QPQM*kV3K?6}{avzGRjZ zjYvzEM|pJj{4HGdOEh-&2;C7Zxsdd#X6KT>CG zxo;@C-6=UMH+~zo{=^m@D+T66An-IfIG4%y7sxY#(tvCzTt*`vkRxWCxutvgQtb@x z8-p8p9@}}YO383);}sLc$tYL~;)5O_i4F#|$5~w&dkLI376zSme>Qm7Tq;Kx`5%S- zI~I$qaCw7P1chXgh$ZGlnDf!?|8R?OQT_+0K8Q`__|M98lb^xFsL3mdp?>C~w)P*n z$PceqdRlIf5EzNRP*+v;@6`8?6w;7TNZ#$=YRg+H-$_!K^xVsidVazjNSqx&+CNAq zz=O!Kq2ZA!q>1ol!~vF3K!fYp#hbu`QJsUv!d(bn4bro906BK1JCD=6Xt8<%c1LQ* zca+$E1=6m+kZ^}o3_ICXxLcr6 zecIqQXJaROLktfX);7@HGB=ediwILEz@G_RMA#cTZojg#RXc7VSf4yb*dm*`q#Pg7 z+PhP#3>h9E1Ao?~5K#@~AH#M>jc>{#B3>o;*r+I2r8;d1TZkmyC?s~bwOrvY()}y( zj9rtCJj4uZQ0+k-R_(Ul8`|zN=zZ+^)`cG!0&>Ja(LV&A;I5zi_gfx50OKs4v4WcL zfBT+-85`qqQB}+ByI6oBc7Mo^b4(?ne9ySi z<24N_h2%E9;{bMJG0-D|dKg$je!$z?-LwYMITE*u=6J&P5gH+qi|w|Xuh0yHT_IeX z?`MD|^a04xfWT#gc?BeeYJ@jaU>0X4eUi_jk2V_Wm+7@=s7WBl7QHif>o0LzoCk^& z;+$L!HRVa9;w*u3-x1jq6CgPExm}uQJyd|s9B5+us-;`4YNhUO?sMes3trqH^eb4q z{d0ho${yrigZNgkC)>mSeGv{@@wFOWhag~vJ)HTOLH;){5nFLsa(J4gb6~Zrwzl^A z^eY$v$b|Eze%~FPVI9Jl?5^>G`y`L;MB@f>bpWgS z!pC^)m#7=KL~J!UmoWfQXnxD6|m5y2J=m#y!L zOo&27QvkGVJ(vVCd0~g{H znO+Y}I!oLjA2>yWI65>vQ~-=Y&(pZLBlWmdIl-~3#+!Ov;#zngys5qh5$eY?_W)ux zoSc&83uRIWb*9XZ8>g-^fFylD;f8~zWJ(#D=Tn$KxEK_0J}(0YYaJK-3+(@eo&R0+ z{o7xF{sAM&*4EaXs=_8d7NW2%-NmvGeivx^K@SPTnJhN+%UHq!JKxY!S#nqv4ACzJo-@74R;G z5IP_1k{H3;kPehC$4Q8^SJ!kL^d@TQ3{#rSG`{RjRJ7Ha9?-PpvD@xJ2fV0zyKk+4 zQB{SYDXxh4{4i1jke}24$+$3L{{cTBL3+nQ`LbPn|E|u?&@pn7N!IZW>pB3&Ri!Tf z{^}b&DFc!tP3i8e1=IFH%-1Mk!F?JbM@#8s>w|xe76RU7OKS9`^<%4@LIUCYY~g{m zV_VH@9rUH&#>TVqgX%46$DVX*zN$Zq622J|6i3{`+qyUh(OOxw+gV4iFXN6lZ2mnb z+WOIIcqs-MfVCxjo0j+5QBBi40e&a-?Mk|zz>JB>n>p;qSC4U^LaI*J%{rX`Z_;DD zoy7>Ym>{y6gzV#ZF?z@WpwEh_R}i?U@~}{Pqi3A;wZsXPEaEAPB4wl*mq&W$$li z>6LlnW&(sIG~&LwG*d#`sY=o;i$wt(WgVFD{l;(qs$UnVprnjpIB*fen_%fl`C+ri zl>S+Lm9grKZ%x)_X;!`}Z*)}sxPgrUZ#ePF)gK6nvZ`wPEoEh%uKi!Fz<)kqI7BU3 zj$;fkqpGrs$~6efi2M#gomi-}OEjthS!HsIBgc*1MT!W(r(0+H0ZxZUJPT%Pkwgc8 zE4XqQOLdmZ@LR-j5NDS&=X(FSB#*@C%D#WH|8>luQ|Mf{l4se9DTmxief<)T8*ES9 zJ6&({27Vp8^7HA0FzD@D-OA$-8RGOA{yHczLb$l7(aWsYew76OJSsVi6h?Y*X1L*y zAcFys4i9)PB-kRZ6K?_6K_bV_JshQYnJ2LHcpQs6+5zY#gjE!Qq8qCWVj1ofR)*YL z;Ow#`(EP<$`~5+MX#fYxMNfW5B%Gmc?6qmsi+YccdR8`F4_?mtvFLj?=lCv#_lMQH zedxRz!%X^P6x$q0Km#QV&slxJUk~MqJd_6Ojo&>KHtJl4Vp}s>$ueUPs-Kh)%=yFLEiU>q~4Bl55p|Pd|CBIz_Uv`Go zaJ7(U%p14dKhG~>E)uf^R5?8^d#y%Ub9dRPQ-~ux&v3cOw1h8w+i+Ru?(%z6-oq1< zDDN3@{G3TIJbzih310kF`@#IQ1n$jJYVdEHT#WqDjyww;HqDP*QI11Tph*>90Ybn1 zY}ZjFi`}VsR0U==LkJ=b+9MH!<;tpwCcsB(HX#2|Yvh_f0Wg2uKykBKPLN$rc(p}H zGA5~kU`5$t_v}BPhkwrgF($;O{;7-MrrJUEURj$nrXpv;m14>~43?#lJk0=k2?UW)C5S@xTa#5F1|v04p%dodi3_L6j~y3v z-@&cPUYFVuU0k{O5O`%(Q0gp?QrQND$o{LB{xgU{zK!ajw8?VP=3W0bmf2Y(9L{%l zIoQ-)i8o;uSibngj)#5)?yhTpn#mMIV2q70yBVG>ug2CKLJsuf(BR^%9fbIApA zUP&<`7<2i9L}26%5$l337>FPt3^5TwL@Cbe-$XvWaC`OVx~egvta;HEVp02Dn`^x7 z_m;~{Mfk$yzulw6MRAf+eQ4S5isu^p3rSCY?DqHL*Aqk2LHODHx4CJB0^5Y~Y#DuY z1&`((e}w+Ylx54@_Ldez%P@pA=WThqRdS#|DIo6?6snS!qklh}b* zzizt`5k$~g&l#$}AL$=|nTSQj;q8;3;fl$h%T_6tjpXwtj1}9f!tO!s;*Z=(q((%N zT#`}(%wI3+K~RE*`K3jQjtL5?ahB!rhCt)y_)FP!e=}bJ z>I0(7fw<$p%{~QBFfQbC7JkdSfKK^=&U;i02U@92$QH*1?M)9dhzKEI(hFw!fG5f5 z2fw@k07R*T1t)p{Y$M_@wG3S`?KGfek>g9g!9g0Ax^*ykEl&v-30**90@Me?V2%(- zDKEqgO2Z!A+rX~>1A}m)6UABWx7cl}9aYctVN<2k)fEyth2w2>v5pSH6e2ig9DG50uTVKa$|8=AEZ)Fk1-?!l5nx1I7XaYo{6|-K<+#a4Wwz$M{ZzJ z$Lb>T%*w=@?~u9&DM{7xtqtg9;K>v_FrdeJ3*j9BVs|>mw(f}j_EY`(lJK>CLxJ*q zY{**(Gr&2-ITZHe{4)O}xHvDDWE$`7Z2HfB_zC~OBgY)XHkIZyMZQKxC)GE7f4zmj z|HdD`U<|2P9Jt&KOgplf|L_SFaADL|Ef=8s(>G(E;C+6e=5ivrGT8v zR-J#!_(t0BbuC|)`t1jg$s!3p^ikdnS$HXEqC}prHM*7xBKr^kl3{~Toj9(dWf>@> zsoqHh29$dd{g|!UBm^=b;4wKJd~cIsFpfnA3wi(n0MlrbH*AN~z?3qJ6a zpBl4e1}5tVn^$myMdR1W#x*woIkcz3>P0Myb#U; ztipOuL_llTa`Psr9S~Xt)WtP`ap{A69>E=um`#77vBzol&yWfxAE6w*R7_(g35+ezC6ExJV z{#78t_kR=qaWHk^{2D392g90Y(Y)Stv1`a228KgN5uF_@`WxSd^Y4?JRjd{9i^ujW zRs7@C#W);>;z%nk);;?rQZ+*sJg>F-ZwTkmRkDyBl=L^fAG8B`V|wd*$s^pX`WMoEcTro*|32v!_9X*$8J4=!910E52aH*e#1d41; zgk83|ha_%fOs8-Z$(zvES3`kLkJPomqL6?@iMS#m%%U3DrT6y3hozWd(|>&3|Ls)A zEJNWjA=G|FS|A<6D1A6_wy3`O!oU6W*4SLy8Eu-gLF)VfxP|a#`v)t7~l#+M?>P8fzVcPm7>`K`Vb z9)Z9GnHV0Pm>fL3*AI-%wh=9J0HTUzO^7&x%q3tB#7hmd1%%Odu|eqKkNyLnsyiDl zJa`-gRQVmNK`Lbc4mZnx#A2c=HXe?1}7=0@p!O zoyW*<-8dmK!~-njM-l*>KuSj;u=UE+Eq}ld_O{CuSOob=I(v0dcvm)@w+dl&LC8Xi zX=H|h6w@J`I#KO;P)#EXtP!7LZrw7^v(wox{@d~U|DuxZy&u^uRm_jQ^lBCP$?PIR zi65~5OI+$F44l>9o@LA?6qn7-HzUvR#AMP3KROV50m>1x_eP!F(s+1HV&Img*I8qK zwY4`MGtIf?J$!{|EtGjQ4na{Q6PMEEO4GAu-^NCZD^xseA+eXG#ip52D2Eoz!ap2L$Hd?@B%0< zXLYjWPlTbE8mOu#tU@(?>L?`VRY)mV@RE~Gfdt6pe%{Y_5W|LD!NvTm{4((bRn-W7 zxrnMa08JLpDF=s|ox{Ga262u`(}~043V(@tQxYP=cSK=w`Q4%SFW6n@UHbhHoB-)A zpmxXvNGFWnN^fPlowMJ*<)#F}Z(a6$D%WOP|64TTuM65Z3L(WdUpl^g77cae(I5ii zY}DIx$o)e@Jq>uqZ*#f)5f-}R+TUUpn#xDNBnje^@KDnr@Y=d3x5SZ^jL8dMq+TUPo4o(;+e%2PxqVGdvv^d~JUJc6Zh|tU6JFO4H7Q1@M z!5=x6g=#AIulduzI-gE(--u4uLxIvCcBO&Ov?GW!+HI|q=va1KH_6Dn=sbu?k);gX z_8(FO&thUyy<6eC#6~f~(;8|T7XK^QjD>C^@?h-&GNall1)?ZE6;+qn^N{z(k%_K` z>r~TTf>eQ248|n4aOJYy$A$#)VhT`v!s-b>;7!*6J7_q6JgTk~*w4oh+9Tqa5CALr zAkI#HJCxMViWxxA9i+})^b&w-q+Y%lfYUIpJ6B|)MT9-HV;8W%WNjg!puN8x{HI#5 zQ3>DK+7rXgg4UXbiA=lJ5)f)z*pD3pgR?h7dV3=GtgdBXZoYL99{Xzu)UrL-TD&&t z4{}8-duk+TIlxQo-6fg+h5Y_=WO0tdxR5Py&;|E(5;lcCBBYhm#n?1NeKT#OH#_f^ zb|xj4nCjzbh=v{l38_^;&om5;V>;;p7VYRC)3_{15J;kfHJoRftVu|a`bOr}g)>bq zS6Cfm6j(-lU{$9uLI6YB#WT_&S4kMdK=Xr;EUx?0oVgnFc>38NG71I{CCrY|g#jKi zV!F!9D?~#=V&s|lMYc*8tK1GHU^ttJVf&FE6i2O;FOexZy=O23&~i>_X0V&qBmNhk zm;~GJ^o}~)a;T&xaW-mcbFOZ;s7faQ#hv}5LC!cXcZcj@@BNDe9G8E z0-q#X)d(ZLE8i_eCh&GY@(o$)qR>v@{zhOJpM^GJ!26Nc0eHB65fVLl?x46(Q$afuG8l4rs<3<|m+Qc*URyx;WOWx*%dl;0GZ~ zGy(CCrnHvYi*Tyl;*X60_A!i_xDb}R^y>$;N|J6s(VouDS^orW`!GTv>hEWZf)Ccf z!QnhJ0zJl|Wz=dm@GJ+t!9$7XkJQjPXadQ?(esa17}zEJg8QCjyX*bY z4qOB;5Wf_isEdYjPLXqEieS{N(A()ZNQf}HdwEGC#eZn+I>UQ%02GRRcTFqfs4$Gc z0Ehx(h0pWP!N+O2wm=<^a3%27MZ;<$AcTTTo5k%KTDdoK7trdSWrISbv>Is`X&uha z`2e-Woe(vC3$vn+j!LGT8G^ayT`!{T7cxq??_MnpbM46pv+4>v?yxd2Q=vGTo2XwK zr}GGbG*={43sUNQkDk{9C;LsGq9!dZa7ldTp*szX(5Db=c(w-9E6j@jG!M)UEqinD zvw1r2OPi=Bu{j^Xpb{v8FA@;)j5od}OuemYGi#o@8`sdQY-oc;kwrQHll2+lPD3~! zC)hk6Z%&X8ToGyFuAx=3PuxSm$@PY2I>Dy#OTG-m_a|KcJPm35h*3QNN%SxfMLgb5 zOR5r+CYqRDB#nE6O40%HgZUV7VYm7Z6`X;%m@NCz32+# z=>b|YVzue4$+5-Qb}}0!JOJVRfdZfCF$EpY$-Jr;9-2q@-L|tb@2r0NuyXxrs&A>| zb_@UFS0(|iAk0>NuF?0m8$gABA%$5YUKP7;!1$%-o^(}oLkh&w)4eQ2aI z>aQdTdWG};K1u%!m=kQM$TJvSVAEXw0-Yj)wnlm()dmp$qguIbDnIUc`CR!xTv<*6Ec=;X1QOPBtG=KH1cRgjq;_BrNw;rO1FpB zx-9~?$NCtKX>JsR*)7coQRwz=v1d9>2Xy4P$SdC~JxQdzT334ApY{l=#dxL!y>I`{ z)Exiz`-{V1Pldpbw@jQ}cAlE%R3|_FePSpmhTdnYrE`vgrm^LkiIJ|Wu`Whnt&&pS zG})J? zl|0kI1>l(uEOcFYW4Uo@wUNg8iYI2Pu_CWzHFYDpfA7sqitD^r;D|w?xMvyIKZb(& zQ_b3I;1`D4Hi*arF|a{6lhJ*nwMMT!z|Du?)};)GqTS>dLt&H>w6}zPTowREA4Uji zY&w}hn_c-Z(VHO8=ICsExAai>`+~P`DJ}*GUs#*(J}J8Som_t-Q#w`f%VtLtaTx^*P8{g64IBx{O5uqER~^hJfQAC*Jxk4GU~_BV;zDGn0uFxYR@xqL?TKAx5y^ z_P0M4;jQ>x7cl5zk0KBcAaJHgHP0n=1x$)of!Dcngru>H&a92az?4NMBVM(g3Q-tF zaZ~5YyH2Ys9g0=tf!S1OQp?AP?~4vBN&ehl#2FtD%e)CNgdP;x+#pgrF6?X@-g`JE z33qxDj{qvTIh{QLdqam&bMvY8bPJ)ak)a|rRdNbR(E$tK`4w?aiZg=z#nqkppBE6TtzX)B|5D?>y zSkvV0hoKg_3LyQ5Zrv^oH0E;q!J%quHP7tPWRPBWsM$;KQ|qe9)-H{%FP3ZKj=Zyl zP&g$Sl5a4)y`h6ZX0UGnjlgHHq3JOQlrF&rv5ph3lgWNxAN_0{)UN@Ag0HBHad@>h zn;yU%rG6X;+5$1skUZ}_r4``;BVbBkuLS~x)H*A#-}6m){AApGoo!(2o?gWJv_1Zpl!Cb+#UVT^1+uV2x z%CiwP#>K+%@o5tI>DoM0akgp>qZ z2O|3q8vseo&VcQYS*I4|P^Gn~z}xU;%$y5Xel`CJjp)^jL} z%wXn%AnAOWI;ElAudB_XfFKx|-#Ocr`(wnNW;kdb@Ej?Hqedvu zsM{wIL83#E%N^x;&PqB_A(4HYRw#q}X+~t*zPGFzEtNfwWW76NX5pgoCB*AkGiN?B zsRe&1(4|BfHJ48jMHib`-0+}6oPquOoZO*6vjU&#QHSNk{$>P=gQZ_|o^`Ljb}up6$ch0HN7y&)zc@fo zF&YOQIp`in%k_8S5lD4+tDCXY0kA^Yly}Xg`|Y=TQQv%W3Uj(Owz{OqOad`j;4Ysg z&y{1iE5$Hz0u+{6XJNeBFi-RR3O+(8LFn?Z(L}(*;=J45AFW1W&XZ~$g1LyebBr+X%bIFv9A(Oc>Xj z8GV}b9dL$#pbZY=5@yt73riBjifdOHHRSvEd0>dbNAK=>lb(%h?(z4;TRwadvTNEFwW_^0_WkPfBZ5 zdDzJlnV6XN7F?32m2y~~fZWsz&|sM1;PhEKVOZv&w!Ru5bxHIkVy@c0Ly4kDoZUDl~&Wwt5BFN4J@w|NaBu79-@75R@N5=_@j1l z0y4k*7{7v3K{AFLGw^Af<0*pHdvLxld`R$KJ&Te48>*t&w&muF;Y23pD$iv1&X0Y* zVfz!qcEe&y3mXxg3Dq&UyNchhCtPb_domuS@qAuhKSN%4GnW5U;WI~vC|?lQ&Ws~o zk&%VYnEnDYPt5LuXUlW|L<@%!?T?m&xErggu%Yb;a2{+CeoX&MLR&C$UxpnTb)3~( zqkhv!CmI1mlpNiGrxqTe>4V^A$#rNAQ~1YnPQmBKW$MU+ykAO=J>m zKB)<$qhoMvIAgUr8Yf0>9L%`G#tP{ zAKiax2o?^A22UNzH&tqX*Ha7rp@{jZw(DZmP6g8>krvHNlnBLhMP*+ln!CI27H1zoi6Div(I7Uy?jM$wbLHwhJ!~wW!LOW)qy(={S<-w)#eG&9q|o z96i6Dsfz24N&07Z`=fc;_4-?#r6A=4b#z0`cGI*fN|)^TUH1p9 zr7fk1=JQs|cAM7hTAZZ26*M{A0uzLZnjKo4{B}7+LUNhRrk@b{Y`@+CaUu0&@zzRt z{K}rbb09a_p8UKh2aFZ@WMR;}o3wJmYfOYQsPCfmJP{1kg+2C{AO(ExKL!Wb-hKE1 zcpp%4Moawm^89sbwWMN>qZexToecK&)SWFrDCN}YA7}DbHC@|Dr{GFV*|M}MFoHM( z>r*{&n_SYevOGQa+|EtU*S3ciW$N5(W3^!BOk5@ca|C+ak6e>t3s&qf|BRwD{W$NKu!tiQc-x;OS} z{`PZu!>E2umi)_?o__uC$Tf`sk4qHBvP=%AM_{YW8%FZ_(0Nu?bPRJfXo@^YA1uI@ z;pCVWA5WaEO7YRNC>*<%c8GHpj0U1^Ic%)Ny%XHNWc4n5MM)w zkdBepQ;R()V}d!j5hqIARjuMo5km)K1#7v-j$#Fhdc|9(O^#JiP%y|pxAANVF1Qce zG7Ak2C8~{-`?>{BXFXFxD{p6)qjV7~v?FF!LB@x_mjSjJS;&$vtaU^f3WcmOP_ud# zQ_EdMd&wxp-N8?d~y(j^b6NFQ2~#QJXErS-p5IN*zF#_MFJvQC5!Dp`^56 zQP5pbUBK2Xo^#l`(gtZ@YZH6|zT~_}_#86pLc9HHdyn?j{`c(wgLe(33f0<8 zYNDP49vymnx!?^V#ZSNd&Ux`gan$Y>h$3Nm{9F8q^_fnlu!~hf$0+}!9RNI6QTDjF z3EUV3oJFrNrRl~uUfCA?eP7=9Ez8}u3ch{F>Qjw;6pS)M3C+}cpv#=>P6*}sc8H8S6ouIR{cwm$`?#!kxghNUI9RtUuh%E{V zwG!9-_d2D0N5=v7e(8wwAoETj-Ir%v<)V%Yy3&J8^6V&NJDz6uWNS(&WP!@ov&wHY z$_l6{!d9zkZ=z6*2Qr#E#p~w=FLmLbD>Zhf+;r?@u8q;(=C$6gSE32_ECU~31DO0R zt)6VF-8dlF8nzOc*MIK0Vbt6^YFik4Nf_4bOxTMS*^gbr;FyV(4HZnMk*do)*t6=l zo9ZhrggLF^67=RF0`#Kpnt;)cID144tCoGZw)E;q_@2ZC_YI?5?6hRJ zSnrnPSmDg+vjbz4m06j&x-b*Jf5;A6!6uk-ToU!WY|43M-h4LeDH0<8?GFtNE+-1L z#w{j;v!5}TA#C1c9{#jFA=N)DkXDcpgp|*^f^TnkB*BTR3y`S}rQCoy2p*zbf ztE@`j{bb40NG0b6w2hYN=$p_eA*w) zl&`|1{sl;Y_-_$eD8YWi4KOr8CBf9~bvH;>WJ)E5;Aq7C;shYz5yB^dBW=8wkXiDOp5T zfV~xvc+8E6jP7F(k$mq1h8r`nF5e4QG!8ByG4a63pb4reH>Z_xzq`|~eS_v69-+W^ zTdCTHnv^OrNBvMUZA+2!t`hDdyL?NXNivsjt7s*3 zGDmNOoA!_Gxv|eR$(l!O)eG329KS35?tSxxS?uPydtSnk0{YjTye1pzn+O z@c^!4j`Z51z@{PLr$T~qmmHfZrdP{ft;!p0?^4tY21N3pjRJD;y}iAgU<(NJW}dIp zKlQ|Ei$D9?fa8X&L;1HqSfc}1PoZ_^DZ|m}8hLVu=mbtX+uSvk`KnxsPq7l%VOe?q zlsWnGn`dW9h_6beMNLmWv6#6RaEywSY=L|nS-rQ!@eG9`CUghw+ym6JND4;X0)x7c z2LtAG8D@DE=D0wgIgGE-@akfXHmz-M;!UPA`~#|smF&7&q{KVpIe8-s&-9Z^FNRoS z2&wSCVXk6WiSU=gFL`^GNu}_JzrO3&<2>BX)-O)PL_}6raGYB&4DoA0jgptLDr2(S z?I~hlkZQZRMP_2K^YAs!mWFvmY_jOuNchyon#Q%Iugz}3T;3;a=X1z8?akLKq}@6? zyrz5c@e3U0IgV-B-i(<9Wiv6g-e6}qPtv^|mP!t4xRr0V28Lce#CaqKTNiVoGgHyh zBeG90v;=~oqJlz9%4e9uwCGXa#cT|L_VZBioi}xGa9d7=f|@zW6@N_>X5H3HBRA~K z7~ZA5@89?JCCSmfJ}yW%tB>rUQpS|#&bB1I(6sL~m}=5~^cB{Jky7`MjE>#_VgcT# zV+*)4175ni#q&yU8vAt1YQfcWmcd!J{Ut0hYh z&yI9FJNL?d`K0xX<;%Hmiya@dqIz}{G6M`UYe7&H(*tVqr47o9clXPgtha1u`J~qu z>x@Nn_8uoXrP)U9hP`N3o7>+^pXqE9ZV0+nAO5ADPxhI|&M?D$t6OwTo7I2=N z{d-&7Z+;0ni~sfji+SQ>nP)iL$9pOXW^&0kX_lo07=!xUQ|lBrjTo|a1zy$!WWJgg zNn5>lfS>$j0G(ib+Y#Mj+=q2AYGP+s&!%`>LJrOc86R1d-NulGQ2+J^BhEZ<7VXI| zOy@CU7?DWCXhBShHb3^{0m_~D`aM6i2bV6AJ&`?9{U!Va&|1T_G^(zTK;v}j#5~sWIa@QAdAH^U|<9c z6r{ihaunI}G~y#L2_?u=x@k^y`;Q}Cv^1r$G&%S#gbibMp~BM6T`bFW(`ZpDiA5A` zQL+?KWIo&B4r-MyDGoyuUel1137Y4@E+JeWr$Aaoh3XtyKvP&NfZ)WhkSfBFppvVF ziAg>uJy+=R-gb&d8B{O0T9@J_^0o(am|QH2!@}cO-uU-M)S?2I#0<_qBgu0+KE+-1J_oK0MP!8py#SRvcg2q5q<*;OWv|Uj@ z@ZZRGo_l-Z2D=8$=vQ(+>dZ2%gS4TXAIh+CW(jaaEhrm;H-QXc0JJb{GR5WuaN!~s zP4FITO;`kdt0>%pX#S9j!0HG^qn%ad#-JCq6jz;|6aAFiW@qqaMYjtpf+i%yGksf0 zGhItbBRuA(uN9NTT?=>Z%-M_)+DJja~~EsG^`S+RKl(9$tMh3cZ;E>4&M~PM>2u zJ&7cF{ZxQB&4Edrb!Ct3qjFB8x(LiCmxCx)$~$ZX1cN`HAJsuzqg~wdS&`f2vOYzD1d* zVhHf7ON4n@KKIkFA2ahHJ;rhH2$d1EnBygHANt0)SL&Z)Y_ox3#m!tbjb3`CfReAW z87AyX^D+mR)kMD@Um)a@+C}*sAuq$f{9yUn5O<3L^c$`VSVZl`*5C;F?%hT6o{t%B z;e_V6)9RRk(c4+61KmuJ;zLOInL^fTJI@PUKhd^&f|ZS5VsE*cc5~fkKbXAFtvk!H zEP|X`vb&9)9T%I<0xR<#7>g5J4c8~L3y9eS?q^-_bbIZ!WAg{YItlO<7JfxuHyG{U z_+~KV3Ybqm}T7DQ51cPhYHTE$~IdHT_VH{81F>v?mag)4F z_g$1zpy-|>Mi(7q90|%D)o%c=VA?=QJfb44hC0V3smApH$Ab2ml$6xn*{+-^TG8u) z^z6h166%?H-imYO`O(~;M3f&`EZj^0bm-P01`#%jdC!kHI}eO-D@kV#lw8V1)h#bA z#RB;qml4%*=O8#0Sh^B@$jWMEAT7`a?nE414(u4?-Gwfbbk=apy{Gb5iaFi!@n<+- zI$?Z!@y-*ruwtz?PR^`j?fG(0&F7Wwnr-efKs`w_s_voCBIFQ%F>tSR|0TNy@2lH( z-=IEY%4!H)opMM!F-MMtt+&tiIp;_Zy?JC?U)LdXl4qp)++B$qnz{j;EgePf7pH8( zI(l&5;)aP=?=ZL7=yI$Aedhjmd6i?ie&nLLfgi9bPh8-poL zG#|pHpz!=4toSiJ{i*97bi&`Jgqg3i{ zR!mH+TYk;B(|WVM!X`Da!FcvH{{doU6@xEs4uLP_A|KTvHjNqntxvjl7rK0+2b^6z z_cbhvz`9)@%MD>T2*i<*%dv#?}K|qRILxi7npkB z@yanf?t`x5G{}VxP^I6_x?bFmeM-SRaM5b$YWa3rv&Y^t11mdwYbdwqBj~a=3T@(w zvM=2A;M>0&e2}jF#>)n0>zrWIO=a+}iZdv5%^x7`jaAu>4_PQb;WVSlQ0E&cbtTv9 zLCEaV$egIZ9nx@-K3|BEE17eiZ1?b_O%b}8@FmCOR^712zDA_$#o;jvSEa4BX^VSF z%R5Dyw|BotInBK~A*CFfk&zMLANo}Cs!~z5^vOR~m)H?T3O^iOPHgp;i$eubSI-mb zwnlK5c)J{yQ1$xI>7qIcI-|a#{3B*QXuvk-L>M`4wwR)@?i1;e4kH{J3a-3^B8Fd*n{d|4#xg9QFzRXScu0PDG z(pgV@()I?~EjYc82P^Rw-m>5RaJ5&yl-w)S2OaE*Gi440xC|xd#9UYmdY0+>Qw7Qq zAe4&-4|iJy4BAV~&Tm$ey`bbkXE?9kKe{0}x|L)7y{i1mAvqMv(*E0wo|FAO3zc-J zobJ?2)|3InWmZG4N&bw)bs(Y%T+rBHctJ)5D{J-@OB2t6tLbNrLi<$LmG2T*lZXxi zd~L@;f~3Sm;tpM_nwP5Dtj<;vpw)x*Wap(K(7JacdCa<$VYgfXh8my$>orj!SkM*lWA7!3%tF~7mbr{CQ?)mad~CepQii2$E|XN~p@I>ziZHBU&t(E7F2nj;Sh z6y`z-)=W6^WKRy>J7RajogATOHu3Yxmsm}kDK1VPuHn`3SQWi6p2|m8$B}RQTyxL6 z%SE5&(tgk7rS*LmWx1%tU=SjbMAbgUch!DhWh%QH_BF4nYtoWHhQmKy~3cwM5{H~_GV!> zGOtD7d;IC|yjQy!x&Fg~VUQT$#17LFH)_y6DJGl}!gPML#wQD8v+)j_(`GtxE1n}I z!EoV(e;8({HZj*ueRb6uUv#awo zQ4;3E#qAI;(uEPNoC=;3EZA1193@tNG$j(e_Dns`JoRi3BfV$LB=~4?D%4EI8a6~- zu$@pyc$?w$v#h8J?GM5`>T{;z{_Iopn_6nBZ4%rS0_f6f%lB?k%#tmAT5k9<$V88i z|9YhM?dT=!EL~Sl2%xwegD8}m!cFx8P50%({5GG-Z2Cw6h`PZwDbq`j+HusIf<8abM!{7Q*JxaC#`DgKeZbl_@e zy7gH3kFQDVY&}0hRCY;(pLl_j)vE6qz1yVD1GVEzu%GqqYm*Ow+%&jYkd%D}QXy&= zCcQH3QexGh(XG^UrRE~Bdf;YHKKQ&hRkM4ZPC7*< zu+Zn$ataWW$-lDd&w!I{2uxFx?P%1_-_+7#hoz@S-Wx(^;0NTY>-A^CBP;{#4p2O= zdHWW&1}1ObYI(BCaeB?&N%lXeksB8dK<4g{h#P)ByE&L>GXrF?6EbgZG4Iy^2M6JFm-_3Y@O0c-FxRe@BKl1d@iSeW6ps2F&0)_6{K2ulRF=bric3iayb5@DMfnH zsSNjN)ssYH?>t8REJ4ykwL|eni68KuvN$a47yHgCwJxNdbYv`vuNDBO^FD_dQdu1~ zUl|@CX45QDzioTJ|9XjCT|4DDv?qG@A8KdY#ccEM5uH^_Ghh3X-Jx=xrnwq64A3vH zF?pK95sI<^a^TaG;%~k)yGXnb4nrhet%Kl584T3ebH^6wPvI29%yeoCD!Qd~k6r1= zVf}s3LvSYoJJ7}ewg)HoOxD2q=EIIe3#1hD{9U%vYuv9(3R`W?i8ro;W%E>#xTBBZ z*Ra8@z0U4%t~(FZh2AaSWI!!mN_&Wog7e0ayJ;YGwQlfjhRyIAq|dv>+@uNFHEqgfA!_%OJurF~qI zzHsN$8S9mf#W36NJf%;-dXaXs;XG#@EcJUAb%*`oU>+U`@y40!E`K`JlJuxW;tqd% z+b4%K<>Ck2GA{0x+nsuN@JK!Fu(kA)wVD}n_cP~oqYhr7hwLa)cl&F8~^ z{cGu@qbzgx);IAYUf!EE@vWH@2QzGzY4FU`6Y?LXL__Y^eSQa*`)&E9_z{sV9hvmL zYW+kwx&tS-s?5j2Bpsx~SmwKOQmCk?Sh7HD@&&l}tyc>)gk{>uPh5!oMpTy?Q2do$ zR~U_6m~S`m*nC67NGQ#Mu5h-#&p>}(JnmvsfdN*gc=Ni`+AG7+W^subt=9Z+4RtR! zrB#!5zR`8n(&hDrDd6I=y=Gz8;wath)tr83R}kP~4$_a?*8RQusl7)ZeQ9uCyU3@c zV*s$5iTK*U*7N5_a?p75MF+V^TW6N&C8#K__$ z0=puIZ_1MzAb#|T2OBQ9b9Ydp3KyR;<-k%aWP@s%&B5}gN6+74iC(NjTDU0&`xids zl55=8nb4kV9K|b&tQ;KG#^~}=81D&Mrpjj!6N*@L=gJ5x(Zr5hy#sR5Kw z>244Nk?wA#5kb18yF0&Y){ggmpYK`j{olAo7tB5PeVuh2zq5Z@?K(VQ(YEl;pL%4> z(Z~#$s=$Nda$){LaV?q21Cmq&amFjN%!VRTGM=jDbe*CD>FRC4*@c`@TfPwbx3ddY zwPQqH+80k^r|+=wLb}~v0&u;pBvUk1^(yp2$-Lolm3K4$(ED|zZE{W?X=nKTyM!q(v?dQA3XR~ zVJk;}AaQeXq0r)$2kS8g?{;D$%`G}^@x^>pM9lT~S;MExnB{OdU`Kq6xuenS2ZgFl zJpcoj>JK0?ak7N2u39FM50Y@x_*4S55Y1!pFY6SA`B<=Iw;jRJ*E69e&-T2%`YtbnYPq-l7S$Q){zA0}=;O2~bF}Wye^?{-!_p2uwWT46Tj0AX!?`Y&3+`93s zR^!C*>Sb3@gyxWuO1%ZQ`mkNko#-XgL#>Vd&B^h;R=Z4eQ5h{+Ih998VTzo80f97DSToll|Xui5B91T!wF}u149HJ&WAP^I*vBbKu+Yw@=Kti*lHrirLg zdz|F=)vjXiRge^kSAdQVO&Gd9{QQ-h8aE*QqE%~s66l9>d#bI3ZypYYe&B5-eb_&7 zF;p?G0p^mY7EjwkP#8RqwX`~cO>Q86A?!s9B++#^PLV*zKnisd=D zAFls9U&iSX4HZd5A^(y+9O)>uKaV9>JTEDnJ9sezl86C+BA>8(SOIe_{%7ys-NqrN ziv)-q*hb~^Ellr&^anxVK0ZFFpU~j~u%1mo1|)N$j|Br;yctv^4>VOLK@cI3B`9o! z$&86HV9;EEx=u+e_X|m4VF<5gVS_Ex1j)rSKnx9!iOEt7p%rK4zs*2+#*Ke{mKg8Y z;?%sHSQQp*#qh&ZEh-I+eFDH&tjsZJJB;`TiRf3-fqGJ_f&%OJC|K56z%y+ za3VbAjSmDFPHheuU$oP#4YV29LWa+Ozg!)#5ZzsQ$T9AuGidPmM0yugxMGNC!*Zzi zTQmXqQQ|D<#O3K;k8!{iobjC1^#Crb47Ve?U0?dkpQPO)>CQqszbv%0GOM?)_NK3F z0wr=I(49z+h$1-Wz~0Pf4}4sgG}%8kIpn##&7sz0YZmi$RakcuybqTlO-=A~tKVofIP{yZOUklcTqg)vKX)ys z$(eLsAXoX#oG2n>m*6Mp@o47bw29pyYEXlj$-CZnfp|N_FLwBUi2g*fGFIg~;jom4 zM*>I9c7hI%N#}vp!lw_fzg{w3>5vg?!viLBb@|I2E&y92>iB`gX<(+3yEuc;Pasb_AhuzUr`-~E9<^8a;(woC4>k{e zW6PIBt8AQAc({%J?~5OuOuOBXUr@7~X0Mrs!(9N-JOfD64;firDrYxVgo2B zR7OsBgut@3dP8Mm#zV#brPG1@pY>L@Tw@3d<*^_3OtAsrlkJe;FmUC>Nv8Dx$E0}Z3M3TtxXL+^Hp6p%B4Y?mB3ook!=wgRcj3<#cK zQuwmgfXwFUkP@FoKeG(HehO>KcFURe4q`0#!+qkC{Fn3|-OB1=oL>58g8KQ3 zc_rGsQ@=KTt8$XK=61nDggR7zDyAnTvTLs4DPxw($<^a9YbbOE)<+P4yFO8+k?eVD zQ5LWQjHPfc{bqWD=*(afr`FT-9KWwoCZ7pAx^qujtU+e9nQ%{#0BHUTfINN*_l_@Nr{DI5E&>_w=U;=`g9iz*lp)CH#w5M3D?@|J5ooWQD;x6(JBaukmt@$eQ{i8`pWS%anEL?d+1Uu<`XoiP4e* zMbG1nY(CF(mzBPBe?1qHhsgXGl(}P_Y0u3vj9l$4e9_m9kd}mY743X;mINUR^q~@k zad-Z9VC7_p`IyNwQXV`P4+8`v&)(6kj9v3GUCDJ(7|auNMZ2(WJXLK2O4zDhu$xKq z^k|D4jf7o`Y~#mdgdXQdMMi#+ep@()o6n%nXFbAmAcyhxNho5y*mcjetkL{OqUfU$ z7{0=BdNzQ(SRQ?ruYasOmySY_k^iw0M6Tn-HWivrUMIpGOpr_!ihOpjLl|@JcL@cN z{hst7W$av2Web*{TyuAj8DHdyiQ$<4TomEl-mI@E>xe&fj0OFVA%&|BL!)308ejSP zl)#%zvW-Qqm@rJ96#tX@?(Xi?O$LE$)ZI5;Bq@S^E-5}Jd-Bk|+yxUeGn~QSSpP+Q zLGF@)y!@9*H9qO(1!gDbKiC<%zeHVTh{1#3%F+`PL_tv0f6uoU_hSbQ5dU0yb(0D0c4PZErRL- z_MAvFI$e$$A==$_CFO$e#l|Pl6(FdX03hN=To_S6OPlu%^A0%5!GO%oJ%`uTx$*lp zfF`Kx>SkXeCK|jN(5ep5g<=JDf#xSjTL`^&S%QGYCyEl_zejOo2WOb7Jg}%zAA|`BPUnh6}e3#P%|d@TNN4iR&Vm zMMZe1=*|an%o5t;*x+98j#P#q`lDM~^=-T3_zmY7pw-0gjnO>}01%2jJI{$Q7B5t> zf7`pSA#N6eX_PJyC04iA8i3$D_>^J2a525KRC91`v?Qx|WwNGXerbu|8;nu6V#bW- zkBRrca`J#=)C~y4jXSIxGtLPfZ=*jxE?iJI7U#4tx*G2hw=~8&z)K2&OiBe8$?K2x zd3=)QK1@5u*A`BK!IV>^ZeNGv9%Q(5s{nAIVtE5-y%s-j!EU-Y$MQU!4pOa8s(A%2 zF1O^H{ZR97m_K7uQ0CnAgE2%{0Kw({=3wo}qiO40g(9+ZJ&qbZ+TzO1?BV}h-NUF` z>*z^44m{xmCKQqzrY+a8uv$^ z1WBM_Xn<6BoVVzuu{-fh#EOx`6xLqChs#goRx;Fw`@LS58h8-HscVdjz``E`e3&^W z5+g4*iJ?&7*nZsn8=x>O^@+jAZ42l`NQnz82!zm86cEgIfWJKGju5)GjBeFXP>l8b zS8dQxW0+|Nm#+1N@WiQ|;`i8qT7KX29+Y!MYqyxK^C4eJ@^HN)wdu2@8>`E#K-ney zbh}Q2{!qwpu7Z~PQ2F9`MPWD#h5Gf(SYIBxfM8{a6AYP4+cdS!|bw=Oz! z{-oi-&vVu`GDpI`(JC;yDdWMzvnBKCWX;X!6p1aUN$SDfZTCvx1#k=Q29Uz&g{)&q zA?)1TF@W}%jl2S0wCq|SM*8Y1;c>-#5&I`^VjRxg=vVgBOgdDOPlXbkV&@wH<-yX&PNkz#j% zZYzTrh}OkY@xFMAW`F%88oEUTjx9hJW(D+}D3BP-gIpeK7K(dF90NK;TswQNkJvFj zLBWF&4w|TMzxA=gz5|j^##qZuY}WrKN%6afB*1kqICq0&Ioa1yx#aIbAJ8d(%1*}| zk#r>Ek?~SKNZf@+{YZ{=Qi4Dtyz7EvtR^c`oXlkD;G95ULHmYSsJ6oLdTCC%Ex(Gdh#zMgAc4omFa7L0Hy$?ELcN z!gM1ekK8nK|83o~(;_s6v@EGJAID#E%k4Dsn7+yAt8+%PcKE`n;9i;0DM5mcM2%@e zkvVtdR#B&L!*6p3u*0)Hp>+`lSd>En6s{A1W}?~~K?xJJjI1OQ$Q9(XEE)t1L?A^U zyK6a=8)ql2G+VNm)e&t%b)k;@bVHx(%Q{ZXDs~f?sYXC+Wl+WW7fr=b1n5Ag+#V47%2}3Qt8LX_>qK@v#C>7&mzDV+1|^!+oI+I8coCIhGCs%yj#`aV+lbFpPi$=j2Z~jX#Gipr{Y_F4{d^e)(@RWrswQ)G zY*o;I@Bw&}d4O0h29kUh)MW5Cm1=|jL`zDlmXDnngB)a^R;i|si6PILmUet*H-?B)ZhO){}MK3 z2tsZn>_>gNGJrEBn5*_ZbQ$SXLQ>$Ap67ddSg*-b$>KM|66bV-Tb~O=cH*z5*aJA^ zB2Sk+F9u$kKL`{NSrA`Xk?P@-zVI)eEH?Q7d>H-KLZ5rd<<|VYTmvFoXYK6YKtdYy zN?HH$Xm}e!L8i3WbnEZGHgG{xu`u`LH=5EGgU1Vtw%YU71n1MM)+AbAZdNT}6SWzK zzMpF|?BSgqlTt33(8oU0lwYLerE^I9ogAh~Dcw5L+Q`K}n$bCOATrUqqLsOp)!gSk zwK08f@3r5>(f#6}y5eH>!h|lsABQcCbYuYQ>?2(03%H2cpAA9(yz6g+i^gMmNJ%B; zXg<=~gLD%%vdMxL=OH}#nV-Z|4GMRikI6l`;h+VF^LPl26FO}UPfZ0+pB4^VuP+^z zC*{}eqxDpAA}>E_97OhXi2&M39H>_!X$@;}78jb{R}fqOb*#vz{wDkBH7{@;W)J%x z(f|MXBRn1{07=azMXpgGsN)meP-Rc*jJZDS>p#%rj-r%3J;eh1wB#+)!@o={knA`yWn(Cr(#Cuyz0wPUpay(=%^H-Z7G#_?cBl2uZ zqV4aC4qzFe^G+tZjS>pS7`0b+k4v`y_51~fJj_}(a&};rf0P6Quz-2coq|tML>_9onDt;Mj;0_ z>M-j>mVv<-m4r*->Ff0oUR{9BL)`;T-N;lV$W<~xO514*HbnG;jRY$I%QnBbIF_&= z_@5ueP$Vw;Y__3z*d3iBHl~HsvX;I%y{1-?zHw1%RFE@XNPrt_Gb<5){o#fA>Cu?M z7b$ezhUU|nwiD&s`e89u*G$a#(LC{wATlUEA$u}r9R!m{#Noa@ZDEXax0bIDHM>{+ zWIyWfBV}U{@{_M<1TRWx2vTfYt7gh4AC2+i7uzwVT|@Wf>?Gi`vas}L$x1uuw-P7F z^2r+;=(nk03ubgL@#{6A?ALpyWr`@sT+VmeOn`VTJR*V#_Y@>PHKbU}An`XgF&TWB z2Od4GTpb3W6hYr?=#&Yq4FcPGGpqsG4)HImK_Ff!7;%ERBsZ9?tk3M zP_0KSs)W-?z7f%S&+OTyLTh6cvNw1Vto*M1V@9y;qhTPkT9>kTQqU&9njU6vb=?2d9s8gL{$W~)So*w>6o*pEy%OxnJ2C~_E4Db$m zS|StcgWqhfLpV-{5C6U+2q*H^ftfz|)euv?b1hZL4?N&BvmK4r)Sd&{sioAuE@zZm zhm);~Wn5Fe7WWJdPbou|2CV6iSm-=l>ZX;Q1GjtKf6Uw~#;Q+fD4CfC&$s=T2%t9O zLeCuou+a+<+B2)G_0Yy2bkYd`Ih-;OpqZ_Ov*)S5( zmhQKK=PI;*Cb#vsB^ZJh*M+EFQH!DoH&y8nT)K57>@aMRwogI!o~f4<3JyGHKB32;0B};%qv7YmFzbJjVZkRq^+TYlkA@YG8RD(# zrNiuP<@o0+0Pz4ga|%C(5xlf&wZgEucP-S(*DRRc+13Y|nU|3C#H&MR(d?Y|ub#?W|GDtf${V#H`P^2~PTQWG0+YZ@{r8Xgb56$E+<%>y{Pwe_dY7U!! zE1p^7<01|zPtsn5hNR?Pv&8dQ7OQToy*3zJE*Roz-7?7U!DI7sbBGumY^5%fGq{zK zGeovLjLH1t^V?qgG=eW4Bmt#ir6VQQ;a#L~JCC}9+ zt+UjSD7LS;i~PA)0}Nko3G;dn=4dMblY;Mh&j&;whE>k#*yL)jyuNKP-=NgYvfxRP z;P}T3{YtTFcOdWQ11Ee*O{8@1U%7@fu|X%9#SEszt9f}$ddMUjgw&iJ_5%Pf{AM{X znEz1BK}hWx46zmN@-)&(tf~u|yzj;sRZbGa3c5w>lgVqiSD%2M(rZ0H_gq zyh}dnlI9cci-Nvjw){{=#+&m|))5Bg(6nr>G@6pA$M>_h3ABcoMMVWaq}K+lu1_}@ z;NgLKqZ4dlxL$L5UgUA+oc;BydQAstlf5r{AYV7G6fcmiQZXX@pRho-pzUKQ6|V1m+;bo z^=GYGi>z_VIS7TiR!_W&`1X$eNlBpms%thTbr01Nx_WB0yV$;SW;jKj%*Fx%#nFOI zng(-dIIR1=5r-)9+WWsxI#4k6SkyBC#DJ<321}>LZIl*H`wMH^roQa%Mqu7Z&~(4n zz4Pu(YuyM+>7J5)>(YV8!^haHOdoj%O8QmXh#Oia=Nt_UgZozK+%6_{Q+k^KWBAU8 z8{B~5RV^PvCkeX?f%A)i(IAo%X7xy~%#sDDGqo0g(<^3uef`ui&f`zr(SMXmNJU&= zi{+u#mKi(@aQoHhoR9g_W;8g))4B({c7Wj8c?Ab{qK)E$*o{F;Vx7MECdfjjJxZ#e zi02)yqSt||K-!9})4fTi3Nhyt#ms6tESLZb9W@uYQUW7SWkMQfOm3Kn-{CJLHBoMgIuz@0w83s`<`(Wlsd^?5-W)sTW&P##RE`h3 zBnV_U)2E7=SKBRHl%MhpQjCT@m%k=V2I-bmVXw;)Eja@)%pl93XQ=cNc14c zN;r;R{pnLKU=%<*J{KMU>BMIsitY#M;l>7?<2Iqc{aFA0ln4@7A`I5;c~Bd8O?6oA zSL?>pVgI0*>dd+)HHUmzZyq6$ZAUnq=4BWqsfQC>M4B%pvPlRFZ;0aI8cu{|$PN(< zYFFdo&PbW)kOi70(tVM)Wn3GO1{ZnYN-`CPIrI*U9T9rxb^P2P9&Ph4uc`$u>M`zvyZ>6=N7U~ zf}8wx8q9vTE@VmQ6V49irxdxC32td~Z9TsC0?utPcOgFP8Il4fs4E~V20JE@@d6f% za1sBoee{9|G0cXLRNIV;*E7#b&*9 zk3IL7)u=#jXqgVeakJok$VZ7knfv}a(d1u1flP8ee@zrlYoA}R;jBFD{G{}GFaKIq zRGjmn!?Ug9z|}FvJVMn3qa=nBEeXVz_MK)KT%@-2lL2PoCwMF(T1TPTRd0BRVqLP0 ziiZwoGI}wCMwR=OkjR(Co+1+#)KvOHl%UWz=c#xf9FFuwcSx|k2t+adQ1ye2dCM3N zf_U^Han&BuZ7x{tetQgYlmq_UrYVNxRz*Jc=Nk)|(t*35rA|vj#%uG5y;j#Q_}grf z+2XxN>Cfc{O3Z>sJ(HK+!mSBcTT)!k?|5FGESc=geg~~0=F!nnbv->!v{YN>5;SBZ zb91?d?^_%$HLp~E{rU(%f0-4cxzxU+H9!DW#_0`ZbNl1`@}CC-AG+iK!o5haR0r$n zFPUR+LVq$^RKCsQ?K02=Wmyr^w|+i1Oy?Aw@)m{k3&(48O44k5?Dim*XSd{gnU-0` z!?|^_$x0L1n_oA}lFRu|RwRz!$-AE2=I;V?eba8r3WkiQx_(%xhsl`}+x>RP>u~u7 zw;v=CNDuUx;Spxm@TaUxRiTkkU3L@Xyp{xOD_ng|@uv1%el%Ur%9!q(^U2F%6phlm zE6b+*9*LD=X{Ik}Fg8@BzZ{TDvF-Ifv1MJYQxw7mSKWo!URrUpQu(^u{zBxcuZ+k3 zih%R{{XMr+AhON@YoAqsDC`cf`-Gx3?MWJim3S|ppfjc617*Jj`UlO>v~!-lBE@B( z%YJpbZ&8N4&Bi7NLM973mjnXVFHEO2e8vH#mV=$SoeqD0pZqH*%j-c)jH9{AFR5)W zhL?jLfP-l!v_4*DIWsISM3mgaCOP5o(80#8_jS#ode#GEqczK05NCvu+A`Ox)G}Kg zgwk0XYi(nzz|^ml?BT9@Z(vp3R9JeHy61LiWMpJ}z;j;~wwkbbm6Eek_mfvu>&0E` zqa0pJ5bx_X-@yQ%OQ#UI-K&UxHmwTkhPr1((P0=D3w8|uw>as4d4aru^Z1fP$bhu^ zoeS($Jf!%5mS)ZDyfEl7{G~*tAGJcp!p^Rs{>B3_<%&0dy{Zmk3Fq5Ox__ZlUqSRv zM&d%?q&3`z`DiRpS%T_YiA@FX$!&road(gf`8d(*un&D!xY4$l^Yv?_3x+JraQf4I z0(2{al&o(sf{4c%) zXDD$yX6N_4DUsgCNQ?*i&?6=4EYzXBMEB`$>q+;ow-AQqTIqy_%a{WctBSXN82Fzu zRhV6&t$Dn_Kf3#?_>Zm z0V!DpqI%jy?rFE87C2bshs5tS$RiGLRgs8;O%Ay~*o#AJVa-@HtOZL=$@j@-MpdE4 zzH(2GIB#P24dksoyqJ$QLg9E~k$0bZt_}4DtR3FmK*!4A^lwYDdr}E={!2tVq5;l? ztkOL4rhOR4OOc;U;5Hs?DWpj^dbSRMv=&&$LIDM-GhWatk_QO{JjP(zUO&jSI(mAP z_U%XL|8sgaAP!oJ+htA$5zWnadoM>YFBFcn<)c{|Cu$4h#Nvr8p1d_(b10D*sGNajdSWjtLldgyT{0KVv$^RonCv$+{_l@1<=R`#c*rk+x&4!E%Mv#Bf#VqN*hx-JU92;Li^%DM|Ys1 zwpN0L5mkmbIXMYWN=jP4m*w@R6Y?L`I-DO0%-~Pl8pwey@myeC*k+Oee3#+Dv|{Z5 zJO;H{A)Cy^?=N3JP>b;wUp@W3?syUyk%HH}a1X29szyF!Bva~Vxl{6ZrJAhLDCSkt z%|4-c?|SFb9&_fcx-0Mb1;H@27XS9OlIfyupLdw5#Is{_ZNYe!%~Q6Z*z)Q8^_#K9 zM>R%E?4!i}wq<}hnuT!J?T3S-NFKzbd9>3d0Jl3qSOrZWm(2pCT0YlZ<0${b6t7EH zQOD)bcx%5x6g^9Hs-G)poBmA217>QOX(=(4zc2kl-@@Pki!mc3cohEL2l6|ViN_H% z?m7vt=pDZxXYDrx242xpesC4Z0ON_s>xA|-(yW*t={d-Kzjq)6ApG)l4!^)J^0;+n zpJo}9Gi}0Zy&a!_3HKQrO5FzkE!GFBf5{5aY>~hTI9lb9g_cW!nFm`CwQw|8*V}j&dPs{DZpD9To5*ZylPsVamwPGx0x(@B4n+@ZKGEao#u-(z$>o%_;!$k z<=ezQisn+Mghkh+_t&VZlXZ>l=8ij7kuUv>b^0#76^0`K9J!;T<7JuJJFUnQDkh$W zhelhBgZg&|Hs<<{=S7m-!|9~9lR>5m zEi5c3G)UED+~4cva4V_P%Zca5#~lgWu5Kd1WkVRg$60QYku?O+iDFQ*jTRaC|F|Yr zZau1wmR5DeaBI&(s^RYsu9N%}aAEe|2if$k&%<9t_5O6;2Hb{ZVaV{KDC$1H`T0du zUs8+TMSvFiAgOgN^2T@;x2qn18-9U(Z_;jf+uVrTtbjg0_v94j1&xRY{R5BZ)^0qTOlBhE8WA2Q=?APJT>&E=DWVATZ~8x&A|u6mh#W zv;u4FNzK>|0V__WS5`jz&(nQJ%IY-E%$YR%57rkl1CTaUkub~e1eSYNKgzAAxF)M# zSC|by9hb#aWC9IENvU5`D^JHq+tT-X#8T*_I6uU2MXj#(&inAD14H1+dnOoc%418? zVz86+73F}z)Rld`fRz>kum+4lE6!4?%&2AvvS^xc=&H2)*lNL7FIG^10y)^2G{w=u8_PA(b3+z;9KDhOr)ZRz@hy3cIFzI)duYR zJvk}m&H+u;;pit6C}~gqdv5a_T_f=ZyB{B-jk&BfU4i zm5x7qg9M7!Y+2|XO38jxj@>?=@9Jb-R`-7^_<PYOaaKSV2ot(3ePbfF zh5Qt7E9>q(v@`?7qLW#-KYN)!bwi(bnyA``@A`}PXd9A!pIl=@pk>9bU~lGpNv{+^ zKIQj8Kx`d+YkOTuR`UQ1rNI$AAL>M69sF<+Ezx?c{p@?kVrWKq)=ZG_RA?KWnsEFqup8W#Yx`Vv_n&{{Wo`BO= z0|VEh{c)>M6OSN4&4!}gacdk8!3@NUUq|NW!H>n$HXlpu87;Wwjs-61+$*CcnC`K| zKeaFiEzYa3Q`=WCU-K!B0ToBSi;#gMozk%7mkAf&o3C^(gPxx*F3~_Clmy9ZV7idq z!v>rnx6p+WIQqq`1PkfVUg%=u4pOwrn((H8JWvNJl6Xn~v{3l_0P~O7IwRls5ZNfO zPpUwaAZdbQ3=BY)Q*)nQE+r^^*>k1m4g<+XQcpq9NQ3(27=u{o_mUYoz4 z6Wx!TuhJ1LU0UsdWNZsS@W^LAgJCf^>i|jE5LYmsJSF0Zs;zaW5;x5tB@oefdNk#> z<*_o7=1aIdEpB+#5l^IE>{jvdrl7jiYh`_;4pPwYg6C|=)q(?I=)L`NNsyCn1Ga6L zz<#yXaH+6CX|y=H_H9lq5l}sW94HIzq~j$aK@;MP0bqQG^3jM8s2f0K^a!xu2HVh9 zXLU8T=N^j6uraVAp@AlRHQ_f>ZDldynp+dc{ zXCk^QcoDo%wPH}~{^9<%30IqSxyh;Dxjd$Ek5JN&9?#i9e~kwHUBzibdg3e6KL3~^ zzv72)y-ewM3~4N__4kynRN3gHSZCeloNASwGribD+JyV&X^2~u=XN8^PW|5$9r8|X zoKab^kGq`31~f?Wl;1FLS3?)vpcs!1W0L!876qz7o9n1DD7rTJ^og-4llbthpg?Sl zwDWo_I_m*vD{M^?YuOj4 z-_d9qE&ryDLBa;kO#$WLd&n7Ubup-(BGODO0FdLWnn~f58MmA1oJw6ITAh|$9ZfcB zRtkRn5?b6T{rraKEbsM?;HuV+?rud4N zPvInBM2A5S%tf4N+|i2dPf@5ZZ_|g4NmXtlXdzgM+L`($u$Bi4R)5lChkfGX?*{J0 zzeM^VHTg7g7YSBpT)yt;?N!N0uoxZ&Y|`5Yi7l_z@+vyPv9{oPE)Dk8L~*JB!umYG zKKait4t-rS^lm~E-#0x51o(?xW0^{(3PuMjND|dm*p$zjb(CB`ytQ^so z+{ouZggRFwV*XPN@~>k4|EQ$?(OBPT0xNqh?hNg4@L_7sktE*yVO8Qa!aC;ff@A7O zE#0X^|ALlg9Dd5?8(*WD((64cT$>%TR9K&ax%VV;c}um2Om(kB>VhZ5_w)_+>Y1qg zYwV)Snt8DM>2cE~ItqZw_v-;ASoMysadgFNK9c6rH{IptUIE8^1e(KJ>r%8nR;BLO#VemY7V9c(%LezIy%w_3)^}A)%;;EWu0TGbwM^!u zvvo1%kY97X$ntnSM0D#%87woWo6Bh`|Ct+=qpiUjSS>SAs|2`yUw0W^@cbk#w4E;h zOe3k!g644wN)O(D5CnnDvdIVnq4(r-%Dw8Y@&%>Zpb7}8BTw8xVJQzOwoUZq+Cq4lIi@5kP z=0_J4+$%2UE>KI9yysGDl~`pLtVyhVF^ z$=@>W3ElluWPA9ak?=nr z4#_NY({{;Lv>DIv1@*JeWly8p>l0g7Bh5V1Rq5JRtnUL1=}R!y&o)&_68kKKp;Kv}h8E>wU4c=D zUZGoX%-d6ROAS!Nbb7MDAq7^g->8*_lQ-&xqD_JN@t=tPKL7kz#RWrmHK)hU@=}A! zoZzmZMJ?~HO>s-R=uq6&fAPIMC9I2eC;%F}_#ry~y0z8>k@tpVb>|M*V*veQ>d$u~ zFe$hGZpk5ji&Isjfc1@6_p(~Cn@aXrB1TctyA&%{EHIvr-MF5fyJw$E4(E}?)522K zeh>|AWETJy(k@}_^!4rZEilA}Kxp6O$HBgi-|Y{1oE+-Yyr}LpTWM@MQ2Kd|PQ>L~ z{FQD-&XA}wrL^*^V(N}+q9?}Vha29zKnGXl*!)iY<#Mc{@>jEv&Hg-vCFIG3E5Sl6 zxrQs)sG|3Ra1#ps^42YiYp=pv=GzHurypqLNsKQT#ROyINxW5|P(VV%1s%DNnAT*T zI5)ud6yuCUj$Q!BO|f(`qsWbET1)WXWPmA|pu3_WnkBmgR(&+G>cd1ZmLc~w=_*QulWBgsJt zpVO_{J!Pj$Kr_7iW`8-Y=5d=p-5YtZi8i0LrQvLH3%e5yEoy22K>Cg?&m(bHu!4i@ zb0qQLyr@rC+6d z9nU~jc}w3uSmbu>J9`KgnREeNev~c{mn_IhpN5$)L=j6JBhiOD=MNs___6=nC44Bv z7L-S}>ReAiUgC9iHjU{tNyaM4`!5#2YHIgd>}KWMwK66sj6Sq<1QT|BTk-8JIogO0 z5J2RNv){h9?2P1CjbR*vkWKJ&*(1GNHUVf!?7PfxpPWPrysA+=(BatEEx^s^K24>H zxdSMvtKW-VE=ga7fyH+ZfL`Nzds_)FK%X|b#Rh;}T2|GR{ttOihioHZy{|zB>rMFk zD5{_M3FfT#jYhTDz2?H`y9|{}ZXd^UftFNdoTuDc$*9)7=WweOqe=bBvl|~?{aP|l zfgBr^`>*Mg20G8ib=&C(OyAu|AEn!U(U)l}t}nM&|9v(&Zlh^Qr*crT{VFxAAHUZ_)KnYVs>@=4Z|Yx;vnx}%<>)i!IR#f0Qb0WRMb z-(f|^A~m{%t(IKQc}U%*UhbB}C(baoJi37hkZw6uF&_L%&W8ce;aW=O4MN2A`13wN z2L=%n@kE7!w`h7C_eWleI?l%;GRdiVm}Fw#jD*UBr^MvstIhRPOsOqxFS*~6+AUd) zDBg__e0A>fxs(3&rMZj{Pbhb2tz=Uav&7STRZN8=)vina{MF<1AG<) zhQ!nA%hR>b0Fx1H>Y@XO+hE@kr}A95We^6k08#Sq61|}PDdq`wOw0o~%VQ~t-F7CQ znb#R;?&sCz))oHs%^mMmU{t|JO#?4Pbqdg6bE2jvMlV1@|LSP+br(=pX;R~c`_r

IQm^k@A?( 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�fHsjw1Gjz`F}nj(lqeQVAPkt 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 xk8y~ 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

openPanel("configure-assemblies")} + onClick={() => openPanel("configure")} /> void } -const OptionCard: React.FC = ({ value, onSelected }) => { +const OptionCard: React.FC = ({ value, index, onSelected }) => { return ( /* Box containing the entire card */ @@ -56,7 +57,7 @@ const OptionCard: React.FC = ({ value, onSelected }) => { {/* Label for joint index and type (grey if child) */} @@ -79,29 +80,27 @@ const OptionCard: React.FC = ({ value, onSelected }) => { interface SelectMenuProps { options: SelectMenuOption[] onOptionSelected: (val: SelectMenuOption | undefined) => void - headerText: string + defaultHeaderText: string indentation?: number } -const SelectMenu: React.FC = ({ options, onOptionSelected, headerText, indentation }) => { +const SelectMenu: React.FC = ({ + options, + onOptionSelected, + defaultHeaderText: headerText, + indentation, +}) => { const [selectedOption, setSelectedOption] = useState(undefined) useEffect(() => { if (selectedOption == undefined) return if (!options.some(o => o.name == selectedOption.name)) setSelectedOption(undefined) - }, [options]) + }, [options, selectedOption]) return ( <> - + {selectedOption != undefined ? ( From 78a0aafb535b72772323bc85e19258206048f2b2 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Thu, 1 Aug 2024 16:31:57 -0700 Subject: [PATCH 130/344] Refactor --- fission/src/systems/physics/Mechanism.ts | 2 +- fission/src/systems/physics/PhysicsSystem.ts | 21 +++++------- .../behavior/synthesis/ArcadeDriveBehavior.ts | 15 ++++----- .../behavior/synthesis/GenericArmBehavior.ts | 6 ++-- .../synthesis/GenericElevatorBehavior.ts | 7 ++-- .../systems/simulation/driver/HingeDriver.ts | 33 +++++++++---------- .../systems/simulation/driver/SliderDriver.ts | 31 ++++++++--------- .../systems/simulation/driver/WheelDriver.ts | 31 ++++++++--------- .../simulation/wpilib_brain/WPILibBrain.ts | 4 +-- 9 files changed, 69 insertions(+), 81 deletions(-) diff --git a/fission/src/systems/physics/Mechanism.ts b/fission/src/systems/physics/Mechanism.ts index 4a240576ed..ae5e0722d3 100644 --- a/fission/src/systems/physics/Mechanism.ts +++ b/fission/src/systems/physics/Mechanism.ts @@ -7,8 +7,8 @@ export interface MechanismConstraint { parentBody: Jolt.BodyID childBody: Jolt.BodyID constraint: Jolt.Constraint - info?: mirabuf.IInfo maxVelocity: number + info?: mirabuf.IInfo } class Mechanism { diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 8e2e4d806c..7e8e592d30 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -351,7 +351,7 @@ class PhysicsSystem extends WorldSystem { const bodyA = this.GetBody(bodyIdA) const bodyB = this.GetBody(bodyIdB) - const constraints: [Jolt.Constraint, number][] = [] + const constraints: Jolt.Constraint[] = [] let listener: Jolt.PhysicsStepListener | undefined = undefined // maxForce becomes maxForce for sliders, maxTorque for hinges, and maxAcceleration for wheels @@ -384,8 +384,8 @@ class PhysicsSystem extends WorldSystem { bodyB, parser.assembly.info!.version! ) - constraints.push([res[0], maxVel ?? 40]) - constraints.push([res[1], maxVel ?? 40]) + constraints.push(res[0]) + constraints.push(res[1]) listener = res[2] } else { const res = this.CreateWheelConstraint( @@ -396,19 +396,17 @@ class PhysicsSystem extends WorldSystem { bodyA, parser.assembly.info!.version! ) - constraints.push([res[0], maxVel ?? 40]) - constraints.push([res[1], maxVel ?? 40]) + constraints.push(res[0]) + constraints.push(res[1]) listener = res[2] } } else { - constraints.push( - [this.CreateHingeConstraint(jInst, jDef, maxForce ?? 200, bodyA, bodyB, parser.assembly.info!.version!), maxVel ? maxVel : 40] - ) + constraints.push(this.CreateHingeConstraint(jInst, jDef, maxForce ?? 200, bodyA, bodyB, parser.assembly.info!.version!)) } break case mirabuf.joint.JointMotion.SLIDER: - constraints.push([this.CreateSliderConstraint(jInst, jDef, maxForce ?? 200, bodyA, bodyB), maxVel ? maxVel : 40]) + constraints.push(this.CreateSliderConstraint(jInst, jDef, maxForce ?? 200, bodyA, bodyB)) break default: console.debug("Unsupported joint detected. Skipping...") @@ -420,10 +418,9 @@ class PhysicsSystem extends WorldSystem { mechanism.AddConstraint({ parentBody: bodyIdA, childBody: bodyIdB, - constraint: x[0], - maxVelocity: x[1], + constraint: x, + maxVelocity: maxVel ?? 30, info: jInst.info ?? undefined, // remove possibility for null - }) ) } diff --git a/fission/src/systems/simulation/behavior/synthesis/ArcadeDriveBehavior.ts b/fission/src/systems/simulation/behavior/synthesis/ArcadeDriveBehavior.ts index 035db861d0..2146f64a72 100644 --- a/fission/src/systems/simulation/behavior/synthesis/ArcadeDriveBehavior.ts +++ b/fission/src/systems/simulation/behavior/synthesis/ArcadeDriveBehavior.ts @@ -23,19 +23,16 @@ class ArcadeDriveBehavior extends Behavior { } // Sets the drivetrains target linear and rotational velocity - private DriveSpeeds(linearVelocity: number, rotationVelocity: number) { - const leftSpeed = linearVelocity + rotationVelocity - const rightSpeed = linearVelocity - rotationVelocity + private DriveSpeeds(driveInput: number, turnInput: number) { + const leftDirection = driveInput + turnInput + const rightDirection = driveInput - turnInput - this.leftWheels.forEach(wheel => (wheel.targetWheelSpeed = leftSpeed)) - this.rightWheels.forEach(wheel => (wheel.targetWheelSpeed = rightSpeed)) + this.leftWheels.forEach(wheel => (wheel.accelerationDirection = leftDirection)) + this.rightWheels.forEach(wheel => (wheel.accelerationDirection = rightDirection)) } public Update(_: number): void { - const driveInput = InputSystem.getInput("arcadeDrive", this._brainIndex) - const turnInput = InputSystem.getInput("arcadeTurn", this._brainIndex) - - this.DriveSpeeds(driveInput, turnInput) + this.DriveSpeeds(InputSystem.getInput("arcadeDrive", this._brainIndex), InputSystem.getInput("arcadeTurn", this._brainIndex)) } } diff --git a/fission/src/systems/simulation/behavior/synthesis/GenericArmBehavior.ts b/fission/src/systems/simulation/behavior/synthesis/GenericArmBehavior.ts index 1d55bfd9e6..8a15a51c2a 100644 --- a/fission/src/systems/simulation/behavior/synthesis/GenericArmBehavior.ts +++ b/fission/src/systems/simulation/behavior/synthesis/GenericArmBehavior.ts @@ -16,9 +16,9 @@ class GenericArmBehavior extends Behavior { this._brainIndex = brainIndex } - // Sets the arms target rotational velocity - rotateArm(rotationalVelocity: number) { - this._hingeDriver.targetVelocity = rotationalVelocity + // Sets the arm's acceleration direction + rotateArm(input: number) { + this._hingeDriver.accelerationDirection = input } public Update(_: number): void { diff --git a/fission/src/systems/simulation/behavior/synthesis/GenericElevatorBehavior.ts b/fission/src/systems/simulation/behavior/synthesis/GenericElevatorBehavior.ts index cc5dfda7ee..198ae96fd5 100644 --- a/fission/src/systems/simulation/behavior/synthesis/GenericElevatorBehavior.ts +++ b/fission/src/systems/simulation/behavior/synthesis/GenericElevatorBehavior.ts @@ -16,10 +16,9 @@ class GenericElevatorBehavior extends Behavior { this._brainIndex = brainIndex } - // Changes the elevators target position - moveElevator(linearVelocity: number) { - // Multiplied by velocity in driver - this._sliderDriver.targetVelocity = linearVelocity + // Changes the elevator's acceleration direction + moveElevator(input: number) { + this._sliderDriver.accelerationDirection = input } public Update(_: number): void { diff --git a/fission/src/systems/simulation/driver/HingeDriver.ts b/fission/src/systems/simulation/driver/HingeDriver.ts index 85babb888d..31f752a6f9 100644 --- a/fission/src/systems/simulation/driver/HingeDriver.ts +++ b/fission/src/systems/simulation/driver/HingeDriver.ts @@ -8,23 +8,15 @@ class HingeDriver extends Driver { private _constraint: Jolt.HingeConstraint private _controlMode: DriverControlMode = DriverControlMode.Velocity - private _targetVelocity: number = 0.0 + private _accelerationDirection: number = 0.0 private _targetAngle: number private _maxVelocity: number - public get maxVelocity(): number { - return this._maxVelocity + public get accelerationDirection(): number { + return this._accelerationDirection } - - public set maxVelocity(radsPerSec: number) { - this._maxVelocity = radsPerSec - } - - public get targetVelocity(): number { - return this._targetVelocity - } - public set targetVelocity(radsPerSec: number) { - this._targetVelocity = radsPerSec + public set accelerationDirection(radsPerSec: number) { + this._accelerationDirection = radsPerSec } public get targetAngle(): number { @@ -34,6 +26,13 @@ class HingeDriver extends Driver { this._targetAngle = Math.max(this._constraint.GetLimitsMin(), Math.min(this._constraint.GetLimitsMax(), rads)) } + public get maxVelocity(): number { + return this._maxVelocity + } + public set maxVelocity(radsPerSec: number) { + this._maxVelocity = radsPerSec + } + public get maxForce() { return this._constraint.GetMotorSettings().mMaxTorqueLimit } @@ -67,6 +66,8 @@ class HingeDriver extends Driver { super(info) this._constraint = constraint + this._maxVelocity = maxVelocity + this._targetAngle = this._constraint.GetCurrentAngle() const motorSettings = this._constraint.GetMotorSettings() const springSettings = motorSettings.mSpringSettings @@ -74,18 +75,14 @@ class HingeDriver extends Driver { // These values were selected based on the suggestions of the documentation for stiff control. springSettings.mFrequency = 20 * (1.0 / GetLastDeltaT()) springSettings.mDamping = 0.995 - motorSettings.mSpringSettings = springSettings - this._targetAngle = this._constraint.GetCurrentAngle() - this._maxVelocity = maxVelocity - this.controlMode = DriverControlMode.Velocity } public Update(_: number): void { if (this._controlMode == DriverControlMode.Velocity) { - this._constraint.SetTargetAngularVelocity(this._targetVelocity * this._maxVelocity) + this._constraint.SetTargetAngularVelocity(this._accelerationDirection * this._maxVelocity) } else if (this._controlMode == DriverControlMode.Position) { //TODO add maxVel to diff this._constraint.SetTargetAngle(this._targetAngle) diff --git a/fission/src/systems/simulation/driver/SliderDriver.ts b/fission/src/systems/simulation/driver/SliderDriver.ts index f00249f521..192ac99400 100644 --- a/fission/src/systems/simulation/driver/SliderDriver.ts +++ b/fission/src/systems/simulation/driver/SliderDriver.ts @@ -8,23 +8,15 @@ class SliderDriver extends Driver { private _constraint: Jolt.SliderConstraint private _controlMode: DriverControlMode = DriverControlMode.Velocity - private _targetVelocity: number = 0.0 + private _accelerationDirection: number = 0.0 private _targetPosition: number = 0.0 private _maxVelocity: number = 1.0 - public get maxVelocity(): number { - return this._maxVelocity - } - - public set maxVelocity(radsPerSec: number) { - this._maxVelocity = radsPerSec - } - - public get targetVelocity(): number { - return this._targetVelocity + public get accelerationDirection(): number { + return this._accelerationDirection } - public set targetVelocity(radsPerSec: number) { - this._targetVelocity = radsPerSec + public set accelerationDirection(radsPerSec: number) { + this._accelerationDirection = radsPerSec } public get targetPosition(): number { @@ -37,6 +29,13 @@ class SliderDriver extends Driver { ) } + public get maxVelocity(): number { + return this._maxVelocity + } + public set maxVelocity(radsPerSec: number) { + this._maxVelocity = radsPerSec + } + public get maxForce(): number { return this._constraint.GetMotorSettings().mMaxForceLimit } @@ -69,23 +68,21 @@ class SliderDriver extends Driver { super(info) this._constraint = constraint + this._maxVelocity = maxVelocity const motorSettings = this._constraint.GetMotorSettings() const springSettings = motorSettings.mSpringSettings springSettings.mFrequency = 20 * (1.0 / GetLastDeltaT()) springSettings.mDamping = 0.999 - motorSettings.mSpringSettings = springSettings - this._maxVelocity = maxVelocity - this._constraint.SetMotorState(JOLT.EMotorState_Velocity) this.controlMode = DriverControlMode.Velocity } public Update(_: number): void { if (this._controlMode == DriverControlMode.Velocity) { - this._constraint.SetTargetVelocity(this._targetVelocity * this._maxVelocity) + this._constraint.SetTargetVelocity(this._accelerationDirection * this._maxVelocity) } else if (this._controlMode == DriverControlMode.Position) { //TODO: MaxVel checks diff this._constraint.SetTargetPosition(this._targetPosition) diff --git a/fission/src/systems/simulation/driver/WheelDriver.ts b/fission/src/systems/simulation/driver/WheelDriver.ts index 90dab968cd..35aa702494 100644 --- a/fission/src/systems/simulation/driver/WheelDriver.ts +++ b/fission/src/systems/simulation/driver/WheelDriver.ts @@ -14,22 +14,30 @@ class WheelDriver extends Driver { public device?: string private _reversed: boolean - private _targetWheelSpeed: number = 0.0 + private _accelerationDirection: number = 0.0 private _prevVel: number = 0.0 private _maxVelocity = 30.0 private _maxAcceleration = 1.5 - public get targetWheelSpeed(): number { - return this._targetWheelSpeed + private _targetVelocity = () => { + let vel = this._accelerationDirection * (this._reversed ? -1 : 1) * this._maxVelocity + + if (vel - this._prevVel < -this._maxAcceleration) vel = this._prevVel - this._maxAcceleration + if (vel - this._prevVel > this._maxAcceleration) vel = this._prevVel + this._maxAcceleration + + return vel } - public set targetWheelSpeed(radsPerSec: number) { - this._targetWheelSpeed = radsPerSec + + public get accelerationDirection(): number { + return this._accelerationDirection + } + public set accelerationDirection(radsPerSec: number) { + this._accelerationDirection = radsPerSec } public get maxVelocity(): number { return this._maxVelocity } - public set maxVelocity(radsPerSec: number) { this._maxVelocity = radsPerSec } @@ -37,7 +45,6 @@ class WheelDriver extends Driver { public get maxForce(): number { return this._maxAcceleration } - public set maxForce(acc: number) { this._maxAcceleration = acc } @@ -60,6 +67,7 @@ class WheelDriver extends Driver { this._maxVelocity = maxVel const controller = JOLT.castObject(this._constraint.GetController(), JOLT.WheeledVehicleController) this._maxAcceleration = controller.GetEngine().mMaxTorque + this._reversed = reversed this.deviceType = deviceType this.device = device @@ -69,14 +77,7 @@ class WheelDriver extends Driver { } public Update(_: number): void { - let vel = this._targetWheelSpeed * (this._reversed ? -1 : 1) * this._maxVelocity - // if (InputSystem.isKeyPressed("KeyW")) console.log(`vel 1: ${vel}`) - - if (vel - this._prevVel < -this._maxAcceleration) vel = this._prevVel - this._maxAcceleration - if (vel - this._prevVel > this._maxAcceleration) vel = this._prevVel + this._maxAcceleration - // if (vel != 0) console.log(`prev: ${this._prevVel} vel 3: ${vel}`) - - + const vel = this._targetVelocity() this._wheel.SetAngularVelocity(vel) this._prevVel = vel } diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index 57ca1a40bd..63bbba188d 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -298,9 +298,9 @@ export class PWMGroup extends SimOutputGroup { this.drivers.forEach(d => { if (d instanceof WheelDriver) { - d.targetWheelSpeed = average * 40 + d.accelerationDirection = average * 40 } else if (d instanceof HingeDriver || d instanceof SliderDriver) { - d.targetVelocity = average * 40 + d.accelerationDirection = average * 40 } d.Update(_deltaT) }) From 064636d1d4e3fc9b968a1adc02d2bddc91eedd4e Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Thu, 1 Aug 2024 16:34:15 -0700 Subject: [PATCH 131/344] !tried using reflection to fix private constructor issue, failed --- .../.settings/org.eclipse.buildship.core.prefs | 4 ++-- .../autodesk/synthesis/revrobotics/CANSparkMax.java | 8 ++++---- .../synthesis/revrobotics/SparkAbsoluteEncoder.java | 12 ++++++++++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/simulation/SyntheSimJava/.settings/org.eclipse.buildship.core.prefs b/simulation/SyntheSimJava/.settings/org.eclipse.buildship.core.prefs index bc1ff1181e..40e6f3c167 100644 --- a/simulation/SyntheSimJava/.settings/org.eclipse.buildship.core.prefs +++ b/simulation/SyntheSimJava/.settings/org.eclipse.buildship.core.prefs @@ -1,11 +1,11 @@ -arguments=--init-script /Users/colbura/cache/jdtls/config/org.eclipse.osgi/55/0/.cp/gradle/init/init.gradle +arguments=--init-script /Users/colbura/wpilib/2024/vscode/code-portable-data/user-data/User/globalStorage/redhat.java/1.26.2023121408/config_mac/org.eclipse.osgi/53/0/.cp/gradle/init/init.gradle --init-script /Users/colbura/wpilib/2024/vscode/code-portable-data/user-data/User/globalStorage/redhat.java/1.26.2023121408/config_mac/org.eclipse.osgi/53/0/.cp/gradle/protobuf/init.gradle auto.sync=false build.scans.enabled=false connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.project.dir= eclipse.preferences.version=1 gradle.user.home= -java.home=/opt/homebrew/Cellar/openjdk/22.0.2/libexec/openjdk.jdk/Contents/Home +java.home=/Users/colbura/wpilib/2024/jdk jvm.arguments= offline.mode=false override.workspace.settings=true diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java index 3f9e2a9fbc..d83fd5367b 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java @@ -12,7 +12,7 @@ public class CANSparkMax extends com.revrobotics.CANSparkMax { private CANMotor m_motor; - private CANEncoder m_encoder; + public CANEncoder m_encoder; /** * Creates a new CANSparkMax, wrapped with simulation support. @@ -49,13 +49,13 @@ public REVLibError setIdleMode(com.revrobotics.CANSparkBase.IdleMode mode) { @Override public SparkAbsoluteEncoder getAbsoluteEncoder() { - return new SparkAbsoluteEncoder(this.m_encoder, this, com.revrobotics.SparkAbsoluteEncoder.Type.kDutyCycle); + return new SparkAbsoluteEncoder(this.m_encoder, com.revrobotics.SparkAbsoluteEncoder.Type.kDutyCycle); } @Override public SparkAbsoluteEncoder getAbsoluteEncoder(com.revrobotics.SparkAbsoluteEncoder.Type type) { - return new SparkAbsoluteEncoder(this.m_encoder, this, type); - } + return new SparkAbsoluteEncoder(this.m_encoder, type); + } // TODO: Finish following // @Override diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/SparkAbsoluteEncoder.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/SparkAbsoluteEncoder.java index 32187d00ef..4762a490a4 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/SparkAbsoluteEncoder.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/SparkAbsoluteEncoder.java @@ -1,11 +1,19 @@ package com.autodesk.synthesis.revrobotics; +import java.lang.reflect.Constructor; + import com.autodesk.synthesis.CANEncoder; import com.revrobotics.CANSparkBase; -public class SparkAbsoluteEncoder extends com.revrobotics.SparkAbsoluteEncoder { +public class SparkAbsoluteEncoder extends com.revrobotics.SparkAbsoluteEncoder{ private CANEncoder encoder; - public SparkAbsoluteEncoder(CANEncoder encoder, CANSparkBase base, com.revrobotics.SparkAbsoluteEncoder.Type type) { + public SparkAbsoluteEncoder(CANEncoder encoder, com.revrobotics.SparkAbsoluteEncoder.Type type) throws Exception { + try { + Constructor constructor = com.revrobotics.SparkAbsoluteEncoder.class.getConstructor(com.revrobotics.CANSparkBase.class, com.revrobotics.SparkAbsoluteEncoder.Type.class); + } catch (NoSuchMethodException | SecurityException e) { + e.printStackTrace(); + } + this.encoder = encoder; } From 9a3bd5011c47f4bd3aa01604ec95d97a70686231 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Thu, 1 Aug 2024 16:40:02 -0700 Subject: [PATCH 132/344] lol I forgot to save, idk how to use vscode since my jdtls broke --- .../synthesis/revrobotics/CANSparkMax.java | 18 ++++++++++++---- .../revrobotics/SparkAbsoluteEncoder.java | 21 +++++++++++-------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java index d83fd5367b..b53a87ee58 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java @@ -48,13 +48,23 @@ public REVLibError setIdleMode(com.revrobotics.CANSparkBase.IdleMode mode) { } @Override - public SparkAbsoluteEncoder getAbsoluteEncoder() { - return new SparkAbsoluteEncoder(this.m_encoder, com.revrobotics.SparkAbsoluteEncoder.Type.kDutyCycle); + public SparkAbsoluteEncoder getAbsoluteEncoder() throws Exception{ + try { + return new SparkAbsoluteEncoder(this.m_encoder, com.revrobotics.SparkAbsoluteEncoder.Type.kDutyCycle); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } @Override - public SparkAbsoluteEncoder getAbsoluteEncoder(com.revrobotics.SparkAbsoluteEncoder.Type type) { - return new SparkAbsoluteEncoder(this.m_encoder, type); + public SparkAbsoluteEncoder getAbsoluteEncoder(com.revrobotics.SparkAbsoluteEncoder.Type type) throws Exception { + try { + return new SparkAbsoluteEncoder(this.m_encoder, type); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } // TODO: Finish following diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/SparkAbsoluteEncoder.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/SparkAbsoluteEncoder.java index 4762a490a4..4c15778e3c 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/SparkAbsoluteEncoder.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/SparkAbsoluteEncoder.java @@ -4,26 +4,29 @@ import com.autodesk.synthesis.CANEncoder; import com.revrobotics.CANSparkBase; -public class SparkAbsoluteEncoder extends com.revrobotics.SparkAbsoluteEncoder{ - private CANEncoder encoder; +public class SparkAbsoluteEncoder extends com.revrobotics.SparkAbsoluteEncoder { + private CANSparkBase base; - public SparkAbsoluteEncoder(CANEncoder encoder, com.revrobotics.SparkAbsoluteEncoder.Type type) throws Exception { + // We're prettys sure that it's impossible to make a child class of this parent class with a constructor, because the parent's constructor is private + // Reflection didn't work since a child constructor __needs__ a super() call at the top of the body + // Passing in the constuctor wouldn't work either, since it's just a function pointer and the child constructor would have no idea that it points to its super + public SparkAbsoluteEncoder(CANSparkBase base, com.revrobotics.SparkAbsoluteEncoder.Type type) throws Exception { try { - Constructor constructor = com.revrobotics.SparkAbsoluteEncoder.class.getConstructor(com.revrobotics.CANSparkBase.class, com.revrobotics.SparkAbsoluteEncoder.Type.class); - } catch (NoSuchMethodException | SecurityException e) { + Constructor constructor = com.revrobotics.SparkAbsoluteEncoder.class.getDeclaredConstructor(com.revrobotics.CANSparkBase.class, com.revrobotics.SparkAbsoluteEncoder.Type.class); + constructor.setAccessible(true); + com.revrobotics.SparkAbsoluteEncoder parent = constructor.newInstance(base, type); + } catch (Exception e) { e.printStackTrace(); } - - this.encoder = encoder; } @Override public double getPosition() { - return encoder.getPosition(); + return this.base.m_encoder.getPosition(); } @Override public double getVelocity() { - return encoder.getVelocity(); + return this.base.m_encoder.getVelocity(); } } From 6944946a0e1c373c50d4e4a81d96d7cf01d1ff64 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Thu, 1 Aug 2024 16:44:09 -0700 Subject: [PATCH 133/344] Max velocity on position setting --- fission/src/systems/simulation/driver/HingeDriver.ts | 7 ++++++- fission/src/systems/simulation/driver/SliderDriver.ts | 10 ++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/fission/src/systems/simulation/driver/HingeDriver.ts b/fission/src/systems/simulation/driver/HingeDriver.ts index 31f752a6f9..8a24e4e43f 100644 --- a/fission/src/systems/simulation/driver/HingeDriver.ts +++ b/fission/src/systems/simulation/driver/HingeDriver.ts @@ -12,6 +12,8 @@ class HingeDriver extends Driver { private _targetAngle: number private _maxVelocity: number + private _prevAng: number = 0.0 + public get accelerationDirection(): number { return this._accelerationDirection } @@ -84,7 +86,10 @@ class HingeDriver extends Driver { if (this._controlMode == DriverControlMode.Velocity) { this._constraint.SetTargetAngularVelocity(this._accelerationDirection * this._maxVelocity) } else if (this._controlMode == DriverControlMode.Position) { - //TODO add maxVel to diff + let ang = this._targetAngle + + if (ang - this._prevAng < -this.maxVelocity) ang = this._prevAng - this._maxVelocity + if (ang - this._prevAng > this.maxVelocity) ang = this._prevAng + this._maxVelocity this._constraint.SetTargetAngle(this._targetAngle) } } diff --git a/fission/src/systems/simulation/driver/SliderDriver.ts b/fission/src/systems/simulation/driver/SliderDriver.ts index 192ac99400..2c23c89304 100644 --- a/fission/src/systems/simulation/driver/SliderDriver.ts +++ b/fission/src/systems/simulation/driver/SliderDriver.ts @@ -12,6 +12,8 @@ class SliderDriver extends Driver { private _targetPosition: number = 0.0 private _maxVelocity: number = 1.0 + private _prevPos: number = 0.0 + public get accelerationDirection(): number { return this._accelerationDirection } @@ -84,8 +86,12 @@ class SliderDriver extends Driver { if (this._controlMode == DriverControlMode.Velocity) { this._constraint.SetTargetVelocity(this._accelerationDirection * this._maxVelocity) } else if (this._controlMode == DriverControlMode.Position) { - //TODO: MaxVel checks diff - this._constraint.SetTargetPosition(this._targetPosition) + let pos = this._targetPosition + + if (pos - this._prevPos < -this.maxVelocity) pos = this._prevPos - this._maxVelocity + if (pos - this._prevPos > this.maxVelocity) pos = this._prevPos + this._maxVelocity + + this._constraint.SetTargetPosition(pos) } } } From f8d6edf253c8ab023b16630cfe6be25982088e8a Mon Sep 17 00:00:00 2001 From: a-crowell Date: Thu, 1 Aug 2024 17:08:17 -0700 Subject: [PATCH 134/344] Field bug and range adjustments --- fission/src/systems/physics/PhysicsSystem.ts | 18 ++++++++++++++---- .../configuring/ConfigureJointsPanel.tsx | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 7e8e592d30..1d325c1059 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -62,6 +62,10 @@ const FLOOR_FRICTION = 0.7 const SUSPENSION_MIN_FACTOR = 0.1 const SUSPENSION_MAX_FACTOR = 0.3 +// Motor constants +const VELOCITY_DEFAULT = 30 +const ACCELERATION_DEFAULT = 150 + /** * The PhysicsSystem handles all Jolt Physics interactions within Synthesis. * This system can create physical representations of objects such as Robots, @@ -355,8 +359,14 @@ class PhysicsSystem extends WorldSystem { let listener: Jolt.PhysicsStepListener | undefined = undefined // maxForce becomes maxForce for sliders, maxTorque for hinges, and maxAcceleration for wheels - let maxVel = jointData.motorDefinitions![jDef.motorReference].simpleMotor?.maxVelocity - let maxForce = jointData.motorDefinitions![jDef.motorReference].simpleMotor?.stallTorque + let maxVel = VELOCITY_DEFAULT + let maxForce; + const motor = jointData.motorDefinitions![jDef.motorReference] + if (motor && motor.simpleMotor) { + maxVel = motor.simpleMotor.maxVelocity ?? VELOCITY_DEFAULT + maxForce = motor.simpleMotor.stallTorque ?? ACCELERATION_DEFAULT + } + const motors = PreferencesSystem.getRobotPreferences(parser.assembly.info?.name ?? "").motors if (motors) { const thisMotor = motors.filter(x => x.name == jInst.info?.name) @@ -401,7 +411,7 @@ class PhysicsSystem extends WorldSystem { listener = res[2] } } else { - constraints.push(this.CreateHingeConstraint(jInst, jDef, maxForce ?? 200, bodyA, bodyB, parser.assembly.info!.version!)) + constraints.push(this.CreateHingeConstraint(jInst, jDef, maxForce ?? 50, bodyA, bodyB, parser.assembly.info!.version!)) } break case mirabuf.joint.JointMotion.SLIDER: @@ -419,7 +429,7 @@ class PhysicsSystem extends WorldSystem { parentBody: bodyIdA, childBody: bodyIdB, constraint: x, - maxVelocity: maxVel ?? 30, + maxVelocity: maxVel ?? VELOCITY_DEFAULT, info: jInst.info ?? undefined, // remove possibility for null }) ) diff --git a/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx index 1268dbc229..890d4132b5 100644 --- a/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx @@ -99,7 +99,7 @@ const JointRow: React.FC = ({ robot, driver }) => { /> Date: Thu, 1 Aug 2024 17:36:08 -0700 Subject: [PATCH 135/344] Refactor and doc --- fission/src/systems/physics/PhysicsSystem.ts | 31 ++++++--------- fission/src/ui/components/MainHUD.tsx | 1 - .../configuring/ConfigureJointsPanel.tsx | 38 +++++++++---------- 3 files changed, 30 insertions(+), 40 deletions(-) diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 1d325c1059..8d91986ced 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -62,9 +62,8 @@ const FLOOR_FRICTION = 0.7 const SUSPENSION_MIN_FACTOR = 0.1 const SUSPENSION_MAX_FACTOR = 0.3 -// Motor constants +// Motor constant const VELOCITY_DEFAULT = 30 -const ACCELERATION_DEFAULT = 150 /** * The PhysicsSystem handles all Jolt Physics interactions within Synthesis. @@ -358,23 +357,14 @@ class PhysicsSystem extends WorldSystem { const constraints: Jolt.Constraint[] = [] let listener: Jolt.PhysicsStepListener | undefined = undefined - // maxForce becomes maxForce for sliders, maxTorque for hinges, and maxAcceleration for wheels - let maxVel = VELOCITY_DEFAULT - let maxForce; - const motor = jointData.motorDefinitions![jDef.motorReference] - if (motor && motor.simpleMotor) { - maxVel = motor.simpleMotor.maxVelocity ?? VELOCITY_DEFAULT - maxForce = motor.simpleMotor.stallTorque ?? ACCELERATION_DEFAULT - } + // Motor preferences and mirabuf + const prefMotors = PreferencesSystem.getRobotPreferences(parser.assembly.info?.name ?? "").motors + const prefMotor = prefMotors ? prefMotors.filter(x => x.name == jInst.info?.name) : undefined + const miraMotor = jointData.motorDefinitions![jDef.motorReference] - const motors = PreferencesSystem.getRobotPreferences(parser.assembly.info?.name ?? "").motors - if (motors) { - const thisMotor = motors.filter(x => x.name == jInst.info?.name) - if (thisMotor[0]) { - maxVel = thisMotor[0].maxVelocity - maxForce = thisMotor[0].maxForce - } - } + // If preference motor exists, sets vel/acc to prefMotor. Then, if mirabuf motor exists, sets vel/acc to miraMotor. Then default + let maxVel = (prefMotor && prefMotor[0]) ? prefMotor[0].maxVelocity : ((miraMotor && miraMotor.simpleMotor) ? miraMotor.simpleMotor.maxVelocity ?? VELOCITY_DEFAULT : VELOCITY_DEFAULT) + let maxForce = (prefMotor && prefMotor[0]) ? prefMotor[0].maxForce : ((miraMotor && miraMotor.simpleMotor) ? miraMotor.simpleMotor.stallTorque : undefined) switch (jDef.jointMotionType!) { case mirabuf.joint.JointMotion.REVOLUTE: @@ -574,7 +564,7 @@ class PhysicsSystem extends WorldSystem { } sliderConstraintSettings.mMotorSettings.mMaxForceLimit = maxForce - sliderConstraintSettings.mMotorSettings.mMinForceLimit = -sliderConstraintSettings.mMotorSettings.mMaxForceLimit + sliderConstraintSettings.mMotorSettings.mMinForceLimit = -maxForce const constraint = sliderConstraintSettings.Create(bodyA, bodyB) @@ -634,6 +624,9 @@ class PhysicsSystem extends WorldSystem { vehicleSettings.mWheels.clear() vehicleSettings.mWheels.push_back(wheelSettings) + // Other than maxTorque, these controller settings are not being used as of now + // because ArcadeDriveBehavior goes directly to the WheelDrivers. + // MaxTorque is only used as communication for WheelDriver to get maxAcceleration const controllerSettings = new JOLT.WheeledVehicleControllerSettings() controllerSettings.mEngine.mMaxTorque = maxAcc controllerSettings.mTransmission.mClutchStrength = 10.0 diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index af43cd3521..723b392ab9 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -10,7 +10,6 @@ import APS, { APS_USER_INFO_UPDATE_EVENT } from "@/aps/APS" import { UserIcon } from "./UserIcon" import { Button } from "@mui/base/Button" import { ButtonIcon, SynthesisIcons } from "./StyledComponents" -import Synthesis from "@/Synthesis" type ButtonProps = { value: string diff --git a/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx index 890d4132b5..51fc4a3251 100644 --- a/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx @@ -23,10 +23,6 @@ type JointRowProps = { } const JointRow: React.FC = ({ robot, driver }) => { - const wheelDrivers = useMemo(() => { - return robot?.mechanism ? - World.SimulationSystem.GetSimulationLayer(robot.mechanism)?.drivers.filter(x => x instanceof WheelDriver) : undefined - }, [robot]) const driverSwitch = (driver: Driver, slider: unknown, hinge: unknown, drivetrain: unknown) => { switch (driver.constructor) { @@ -48,6 +44,8 @@ const JointRow: React.FC = ({ robot, driver }) => { const onChange = (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 @@ -135,25 +133,24 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, 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) { - if (!(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 (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 - } + 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 } } }) @@ -222,6 +219,7 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, {drivers ? ( + {/** Drivetrain row. Then other SliderDrivers and HingeDrivers */} { From f63d45c58a74778a1ecde4cdabb6cd42c30ca2c9 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Thu, 1 Aug 2024 17:44:06 -0700 Subject: [PATCH 136/344] Refactor and formatting --- fission/src/Synthesis.tsx | 2 +- fission/src/systems/physics/PhysicsSystem.ts | 37 ++++-- .../systems/preferences/PreferenceTypes.ts | 2 +- .../behavior/synthesis/ArcadeDriveBehavior.ts | 5 +- .../systems/simulation/driver/HingeDriver.ts | 2 +- .../systems/simulation/driver/SliderDriver.ts | 4 +- fission/src/ui/components/MainHUD.tsx | 6 +- .../configuring/ConfigureJointsPanel.tsx | 108 +++++++++--------- 8 files changed, 96 insertions(+), 70 deletions(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index c41ec83b31..f33227e360 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -254,7 +254,7 @@ const initialPanels: ReactElement[] = [ , , , - + , ] export default Synthesis diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 8d91986ced..eaeb5f3dcc 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -327,7 +327,7 @@ class PhysicsSystem extends WorldSystem { const jointData = parser.assembly.data!.joints! for (const [jGuid, jInst] of Object.entries(jointData.jointInstances!) as [ string, - mirabuf.joint.JointInstance + mirabuf.joint.JointInstance, ][]) { if (jGuid == GROUNDED_JOINT_ID) continue @@ -357,22 +357,32 @@ class PhysicsSystem extends WorldSystem { const constraints: Jolt.Constraint[] = [] let listener: Jolt.PhysicsStepListener | undefined = undefined - // Motor preferences and mirabuf + // Motor velocity and acceleration. Prioritizes preferences then mirabuf. const prefMotors = PreferencesSystem.getRobotPreferences(parser.assembly.info?.name ?? "").motors const prefMotor = prefMotors ? prefMotors.filter(x => x.name == jInst.info?.name) : undefined const miraMotor = jointData.motorDefinitions![jDef.motorReference] - // If preference motor exists, sets vel/acc to prefMotor. Then, if mirabuf motor exists, sets vel/acc to miraMotor. Then default - let maxVel = (prefMotor && prefMotor[0]) ? prefMotor[0].maxVelocity : ((miraMotor && miraMotor.simpleMotor) ? miraMotor.simpleMotor.maxVelocity ?? VELOCITY_DEFAULT : VELOCITY_DEFAULT) - let maxForce = (prefMotor && prefMotor[0]) ? prefMotor[0].maxForce : ((miraMotor && miraMotor.simpleMotor) ? miraMotor.simpleMotor.stallTorque : undefined) + let maxVel = VELOCITY_DEFAULT + let maxForce + if (prefMotor && prefMotor[0]) { + maxVel = prefMotor[0].maxVelocity + maxForce = prefMotor[0].maxForce + } else if (miraMotor && miraMotor.simpleMotor) { + maxVel = miraMotor.simpleMotor.maxVelocity ?? VELOCITY_DEFAULT + maxForce = miraMotor.simpleMotor.stallTorque + } switch (jDef.jointMotionType!) { case mirabuf.joint.JointMotion.REVOLUTE: if (this.IsWheel(jDef)) { - const prefVel = PreferencesSystem.getRobotPreferences(parser.assembly.info?.name ?? "").driveVelocity + const prefVel = PreferencesSystem.getRobotPreferences( + parser.assembly.info?.name ?? "" + ).driveVelocity if (prefVel > 0) maxVel = prefVel - const prefAcc = PreferencesSystem.getRobotPreferences(parser.assembly.info?.name ?? "").driveAcceleration + const prefAcc = PreferencesSystem.getRobotPreferences( + parser.assembly.info?.name ?? "" + ).driveAcceleration if (prefAcc > 0) maxForce = prefAcc if (parser.directedGraph.GetAdjacencyList(rnA.id).length > 0) { @@ -401,11 +411,19 @@ class PhysicsSystem extends WorldSystem { listener = res[2] } } else { - constraints.push(this.CreateHingeConstraint(jInst, jDef, maxForce ?? 50, bodyA, bodyB, parser.assembly.info!.version!)) + constraints.push( + this.CreateHingeConstraint( + jInst, + jDef, + maxForce ?? 50, + bodyA, + bodyB, + parser.assembly.info!.version! + ) + ) } break case mirabuf.joint.JointMotion.SLIDER: - constraints.push(this.CreateSliderConstraint(jInst, jDef, maxForce ?? 200, bodyA, bodyB)) break default: @@ -496,7 +514,6 @@ class PhysicsSystem extends WorldSystem { hingeConstraintSettings.mMotorSettings.mMaxTorqueLimit = torque hingeConstraintSettings.mMotorSettings.mMinTorqueLimit = -torque - const constraint = hingeConstraintSettings.Create(bodyA, bodyB) this._constraints.push(constraint) this._joltPhysSystem.AddConstraint(constraint) diff --git a/fission/src/systems/preferences/PreferenceTypes.ts b/fission/src/systems/preferences/PreferenceTypes.ts index d044a1ce22..2402ead0c5 100644 --- a/fission/src/systems/preferences/PreferenceTypes.ts +++ b/fission/src/systems/preferences/PreferenceTypes.ts @@ -91,7 +91,7 @@ export function DefaultRobotPreferences(): RobotPreferences { parentNode: undefined, }, driveVelocity: 0, - driveAcceleration: 0 + driveAcceleration: 0, } } diff --git a/fission/src/systems/simulation/behavior/synthesis/ArcadeDriveBehavior.ts b/fission/src/systems/simulation/behavior/synthesis/ArcadeDriveBehavior.ts index 2146f64a72..72de9b4da3 100644 --- a/fission/src/systems/simulation/behavior/synthesis/ArcadeDriveBehavior.ts +++ b/fission/src/systems/simulation/behavior/synthesis/ArcadeDriveBehavior.ts @@ -32,7 +32,10 @@ class ArcadeDriveBehavior extends Behavior { } public Update(_: number): void { - this.DriveSpeeds(InputSystem.getInput("arcadeDrive", this._brainIndex), InputSystem.getInput("arcadeTurn", this._brainIndex)) + this.DriveSpeeds( + InputSystem.getInput("arcadeDrive", this._brainIndex), + InputSystem.getInput("arcadeTurn", this._brainIndex) + ) } } diff --git a/fission/src/systems/simulation/driver/HingeDriver.ts b/fission/src/systems/simulation/driver/HingeDriver.ts index 8a24e4e43f..243c0c4441 100644 --- a/fission/src/systems/simulation/driver/HingeDriver.ts +++ b/fission/src/systems/simulation/driver/HingeDriver.ts @@ -87,7 +87,7 @@ class HingeDriver extends Driver { this._constraint.SetTargetAngularVelocity(this._accelerationDirection * this._maxVelocity) } else if (this._controlMode == DriverControlMode.Position) { let ang = this._targetAngle - + if (ang - this._prevAng < -this.maxVelocity) ang = this._prevAng - this._maxVelocity if (ang - this._prevAng > this.maxVelocity) ang = this._prevAng + this._maxVelocity this._constraint.SetTargetAngle(this._targetAngle) diff --git a/fission/src/systems/simulation/driver/SliderDriver.ts b/fission/src/systems/simulation/driver/SliderDriver.ts index 2c23c89304..0cfbb2dc8d 100644 --- a/fission/src/systems/simulation/driver/SliderDriver.ts +++ b/fission/src/systems/simulation/driver/SliderDriver.ts @@ -87,10 +87,10 @@ class SliderDriver extends Driver { this._constraint.SetTargetVelocity(this._accelerationDirection * this._maxVelocity) } else if (this._controlMode == DriverControlMode.Position) { let pos = this._targetPosition - + if (pos - this._prevPos < -this.maxVelocity) pos = this._prevPos - this._maxVelocity if (pos - this._prevPos > this.maxVelocity) pos = this._prevPos + this._maxVelocity - + this._constraint.SetTargetPosition(pos) } } diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 723b392ab9..e4ed7d8211 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -119,7 +119,11 @@ const MainHUD: React.FC = () => { openPanel("scoring-zones") }} /> - openPanel("joint-config")} /> + openPanel("joint-config")} + /> = ({ robot, driver }) => { - const driverSwitch = (driver: Driver, slider: unknown, hinge: unknown, drivetrain: unknown) => { switch (driver.constructor) { case SliderDriver: @@ -38,14 +37,19 @@ const JointRow: React.FC = ({ robot, driver }) => { } const [velocity, setVelocity] = useState( - (driver as SliderDriver || driver as HingeDriver || driver as WheelDriver).maxVelocity) + ((driver as SliderDriver) || (driver as HingeDriver) || (driver as WheelDriver)).maxVelocity + ) const [force, setForce] = useState( - (driver as SliderDriver || driver as HingeDriver || driver as WheelDriver).maxForce) + ((driver as SliderDriver) || (driver as HingeDriver) || (driver as WheelDriver)).maxForce + ) const onChange = (vel: number, force: number) => { if (driver instanceof WheelDriver) { - const wheelDrivers = robot?.mechanism ? - World.SimulationSystem.GetSimulationLayer(robot.mechanism)?.drivers.filter(x => x instanceof WheelDriver) : undefined + 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 @@ -55,22 +59,24 @@ const JointRow: React.FC = ({ robot, driver }) => { PreferencesSystem.getRobotPreferences(robot.assemblyName).driveVelocity = vel PreferencesSystem.getRobotPreferences(robot.assemblyName).driveAcceleration = force } else { - (driver as SliderDriver || driver as HingeDriver).maxVelocity = vel; - (driver as SliderDriver || driver as HingeDriver).maxForce = force + ((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel + ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force // 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 - }) : [] + 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}) - + maxForce: force, + }) + PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor } } @@ -78,11 +84,12 @@ const JointRow: React.FC = ({ robot, driver }) => { PreferencesSystem.savePreferences() } - return ( - + = ({ robot, driver }) => { label="Max Velocity" format={{ minimumFractionDigits: 2, maximumFractionDigits: 2 }} onChange={(_, _velocity: number | number[]) => { - setVelocity(_velocity as number); + setVelocity(_velocity as number) onChange(_velocity as number, force) }} step={0.01} @@ -102,7 +109,7 @@ const JointRow: React.FC = ({ robot, driver }) => { label={driverSwitch(driver, "Max Force", "Max Torque", "Max Accel.") as string} format={{ minimumFractionDigits: 2, maximumFractionDigits: 2 }} onChange={(_, _force: number | number[]) => { - setForce(_force as number); + setForce(_force as number) onChange(velocity, _force as number) }} step={0.01} @@ -112,9 +119,7 @@ const JointRow: React.FC = ({ robot, driver }) => { ) } - -const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, sidePadding}) => { - +const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, sidePadding }) => { const [selectedRobot, setSelectedRobot] = useState(undefined) const [origPref, setOrigPref] = useState(undefined) @@ -129,8 +134,9 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, }, []) const drivers = useMemo(() => { - return selectedRobot?.mechanism ? - World.SimulationSystem.GetSimulationLayer(selectedRobot.mechanism)?.drivers : undefined + 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() @@ -139,23 +145,22 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, 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 + 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 + 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 + setOrigPref({ ...PreferencesSystem.getRobotPreferences(robot.assemblyName) }) // clone } function Cancel() { @@ -167,13 +172,12 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, } else { if (driver.info && driver.info.name) { const motor = origPref.motors.filter(x => { - if (x.name) - return x.name == driver.info?.name + if (x.name) return x.name == driver.info?.name return false - })[0]; + })[0] if (motor) { - (driver as SliderDriver || driver as HingeDriver).maxVelocity = motor.maxVelocity; - (driver as SliderDriver || driver as HingeDriver).maxForce = motor.maxForce + ((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = motor.maxVelocity + ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = motor.maxForce } } } @@ -183,15 +187,16 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, PreferencesSystem.savePreferences() } - return ( - } panelId={panelId} openLocation={openLocation} sidePadding={sidePadding} - onAccept={() => { PreferencesSystem.savePreferences()}} + onAccept={() => { + PreferencesSystem.savePreferences() + }} onCancel={Cancel} acceptEnabled={true} > @@ -216,7 +221,6 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, ) : ( <> - {drivers ? ( {/** Drivetrain row. Then other SliderDrivers and HingeDrivers */} @@ -229,29 +233,27 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, 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 - })()} - /> - - ))} + {drivers + .filter(x => x instanceof SliderDriver || x instanceof HingeDriver) + .map((driver: Driver, i: number) => ( + { + return selectedRobot + })()} + driver={(() => { + return driver + })()} + /> + ))} ) : ( )} )} - - - ) } -export default ConfigureJointsPanel \ No newline at end of file +export default ConfigureJointsPanel From 12ca571ac727d6e019845b97523e62997719fbc1 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Thu, 1 Aug 2024 18:28:02 -0700 Subject: [PATCH 137/344] Acceleration string --- fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx index 9af0ed7548..d183021153 100644 --- a/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx @@ -106,7 +106,7 @@ const JointRow: React.FC = ({ robot, driver }) => { min={driverSwitch(driver, 100, 20, 0.1) as number} max={driverSwitch(driver, 800, 150, 15) as number} value={force} - label={driverSwitch(driver, "Max Force", "Max Torque", "Max Accel.") as string} + label={driverSwitch(driver, "Max Force", "Max Torque", "Max Acceleration") as string} format={{ minimumFractionDigits: 2, maximumFractionDigits: 2 }} onChange={(_, _force: number | number[]) => { setForce(_force as number) From 03bed7bc3664e58229558058ee7ab889e8ad565b Mon Sep 17 00:00:00 2001 From: PepperLola Date: Thu, 1 Aug 2024 21:19:46 -0700 Subject: [PATCH 138/344] started on gyro support --- .../simulation/wpilib_brain/SimInput.ts | 52 ++++++++++++++++++- .../simulation/wpilib_brain/WPILibBrain.ts | 23 +++++++- fission/src/ui/panels/WSViewPanel.tsx | 8 +-- .../src/main/java/frc/robot/Robot.java | 9 ++++ 4 files changed, 85 insertions(+), 7 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimInput.ts b/fission/src/systems/simulation/wpilib_brain/SimInput.ts index 0962bd8bb6..6061d9a47e 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimInput.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimInput.ts @@ -1,5 +1,9 @@ +import World from "@/systems/World" import EncoderStimulus from "../stimulus/EncoderStimulus" -import { SimCANEncoder } from "./WPILibBrain" +import { SimCANEncoder, SimGyro } from "./WPILibBrain" +import Mechanism from "@/systems/physics/Mechanism" +import Jolt from "@barclah/jolt-physics" +import JOLT from "@/util/loading/JoltSyncLoader" export interface SimInput { Update: (deltaT: number) => void @@ -20,3 +24,49 @@ export class SimEncoderInput implements SimInput { SimCANEncoder.SetRawInputPosition(`${this._device}`, this._stimulus.positionValue * this._conversionFactor) } } + +export class SimGyroInput implements SimInput { + private _device: string + private _robot: Mechanism + private _joltID?: Jolt.BodyID + private _joltBody?: Jolt.Body + + private static AXIS_X: Jolt.Vec3 = new JOLT.Vec3(1, 0, 0) + private static AXIS_Y: Jolt.Vec3 = new JOLT.Vec3(0, 1, 0) + private static AXIS_Z: Jolt.Vec3 = new JOLT.Vec3(0, 0, 1) + + constructor(device: string, robot: Mechanism) { + this._device = device + this._robot = robot + this._joltID = this._robot.nodeToBody.get(this._robot.rootBody) + + if (this._joltID) + this._joltBody = World.PhysicsSystem.GetBody(this._joltID) + } + + private GetAxis(axis: Jolt.Vec3): number { + return (this._joltBody?.GetRotation().GetRotationAngle(axis) ?? 0) * 180 / Math.PI + } + + private GetX(): number { + return this.GetAxis(SimGyroInput.AXIS_X) + } + + private GetY(): number { + return this.GetAxis(SimGyroInput.AXIS_Y) + } + + private GetZ(): number { + return this.GetAxis(SimGyroInput.AXIS_Z) + } + + public Update(_deltaT: number) { + const x = this.GetX() + const y = this.GetY() + const z = this.GetZ() + // console.log(`${this._device}\n${x}\n${y}\n${z}`) + SimGyro.SetAngleX(this._device, x); + SimGyro.SetAngleY(this._device, y); + SimGyro.SetAngleZ(this._device, z); + } +} diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index cd24bb95b8..1e83bc686e 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -7,7 +7,7 @@ import { SimulationLayer } from "../SimulationSystem" import World from "@/systems/World" import { SimOutputGroup } from "./SimOutput" -import { SimInput } from "./SimInput" +import { SimGyroInput, SimInput } from "./SimInput" const worker = new WPILibWSWorker() @@ -17,7 +17,7 @@ const CANMOTOR_DUTY_CYCLE = "angle_x", angle); + } + + public static SetAngleY(device: string, angle: number): boolean { + return SimGeneric.Set("Gyro", device, ">angle_y", angle); + } + + public static SetAngleZ(device: string, angle: number): boolean { + return SimGeneric.Set("Gyro", device, ">angle_z", angle); + } +} + worker.addEventListener("message", (eventData: MessageEvent) => { let data: any | undefined try { @@ -230,6 +246,8 @@ class WPILibBrain extends Brain { console.warn("SimulationLayer is undefined") return } + + this.addSimInput(new SimGyroInput("Gyro:ADXRS450[0]", mechanism)); } public addSimOutputGroup(device: SimOutputGroup) { @@ -243,6 +261,7 @@ class WPILibBrain extends Brain { public Update(deltaT: number): void { this._simOutputs.forEach(d => d.Update(deltaT)) this._simInputs.forEach(i => i.Update(deltaT)) + console.log(simMap) } public Enable(): void { diff --git a/fission/src/ui/panels/WSViewPanel.tsx b/fission/src/ui/panels/WSViewPanel.tsx index 1781572229..f7b47eff28 100644 --- a/fission/src/ui/panels/WSViewPanel.tsx +++ b/fission/src/ui/panels/WSViewPanel.tsx @@ -107,12 +107,12 @@ function generateTableBody() { ) : ( <> )} - {simMap.has("Joystick") ? ( - [...simMap.get("Joystick")!.entries()].map(x => { + {simMap.has("Gyro") ? ( + [...simMap.get("Gyro")!.entries()].map(x => { return ( - Joystick + Gyro {x[0]} @@ -213,7 +213,7 @@ const WSViewPanel: React.FC = ({ panelId }) => { setSelectedType(v as unknown as SimType)} /> {deviceSelect} diff --git a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java index 455b23fba6..3dc569c051 100644 --- a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java +++ b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java @@ -9,9 +9,12 @@ // import com.revrobotics.CANSparkMax; import com.revrobotics.CANSparkLowLevel.MotorType; +import edu.wpi.first.wpilibj.ADXRS450_Gyro; +import edu.wpi.first.wpilibj.AnalogGyro; import edu.wpi.first.wpilibj.TimedRobot; import edu.wpi.first.wpilibj.XboxController; import edu.wpi.first.wpilibj.motorcontrol.Spark; +import edu.wpi.first.wpilibj.simulation.ADXRS450_GyroSim; import edu.wpi.first.wpilibj.smartdashboard.SendableChooser; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; @@ -35,6 +38,8 @@ public class Robot extends TimedRobot { private CANSparkMax m_SparkMax = new CANSparkMax(1, MotorType.kBrushless); private TalonFX m_Talon = new TalonFX(2); private XboxController m_Controller = new XboxController(0); + private ADXRS450_Gyro m_Gyro; + private ADXRS450_GyroSim m_GyroSim; /** * This function is run when the robot is first started up and should be used for any @@ -45,6 +50,9 @@ public void robotInit() { m_chooser.setDefaultOption("Default Auto", kDefaultAuto); m_chooser.addOption("My Auto", kCustomAuto); SmartDashboard.putData("Auto choices", m_chooser); + m_Gyro = new ADXRS450_Gyro(); + m_GyroSim = new ADXRS450_GyroSim(m_Gyro); + m_Gyro.calibrate(); } /** @@ -103,6 +111,7 @@ public void teleopInit() {} public void teleopPeriodic() { m_Spark1.set(m_Controller.getLeftY()); m_Spark2.set(-m_Controller.getRightY()); + System.out.println(m_Gyro.getAngle()); // m_Spark1.set(0.25); // m_Spark2.set(0.25); m_SparkMax.set(0.75); From 2cc33316b2751eaf08746200704ac9b6c3bab2e8 Mon Sep 17 00:00:00 2001 From: LucaHaverty Date: Fri, 2 Aug 2024 12:15:19 -0700 Subject: [PATCH 139/344] Combined config panel support for sequential joints --- fission/src/Synthesis.tsx | 2 - .../assembly-config/ConfigurePanel.tsx | 9 +- .../SequentialBehaviorsInterface.tsx} | 190 +++++++----------- 3 files changed, 77 insertions(+), 124 deletions(-) rename fission/src/ui/panels/{SequentialBehaviorsPanel.tsx => configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx} (53%) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 9acadc2acb..e9b1a92efd 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -54,7 +54,6 @@ import SceneOverlay from "./ui/components/SceneOverlay.tsx" import WPILibWSWorker from "@/systems/simulation/wpilib_brain/WPILibWSWorker.ts?worker" import WSViewPanel from "./ui/panels/WSViewPanel.tsx" import Lazy from "./util/Lazy.ts" -import SequentialBehaviorsPanel from "./ui/panels/SequentialBehaviorsPanel.tsx" import RCConfigPWMGroupModal from "@/modals/configuring/rio-config/RCConfigPWMGroupModal.tsx" import RCConfigCANGroupModal from "@/modals/configuring/rio-config/RCConfigCANGroupModal.tsx" @@ -240,7 +239,6 @@ const initialPanels: ReactElement[] = [ openLocation="right" sidePadding={8} />, - , , , , diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index d1873f70cc..7aa17571c8 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -10,6 +10,7 @@ import { AiOutlinePlus } from "react-icons/ai" 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" @@ -73,7 +74,7 @@ const AssemblySelection: React.FC = ({ configuratio enum ConfigMode { INTAKE, EJECTOR, - JOINTS, + MOTORS, CONTROLS, SCORING_ZONES, } @@ -90,7 +91,7 @@ class ConfigModeSelectionOption extends SelectMenuOption { const robotModes = [ new ConfigModeSelectionOption("Intake", ConfigMode.INTAKE), new ConfigModeSelectionOption("Ejector", ConfigMode.EJECTOR), - new ConfigModeSelectionOption("Joints", ConfigMode.JOINTS), + new ConfigModeSelectionOption("Motors", ConfigMode.MOTORS), new ConfigModeSelectionOption("Controls", ConfigMode.CONTROLS), ] const fieldModes = [new ConfigModeSelectionOption("Scoring Zones", ConfigMode.SCORING_ZONES)] @@ -124,8 +125,8 @@ const ConfigInterface: React.FC = ({ configMode, assembly return case ConfigMode.EJECTOR: return - case ConfigMode.JOINTS: - return + case ConfigMode.MOTORS: + return case ConfigMode.CONTROLS: return case ConfigMode.SCORING_ZONES: { diff --git a/fission/src/ui/panels/SequentialBehaviorsPanel.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx similarity index 53% rename from fission/src/ui/panels/SequentialBehaviorsPanel.tsx rename to fission/src/ui/panels/configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx index d1302fe69a..0f30cc6cea 100644 --- a/fission/src/ui/panels/SequentialBehaviorsPanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx @@ -1,18 +1,16 @@ -import React, { useMemo, useReducer, useState } from "react" -import Panel, { PanelPropsImpl } from "../components/Panel" +import React, { useCallback, useEffect, useReducer, useState } from "react" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" -import Label, { LabelSize } from "../components/Label" -import World from "@/systems/World" -import { MiraType } from "@/mirabuf/MirabufLoader" -import { FaArrowRightArrowLeft, FaGear, FaXmark } from "react-icons/fa6" +import Label, { LabelSize } from "@/ui/components/Label" +import { FaArrowRightArrowLeft, FaXmark } from "react-icons/fa6" import { Box, Button as MUIButton, Divider, styled, alpha, Icon } from "@mui/material" -import Button, { ButtonSize } from "../components/Button" +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 "../components/Checkbox" +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" const UnselectParentIcon = const InvertIcon = @@ -117,8 +115,8 @@ const BehaviorCard: React.FC = ({ sx={{ borderColor: lookingForParent == undefined || - lookingForParent == behavior || - behavior.parentJointIndex != undefined + lookingForParent == behavior || + behavior.parentJointIndex != undefined ? "transparent" : "#888888", }} @@ -174,11 +172,7 @@ const BehaviorCard: React.FC = ({ * Joint 4 * * Joint 2 (child of 4) */ -function sortBehaviors( - behaviors: SequentialBehaviorPreferences[] | undefined -): SequentialBehaviorPreferences[] | undefined { - if (behaviors == undefined) return undefined - +function sortBehaviors(behaviors: SequentialBehaviorPreferences[]): SequentialBehaviorPreferences[] { // Sort the behaviors in order of joint index behaviors.sort((a, b) => { return a.jointIndex - b.jointIndex @@ -209,118 +203,78 @@ function sortBehaviors( return sortedBehaviors } -const SequentialBehaviorsPanel: React.FC = ({ panelId }) => { - const [selectedRobot, setSelectedRobot] = useState(undefined) - const [behaviors, setBehaviors] = useState(undefined) - const [lookingForParent, setLookingForParent] = useState(undefined) +interface SequentialBehaviorProps { + selectedRobot: MirabufSceneObject +} - 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 SequentialBehaviorsInterface: React.FC = ({ selectedRobot }) => { + const [behaviors, setBehaviors] = useState( + 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 [lookingForParent, setLookingForParent] = useState(undefined) const [_, update] = useReducer(x => { setBehaviors(sortBehaviors(behaviors)) return !x }, false) - return ( - } - panelId={panelId} - cancelEnabled={false} - onAccept={() => { - if (selectedRobot == undefined || behaviors == undefined) return - PreferencesSystem.getRobotPreferences(selectedRobot.assemblyName).sequentialConfig = behaviors - PreferencesSystem.savePreferences() - }} - > - {selectedRobot == undefined || behaviors == undefined ? ( - <> - - {/** Scroll view for selecting a robot to configure */} -
- {robots.map(mirabufSceneObject => { - return ( - - ) - })} -
- - ) : ( -
- - Set Parent Behaviors - - - {behaviors.map(behavior => { - const jointIndex = behavior.jointIndex - return ( - { - if (behavior.parentJointIndex != undefined) { - behavior.parentJointIndex = undefined - update() - } else { - setLookingForParent(lookingForParent == behavior ? undefined : behavior) - } - update() - }} - lookingForParent={lookingForParent} - onBehaviorSelected={() => { - if (lookingForParent) lookingForParent.parentJointIndex = behavior.jointIndex - setLookingForParent(undefined) - update() - }} - hasChild={behaviors.some(b => b.parentJointIndex == behavior.jointIndex)} - /> - ) - })} -
- )} -
+ return () => { + ConfigurationSavedEvent.RemoveListener(saveEvent) + } + }, [saveEvent]) + + + return ( +
+ + Set Parent Behaviors + + + {behaviors.map(behavior => { + const jointIndex = behavior.jointIndex + return ( + { + if (behavior.parentJointIndex != undefined) { + behavior.parentJointIndex = undefined + update() + } else { + setLookingForParent(lookingForParent == behavior ? undefined : behavior) + } + update() + }} + lookingForParent={lookingForParent} + onBehaviorSelected={() => { + if (lookingForParent) lookingForParent.parentJointIndex = behavior.jointIndex + setLookingForParent(undefined) + update() + }} + hasChild={behaviors.some(b => b.parentJointIndex == behavior.jointIndex)} + /> + ) + })} +
) } -export default SequentialBehaviorsPanel +export default SequentialBehaviorsInterface From 6f1fe3d7d011f4fce3c5f75af9fe6d06983fc519 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Fri, 2 Aug 2024 13:27:54 -0700 Subject: [PATCH 140/344] Requested changes --- .../systems/simulation/driver/HingeDriver.ts | 28 +++++-------------- .../systems/simulation/driver/SliderDriver.ts | 26 ++++------------- .../systems/simulation/driver/WheelDriver.ts | 22 +++------------ .../configuring/ConfigureJointsPanel.tsx | 6 ++-- 4 files changed, 20 insertions(+), 62 deletions(-) diff --git a/fission/src/systems/simulation/driver/HingeDriver.ts b/fission/src/systems/simulation/driver/HingeDriver.ts index 243c0c4441..a356aa8062 100644 --- a/fission/src/systems/simulation/driver/HingeDriver.ts +++ b/fission/src/systems/simulation/driver/HingeDriver.ts @@ -8,19 +8,12 @@ class HingeDriver extends Driver { private _constraint: Jolt.HingeConstraint private _controlMode: DriverControlMode = DriverControlMode.Velocity - private _accelerationDirection: number = 0.0 private _targetAngle: number - private _maxVelocity: number + public accelerationDirection: number = 0.0 + public maxVelocity: number private _prevAng: number = 0.0 - public get accelerationDirection(): number { - return this._accelerationDirection - } - public set accelerationDirection(radsPerSec: number) { - this._accelerationDirection = radsPerSec - } - public get targetAngle(): number { return this._targetAngle } @@ -28,13 +21,6 @@ class HingeDriver extends Driver { this._targetAngle = Math.max(this._constraint.GetLimitsMin(), Math.min(this._constraint.GetLimitsMax(), rads)) } - public get maxVelocity(): number { - return this._maxVelocity - } - public set maxVelocity(radsPerSec: number) { - this._maxVelocity = radsPerSec - } - public get maxForce() { return this._constraint.GetMotorSettings().mMaxTorqueLimit } @@ -68,7 +54,7 @@ class HingeDriver extends Driver { super(info) this._constraint = constraint - this._maxVelocity = maxVelocity + this.maxVelocity = maxVelocity this._targetAngle = this._constraint.GetCurrentAngle() const motorSettings = this._constraint.GetMotorSettings() @@ -84,13 +70,13 @@ class HingeDriver extends Driver { public Update(_: number): void { if (this._controlMode == DriverControlMode.Velocity) { - this._constraint.SetTargetAngularVelocity(this._accelerationDirection * this._maxVelocity) + this._constraint.SetTargetAngularVelocity(this.accelerationDirection * this.maxVelocity) } else if (this._controlMode == DriverControlMode.Position) { let ang = this._targetAngle - if (ang - this._prevAng < -this.maxVelocity) ang = this._prevAng - this._maxVelocity - if (ang - this._prevAng > this.maxVelocity) ang = this._prevAng + this._maxVelocity - this._constraint.SetTargetAngle(this._targetAngle) + if (ang - this._prevAng < -this.maxVelocity) ang = this._prevAng - this.maxVelocity + if (ang - this._prevAng > this.maxVelocity) ang = this._prevAng + this.maxVelocity + this._constraint.SetTargetAngle(ang) } } } diff --git a/fission/src/systems/simulation/driver/SliderDriver.ts b/fission/src/systems/simulation/driver/SliderDriver.ts index 0cfbb2dc8d..d80a3fa681 100644 --- a/fission/src/systems/simulation/driver/SliderDriver.ts +++ b/fission/src/systems/simulation/driver/SliderDriver.ts @@ -8,19 +8,12 @@ class SliderDriver extends Driver { private _constraint: Jolt.SliderConstraint private _controlMode: DriverControlMode = DriverControlMode.Velocity - private _accelerationDirection: number = 0.0 private _targetPosition: number = 0.0 - private _maxVelocity: number = 1.0 + public accelerationDirection: number = 0.0 + public maxVelocity: number = 1.0 private _prevPos: number = 0.0 - public get accelerationDirection(): number { - return this._accelerationDirection - } - public set accelerationDirection(radsPerSec: number) { - this._accelerationDirection = radsPerSec - } - public get targetPosition(): number { return this._targetPosition } @@ -31,13 +24,6 @@ class SliderDriver extends Driver { ) } - public get maxVelocity(): number { - return this._maxVelocity - } - public set maxVelocity(radsPerSec: number) { - this._maxVelocity = radsPerSec - } - public get maxForce(): number { return this._constraint.GetMotorSettings().mMaxForceLimit } @@ -70,7 +56,7 @@ class SliderDriver extends Driver { super(info) this._constraint = constraint - this._maxVelocity = maxVelocity + this.maxVelocity = maxVelocity const motorSettings = this._constraint.GetMotorSettings() const springSettings = motorSettings.mSpringSettings @@ -84,12 +70,12 @@ class SliderDriver extends Driver { public Update(_: number): void { if (this._controlMode == DriverControlMode.Velocity) { - this._constraint.SetTargetVelocity(this._accelerationDirection * this._maxVelocity) + this._constraint.SetTargetVelocity(this.accelerationDirection * this.maxVelocity) } else if (this._controlMode == DriverControlMode.Position) { let pos = this._targetPosition - if (pos - this._prevPos < -this.maxVelocity) pos = this._prevPos - this._maxVelocity - if (pos - this._prevPos > this.maxVelocity) pos = this._prevPos + this._maxVelocity + if (pos - this._prevPos < -this.maxVelocity) pos = this._prevPos - this.maxVelocity + if (pos - this._prevPos > this.maxVelocity) pos = this._prevPos + this.maxVelocity this._constraint.SetTargetPosition(pos) } diff --git a/fission/src/systems/simulation/driver/WheelDriver.ts b/fission/src/systems/simulation/driver/WheelDriver.ts index 35aa702494..916a6c6a28 100644 --- a/fission/src/systems/simulation/driver/WheelDriver.ts +++ b/fission/src/systems/simulation/driver/WheelDriver.ts @@ -14,13 +14,13 @@ class WheelDriver extends Driver { public device?: string private _reversed: boolean - private _accelerationDirection: number = 0.0 + public accelerationDirection: number = 0.0 private _prevVel: number = 0.0 - private _maxVelocity = 30.0 + public maxVelocity = 30.0 private _maxAcceleration = 1.5 private _targetVelocity = () => { - let vel = this._accelerationDirection * (this._reversed ? -1 : 1) * this._maxVelocity + let vel = this.accelerationDirection * (this._reversed ? -1 : 1) * this.maxVelocity if (vel - this._prevVel < -this._maxAcceleration) vel = this._prevVel - this._maxAcceleration if (vel - this._prevVel > this._maxAcceleration) vel = this._prevVel + this._maxAcceleration @@ -28,20 +28,6 @@ class WheelDriver extends Driver { return vel } - public get accelerationDirection(): number { - return this._accelerationDirection - } - public set accelerationDirection(radsPerSec: number) { - this._accelerationDirection = radsPerSec - } - - public get maxVelocity(): number { - return this._maxVelocity - } - public set maxVelocity(radsPerSec: number) { - this._maxVelocity = radsPerSec - } - public get maxForce(): number { return this._maxAcceleration } @@ -64,7 +50,7 @@ class WheelDriver extends Driver { super(info) this._constraint = constraint - this._maxVelocity = maxVel + this.maxVelocity = maxVel const controller = JOLT.castObject(this._constraint.GetController(), JOLT.WheeledVehicleController) this._maxAcceleration = controller.GetEngine().mMaxTorque diff --git a/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx index d183021153..a290037354 100644 --- a/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureJointsPanel.tsx @@ -14,7 +14,7 @@ import ScrollView from "@/ui/components/ScrollView" import Slider from "@/ui/components/Slider" import Stack, { StackDirection } from "@/ui/components/Stack" import { Box } from "@mui/material" -import { useMemo, useState } from "react" +import { useCallback, useMemo, useState } from "react" import { FaGear } from "react-icons/fa6" type JointRowProps = { @@ -43,7 +43,7 @@ const JointRow: React.FC = ({ robot, driver }) => { ((driver as SliderDriver) || (driver as HingeDriver) || (driver as WheelDriver)).maxForce ) - const onChange = (vel: number, force: number) => { + const onChange = useCallback((vel: number, force: number) => { if (driver instanceof WheelDriver) { const wheelDrivers = robot?.mechanism ? World.SimulationSystem.GetSimulationLayer(robot.mechanism)?.drivers.filter( @@ -82,7 +82,7 @@ const JointRow: React.FC = ({ robot, driver }) => { } PreferencesSystem.savePreferences() - } + }, [driver, velocity, force]) return ( From 8d9f4301471f9617afc7db942c6cbeaa1bb8f5a0 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Fri, 2 Aug 2024 16:51:36 -0700 Subject: [PATCH 141/344] added init to can motor and encoder --- .../main/java/com/autodesk/synthesis/CANEncoder.java | 2 ++ .../main/java/com/autodesk/synthesis/CANMotor.java | 4 ++++ .../JavaSample/src/main/java/frc/robot/Robot.java | 12 ++++++------ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANEncoder.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANEncoder.java index fa74dbbe0f..69fe35dd0b 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANEncoder.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANEncoder.java @@ -14,6 +14,7 @@ public class CANEncoder { private SimDevice m_device; + private SimDouble m_init; private SimDouble m_position; private SimDouble m_velocity; @@ -26,6 +27,7 @@ public class CANEncoder { public CANEncoder(String name, int deviceId) { m_device = SimDevice.create(name, deviceId); + m_device = SimDevice.create(name, deviceId); m_position = m_device.createDouble("position", Direction.kInput, 0.0); m_velocity = m_device.createDouble("velocity", Direction.kInput, 0.0); } diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java index 7dd36e8e93..341f37c053 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java @@ -17,6 +17,8 @@ public class CANMotor { private SimDevice m_device; + private SimBoolean m_init; + private SimDouble m_percentOutput; private SimBoolean m_brakeMode; private SimDouble m_neutralDeadband; @@ -43,6 +45,8 @@ public CANMotor(String name, int deviceId, double defaultPercentOutput, boolean double defaultNeutralDeadband) { m_device = SimDevice.create(name, deviceId); + m_init = m_device.createBoolean("init", Direction.kOutput, true); + m_percentOutput = m_device.createDouble("percentOutput", Direction.kOutput, 0.0); m_brakeMode = m_device.createBoolean("brakeMode", Direction.kOutput, false); m_neutralDeadband = m_device.createDouble("neutralDeadband", Direction.kOutput, 0.5); diff --git a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java index c9150e5025..134222b08b 100644 --- a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java +++ b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java @@ -7,7 +7,7 @@ import com.revrobotics.CANSparkLowLevel.MotorType; import edu.wpi.first.wpilibj.TimedRobot; -//import edu.wpi.first.wpilibj.motorcontrol.Spark; +import edu.wpi.first.wpilibj.motorcontrol.Spark; import edu.wpi.first.wpilibj.smartdashboard.SendableChooser; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; @@ -29,7 +29,7 @@ public class Robot extends TimedRobot { private String m_autoSelected; private final SendableChooser m_chooser = new SendableChooser<>(); - // private Spark m_Spark = new Spark(0); + private Spark m_Spark = new Spark(0); private CANSparkMax m_SparkMax = new CANSparkMax(1, MotorType.kBrushless); private com.autodesk.synthesis.ctre.TalonFX m_Talon = new com.autodesk.synthesis.ctre.TalonFX(2); @@ -79,7 +79,7 @@ public void robotPeriodic() { @Override public void autonomousInit() { m_autoSelected = m_chooser.getSelected(); - // m_autoSelected = SmartDashboard.getString("Auto Selector", kDefaultAuto); + //m_autoSelected = SmartDashboard.getString("Auto Selector", kDefaultAuto); System.out.println("Auto selected: " + m_autoSelected); } @@ -87,7 +87,7 @@ public void autonomousInit() { @Override public void autonomousPeriodic() { - // m_Spark.set(0.5); + m_Spark.set(0.5); m_SparkMax.set(1.0); //m_SparkMax.setNeutralDeadband(0.01); // FIXME: for some reason I can't set // the deadband even after defining the function in the child @@ -113,7 +113,7 @@ public void teleopInit() { /** This function is called periodically during operator control. */ @Override public void teleopPeriodic() { - // m_Spark.set(0.25); + m_Spark.set(0.25); m_SparkMax.set(0.75); m_Talon.set(-0.5); } @@ -121,7 +121,7 @@ public void teleopPeriodic() { /** This function is called once when the robot is disabled. */ @Override public void disabledInit() { - // m_Spark.set(0.0); + m_Spark.set(0.0); m_SparkMax.set(0.0); m_Talon.set(0.0); } From 05d9acf185883c9a4d2d8a5a8e8c508485cd2b1e Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Fri, 2 Aug 2024 17:47:44 -0700 Subject: [PATCH 142/344] workaround for sim types --- fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts | 6 +++++- .../src/main/java/com/autodesk/synthesis/CANEncoder.java | 5 +++-- .../src/main/java/com/autodesk/synthesis/ctre/TalonFX.java | 2 +- .../com/autodesk/synthesis/revrobotics/CANSparkMax.java | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index 75eeda0328..32bee5b14a 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -220,6 +220,10 @@ worker.addEventListener("message", (eventData: MessageEvent) => { return } + if (data.type == "SimDevice") { + ;[data.type, data.device] = data.device.split("/") + } + if (!Object.values(SimType).includes(data.type)) { console.log("Unknown data type, bailing out") return @@ -241,7 +245,7 @@ function UpdateSimMap(type: SimType, device: string, updateData: any) { typeMap.set(device, currentData) } - Object.entries(updateData).forEach(([key, value]) => currentData[key] = value) + Object.entries(updateData).forEach(([key, value]) => (currentData[key] = value)) window.dispatchEvent(new SimMapUpdateEvent(false)) } diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANEncoder.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANEncoder.java index 69fe35dd0b..e45c228f2e 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANEncoder.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANEncoder.java @@ -2,6 +2,7 @@ import edu.wpi.first.hal.SimDevice; import edu.wpi.first.hal.SimDouble; +import edu.wpi.first.hal.SimBoolean; import edu.wpi.first.hal.SimDevice.Direction; /** @@ -14,7 +15,7 @@ public class CANEncoder { private SimDevice m_device; - private SimDouble m_init; + private SimBoolean m_init; private SimDouble m_position; private SimDouble m_velocity; @@ -27,7 +28,7 @@ public class CANEncoder { public CANEncoder(String name, int deviceId) { m_device = SimDevice.create(name, deviceId); - m_device = SimDevice.create(name, deviceId); + m_init = m_device.createBoolean("init", Direction.kOutput, true); m_position = m_device.createDouble("position", Direction.kInput, 0.0); m_velocity = m_device.createDouble("velocity", Direction.kInput, 0.0); } diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java index 4a242a97e3..3ac0d32a66 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java @@ -16,7 +16,7 @@ public class TalonFX extends com.ctre.phoenix6.hardware.TalonFX { public TalonFX(int deviceNumber) { super(deviceNumber); - this.m_motor = new CANMotor("SYN TalonFX", deviceNumber, 0.0, false, 0.3); + this.m_motor = new CANMotor("CANMotor/SYN TalonFX", deviceNumber, 0.0, false, 0.3); } @Override diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java index 71bdcea853..1f8ef11f6f 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java @@ -24,8 +24,8 @@ public class CANSparkMax extends com.revrobotics.CANSparkMax { public CANSparkMax(int deviceId, MotorType motorType) { super(deviceId, motorType); - this.m_motor = new CANMotor("SYN CANSparkMax", deviceId, 0.0, false, 0.3); - this.m_encoder = new CANEncoder("SYN CANSparkMax/Encoder", deviceId); + this.m_motor = new CANMotor("CANMotor/SYN CANSparkMax", deviceId, 0.0, false, 0.3); + this.m_encoder = new CANEncoder("CANEncoder/SYN CANSparkMax", deviceId); } @Override From 781cbc08e109d0cde6c364654bc12df2f0f933ea Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Fri, 2 Aug 2024 18:04:20 -0700 Subject: [PATCH 143/344] added filter for non-Synthesim CAN devices --- fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index 32bee5b14a..f76b0112e7 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -224,6 +224,12 @@ worker.addEventListener("message", (eventData: MessageEvent) => { ;[data.type, data.device] = data.device.split("/") } + if ((data.type == "CANMotor" || data.type == "CANEncoder") && data.device.split(" ")[0] != "SYN") { + console.log("Non-Synthesim CANMotor discarded") + return + } + + if (!Object.values(SimType).includes(data.type)) { console.log("Unknown data type, bailing out") return From 289095a4c3d436fd2cdfe21cf0ba19ba3b0eb67f Mon Sep 17 00:00:00 2001 From: KyroVibe Date: Fri, 2 Aug 2024 22:15:17 -0600 Subject: [PATCH 144/344] Fixed SimDevice type recognition --- simulation/SyntheSimJava/.gitignore | 4 ++++ .../src/main/java/com/autodesk/synthesis/CANEncoder.java | 2 +- .../src/main/java/com/autodesk/synthesis/CANMotor.java | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/simulation/SyntheSimJava/.gitignore b/simulation/SyntheSimJava/.gitignore index 2e559f72b7..b7691f9c47 100644 --- a/simulation/SyntheSimJava/.gitignore +++ b/simulation/SyntheSimJava/.gitignore @@ -7,3 +7,7 @@ build/ ctre-sil/ bin/ + +.settings/ +.classpath + diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANEncoder.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANEncoder.java index e45c228f2e..d00e5439b8 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANEncoder.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANEncoder.java @@ -26,7 +26,7 @@ public class CANEncoder { * @param deviceId CAN Device ID. */ public CANEncoder(String name, int deviceId) { - m_device = SimDevice.create(name, deviceId); + m_device = SimDevice.create(String.format("%s:%s", "CANEncoder", name), deviceId); m_init = m_device.createBoolean("init", Direction.kOutput, true); m_position = m_device.createDouble("position", Direction.kInput, 0.0); diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java index 341f37c053..cd5e1d55d2 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java @@ -43,7 +43,7 @@ public class CANMotor { */ public CANMotor(String name, int deviceId, double defaultPercentOutput, boolean defaultBrakeMode, double defaultNeutralDeadband) { - m_device = SimDevice.create(name, deviceId); + m_device = SimDevice.create(String.format("%s:%s", "CANMotor", name), deviceId); m_init = m_device.createBoolean("init", Direction.kOutput, true); From 33f72b08303cfd683c8988ada6e7bdbc65101038 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Mon, 5 Aug 2024 10:53:35 -0700 Subject: [PATCH 145/344] Subsystem --- fission/src/Synthesis.tsx | 4 ++-- fission/src/ui/components/MainHUD.tsx | 4 ++-- ...sPanel.tsx => ConfigureSubsystemsPanel.tsx} | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) rename fission/src/ui/panels/configuring/{ConfigureJointsPanel.tsx => ConfigureSubsystemsPanel.tsx} (95%) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index f33227e360..62b1a0e76f 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -40,7 +40,7 @@ import SpawnLocationsPanel from "@/panels/SpawnLocationPanel" import ConfigureGamepiecePickupPanel from "@/panels/configuring/ConfigureGamepiecePickupPanel" import ConfigureShotTrajectoryPanel from "@/panels/configuring/ConfigureShotTrajectoryPanel" import ScoringZonesPanel from "@/panels/configuring/scoring/ScoringZonesPanel" -import ConfigureJointsPanel from "@/panels/configuring/ConfigureJointsPanel" +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" @@ -254,7 +254,7 @@ const initialPanels: ReactElement[] = [ , , , - , + , ] export default Synthesis diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index e4ed7d8211..52d1493c80 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -120,9 +120,9 @@ const MainHUD: React.FC = () => { }} /> openPanel("joint-config")} + onClick={() => openPanel("subsystem-config")} /> = ({ robot, driver }) => { +const SubsystemRow: React.FC = ({ robot, driver }) => { const driverSwitch = (driver: Driver, slider: unknown, hinge: unknown, drivetrain: unknown) => { switch (driver.constructor) { case SliderDriver: @@ -82,7 +82,7 @@ const JointRow: React.FC = ({ robot, driver }) => { } PreferencesSystem.savePreferences() - }, [driver, velocity, force]) + }, [driver, robot.mechanism, robot.assemblyName]) return ( @@ -119,7 +119,7 @@ const JointRow: React.FC = ({ robot, driver }) => { ) } -const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, sidePadding }) => { +const ConfigureSubsystemsPanel: React.FC = ({ panelId, openLocation, sidePadding }) => { const [selectedRobot, setSelectedRobot] = useState(undefined) const [origPref, setOrigPref] = useState(undefined) @@ -189,7 +189,7 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, return ( } panelId={panelId} openLocation={openLocation} @@ -224,7 +224,7 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, {drivers ? ( {/** Drivetrain row. Then other SliderDrivers and HingeDrivers */} - { return selectedRobot @@ -236,7 +236,7 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, {drivers .filter(x => x instanceof SliderDriver || x instanceof HingeDriver) .map((driver: Driver, i: number) => ( - { return selectedRobot @@ -248,7 +248,7 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, ))} ) : ( - + )} )} @@ -256,4 +256,4 @@ const ConfigureJointsPanel: React.FC = ({ panelId, openLocation, ) } -export default ConfigureJointsPanel +export default ConfigureSubsystemsPanel From e20c8341b5d2004455cfbd4d03bb00387b59f0ce Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Mon, 5 Aug 2024 10:59:58 -0700 Subject: [PATCH 146/344] changed cansparkmax names to conform to new type format --- .../simulation/wpilib_brain/WPILibBrain.ts | 71 ++++--------------- .../synthesis/revrobotics/CANSparkMax.java | 4 +- 2 files changed, 16 insertions(+), 59 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index f76b0112e7..2e723be4c0 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -30,7 +30,6 @@ export enum SimType { PWM, CANMotor, Solenoid, - SimDevice, CANEncoder, } @@ -138,7 +137,7 @@ export class SimCAN { public static GetDeviceWithID(id: number, type: SimType): any { const id_exp = /.*\[(\d+)\]/g const entries = [...simMap.entries()].filter( - ([simType, _data]) => simType == type || simType == SimType.SimDevice + ([simType, _data]) => simType == type ) for (const [_simType, data] of entries) { for (const key of data.keys()) { @@ -198,7 +197,7 @@ export class SimCANEncoder { public static SetPosition(device: string, position: number): boolean { return SimGeneric.Set(SimType.CANEncoder, device, CANENCODER_POSITION, position) - } + } worker.addEventListener("message", (eventData: MessageEvent) => { @@ -215,25 +214,7 @@ worker.addEventListener("message", (eventData: MessageEvent) => { } } - if (!data?.type) { - console.log("No data, bailing out") - return - } - - if (data.type == "SimDevice") { - ;[data.type, data.device] = data.device.split("/") - } - - if ((data.type == "CANMotor" || data.type == "CANEncoder") && data.device.split(" ")[0] != "SYN") { - console.log("Non-Synthesim CANMotor discarded") - return - } - - - if (!Object.values(SimType).includes(data.type)) { - console.log("Unknown data type, bailing out") - return - } + if (!data?.type || data.split(" ")[0] != "SYN" || !Object.values(SimType).includes(data.type)) return UpdateSimMap(data.type, data.device, data.data) }) @@ -320,17 +301,8 @@ abstract class SimOutputGroup { this.type = type } - public abstract Update(deltaT: number): void -} - -// Averaging is probably not the right solution - -export class PWMGroup extends SimOutputGroup { - public constructor(name: string, ports: number[], drivers: Driver[]) { - super(name, ports, drivers, SimType.PWM) - } - - public Update(_deltaT: number) { + // Averaging is probably not the right solution + public Update(deltaT: number): void { const average = this.ports.reduce((sum, port) => { const speed = SimPWM.GetSpeed(`${port}`) ?? 0 @@ -344,36 +316,21 @@ export class PWMGroup extends SimOutputGroup { } else if (d instanceof HingeDriver || d instanceof SliderDriver) { d.targetVelocity = average * 40 } - d.Update(_deltaT) + d.Update(deltaT) }) + } } -export class CANGroup extends SimOutputGroup { + +export class PWMGroup extends SimOutputGroup { public constructor(name: string, ports: number[], drivers: Driver[]) { - super(name, ports, drivers, SimType.CANMotor) + super(name, ports, drivers, SimType.PWM) } +} - public Update(_deltaT: number) { - for (const port of this.ports) { - const device = SimCAN.GetDeviceWithID(port, this.type) - console.log(port, device) - } - - const average = - this.ports.reduce((sum, port) => { - const speed = SimPWM.GetSpeed(`${port}`) ?? 0 - console.debug(port, speed) - return sum + speed - }, 0) / this.ports.length - - this.drivers.forEach(d => { - if (d instanceof WheelDriver) { - d.targetWheelSpeed = average * 40 - } else if (d instanceof HingeDriver || d instanceof SliderDriver) { - d.targetVelocity = average * 40 - } - d.Update(_deltaT) - }) +export class CANGroup extends SimOutputGroup { + public constructor(name: string, ports: number[], drivers: Driver[]) { + super(name, ports, drivers, SimType.CANMotor) } } diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java index 1f8ef11f6f..5bfb81350f 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java @@ -24,8 +24,8 @@ public class CANSparkMax extends com.revrobotics.CANSparkMax { public CANSparkMax(int deviceId, MotorType motorType) { super(deviceId, motorType); - this.m_motor = new CANMotor("CANMotor/SYN CANSparkMax", deviceId, 0.0, false, 0.3); - this.m_encoder = new CANEncoder("CANEncoder/SYN CANSparkMax", deviceId); + this.m_motor = new CANMotor("SYN CANSparkMax", deviceId, 0.0, false, 0.3); + this.m_encoder = new CANEncoder("SYN CANSparkMax", deviceId); } @Override From dcca428e55c4b1a6477acfb8a87b8be5ef31151b Mon Sep 17 00:00:00 2001 From: Dhruv Arora Date: Mon, 5 Aug 2024 20:45:41 -0700 Subject: [PATCH 147/344] Formatting --- fission/src/systems/scene/SceneRenderer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fission/src/systems/scene/SceneRenderer.ts b/fission/src/systems/scene/SceneRenderer.ts index 25effaebdf..c610259076 100644 --- a/fission/src/systems/scene/SceneRenderer.ts +++ b/fission/src/systems/scene/SceneRenderer.ts @@ -192,7 +192,8 @@ class SceneRenderer extends WorldSystem { this._light.shadow.bias = 0.0 this._light.shadow.normalBias = 0.01 this._scene.add(this._light) - } else if (quality === "High") { // setting light to cascading shadows + } else if (quality === "High") { + // setting light to cascading shadows this._light = new CSM({ parent: this._scene, camera: this._mainCamera, From 2733f083f8af4adc428d6f3c2d1f88c08b8ed0bb Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 6 Aug 2024 09:18:55 -0700 Subject: [PATCH 148/344] Move logging --- exporter/SynthesisFusionAddin/src/logging.py | 105 ------------------- 1 file changed, 105 deletions(-) delete mode 100644 exporter/SynthesisFusionAddin/src/logging.py diff --git a/exporter/SynthesisFusionAddin/src/logging.py b/exporter/SynthesisFusionAddin/src/logging.py deleted file mode 100644 index 2bf9ce191b..0000000000 --- a/exporter/SynthesisFusionAddin/src/logging.py +++ /dev/null @@ -1,105 +0,0 @@ -import functools -import inspect -import logging.handlers -import os -import pathlib -import sys -import time -import traceback -from datetime import date, datetime -from typing import cast - -import adsk.core - -from .strings import INTERNAL_ID -from .UI.OsHelper import getOSPath - -MAX_LOG_FILES_TO_KEEP = 10 -TIMING_LEVEL = 25 - - -class SynthesisLogger(logging.Logger): - def timing(self, msg: str, *args: any, **kwargs: any) -> None: - return self.log(TIMING_LEVEL, msg, *args, **kwargs) - - def cleanupHandlers(self) -> None: - for handler in self.handlers: - handler.close() - - -def setupLogger() -> None: - now = datetime.now().strftime("%H-%M-%S") - today = date.today() - logFileFolder = getOSPath(f"{pathlib.Path(__file__).parent.parent}", "logs") - logFiles = [os.path.join(logFileFolder, file) for file in os.listdir(logFileFolder) if file.endswith(".log")] - logFiles.sort() - if len(logFiles) >= MAX_LOG_FILES_TO_KEEP: - for file in logFiles[: len(logFiles) - MAX_LOG_FILES_TO_KEEP]: - os.remove(file) - - logFileName = f"{logFileFolder}{getOSPath(f'{INTERNAL_ID}-{today}-{now}.log')}" - logHandler = logging.handlers.WatchedFileHandler(logFileName, mode="w") - logHandler.setFormatter(logging.Formatter("%(name)s - %(levelname)s - %(message)s")) - - logging.setLoggerClass(SynthesisLogger) - logging.addLevelName(TIMING_LEVEL, "TIMING") - logger = getLogger(INTERNAL_ID) - logger.setLevel(10) # Debug - logger.addHandler(logHandler) - - -def getLogger(name: str | None = None) -> SynthesisLogger: - if not name: - # Inspect the caller stack to automatically get the module from which the function is being called from. - name = f"{INTERNAL_ID}.{'.'.join(inspect.getmodule(inspect.stack()[1][0]).__name__.split('.')[1:])}" - - return cast(SynthesisLogger, logging.getLogger(name)) - - -# Log function failure decorator. -def logFailure(func: callable = None, /, *, messageBox: bool = False) -> callable: - def wrap(func: callable) -> callable: - @functools.wraps(func) - def wrapper(*args: any, **kwargs: any) -> any: - try: - return func(*args, **kwargs) - except BaseException: - excType, excValue, excTrace = sys.exc_info() - tb = traceback.TracebackException(excType, excValue, excTrace) - formattedTb = "".join(list(tb.format())[2:]) # Remove the wrapper func from the traceback. - clsName = "" - if args and hasattr(args[0], "__class__"): - clsName = args[0].__class__.__name__ + "." - - getLogger(f"{INTERNAL_ID}.{clsName}{func.__name__}").error(f"Failed:\n{formattedTb}") - if messageBox: - ui = adsk.core.Application.get().userInterface - ui.messageBox(f"Internal Failure: {formattedTb}", "Synthesis: Error") - - return wrapper - - if func is None: - # Called with parens. - return wrap - - # Called without parens. - return wrap(func) - - -# Time function decorator. -def timed(func: callable) -> callable: - def wrapper(*args: any, **kwargs: any) -> any: - startTime = time.perf_counter() - result = func(*args, **kwargs) - endTime = time.perf_counter() - runTime = f"{endTime - startTime:5f}s" - - clsName = "" - if args and hasattr(args[0], "__class__"): - clsName = args[0].__class__.__name__ + "." - - logger = getLogger(f"{INTERNAL_ID}.{clsName}{func.__name__}") - logger.timing(f"Runtime of '{func.__name__}' took '{runTime}'.") - return result - - return wrapper From 358064946283edafc6a96d984122c6db84da99fa Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:01:11 -0700 Subject: [PATCH 149/344] Revert back to previous state + remove `general_imports.py` --- exporter/SynthesisFusionAddin/Synthesis.py | 6 +-- exporter/SynthesisFusionAddin/src/APS/APS.py | 6 +-- .../SynthesisFusionAddin/src/Dependencies.py | 2 +- .../SynthesisFusionAddin/src/GlobalManager.py | 5 --- exporter/SynthesisFusionAddin/src/Logging.py | 4 +- .../src/Parser/ExporterOptions.py | 6 +-- .../src/Parser/SynthesisParser/Components.py | 12 +++--- .../Parser/SynthesisParser/JointHierarchy.py | 10 ++--- .../src/Parser/SynthesisParser/Joints.py | 11 +++-- .../src/Parser/SynthesisParser/Materials.py | 15 ++----- .../src/Parser/SynthesisParser/Parser.py | 16 +++---- .../SynthesisParser/PhysicalProperties.py | 6 +-- .../src/Parser/SynthesisParser/RigidGroup.py | 2 +- .../SynthesisFusionAddin/src/UI/Camera.py | 11 +++-- .../src/UI/ConfigCommand.py | 28 ++++++------- .../src/UI/Configuration/SerialCommand.py | 2 +- .../src/UI/CreateCommandInputsHelper.py | 2 +- .../src/UI/CustomGraphics.py | 4 +- .../SynthesisFusionAddin/src/UI/Events.py | 7 +++- .../src/UI/FileDialogConfig.py | 6 +-- .../src/UI/GamepieceConfigTab.py | 10 ++--- .../src/UI/GeneralConfigTab.py | 14 +++---- exporter/SynthesisFusionAddin/src/UI/HUI.py | 8 ++-- .../SynthesisFusionAddin/src/UI/Helper.py | 6 ++- .../src/UI/JointConfigTab.py | 9 ++-- .../src/UI/MarkingMenu.py | 5 +-- .../src/UI/ShowAPSAuthCommand.py | 2 +- .../src/UI/ShowWebsiteCommand.py | 3 +- .../SynthesisFusionAddin/src/UI/Toolbar.py | 5 +-- exporter/SynthesisFusionAddin/src/__init__.py | 7 ++-- .../src/general_imports.py | 42 ------------------- exporter/SynthesisFusionAddin/src/strings.py | 4 -- 32 files changed, 105 insertions(+), 171 deletions(-) delete mode 100644 exporter/SynthesisFusionAddin/src/general_imports.py delete mode 100644 exporter/SynthesisFusionAddin/src/strings.py diff --git a/exporter/SynthesisFusionAddin/Synthesis.py b/exporter/SynthesisFusionAddin/Synthesis.py index 20f3fb35d6..7f8ffd34e2 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.py +++ b/exporter/SynthesisFusionAddin/Synthesis.py @@ -15,8 +15,8 @@ setupLogger() try: - from .src.general_imports import APP_NAME, DESCRIPTION, INTERNAL_ID, gm - from .src.UI import ( + from src import APP_NAME, DESCRIPTION, INTERNAL_ID, gm + from src.UI import ( HUI, Camera, ConfigCommand, @@ -24,7 +24,7 @@ ShowAPSAuthCommand, ShowWebsiteCommand, ) - from .src.UI.Toolbar import Toolbar + from src.UI.Toolbar import Toolbar except (ImportError, ModuleNotFoundError) as error: getLogger().warn(f"Running resolve dependencies with error of:\n{error}") result = resolveDependencies() diff --git a/exporter/SynthesisFusionAddin/src/APS/APS.py b/exporter/SynthesisFusionAddin/src/APS/APS.py index a65728ec23..9f73eb9e0b 100644 --- a/exporter/SynthesisFusionAddin/src/APS/APS.py +++ b/exporter/SynthesisFusionAddin/src/APS/APS.py @@ -10,13 +10,13 @@ import requests -from ..general_imports import INTERNAL_ID, gm, my_addin_path -from ..Logging import getLogger +from src import gm, ADDIN_PATH +from src.Logging import getLogger logger = getLogger() CLIENT_ID = "GCxaewcLjsYlK8ud7Ka9AKf9dPwMR3e4GlybyfhAK2zvl3tU" -auth_path = os.path.abspath(os.path.join(my_addin_path, "..", ".aps_auth")) +auth_path = os.path.abspath(os.path.join(ADDIN_PATH, "..", ".aps_auth")) APS_AUTH = None APS_USER_INFO = None diff --git a/exporter/SynthesisFusionAddin/src/Dependencies.py b/exporter/SynthesisFusionAddin/src/Dependencies.py index b9ad31f7be..a61119c1d6 100644 --- a/exporter/SynthesisFusionAddin/src/Dependencies.py +++ b/exporter/SynthesisFusionAddin/src/Dependencies.py @@ -9,7 +9,7 @@ import adsk.core import adsk.fusion -from .Logging import getLogger, logFailure +from src.Logging import getLogger, logFailure logger = getLogger() system = platform.system() diff --git a/exporter/SynthesisFusionAddin/src/GlobalManager.py b/exporter/SynthesisFusionAddin/src/GlobalManager.py index a31688d119..fb95bda33b 100644 --- a/exporter/SynthesisFusionAddin/src/GlobalManager.py +++ b/exporter/SynthesisFusionAddin/src/GlobalManager.py @@ -1,13 +1,8 @@ """ Initializes the global variables that are set in the run method to reduce hanging commands. """ -import logging - import adsk.core import adsk.fusion -from .general_imports import * -from .strings import * - class GlobalManager(object): """Global Manager instance""" diff --git a/exporter/SynthesisFusionAddin/src/Logging.py b/exporter/SynthesisFusionAddin/src/Logging.py index 2bf9ce191b..dcfa863c67 100644 --- a/exporter/SynthesisFusionAddin/src/Logging.py +++ b/exporter/SynthesisFusionAddin/src/Logging.py @@ -11,8 +11,8 @@ import adsk.core -from .strings import INTERNAL_ID -from .UI.OsHelper import getOSPath +from src import INTERNAL_ID +from src.UI.OsHelper import getOSPath MAX_LOG_FILES_TO_KEEP = 10 TIMING_LEVEL = 25 diff --git a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py index 9151a7115d..69e9bbef5d 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py +++ b/exporter/SynthesisFusionAddin/src/Parser/ExporterOptions.py @@ -11,9 +11,9 @@ import adsk.core from adsk.fusion import CalculationAccuracy, TriangleMeshQualityOptions -from ..Logging import logFailure, timed -from ..strings import INTERNAL_ID -from ..Types import ( +from src import INTERNAL_ID +from src.Logging import logFailure, timed +from src.Types import ( KG, ExportLocation, ExportMode, diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py index aa77cac500..0a034ca33e 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py @@ -9,12 +9,12 @@ from proto.proto_out import assembly_pb2, joint_pb2, material_pb2, types_pb2 -from ...Logging import logFailure, timed -from ...Types import ExportMode -from ..ExporterOptions import ExporterOptions -from . import PhysicalProperties -from .PDMessage import PDMessage -from .Utilities import * +from src.Logging import logFailure +from src.Types import ExportMode +from src.Parser.ExporterOptions import ExporterOptions +from src.Parser.SynthesisParser import PhysicalProperties +from src.Parser.SynthesisParser.PDMessage import PDMessage +from src.Parser.SynthesisParser.Utilities import * # TODO: Impelement Material overrides diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py index 53dd3d10fa..313b6d9fa2 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py @@ -8,11 +8,11 @@ from proto.proto_out import joint_pb2, types_pb2 -from ...general_imports import * -from ...Logging import getLogger, logFailure -from ..ExporterOptions import ExporterOptions -from .PDMessage import PDMessage -from .Utilities import guid_component, guid_occurrence +from src import gm +from src.Logging import getLogger, logFailure +from src.Parser.ExporterOptions import ExporterOptions +from src.Parser.SynthesisParser.PDMessage import PDMessage +from src.Parser.SynthesisParser.Utilities import guid_component, guid_occurrence logger = getLogger() diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py index c79a0240d0..75066533a1 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py @@ -31,12 +31,11 @@ from proto.proto_out import assembly_pb2, joint_pb2, motor_pb2, signal_pb2, types_pb2 -from ...general_imports import * -from ...Logging import getLogger -from ...Types import JointParentType, SignalType -from ..ExporterOptions import ExporterOptions -from .PDMessage import PDMessage -from .Utilities import construct_info, fill_info, guid_occurrence +from src.Logging import getLogger +from src.Types import JointParentType, SignalType +from src.Parser.ExporterOptions import ExporterOptions +from src.Parser.SynthesisParser.PDMessage import PDMessage +from src.Parser.SynthesisParser.Utilities import construct_info, fill_info, guid_occurrence logger = getLogger() diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py index 97d8d47f57..5cf7e17356 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py @@ -1,18 +1,11 @@ -# Should contain Physical and Apperance materials ? -import json -import logging -import math -import traceback - import adsk from proto.proto_out import material_pb2 -from ...general_imports import * -from ...Logging import logFailure, timed -from ..ExporterOptions import ExporterOptions -from .PDMessage import PDMessage -from .Utilities import * +from src.Logging import logFailure +from src.Parser.ExporterOptions import ExporterOptions +from src.Parser.SynthesisParser.PDMessage import PDMessage +from src.Parser.SynthesisParser.Utilities import * OPACITY_RAMPING_CONSTANT = 14.0 diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py index fe034d20b4..533c0a9381 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py @@ -7,14 +7,14 @@ from proto.proto_out import assembly_pb2, types_pb2 -from ...APS.APS import getAuth, upload_mirabuf -from ...general_imports import * -from ...Logging import getLogger, logFailure, timed -from ...Types import ExportLocation, ExportMode -from ...UI.Camera import captureThumbnail, clearIconCache -from ..ExporterOptions import ExporterOptions -from . import Components, JointHierarchy, Joints, Materials, PDMessage -from .Utilities import * +from src import gm +from src.APS.APS import getAuth, upload_mirabuf +from src.Logging import getLogger, logFailure, timed +from src.Types import ExportLocation, ExportMode +from src.UI.Camera import captureThumbnail, clearIconCache +from src.Parser.ExporterOptions import ExporterOptions +from src.Parser.SynthesisParser import Components, JointHierarchy, Joints, Materials, PDMessage +from src.Parser.SynthesisParser.Utilities import * logger = getLogger() diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py index db488c115a..923d9c836a 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py @@ -15,17 +15,13 @@ - Z """ - -import logging -import traceback from typing import Union import adsk from proto.proto_out import types_pb2 -from ...general_imports import INTERNAL_ID -from ...Logging import logFailure +from src.Logging import logFailure @logFailure diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py index 362a2a6e72..4cef6fea38 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py @@ -19,7 +19,7 @@ from proto.proto_out import assembly_pb2 -from ...Logging import logFailure +from src.Logging import logFailure @logFailure diff --git a/exporter/SynthesisFusionAddin/src/UI/Camera.py b/exporter/SynthesisFusionAddin/src/UI/Camera.py index 538968034d..85fa188ff8 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Camera.py +++ b/exporter/SynthesisFusionAddin/src/UI/Camera.py @@ -1,11 +1,10 @@ import os -from adsk.core import SaveImageFileOptions +import adsk.core -from ..general_imports import * -from ..Logging import logFailure, timed -from ..Types import OString -from . import Helper +from src.Logging import logFailure +from src.Types import OString +from src.UI import Helper @logFailure @@ -25,7 +24,7 @@ def captureThumbnail(size=250): path = OString.ThumbnailPath(name) - saveOptions = SaveImageFileOptions.create(str(path.getPath())) + saveOptions = adsk.core.SaveImageFileOptions.create(str(path.getPath())) saveOptions.height = size saveOptions.width = size saveOptions.isAntiAliased = True diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index d7fd6ec6bb..3a1f2e6c76 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -9,22 +9,18 @@ import adsk.core import adsk.fusion -from ..APS.APS import getAuth, getUserInfo, refreshAuthToken -from ..general_imports import * -from ..Logging import getLogger, logFailure -from ..Parser.ExporterOptions import ExporterOptions -from ..Parser.SynthesisParser.Parser import Parser -from ..Parser.SynthesisParser.Utilities import guid_occurrence -from ..Types import ExportLocation, ExportMode -from . import CustomGraphics, FileDialogConfig, Helper, IconPaths -from .Configuration.SerialCommand import SerialCommand -from .GamepieceConfigTab import GamepieceConfigTab -from .GeneralConfigTab import GeneralConfigTab - -# Transition: AARD-1685 -# In the future all components should be handled in this way. -# This import broke everything when attempting to use absolute imports??? Investigate? -from .JointConfigTab import JointConfigTab +from src import gm +from src.APS.APS import getAuth, getUserInfo, refreshAuthToken +from src.Logging import getLogger, logFailure +from src.Parser.ExporterOptions import ExporterOptions +from src.Parser.SynthesisParser.Parser import Parser +from src.Parser.SynthesisParser.Utilities import guid_occurrence +from src.Types import ExportLocation, ExportMode +from src.UI import CustomGraphics, FileDialogConfig, Helper, IconPaths +from src.UI.Configuration.SerialCommand import SerialCommand +from src.UI.GamepieceConfigTab import GamepieceConfigTab +from src.UI.GeneralConfigTab import GeneralConfigTab +from src.UI.JointConfigTab import JointConfigTab # ====================================== CONFIG COMMAND ====================================== diff --git a/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py b/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py index 16084148c5..663afe9337 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py @@ -7,7 +7,7 @@ import json -from ...Types import OString +from src.Types import OString def generateFilePath() -> str: diff --git a/exporter/SynthesisFusionAddin/src/UI/CreateCommandInputsHelper.py b/exporter/SynthesisFusionAddin/src/UI/CreateCommandInputsHelper.py index 536b93b57d..1f78b469bd 100644 --- a/exporter/SynthesisFusionAddin/src/UI/CreateCommandInputsHelper.py +++ b/exporter/SynthesisFusionAddin/src/UI/CreateCommandInputsHelper.py @@ -1,6 +1,6 @@ import adsk.core -from ..Logging import logFailure +from src.Logging import logFailure @logFailure diff --git a/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py b/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py index 52a49fa5d4..d92c26f823 100644 --- a/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py +++ b/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py @@ -4,8 +4,8 @@ import adsk.core import adsk.fusion -from ..general_imports import * -from ..Logging import logFailure +from src import gm +from src.Logging import logFailure @logFailure diff --git a/exporter/SynthesisFusionAddin/src/UI/Events.py b/exporter/SynthesisFusionAddin/src/UI/Events.py index 281ebf3f0e..0c4cf37798 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Events.py +++ b/exporter/SynthesisFusionAddin/src/UI/Events.py @@ -1,7 +1,10 @@ +import json from typing import Sequence, Tuple -from ..general_imports import * -from ..Logging import getLogger +import adsk.core + +from src import gm +from src.Logging import getLogger """ # This file is Special It links all function names to command requests that palletes can make automatically diff --git a/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py b/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py index 6f6f764cd9..8b8b3a2f4c 100644 --- a/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py +++ b/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py @@ -3,10 +3,8 @@ import adsk.core import adsk.fusion -from ..general_imports import * - -# from ..proto_out import Configuration_pb2 -from ..Types import OString +from src import gm +from src.Types import OString def saveFileDialog(defaultPath: str | None = None, defaultName: str | None = None) -> str | bool: diff --git a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py index f0d2f7d4bd..8eb4f80044 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GamepieceConfigTab.py @@ -1,11 +1,11 @@ import adsk.core import adsk.fusion -from ..Logging import logFailure -from ..Parser.ExporterOptions import ExporterOptions -from ..Types import Gamepiece, PreferredUnits, toKg, toLbs -from . import IconPaths -from .CreateCommandInputsHelper import ( +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.UI.CreateCommandInputsHelper import ( createBooleanInput, createTableInput, createTextBoxInput, diff --git a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py index 116b8ca7d7..40a602a406 100644 --- a/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/GeneralConfigTab.py @@ -1,18 +1,18 @@ import adsk.core import adsk.fusion -from ..Logging import logFailure -from ..Parser.ExporterOptions import ( +from src.Logging import logFailure +from src.Parser.ExporterOptions import ( ExporterOptions, ExportLocation, ExportMode, PreferredUnits, ) -from ..Types import KG, toKg, toLbs -from . import IconPaths -from .CreateCommandInputsHelper import createBooleanInput, createTableInput -from .GamepieceConfigTab import GamepieceConfigTab -from .JointConfigTab import JointConfigTab +from src.Types import KG, toKg, toLbs +from src.UI import IconPaths +from src.UI.CreateCommandInputsHelper import createBooleanInput, createTableInput +from src.UI.GamepieceConfigTab import GamepieceConfigTab +from src.UI.JointConfigTab import JointConfigTab class GeneralConfigTab: diff --git a/exporter/SynthesisFusionAddin/src/UI/HUI.py b/exporter/SynthesisFusionAddin/src/UI/HUI.py index 3b52de9999..d1a968d642 100644 --- a/exporter/SynthesisFusionAddin/src/UI/HUI.py +++ b/exporter/SynthesisFusionAddin/src/UI/HUI.py @@ -1,6 +1,8 @@ -from ..general_imports import * -from ..Logging import logFailure -from . import Handlers, OsHelper +import adsk.core + +from src import INTERNAL_ID, gm +from src.Logging import logFailure +from src.UI import Handlers, OsHelper # no longer used diff --git a/exporter/SynthesisFusionAddin/src/UI/Helper.py b/exporter/SynthesisFusionAddin/src/UI/Helper.py index 7c8e3a5930..2f9eeaa12c 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Helper.py +++ b/exporter/SynthesisFusionAddin/src/UI/Helper.py @@ -1,8 +1,10 @@ from inspect import getmembers, isfunction from typing import Union -from ..general_imports import * -from . import HUI, Events +import adsk.core + +from src import gm, APP_NAME, APP_TITLE, INTERNAL_ID +from src.UI import HUI, Events def getDocName() -> str or None: diff --git a/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py index 7e34757783..000080eb79 100644 --- a/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py @@ -4,11 +4,10 @@ import adsk.core import adsk.fusion -from ..general_imports import INTERNAL_ID -from ..Logging import logFailure -from ..Types import Joint, JointParentType, SignalType, Wheel, WheelType -from . import IconPaths -from .CreateCommandInputsHelper import ( +from src.Logging import logFailure +from src.Types import Joint, JointParentType, SignalType, Wheel, WheelType +from src.UI import IconPaths +from src.UI.CreateCommandInputsHelper import ( createBooleanInput, createTableInput, createTextBoxInput, diff --git a/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py b/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py index 30c9f078e6..d6509b16d4 100644 --- a/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py +++ b/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py @@ -1,10 +1,7 @@ -import logging.handlers -import traceback - import adsk.core import adsk.fusion -from ..Logging import logFailure +from src.Logging import logFailure # Ripped all the boiler plate from the example code: https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-c90ce6a2-c282-11e6-a365-3417ebc87622 diff --git a/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py b/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py index bd45cfc062..13e3cec65d 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py @@ -8,8 +8,8 @@ import adsk.core +from src import gm from src.APS.APS import CLIENT_ID, auth_path, convertAuthToken, getCodeChallenge -from src.general_imports import APP_NAME, DESCRIPTION, INTERNAL_ID, gm, my_addin_path from src.Logging import getLogger logger = getLogger() diff --git a/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py b/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py index d7e1539e5e..cc99cffb07 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ShowWebsiteCommand.py @@ -1,8 +1,9 @@ +import traceback import webbrowser import adsk.core -from ..general_imports import * +from src import gm class ShowWebsiteCommandExecuteHandler(adsk.core.CommandEventHandler): diff --git a/exporter/SynthesisFusionAddin/src/UI/Toolbar.py b/exporter/SynthesisFusionAddin/src/UI/Toolbar.py index bfcc34189a..4a0c581f3d 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Toolbar.py +++ b/exporter/SynthesisFusionAddin/src/UI/Toolbar.py @@ -1,6 +1,5 @@ -from ..general_imports import * -from ..Logging import logFailure -from ..strings import INTERNAL_ID +from src.Logging import logFailure +from src import INTERNAL_ID, gm class Toolbar: diff --git a/exporter/SynthesisFusionAddin/src/__init__.py b/exporter/SynthesisFusionAddin/src/__init__.py index f041f71631..c7d951b784 100644 --- a/exporter/SynthesisFusionAddin/src/__init__.py +++ b/exporter/SynthesisFusionAddin/src/__init__.py @@ -6,16 +6,17 @@ APP_NAME = "Synthesis" APP_TITLE = "Synthesis Robot Exporter" DESCRIPTION = "Exports files from Fusion into the Synthesis Format" -INTERNAL_ID = "synthesis" +INTERNAL_ID = "Synthesis" +ADDIN_PATH = os.path.dirname(os.path.realpath(__file__)) A_EP = None # TODO: Will be removed by GH-1010 DEBUG = True # TODO: Will be removed by GH-1010 gm = GlobalManager() -__all__ = ["APP_NAME", "APP_TITLE", "DESCRIPTION", "INTERNAL_ID", "gm"] +__all__ = ["APP_NAME", "APP_TITLE", "DESCRIPTION", "INTERNAL_ID", "ADDIN_PATH", "gm"] # Transition: AARD-1737 # This method of running the resolve dependencies module will be revisited in AARD-1734 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "proto", "proto_out"))) -from proto import deps +# from proto import deps diff --git a/exporter/SynthesisFusionAddin/src/general_imports.py b/exporter/SynthesisFusionAddin/src/general_imports.py deleted file mode 100644 index aabdae5cac..0000000000 --- a/exporter/SynthesisFusionAddin/src/general_imports.py +++ /dev/null @@ -1,42 +0,0 @@ -import json -import os -import pathlib -import sys -import traceback -import uuid -from datetime import datetime -from time import time -from types import FunctionType - -import adsk.core -import adsk.fusion - -from .GlobalManager import * -from .Logging import getLogger -from .strings import * - -logger = getLogger() - -# hard coded to bypass errors for now -PROTOBUF = True -DEBUG = True - -try: - path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) - - path_proto_files = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "proto", "proto_out")) - - if not path in sys.path: - sys.path.insert(1, path) - - if not path_proto_files in sys.path: - sys.path.insert(2, path_proto_files) -except: - logger.error("Failed:\n{}".format(traceback.format_exc())) - -try: - # Setup the global state - gm: GlobalManager = GlobalManager() - my_addin_path = os.path.dirname(os.path.realpath(__file__)) -except: - logger.error("Failed:\n{}".format(traceback.format_exc())) diff --git a/exporter/SynthesisFusionAddin/src/strings.py b/exporter/SynthesisFusionAddin/src/strings.py deleted file mode 100644 index b5d79c055c..0000000000 --- a/exporter/SynthesisFusionAddin/src/strings.py +++ /dev/null @@ -1,4 +0,0 @@ -APP_NAME = "Synthesis" -APP_TITLE = "Synthesis Robot Exporter" -DESCRIPTION = "Exports files from Fusion into the Synthesis Format" -INTERNAL_ID = "Synthesis" From ca0b17576fa411c45f94085bc7738b3e62fe0306 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:01:32 -0700 Subject: [PATCH 150/344] Sort imports --- exporter/SynthesisFusionAddin/src/APS/APS.py | 2 +- .../src/Parser/SynthesisParser/Components.py | 3 +-- .../src/Parser/SynthesisParser/JointHierarchy.py | 1 - .../src/Parser/SynthesisParser/Joints.py | 9 ++++++--- .../src/Parser/SynthesisParser/Materials.py | 1 - .../src/Parser/SynthesisParser/Parser.py | 13 +++++++++---- .../Parser/SynthesisParser/PhysicalProperties.py | 2 +- .../src/Parser/SynthesisParser/RigidGroup.py | 1 - exporter/SynthesisFusionAddin/src/UI/Helper.py | 2 +- exporter/SynthesisFusionAddin/src/UI/Toolbar.py | 2 +- 10 files changed, 20 insertions(+), 16 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/APS/APS.py b/exporter/SynthesisFusionAddin/src/APS/APS.py index 9f73eb9e0b..a6f4f34a5b 100644 --- a/exporter/SynthesisFusionAddin/src/APS/APS.py +++ b/exporter/SynthesisFusionAddin/src/APS/APS.py @@ -10,7 +10,7 @@ import requests -from src import gm, ADDIN_PATH +from src import ADDIN_PATH, gm from src.Logging import getLogger logger = getLogger() diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py index 0a034ca33e..0ac0078d1c 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py @@ -8,13 +8,12 @@ import adsk.fusion from proto.proto_out import assembly_pb2, joint_pb2, material_pb2, types_pb2 - from src.Logging import logFailure -from src.Types import ExportMode from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser import PhysicalProperties from src.Parser.SynthesisParser.PDMessage import PDMessage from src.Parser.SynthesisParser.Utilities import * +from src.Types import ExportMode # TODO: Impelement Material overrides diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py index 313b6d9fa2..5c106d0188 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py @@ -7,7 +7,6 @@ import adsk.fusion from proto.proto_out import joint_pb2, types_pb2 - from src import gm from src.Logging import getLogger, logFailure from src.Parser.ExporterOptions import ExporterOptions diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py index 75066533a1..c5458cbae3 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py @@ -30,12 +30,15 @@ import adsk.fusion from proto.proto_out import assembly_pb2, joint_pb2, motor_pb2, signal_pb2, types_pb2 - from src.Logging import getLogger -from src.Types import JointParentType, SignalType from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser.PDMessage import PDMessage -from src.Parser.SynthesisParser.Utilities import construct_info, fill_info, guid_occurrence +from src.Parser.SynthesisParser.Utilities import ( + construct_info, + fill_info, + guid_occurrence, +) +from src.Types import JointParentType, SignalType logger = getLogger() diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py index 5cf7e17356..ca7fb5480c 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py @@ -1,7 +1,6 @@ import adsk from proto.proto_out import material_pb2 - from src.Logging import logFailure from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser.PDMessage import PDMessage diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py index 533c0a9381..95b124bbe3 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py @@ -6,15 +6,20 @@ from google.protobuf.json_format import MessageToJson from proto.proto_out import assembly_pb2, types_pb2 - from src import gm from src.APS.APS import getAuth, upload_mirabuf from src.Logging import getLogger, logFailure, timed -from src.Types import ExportLocation, ExportMode -from src.UI.Camera import captureThumbnail, clearIconCache from src.Parser.ExporterOptions import ExporterOptions -from src.Parser.SynthesisParser import Components, JointHierarchy, Joints, Materials, PDMessage +from src.Parser.SynthesisParser import ( + Components, + JointHierarchy, + Joints, + Materials, + PDMessage, +) from src.Parser.SynthesisParser.Utilities import * +from src.Types import ExportLocation, ExportMode +from src.UI.Camera import captureThumbnail, clearIconCache logger = getLogger() diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py index 923d9c836a..15e025f4c7 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py @@ -15,12 +15,12 @@ - Z """ + from typing import Union import adsk from proto.proto_out import types_pb2 - from src.Logging import logFailure diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py index 4cef6fea38..d6cbc47c77 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py @@ -18,7 +18,6 @@ import adsk.fusion from proto.proto_out import assembly_pb2 - from src.Logging import logFailure diff --git a/exporter/SynthesisFusionAddin/src/UI/Helper.py b/exporter/SynthesisFusionAddin/src/UI/Helper.py index 2f9eeaa12c..b6942afb4d 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Helper.py +++ b/exporter/SynthesisFusionAddin/src/UI/Helper.py @@ -3,7 +3,7 @@ import adsk.core -from src import gm, APP_NAME, APP_TITLE, INTERNAL_ID +from src import APP_NAME, APP_TITLE, INTERNAL_ID, gm from src.UI import HUI, Events diff --git a/exporter/SynthesisFusionAddin/src/UI/Toolbar.py b/exporter/SynthesisFusionAddin/src/UI/Toolbar.py index 4a0c581f3d..f9d150cc3c 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Toolbar.py +++ b/exporter/SynthesisFusionAddin/src/UI/Toolbar.py @@ -1,5 +1,5 @@ -from src.Logging import logFailure from src import INTERNAL_ID, gm +from src.Logging import logFailure class Toolbar: From ff45ebcf5e1c4c7e64d8abe6b535147a9e83a7c7 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Tue, 6 Aug 2024 10:05:03 -0700 Subject: [PATCH 151/344] Hopeful fix (Works when OPFS on PC, but need Mac to test on) --- fission/src/mirabuf/MirabufLoader.ts | 146 ++++++++++++++++++++------- 1 file changed, 110 insertions(+), 36 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 00c0068733..505d684e1f 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -21,14 +21,39 @@ export interface MirabufRemoteInfo { src: string } -type MiraCache = { [id: string]: MirabufCacheInfo } +type MapCache = { [id: string]: MirabufCacheInfo } +type BackUpBufferCache = { [id: string]: ArrayBuffer } +// Local Storage +const robotMapDirName = "RobotMap" +const fieldMapDirName = "FieldMap" +const backUpRobotsDirName = "BackUpRobots" +const backUpFieldsDirName = "BackUpFields" + +// OPFS const robotsDirName = "Robots" const fieldsDirName = "Fields" const root = await navigator.storage.getDirectory() const robotFolderHandle = await root.getDirectoryHandle(robotsDirName, { create: true }) const fieldFolderHandle = await root.getDirectoryHandle(fieldsDirName, { create: true }) +const canOPFS = await (async() => { + try { + console.log(`trying OPFS`) + const fileHandle = await robotFolderHandle.getFileHandle( + "0", + { create: true } + ) + const writable = await fileHandle.createWritable() + await writable.close() + console.log(`yes OPFS`) + return true + } catch (e) { + console.log(`no OPFS`) + return false + } +})() + export function UnzipMira(buff: Uint8Array): Uint8Array { // Check if file is gzipped via magic gzip numbers 31 139 if (buff[0] == 31 && buff[1] == 139) { @@ -42,21 +67,47 @@ class MirabufCachingService { /** * Get the map of mirabuf keys and paired MirabufCacheInfo from local storage * - * @param {MiraType} miraType Type of Mirabuf Assembly. + * @param {MiraType} miraType Type of Mirabuf Assembly * - * @returns {MiraCache} Map of cached keys and paired MirabufCacheInfo + * @returns {MapCache} Map of cached keys and paired MirabufCacheInfo + */ + public static GetCacheMap(miraType: MiraType): MapCache { + if ( + (window.localStorage.getItem(MIRABUF_LOCALSTORAGE_GENERATION_KEY) ?? "") == MIRABUF_LOCALSTORAGE_GENERATION + ) { + window.localStorage.setItem(MIRABUF_LOCALSTORAGE_GENERATION_KEY, MIRABUF_LOCALSTORAGE_GENERATION) + window.localStorage.setItem(robotMapDirName, "{}") + window.localStorage.setItem(fieldMapDirName, "{}") + return {} + } + + const key = miraType == MiraType.ROBOT ? robotMapDirName : fieldMapDirName + const map = window.localStorage.getItem(key) + + if (map) { + return JSON.parse(map) + } else { + window.localStorage.setItem(key, "{}") + return {} + } + } + + /** + * Get back up cache from local storage. Used if no access to OPFS (Safari and iOS) + * @param miraType Type of Mirabuf Assembly + * @returns IDs and buffers */ - public static GetCacheMap(miraType: MiraType): MiraCache { + public static GetBackUpCache(miraType: MiraType): BackUpBufferCache { if ( (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, "{}") + window.localStorage.setItem(robotMapDirName, "{}") + window.localStorage.setItem(fieldMapDirName, "{}") return {} } - const key = miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName + const key = miraType == MiraType.ROBOT ? backUpRobotsDirName : backUpFieldsDirName const map = window.localStorage.getItem(key) if (map) { @@ -67,6 +118,7 @@ class MirabufCachingService { } } + /** * Cache remote Mirabuf file * @@ -159,19 +211,19 @@ class MirabufCachingService { thumbnailStorageID?: string ): Promise { try { - const map: MiraCache = this.GetCacheMap(miraType) + const map: MapCache = this.GetCacheMap(miraType) const id = map[key].id const _name = map[key].name const _thumbnailStorageID = map[key].thumbnailStorageID - const hi: MirabufCacheInfo = { + const info: MirabufCacheInfo = { id: id, cacheKey: key, miraType: miraType, name: name ?? _name, thumbnailStorageID: thumbnailStorageID ?? _thumbnailStorageID, } - map[key] = hi - window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) + map[key] = info + window.localStorage.setItem(miraType == MiraType.ROBOT ? robotMapDirName : fieldMapDirName, JSON.stringify(map)) return true } catch (e) { console.error(`Failed to cache info\n${e}`) @@ -213,16 +265,18 @@ class MirabufCachingService { */ public static async Get(id: MirabufCacheID, miraType: MiraType): Promise { try { - const fileHandle = await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle( + // If we have access to OPFS, get file handle from OPFS + const fileHandle = canOPFS ? await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle( id, { create: false, } - ) + ) : undefined + + // If we have OPFS file, get buffer from OPFS, otherwise get from local storage + const buff = fileHandle ? await fileHandle.getFile().then(x => x.arrayBuffer()) : this.GetBackUpCache(miraType)[id] - // Get assembly from file - if (fileHandle) { - const buff = await fileHandle.getFile().then(x => x.arrayBuffer()) + if (buff) { const assembly = this.AssemblyFromBuffer(buff) World.AnalyticsSystem?.Event("Cache Get", { key: id, @@ -232,11 +286,10 @@ class MirabufCachingService { }) return assembly } else { - console.error(`Failed to get file handle for ID: ${id}`) - return undefined + console.error(`Failed to find file for id: ${id}`) } } catch (e) { - console.error(`Failed to find file from OPFS\n${e}`) + console.error(`Failed to find file\n${e}`) return undefined } } @@ -256,13 +309,24 @@ class MirabufCachingService { if (map) { delete map[key] window.localStorage.setItem( - miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, + miraType == MiraType.ROBOT ? robotMapDirName : fieldMapDirName, JSON.stringify(map) ) } - const dir = miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle - await dir.removeEntry(id) + if (canOPFS) { + const dir = miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle + await dir.removeEntry(id) + } else { + const backUpCache = this.GetBackUpCache(miraType) + if (backUpCache) { + delete backUpCache[key] + window.localStorage.setItem( + miraType == MiraType.ROBOT ? backUpRobotsDirName : backUpFieldsDirName, + JSON.stringify(backUpCache) + ) + } + } World.AnalyticsSystem?.Event("Cache Remove", { key: key, @@ -287,8 +351,10 @@ class MirabufCachingService { fieldFolderHandle.removeEntry(key) } - window.localStorage.removeItem(robotsDirName) - window.localStorage.removeItem(fieldsDirName) + window.localStorage.removeItem(robotMapDirName) + window.localStorage.removeItem(fieldMapDirName) + window.localStorage.removeItem(backUpRobotsDirName) + window.localStorage.removeItem(backUpFieldsDirName) } // Optional name for when assembly is being decoded anyway like in CacheAndGetLocal() @@ -298,7 +364,6 @@ class MirabufCachingService { miraType?: MiraType, name?: string ): Promise { - // Store in OPFS const backupID = Date.now().toString() try { if (!miraType) { @@ -306,16 +371,8 @@ class MirabufCachingService { miraType = this.AssemblyFromBuffer(miraBuff).dynamic ? MiraType.ROBOT : MiraType.FIELD } - const fileHandle = await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle( - backupID, - { create: true } - ) - const writable = await fileHandle.createWritable() - await writable.write(miraBuff) - await writable.close() - // Local cache map - const map: MiraCache = this.GetCacheMap(miraType) + const map: MapCache = this.GetCacheMap(miraType) const info: MirabufCacheInfo = { id: backupID, cacheKey: key, @@ -323,7 +380,7 @@ class MirabufCachingService { name: name, } map[key] = info - window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) + window.localStorage.setItem(miraType == MiraType.ROBOT ? robotMapDirName : fieldMapDirName, JSON.stringify(map)) World.AnalyticsSystem?.Event("Cache Store", { name: name ?? "-", @@ -331,6 +388,23 @@ class MirabufCachingService { type: miraType == MiraType.ROBOT ? "robot" : "field", fileSize: miraBuff.byteLength, }) + + if (canOPFS) { + // Store in OPFS + const fileHandle = await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle( + backupID, + { create: true } + ) + const writable = await fileHandle.createWritable() + await writable.write(miraBuff) + await writable.close() + } else { + // Store in localStorage + const cache: BackUpBufferCache = this.GetBackUpCache(miraType) + cache[key] = miraBuff + window.localStorage.setItem(miraType == MiraType.ROBOT ? backUpRobotsDirName : backUpFieldsDirName, JSON.stringify(cache)) + } + return info } catch (e) { console.error("Failed to cache mira " + e) @@ -353,7 +427,7 @@ class MirabufCachingService { export enum MiraType { ROBOT = 1, - FIELD = 2, + FIELD, } export default MirabufCachingService From fa668d31a5057495218769a8e7535d6423e7a89d Mon Sep 17 00:00:00 2001 From: a-crowell Date: Tue, 6 Aug 2024 10:16:01 -0700 Subject: [PATCH 152/344] More checks --- fission/src/mirabuf/MirabufLoader.ts | 29 ++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 505d684e1f..4ce17696b4 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -40,14 +40,27 @@ const fieldFolderHandle = await root.getDirectoryHandle(fieldsDirName, { create: const canOPFS = await (async() => { try { console.log(`trying OPFS`) - const fileHandle = await robotFolderHandle.getFileHandle( - "0", - { create: true } - ) - const writable = await fileHandle.createWritable() - await writable.close() - console.log(`yes OPFS`) - return true + + if (robotFolderHandle.name == robotsDirName) { + robotFolderHandle.entries + robotFolderHandle.keys + + const fileHandle = await robotFolderHandle.getFileHandle( + "0", + { create: true } + ) + const writable = await fileHandle.createWritable() + await writable.close() + await fileHandle.getFile() + + robotFolderHandle.removeEntry(fileHandle.name) + + console.log(`yes OPFS`) + return true + } else { + console.log(`no OPFS`) + return false + } } catch (e) { console.log(`no OPFS`) return false From 0048d22abaaff277bb5f0a1cd73af3c254d5d934 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:25:47 -0700 Subject: [PATCH 153/344] Improved state managing --- exporter/SynthesisFusionAddin/Synthesis.py | 20 +------------------ .../SynthesisFusionAddin/src/GlobalManager.py | 5 +++++ .../src/UI/MarkingMenu.py | 7 +++++-- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/exporter/SynthesisFusionAddin/Synthesis.py b/exporter/SynthesisFusionAddin/Synthesis.py index 7f8ffd34e2..8775c80833 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.py +++ b/exporter/SynthesisFusionAddin/Synthesis.py @@ -71,25 +71,7 @@ def stop(_): logger = getLogger(INTERNAL_ID) logger.cleanupHandlers() - for file in gm.files: - try: - os.remove(file) - except OSError: - pass - - # removes path so that proto files don't get confused - - import sys - - path = os.path.abspath(os.path.dirname(__file__)) - - path_proto_files = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "proto", "proto_out")) - - if path in sys.path: - sys.path.remove(path) - - if path_proto_files in sys.path: - sys.path.remove(path_proto_files) + gm.clear() @logFailure diff --git a/exporter/SynthesisFusionAddin/src/GlobalManager.py b/exporter/SynthesisFusionAddin/src/GlobalManager.py index fb95bda33b..6841a58750 100644 --- a/exporter/SynthesisFusionAddin/src/GlobalManager.py +++ b/exporter/SynthesisFusionAddin/src/GlobalManager.py @@ -42,6 +42,11 @@ def __init__(self): def __str__(self): return "GlobalManager" + def clear(self): + for attr, value in self.__dict__.items(): + if isinstance(value, list): + setattr(self, attr, []) + instance = None def __new__(cls): diff --git a/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py b/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py index d6509b16d4..5b90c1b671 100644 --- a/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py +++ b/exporter/SynthesisFusionAddin/src/UI/MarkingMenu.py @@ -1,7 +1,7 @@ import adsk.core import adsk.fusion -from src.Logging import logFailure +from src.Logging import getLogger, logFailure # Ripped all the boiler plate from the example code: https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-c90ce6a2-c282-11e6-a365-3417ebc87622 @@ -12,6 +12,8 @@ entities = [] occurrencesOfComponents = {} +logger = getLogger() + @logFailure(messageBox=True) def setupMarkingMenu(ui: adsk.core.UserInterface): @@ -204,6 +206,7 @@ def stopMarkingMenu(ui: adsk.core.UserInterface): if obj.isValid: obj.deleteMe() else: - ui.messageBox(str(obj) + " is not a valid object") + logger.warn(f"{str(obj)} is not a valid object") + cmdDefs.clear() handlers.clear() From 9f2af7126778804d62f6d6a6ee8fbfdae51ebfdf Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:42:18 -0700 Subject: [PATCH 154/344] Handle dependencies + update setup logger --- exporter/SynthesisFusionAddin/Synthesis.py | 40 ++++++++++---------- exporter/SynthesisFusionAddin/src/Logging.py | 3 +- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/exporter/SynthesisFusionAddin/Synthesis.py b/exporter/SynthesisFusionAddin/Synthesis.py index 8775c80833..31dd3559bd 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.py +++ b/exporter/SynthesisFusionAddin/Synthesis.py @@ -3,35 +3,37 @@ import adsk.core -# Currently required for `resolveDependencies()`, will be required for absolute imports. +# Required for absolute imports. sys.path.append(os.path.dirname(os.path.abspath(__file__))) -from .src.Dependencies import resolveDependencies # isort:skip +from src.Dependencies import resolveDependencies +from src.Logging import logFailure, setupLogger -# Transition: AARD-1741 -# Import order should be removed in AARD-1737 and `setupLogger()` moved to `__init__.py` -from .src.Logging import getLogger, logFailure, setupLogger # isort:skip - -setupLogger() +logger = setupLogger() try: - from src import APP_NAME, DESCRIPTION, INTERNAL_ID, gm - from src.UI import ( - HUI, - Camera, - ConfigCommand, - MarkingMenu, - ShowAPSAuthCommand, - ShowWebsiteCommand, - ) - from src.UI.Toolbar import Toolbar + # Attempt to import required pip dependencies to verify their installation. + import google.protobuf + import requests except (ImportError, ModuleNotFoundError) as error: - getLogger().warn(f"Running resolve dependencies with error of:\n{error}") + logger.warn(f"Running resolve dependencies with error of:\n{error}") result = resolveDependencies() if result: adsk.core.Application.get().userInterface.messageBox("Installed required dependencies.\nPlease restart Fusion.") +from src import APP_NAME, DESCRIPTION, INTERNAL_ID, gm +from src.UI import ( + HUI, + Camera, + ConfigCommand, + MarkingMenu, + ShowAPSAuthCommand, + ShowWebsiteCommand, +) +from src.UI.Toolbar import Toolbar + + @logFailure def run(_): """## Entry point to application from Fusion. @@ -68,9 +70,7 @@ def stop(_): # nm.deleteMe() - logger = getLogger(INTERNAL_ID) logger.cleanupHandlers() - gm.clear() diff --git a/exporter/SynthesisFusionAddin/src/Logging.py b/exporter/SynthesisFusionAddin/src/Logging.py index dcfa863c67..e5f352f480 100644 --- a/exporter/SynthesisFusionAddin/src/Logging.py +++ b/exporter/SynthesisFusionAddin/src/Logging.py @@ -27,7 +27,7 @@ def cleanupHandlers(self) -> None: handler.close() -def setupLogger() -> None: +def setupLogger() -> SynthesisLogger: now = datetime.now().strftime("%H-%M-%S") today = date.today() logFileFolder = getOSPath(f"{pathlib.Path(__file__).parent.parent}", "logs") @@ -46,6 +46,7 @@ def setupLogger() -> None: logger = getLogger(INTERNAL_ID) logger.setLevel(10) # Debug logger.addHandler(logHandler) + return cast(SynthesisLogger, logger) def getLogger(name: str | None = None) -> SynthesisLogger: From b65ff0a8f9ebf7e98a4437eacaeae9319cc28e69 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Tue, 6 Aug 2024 10:55:29 -0700 Subject: [PATCH 155/344] fix port NaN bug --- .../simulation/wpilib_brain/WPILibBrain.ts | 70 ++++++++++++------- .../rio-config/RCConfigCANGroupModal.tsx | 5 +- .../com/autodesk/synthesis/ctre/TalonFX.java | 2 +- .../src/main/java/frc/robot/Robot.java | 40 +++++++---- 4 files changed, 75 insertions(+), 42 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index 2e723be4c0..1be9474bc9 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -22,15 +22,14 @@ const CANMOTOR_SUPPLY_CURRENT = ">supplyCurrent" const CANMOTOR_MOTOR_CURRENT = ">motorCurrent" const CANMOTOR_BUS_VOLTAGE = ">busVoltage" -// const CANMOTOR_DUTY_CYCLE = " simType == type - ) + //const id_exp = /.*\[(\d+)\]/g + const entries = [...simMap.entries()].filter(([simType, _data]) => simType == type) for (const [_simType, data] of entries) { for (const key of data.keys()) { - const result = [...key.matchAll(id_exp)] - if (result?.length <= 0 || result[0].length <= 1) continue - const parsed_id = parseInt(result[0][1]) - if (parsed_id != id) continue - + // const result = [...key.matchAll(id_exp)] + // if (result?.length <= 0 || result[0].length <= 1) continue + // const parsed_id = parseInt(result[0][1]) + // if (parsed_id != id) continue + const res = key.split("[")[1].split("]")[0] + // console.log("id: " + res) + // console.log(id) + if (!res) continue + const parsed_id = parseInt(res) + if (id != parsed_id) continue return data.get(key) } } @@ -197,7 +199,7 @@ export class SimCANEncoder { public static SetPosition(device: string, position: number): boolean { return SimGeneric.Set(SimType.CANEncoder, device, CANENCODER_POSITION, position) - + } } worker.addEventListener("message", (eventData: MessageEvent) => { @@ -214,7 +216,7 @@ worker.addEventListener("message", (eventData: MessageEvent) => { } } - if (!data?.type || data.split(" ")[0] != "SYN" || !Object.values(SimType).includes(data.type)) return + if (!data?.type || !Object.values(SimType).includes(data.type) || data.device.split(" ")[0] != "SYN") return UpdateSimMap(data.type, data.device, data.data) }) @@ -301,6 +303,14 @@ abstract class SimOutputGroup { this.type = type } + public abstract Update(deltaT: number): void +} + +export class PWMGroup extends SimOutputGroup { + public constructor(name: string, ports: number[], drivers: Driver[]) { + super(name, ports, drivers, SimType.PWM) + } + // Averaging is probably not the right solution public Update(deltaT: number): void { const average = @@ -318,19 +328,31 @@ abstract class SimOutputGroup { } d.Update(deltaT) }) - } -} - -export class PWMGroup extends SimOutputGroup { - public constructor(name: string, ports: number[], drivers: Driver[]) { - super(name, ports, drivers, SimType.PWM) - } } export class CANGroup extends SimOutputGroup { public constructor(name: string, ports: number[], drivers: Driver[]) { super(name, ports, drivers, SimType.CANMotor) } + + // Averaging is probably not the right solution + public Update(deltaT: number): void { + const average = + this.ports.reduce((sum, port) => { + const device = SimCAN.GetDeviceWithID(port, SimType.CANMotor) + return sum + device[" { + if (d instanceof WheelDriver) { + d.targetWheelSpeed = average * 40 + } else if (d instanceof HingeDriver || d instanceof SliderDriver) { + d.targetVelocity = average * 40 + } + d.Update(deltaT) + }) + } + } diff --git a/fission/src/ui/modals/configuring/rio-config/RCConfigCANGroupModal.tsx b/fission/src/ui/modals/configuring/rio-config/RCConfigCANGroupModal.tsx index d3904dfffd..a454b97fed 100644 --- a/fission/src/ui/modals/configuring/rio-config/RCConfigCANGroupModal.tsx +++ b/fission/src/ui/modals/configuring/rio-config/RCConfigCANGroupModal.tsx @@ -34,7 +34,7 @@ const RCConfigCANGroupModal: React.FC = ({ modalId }) => { } const cans = simMap.get(SimType.CANMotor) ?? new Map - const devices: [string, any][] = [...cans.entries()].filter(([_, data]) => data[" data[" = ({ modalId }) => { label={p.toString()} defaultState={false} onClick={checked => { - const port = parseInt(p) + const port = parseInt(p.split("[")[1].split("]")[0]) + console.log(port) if (checked && !checkedPorts.includes(port)) { setCheckedPorts([...checkedPorts, port]) } else if (!checked && checkedPorts.includes(port)) { diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java index 3ac0d32a66..4a242a97e3 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/ctre/TalonFX.java @@ -16,7 +16,7 @@ public class TalonFX extends com.ctre.phoenix6.hardware.TalonFX { public TalonFX(int deviceNumber) { super(deviceNumber); - this.m_motor = new CANMotor("CANMotor/SYN TalonFX", deviceNumber, 0.0, false, 0.3); + this.m_motor = new CANMotor("SYN TalonFX", deviceNumber, 0.0, false, 0.3); } @Override diff --git a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java index 134222b08b..92936dbd24 100644 --- a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java +++ b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java @@ -29,9 +29,13 @@ public class Robot extends TimedRobot { private String m_autoSelected; private final SendableChooser m_chooser = new SendableChooser<>(); - private Spark m_Spark = new Spark(0); - private CANSparkMax m_SparkMax = new CANSparkMax(1, MotorType.kBrushless); - private com.autodesk.synthesis.ctre.TalonFX m_Talon = new com.autodesk.synthesis.ctre.TalonFX(2); + private CANSparkMax m_SparkMax1 = new CANSparkMax(1, MotorType.kBrushless); + private CANSparkMax m_SparkMax2 = new CANSparkMax(2, MotorType.kBrushless); + private CANSparkMax m_SparkMax3 = new CANSparkMax(3, MotorType.kBrushless); + private CANSparkMax m_SparkMax4 = new CANSparkMax(4, MotorType.kBrushless); + private CANSparkMax m_SparkMax5 = new CANSparkMax(5, MotorType.kBrushless); + private CANSparkMax m_SparkMax6 = new CANSparkMax(6, MotorType.kBrushless); + //private com.autodesk.synthesis.ctre.TalonFX m_Talon = new com.autodesk.synthesis.ctre.TalonFX(2); /** * This function is run when the robot is first started up and should be used @@ -87,12 +91,12 @@ public void autonomousInit() { @Override public void autonomousPeriodic() { - m_Spark.set(0.5); - m_SparkMax.set(1.0); - //m_SparkMax.setNeutralDeadband(0.01); // FIXME: for some reason I can't set - // the deadband even after defining the function in the child - m_Talon.set(-1.0); - //m_Talon.configureNeutralDeadband(0.2); + m_SparkMax1.set(1.0); + m_SparkMax2.set(1.0); + m_SparkMax3.set(1.0); + m_SparkMax4.set(1.0); + m_SparkMax5.set(1.0); + m_SparkMax6.set(1.0); switch (m_autoSelected) { case kCustomAuto: @@ -113,17 +117,23 @@ public void teleopInit() { /** This function is called periodically during operator control. */ @Override public void teleopPeriodic() { - m_Spark.set(0.25); - m_SparkMax.set(0.75); - m_Talon.set(-0.5); + m_SparkMax1.set(-0.75); + m_SparkMax2.set(-0.75); + m_SparkMax3.set(-0.75); + m_SparkMax4.set(-0.75); + m_SparkMax5.set(-0.75); + m_SparkMax6.set(-0.75); } /** This function is called once when the robot is disabled. */ @Override public void disabledInit() { - m_Spark.set(0.0); - m_SparkMax.set(0.0); - m_Talon.set(0.0); + m_SparkMax1.set(0.0); + m_SparkMax2.set(0.0); + m_SparkMax3.set(0.0); + m_SparkMax4.set(0.0); + m_SparkMax5.set(0.0); + m_SparkMax6.set(0.0); } /** This function is called periodically when disabled. */ From 2ff084593eb34b6dd82ccc6abd526a296770e23f Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:56:59 -0700 Subject: [PATCH 156/344] Final cleanups and handle transition tags --- exporter/SynthesisFusionAddin/Synthesis.py | 1 + exporter/SynthesisFusionAddin/src/Dependencies.py | 15 ++++++--------- .../src/Parser/SynthesisParser/Components.py | 11 +++++------ .../src/Parser/SynthesisParser/JointHierarchy.py | 4 +--- .../src/Parser/SynthesisParser/Joints.py | 2 +- .../src/Parser/SynthesisParser/Materials.py | 2 +- .../src/Parser/SynthesisParser/Parser.py | 2 +- .../src/Parser/SynthesisParser/RigidGroup.py | 2 +- exporter/SynthesisFusionAddin/src/UI/Camera.py | 1 - .../SynthesisFusionAddin/src/UI/ConfigCommand.py | 5 ++--- .../SynthesisFusionAddin/src/UI/CustomGraphics.py | 3 --- exporter/SynthesisFusionAddin/src/UI/Events.py | 2 +- .../src/UI/FileDialogConfig.py | 2 -- exporter/SynthesisFusionAddin/src/UI/Helper.py | 1 - .../SynthesisFusionAddin/src/UI/JointConfigTab.py | 3 --- .../src/UI/ShowAPSAuthCommand.py | 2 -- exporter/SynthesisFusionAddin/src/__init__.py | 13 ++++--------- 17 files changed, 24 insertions(+), 47 deletions(-) diff --git a/exporter/SynthesisFusionAddin/Synthesis.py b/exporter/SynthesisFusionAddin/Synthesis.py index 31dd3559bd..06ead88431 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.py +++ b/exporter/SynthesisFusionAddin/Synthesis.py @@ -5,6 +5,7 @@ # Required for absolute imports. sys.path.append(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "proto", "proto_out"))) from src.Dependencies import resolveDependencies from src.Logging import logFailure, setupLogger diff --git a/exporter/SynthesisFusionAddin/src/Dependencies.py b/exporter/SynthesisFusionAddin/src/Dependencies.py index a61119c1d6..c6c1b26b4c 100644 --- a/exporter/SynthesisFusionAddin/src/Dependencies.py +++ b/exporter/SynthesisFusionAddin/src/Dependencies.py @@ -1,7 +1,6 @@ import importlib.machinery import importlib.util import os -import platform import subprocess import sys from pathlib import Path @@ -9,10 +8,10 @@ import adsk.core import adsk.fusion +from src import SYSTEM from src.Logging import getLogger, logFailure logger = getLogger() -system = platform.system() # Since the Fusion python runtime is separate from the system python runtime we need to do some funky things # in order to download and install python packages separate from the standard library. @@ -29,13 +28,11 @@ def getInternalFusionPythonInstillationFolder() -> str: pythonStandardLibraryModulePath = importlib.machinery.PathFinder.find_spec("os", sys.path).origin # Depending on platform, adjust to folder to where the python executable binaries are stored. - if system == "Windows": + if SYSTEM == "Windows": folder = f"{Path(pythonStandardLibraryModulePath).parents[1]}" - elif system == "Darwin": - folder = f"{Path(pythonStandardLibraryModulePath).parents[2]}/bin" else: - # TODO: System string should be moved to __init__ after GH-1013 - raise RuntimeError("Unsupported platform.") + assert SYSTEM == "Darwin" + folder = f"{Path(pythonStandardLibraryModulePath).parents[2]}/bin" return folder @@ -100,7 +97,7 @@ def resolveDependencies() -> bool | None: adsk.doEvents() pythonFolder = getInternalFusionPythonInstillationFolder() - pythonExecutableFile = "python.exe" if system == "Windows" else "python" # Confirming 110% everything is fine. + pythonExecutableFile = "python.exe" if SYSTEM == "Windows" else "python" # Confirming 110% everything is fine. pythonExecutablePath = os.path.join(pythonFolder, pythonExecutableFile) progressBar = ui.createProgressDialog() @@ -109,7 +106,7 @@ def resolveDependencies() -> bool | None: progressBar.show("Synthesis", f"Installing dependencies...", 0, len(PIP_DEPENDENCY_VERSION_MAP) * 2 + 2, 0) # Install pip manually on macos as it is not included by default? Really? - if system == "Darwin" and not os.path.exists(os.path.join(pythonFolder, "pip")): + if SYSTEM == "Darwin" and not os.path.exists(os.path.join(pythonFolder, "pip")): pipInstallScriptPath = os.path.join(pythonFolder, "get-pip.py") if not os.path.exists(pipInstallScriptPath): executeCommand("curl", "https://bootstrap.pypa.io/get-pip.py", "-o", pipInstallScriptPath) diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py index 0ac0078d1c..fbdba1d891 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py @@ -1,9 +1,4 @@ # Contains all of the logic for mapping the Components / Occurrences -import logging -import traceback -import uuid -from typing import * - import adsk.core import adsk.fusion @@ -12,7 +7,11 @@ from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser import PhysicalProperties from src.Parser.SynthesisParser.PDMessage import PDMessage -from src.Parser.SynthesisParser.Utilities import * +from src.Parser.SynthesisParser.Utilities import ( + fill_info, + guid_component, + guid_occurrence, +) from src.Types import ExportMode # TODO: Impelement Material overrides diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py index 5c106d0188..2bd563b489 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py @@ -1,7 +1,5 @@ import enum -import logging -import traceback -from typing import * +from typing import Union import adsk.core import adsk.fusion diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py index c5458cbae3..0d0cdea910 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py @@ -29,7 +29,7 @@ import adsk.core import adsk.fusion -from proto.proto_out import assembly_pb2, joint_pb2, motor_pb2, signal_pb2, types_pb2 +from proto.proto_out import assembly_pb2, joint_pb2, signal_pb2, types_pb2 from src.Logging import getLogger from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser.PDMessage import PDMessage diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py index ca7fb5480c..d8538b5d9b 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py @@ -4,7 +4,7 @@ from src.Logging import logFailure from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser.PDMessage import PDMessage -from src.Parser.SynthesisParser.Utilities import * +from src.Parser.SynthesisParser.Utilities import construct_info, fill_info OPACITY_RAMPING_CONSTANT = 14.0 diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py index 95b124bbe3..37148fb622 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py @@ -17,7 +17,7 @@ Materials, PDMessage, ) -from src.Parser.SynthesisParser.Utilities import * +from src.Parser.SynthesisParser.Utilities import fill_info from src.Types import ExportLocation, ExportMode from src.UI.Camera import captureThumbnail, clearIconCache diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py index d6cbc47c77..b90a2167fe 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py @@ -12,7 +12,7 @@ - Success """ -from typing import * +from typing import Union import adsk.core import adsk.fusion diff --git a/exporter/SynthesisFusionAddin/src/UI/Camera.py b/exporter/SynthesisFusionAddin/src/UI/Camera.py index 85fa188ff8..02de04083a 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Camera.py +++ b/exporter/SynthesisFusionAddin/src/UI/Camera.py @@ -4,7 +4,6 @@ from src.Logging import logFailure from src.Types import OString -from src.UI import Helper @logFailure diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index 3a1f2e6c76..c50c7feed2 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -10,13 +10,12 @@ import adsk.fusion from src import gm -from src.APS.APS import getAuth, getUserInfo, refreshAuthToken +from src.APS.APS import getAuth, getUserInfo from src.Logging import getLogger, logFailure from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser.Parser import Parser -from src.Parser.SynthesisParser.Utilities import guid_occurrence from src.Types import ExportLocation, ExportMode -from src.UI import CustomGraphics, FileDialogConfig, Helper, IconPaths +from src.UI import FileDialogConfig from src.UI.Configuration.SerialCommand import SerialCommand from src.UI.GamepieceConfigTab import GamepieceConfigTab from src.UI.GeneralConfigTab import GeneralConfigTab diff --git a/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py b/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py index d92c26f823..3b6bd8e2c2 100644 --- a/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py +++ b/exporter/SynthesisFusionAddin/src/UI/CustomGraphics.py @@ -1,6 +1,3 @@ -import logging -import traceback - import adsk.core import adsk.fusion diff --git a/exporter/SynthesisFusionAddin/src/UI/Events.py b/exporter/SynthesisFusionAddin/src/UI/Events.py index 0c4cf37798..64d0f1ea29 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Events.py +++ b/exporter/SynthesisFusionAddin/src/UI/Events.py @@ -1,5 +1,5 @@ import json -from typing import Sequence, Tuple +from typing import Sequence import adsk.core diff --git a/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py b/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py index 8b8b3a2f4c..d2465ae29b 100644 --- a/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py +++ b/exporter/SynthesisFusionAddin/src/UI/FileDialogConfig.py @@ -1,5 +1,3 @@ -from typing import Union - import adsk.core import adsk.fusion diff --git a/exporter/SynthesisFusionAddin/src/UI/Helper.py b/exporter/SynthesisFusionAddin/src/UI/Helper.py index b6942afb4d..ba8bf9b0e9 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Helper.py +++ b/exporter/SynthesisFusionAddin/src/UI/Helper.py @@ -1,5 +1,4 @@ from inspect import getmembers, isfunction -from typing import Union import adsk.core diff --git a/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py b/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py index 000080eb79..b06ae6f2af 100644 --- a/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py +++ b/exporter/SynthesisFusionAddin/src/UI/JointConfigTab.py @@ -1,6 +1,3 @@ -import logging -import traceback - import adsk.core import adsk.fusion diff --git a/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py b/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py index 13e3cec65d..999abd1176 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ShowAPSAuthCommand.py @@ -1,10 +1,8 @@ import json -import os import time import traceback import urllib.parse import urllib.request -import webbrowser import adsk.core diff --git a/exporter/SynthesisFusionAddin/src/__init__.py b/exporter/SynthesisFusionAddin/src/__init__.py index c7d951b784..1e426279bb 100644 --- a/exporter/SynthesisFusionAddin/src/__init__.py +++ b/exporter/SynthesisFusionAddin/src/__init__.py @@ -1,5 +1,5 @@ import os -import sys +import platform from src.GlobalManager import GlobalManager @@ -9,14 +9,9 @@ INTERNAL_ID = "Synthesis" ADDIN_PATH = os.path.dirname(os.path.realpath(__file__)) -A_EP = None # TODO: Will be removed by GH-1010 -DEBUG = True # TODO: Will be removed by GH-1010 +SYSTEM = platform.system() +assert SYSTEM != "Linux" gm = GlobalManager() -__all__ = ["APP_NAME", "APP_TITLE", "DESCRIPTION", "INTERNAL_ID", "ADDIN_PATH", "gm"] - -# Transition: AARD-1737 -# This method of running the resolve dependencies module will be revisited in AARD-1734 -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "proto", "proto_out"))) -# from proto import deps +__all__ = ["APP_NAME", "APP_TITLE", "DESCRIPTION", "INTERNAL_ID", "ADDIN_PATH", "SYSTEM", "gm"] From dd3597b22c39e6d6807b29ece9a6543b4f426a4d Mon Sep 17 00:00:00 2001 From: LucaHaverty Date: Tue, 6 Aug 2024 11:06:08 -0700 Subject: [PATCH 157/344] Completely functional panel --- fission/src/Synthesis.tsx | 2 - fission/src/ui/components/MainHUD.tsx | 5 - fission/src/ui/components/SelectButton.tsx | 2 +- fission/src/ui/components/SelectMenu.tsx | 61 +- .../src/ui/components/StyledComponents.tsx | 4 +- .../configuring/inputs/ChangeInputsModal.tsx | 932 +++++++++--------- .../configuring/ChooseInputSchemePanel.tsx | 2 +- .../assembly-config/ConfigurePanel.tsx | 50 +- .../ConfigureGamepiecePickupInterface.tsx | 10 +- .../SequentialBehaviorsInterface.tsx | 24 +- .../inputs/ConfigureInputsInterface.tsx | 58 +- .../inputs/ConfigureSchemeInterface.tsx | 72 ++ 12 files changed, 655 insertions(+), 567 deletions(-) create mode 100644 fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index e9b1a92efd..42945e5e6a 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -20,7 +20,6 @@ import UpdateAvailableModal from "@/modals/UpdateAvailableModal" import ViewModal from "@/modals/ViewModal" import ConnectToMultiplayerModal from "@/modals/aether/ConnectToMultiplayerModal" import ServerHostingModal from "@/modals/aether/ServerHostingModal" -import ChangeInputsModal from "@/ui/modals/configuring/inputs/ChangeInputsModal.tsx" import ChooseMultiplayerModeModal from "@/modals/configuring/ChooseMultiplayerModeModal" import ChooseSingleplayerModeModal from "@/modals/configuring/ChooseSingleplayerModeModal" import ConfigMotorModal from "@/modals/configuring/ConfigMotorModal" @@ -206,7 +205,6 @@ const initialModals = [ , , , - , , , , diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 2bf38ffa64..17c036db3f 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -100,11 +100,6 @@ const MainHUD: React.FC = () => { icon={SynthesisIcons.MagnifyingGlass} onClick={() => openModal("view")} /> */} - openModal("change-inputs")} - /> = ({ colorClass, size, value, pl return ( - + - ) -} - -const AssemblyCard: React.FC = ({ mira, update }) => { - const { openPanel } = usePanelControlContext() - const { closeModal } = useModalControlContext() - - const brain = useMemo(() => (mira.brain as SynthesisBrain)?.brainIndex, [mira]) - - return ( -
- -
-
-
- ) -} - -const ManageAssembliesModal: React.FC = ({ modalId }) => { - const [_, update] = useReducer(x => !x, false) - - const assemblies = [...World.SceneRenderer.sceneObjects.entries()] - .filter(x => { - const y = x[1] instanceof MirabufSceneObject - return y - }) - .map(x => x[1] as MirabufSceneObject) - - return ( - -
- - {assemblies.map(x => ( - - ))} -
-
- ) -} - -export default ManageAssembliesModal diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index 0d6e0f8214..e9b1dbc47c 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -6,7 +6,6 @@ import Panel, { PanelPropsImpl } from "@/ui/components/Panel" import SelectMenu, { SelectMenuOption } from "@/ui/components/SelectMenu" import { ToggleButton, ToggleButtonGroup } from "@/ui/components/ToggleButtonGroup" import { useMemo, useReducer, useState } from "react" -import { AiOutlinePlus } from "react-icons/ai" import ConfigureGamepiecePickupInterface from "./interfaces/ConfigureGamepiecePickupInterface" import ConfigureShotTrajectoryInterface from "./interfaces/ConfigureShotTrajectoryInterface" import ConfigureScoringZonesInterface from "./interfaces/scoring/ConfigureScoringZonesInterface" @@ -18,9 +17,9 @@ import { usePanelControlContext } from "@/ui/PanelContext" import Button from "@/ui/components/Button" import { setSelectedBrainIndexGlobal } from "../ChooseInputSchemePanel" import ConfigureSchemeInterface from "./interfaces/inputs/ConfigureSchemeInterface" +import { SynthesisIcons } from "@/ui/components/StyledComponents" -const AddIcon = - +/** Option for selecting a robot of field */ class AssemblySelectionOption extends SelectMenuOption { assemblyObject: MirabufSceneObject @@ -36,7 +35,8 @@ interface ConfigurationSelectionProps { } const AssemblySelection: React.FC = ({ configurationType, onAssemblySelected }) => { - const [x, update] = useReducer(x => !x, false) + // Update is used when a robot or field is deleted to update the select menu + const [u, update] = useReducer(x => !x, false) const { openPanel } = usePanelControlContext() const robots = useMemo(() => { @@ -48,7 +48,8 @@ const AssemblySelection: React.FC = ({ configuratio }) as MirabufSceneObject[] return assemblies - }, [x]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [u]) const fields = useMemo(() => { const assemblies = [...World.SceneRenderer.sceneObjects.values()].filter(x => { @@ -59,11 +60,12 @@ const AssemblySelection: React.FC = ({ configuratio }) as MirabufSceneObject[] return assemblies - }, [x]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [u]) + /** Robot or field select menu */ return ( { return new AssemblySelectionOption( `${configurationType == ConfigurationType.ROBOT ? `[${InputSystem.brainIndexSchemeMap.get((assembly.brain as SynthesisBrain).brainIndex)?.schemeName ?? "-"}]` : ""} ${assembly.assemblyName}`, @@ -83,6 +85,7 @@ const AssemblySelection: React.FC = ({ configuratio console.log("Open spawn panel") openPanel("import-mirabuf") }} + noOptionsText={`No ${configurationType == ConfigurationType.ROBOT ? "robots" : "fields"} spawned!`} /> ) } @@ -107,7 +110,7 @@ class ConfigModeSelectionOption extends SelectMenuOption { const robotModes = [ new ConfigModeSelectionOption("Intake", ConfigMode.INTAKE), new ConfigModeSelectionOption("Ejector", ConfigMode.EJECTOR), - new ConfigModeSelectionOption("Motors", ConfigMode.MOTORS), + new ConfigModeSelectionOption("Joints", ConfigMode.MOTORS), new ConfigModeSelectionOption("Controls", ConfigMode.CONTROLS), ] const fieldModes = [new ConfigModeSelectionOption("Scoring Zones", ConfigMode.SCORING_ZONES)] @@ -136,6 +139,7 @@ interface ConfigInterfaceProps { openPanel: (panelId: string) => void } +/** The interface for the actual configuration */ const ConfigInterface: React.FC = ({ configMode, assembly, openPanel }) => { switch (configMode) { case ConfigMode.INTAKE: @@ -144,7 +148,7 @@ const ConfigInterface: React.FC = ({ configMode, assembly, return case ConfigMode.MOTORS: return - case ConfigMode.CONTROLS: + case ConfigMode.CONTROLS: { const brainIndex = (assembly.brain as SynthesisBrain).brainIndex const scheme = InputSystem.brainIndexSchemeMap.get(brainIndex) @@ -160,6 +164,7 @@ const ConfigInterface: React.FC = ({ configMode, assembly, {scheme && } ) + } case ConfigMode.SCORING_ZONES: { const zones = assembly.fieldPreferences?.scoringZones if (zones == undefined) { @@ -173,6 +178,7 @@ const ConfigInterface: React.FC = ({ configMode, assembly, } } +/** An event to save whatever configuration interface is open when it is closed */ export class ConfigurationSavedEvent extends Event { public constructor() { super("ConfigurationSaved") @@ -204,7 +210,7 @@ const ConfigurePanel: React.FC = ({ panelId }) => { return ( = ({ panelId }) => { }} acceptName="Close" > -
+
+ {/** Toggle button group for the robot, field, and input buttons */} = ({ panelId }) => { ) : ( <> + {/** Select menu to pick a robot or field */} { @@ -245,6 +253,7 @@ const ConfigurePanel: React.FC = ({ panelId }) => { setSelectedAssembly(a) }} /> + {/** Nested select menu to pick a configuration mode */} {selectedAssembly != undefined && ( = ({ panelId }) => { }} /> )} + {/** The interface for the selected configuration mode */} {selectedConfigMode != undefined && selectedAssembly != undefined && ( = ({ select {Spacer(10)}
) } @@ -298,6 +365,7 @@ const EditInputInterface: React.FC = ({ input, useGamepad, onInp checkGamepadState() }) + /** Input detection for setting inputs */ useEffect(() => { // // Assign keyboard inputs when a key is pressed if (!useGamepad && selectedInput && chosenKey) { @@ -357,7 +425,7 @@ const EditInputInterface: React.FC = ({ input, useGamepad, onInp }, [chosenKey, chosenButton, chosenGamepadAxis, input, modifierState, onInputChanged, selectedInput, useGamepad]) return ( -
{ if (selectedInput != "") setChosenKey(selectedInput ? e.code : "") setModifierState({ @@ -369,7 +437,7 @@ const EditInputInterface: React.FC = ({ input, useGamepad, onInp }} > {inputConfig()} -
+ ) } diff --git a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx index d2be05336f..0c5906fa78 100644 --- a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx +++ b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx @@ -335,7 +335,7 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { return ( Date: Tue, 6 Aug 2024 16:45:50 -0700 Subject: [PATCH 167/344] Fixed scrollbar --- fission/src/index.css | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/fission/src/index.css b/fission/src/index.css index afa1fa6dc6..f3c04e2d14 100644 --- a/fission/src/index.css +++ b/fission/src/index.css @@ -2,6 +2,24 @@ @tailwind components; @tailwind utilities; +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: "transparent"; +} + +::-webkit-scrollbar-thumb { + background: #444444; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #555; +} + :root { /* font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; */ font-family: "Artifakt"; From 737e92d4282a0747dbcd408f29873132487c7fb3 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Tue, 6 Aug 2024 16:52:24 -0700 Subject: [PATCH 168/344] change java sample test --- .../simulation/wpilib_brain/SimOutput.ts | 4 +- .../simulation/wpilib_brain/WPILibBrain.ts | 16 +- .../rio-config/RCConfigCANGroupModal.tsx | 2 +- .../src/main/java/frc/robot/Robot.java | 189 +++++++++--------- 4 files changed, 106 insertions(+), 105 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimOutput.ts b/fission/src/systems/simulation/wpilib_brain/SimOutput.ts index 640d157aef..94e9498da5 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimOutput.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimOutput.ts @@ -44,7 +44,7 @@ export class PWMOutputGroup extends SimOutputGroup { } } -export class CANGroup extends SimOutputGroup { +export class CANOutputGroup extends SimOutputGroup { public constructor(name: string, ports: number[], drivers: Driver[]) { super(name, ports, drivers, SimType.CANMotor) } @@ -54,7 +54,7 @@ export class CANGroup extends SimOutputGroup { const average = this.ports.reduce((sum, port) => { const device = SimCAN.GetDeviceWithID(port, SimType.CANMotor) - return sum + device[" { diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index aff9c21757..c2a9e5a3a3 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -134,20 +134,14 @@ export class SimCAN { private constructor() {} public static GetDeviceWithID(id: number, type: SimType): any { - //const id_exp = /.*\[(\d+)\]/g + const id_exp = /.*\[(\d+)\]/g const entries = [...simMap.entries()].filter(([simType, _data]) => simType == type) for (const [_simType, data] of entries) { for (const key of data.keys()) { - // const result = [...key.matchAll(id_exp)] - // if (result?.length <= 0 || result[0].length <= 1) continue - // const parsed_id = parseInt(result[0][1]) - // if (parsed_id != id) continue - const res = key.split("[")[1].split("]")[0] - // console.log("id: " + res) - // console.log(id) - if (!res) continue - const parsed_id = parseInt(res) - if (id != parsed_id) continue + const result = [...key.matchAll(id_exp)] + if (result?.length <= 0 || result[0].length <= 1) continue + const parsed_id = parseInt(result[0][1]) + if (parsed_id != id) continue return data.get(key) } } diff --git a/fission/src/ui/modals/configuring/rio-config/RCConfigCANGroupModal.tsx b/fission/src/ui/modals/configuring/rio-config/RCConfigCANGroupModal.tsx index 7b964f47de..83ee24046c 100644 --- a/fission/src/ui/modals/configuring/rio-config/RCConfigCANGroupModal.tsx +++ b/fission/src/ui/modals/configuring/rio-config/RCConfigCANGroupModal.tsx @@ -73,7 +73,7 @@ const RCConfigCANGroupModal: React.FC = ({ modalId }) => { defaultState={false} onClick={checked => { const port = parseInt(p.split("[")[1].split("]")[0]) - console.log(port) + console.log(port) if (checked && !checkedPorts.includes(port)) { setCheckedPorts([...checkedPorts, port]) } else if (!checked && checkedPorts.includes(port)) { diff --git a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java index 871ed9bd45..92936dbd24 100644 --- a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java +++ b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java @@ -6,19 +6,13 @@ import com.revrobotics.CANSparkLowLevel.MotorType; -import edu.wpi.first.wpilibj.ADXRS450_Gyro; -import edu.wpi.first.wpilibj.AnalogGyro; import edu.wpi.first.wpilibj.TimedRobot; -import edu.wpi.first.wpilibj.XboxController; import edu.wpi.first.wpilibj.motorcontrol.Spark; -import edu.wpi.first.wpilibj.simulation.ADXRS450_GyroSim; import edu.wpi.first.wpilibj.smartdashboard.SendableChooser; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; import com.autodesk.synthesis.revrobotics.CANSparkMax; import com.autodesk.synthesis.ctre.TalonFX; -import com.autodesk.synthesis.Joystick; ->>>>>>> 03bed7bc3664e58229558058ee7ab889e8ad565b /** * The VM is configured to automatically run this class, and to call the @@ -42,93 +36,106 @@ public class Robot extends TimedRobot { private CANSparkMax m_SparkMax5 = new CANSparkMax(5, MotorType.kBrushless); private CANSparkMax m_SparkMax6 = new CANSparkMax(6, MotorType.kBrushless); //private com.autodesk.synthesis.ctre.TalonFX m_Talon = new com.autodesk.synthesis.ctre.TalonFX(2); - private Spark m_Spark1 = new Spark(0); - private Spark m_Spark2 = new Spark(1); - private TalonFX m_Talon = new TalonFX(2); - private XboxController m_Controller = new XboxController(0); - private ADXRS450_Gyro m_Gyro; - private ADXRS450_GyroSim m_GyroSim; - - /** - * This function is run when the robot is first started up and should be used for any - * initialization code. - */ - @Override - public void robotInit() { - m_chooser.setDefaultOption("Default Auto", kDefaultAuto); - m_chooser.addOption("My Auto", kCustomAuto); - SmartDashboard.putData("Auto choices", m_chooser); - m_Gyro = new ADXRS450_Gyro(); - m_GyroSim = new ADXRS450_GyroSim(m_Gyro); - m_Gyro.calibrate(); - } - - /** - * This function is called every 20 ms, no matter the mode. Use this for items like diagnostics - * that you want ran during disabled, autonomous, teleoperated and test. - * - *

This runs after the mode specific periodic functions, but before LiveWindow and - * SmartDashboard integrated updating. - */ - @Override - public void robotPeriodic() {} - - /** - * This autonomous (along with the chooser code above) shows how to select between different - * autonomous modes using the dashboard. The sendable chooser code works with the Java - * SmartDashboard. If you prefer the LabVIEW Dashboard, remove all of the chooser code and - * uncomment the getString line to get the auto name from the text box below the Gyro - * - *

You can add additional auto modes by adding additional comparisons to the switch structure - * below with additional strings. If using the SendableChooser make sure to add them to the - * chooser code above as well. - */ - @Override - public void autonomousInit() { - m_autoSelected = m_chooser.getSelected(); - // m_autoSelected = SmartDashboard.getString("Auto Selector", kDefaultAuto); - System.out.println("Auto selected: " + m_autoSelected); - } - - /** This function is called periodically during autonomous. */ - @Override - public void autonomousPeriodic() { - - m_Spark1.set(0.5); - m_Spark2.set(-0.5); - m_SparkMax.set(1.0); - m_Talon.set(-1.0); - - switch (m_autoSelected) { - case kCustomAuto: - // Put custom auto code here - break; - case kDefaultAuto: - default: - // Put default auto code here - break; + + /** + * This function is run when the robot is first started up and should be used + * for any + * initialization code. + */ + @Override + public void robotInit() { + m_chooser.setDefaultOption("Default Auto", kDefaultAuto); + m_chooser.addOption("My Auto", kCustomAuto); + SmartDashboard.putData("Auto choices", m_chooser); + } + + /** + * This function is called every 20 ms, no matter the mode. Use this for items + * like diagnostics + * that you want ran during disabled, autonomous, teleoperated and test. + * + *

+ * This runs after the mode specific periodic functions, but before LiveWindow + * and + * SmartDashboard integrated updating. + */ + @Override + public void robotPeriodic() { + } + + /** + * This autonomous (along with the chooser code above) shows how to select + * between different + * autonomous modes using the dashboard. The sendable chooser code works with + * the Java + * SmartDashboard. If you prefer the LabVIEW Dashboard, remove all of the + * chooser code and + * uncomment the getString line to get the auto name from the text box below the + * Gyro + * + *

+ * You can add additional auto modes by adding additional comparisons to the + * switch structure + * below with additional strings. If using the SendableChooser make sure to add + * them to the + * chooser code above as well. + */ + @Override + public void autonomousInit() { + m_autoSelected = m_chooser.getSelected(); + //m_autoSelected = SmartDashboard.getString("Auto Selector", kDefaultAuto); + System.out.println("Auto selected: " + m_autoSelected); + } + + /** This function is called periodically during autonomous. */ + @Override + public void autonomousPeriodic() { + + m_SparkMax1.set(1.0); + m_SparkMax2.set(1.0); + m_SparkMax3.set(1.0); + m_SparkMax4.set(1.0); + m_SparkMax5.set(1.0); + m_SparkMax6.set(1.0); + + switch (m_autoSelected) { + case kCustomAuto: + // Put custom auto code here + break; + case kDefaultAuto: + default: + // Put default auto code here + break; + } + } + + /** This function is called once when teleop is enabled. */ + @Override + public void teleopInit() { + } + + /** This function is called periodically during operator control. */ + @Override + public void teleopPeriodic() { + m_SparkMax1.set(-0.75); + m_SparkMax2.set(-0.75); + m_SparkMax3.set(-0.75); + m_SparkMax4.set(-0.75); + m_SparkMax5.set(-0.75); + m_SparkMax6.set(-0.75); + } + + /** This function is called once when the robot is disabled. */ + @Override + public void disabledInit() { + m_SparkMax1.set(0.0); + m_SparkMax2.set(0.0); + m_SparkMax3.set(0.0); + m_SparkMax4.set(0.0); + m_SparkMax5.set(0.0); + m_SparkMax6.set(0.0); } -/** This function is called periodically during operator control. */ - @Override - public void teleopPeriodic() { - m_Spark1.set(m_Controller.getLeftY()); - m_Spark2.set(-m_Controller.getRightY()); - System.out.println(m_Gyro.getAngle()); - m_SparkMax.set(0.75); - m_Talon.set(-0.5); - } - - /** This function is called once when the robot is disabled. */ - @Override - public void disabledInit() { - m_Spark1.set(0.0); - m_Spark2.set(0.0); - m_SparkMax.set(0.0); - m_Talon.set(0.0); - } - - /** This function is called periodically when disabled. */ @Override public void disabledPeriodic() { From 510bf5f420e619ddc67174995073d92c0a654826 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Wed, 7 Aug 2024 10:05:08 -0700 Subject: [PATCH 169/344] verify canencoder sim --- fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts | 3 +++ .../ui/modals/configuring/rio-config/RCConfigEncoderModal.tsx | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index c2a9e5a3a3..8557aea794 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -104,6 +104,9 @@ export class SimGeneric { selectedData[field] = value data[field] = value + + console.log("field " + field + " device " + device + " value " + value) + worker.postMessage({ command: "update", data: { diff --git a/fission/src/ui/modals/configuring/rio-config/RCConfigEncoderModal.tsx b/fission/src/ui/modals/configuring/rio-config/RCConfigEncoderModal.tsx index f943ccfeea..b3743b9c6a 100644 --- a/fission/src/ui/modals/configuring/rio-config/RCConfigEncoderModal.tsx +++ b/fission/src/ui/modals/configuring/rio-config/RCConfigEncoderModal.tsx @@ -5,7 +5,7 @@ import Label, { LabelSize } from "@/components/Label" import Input from "@/components/Input" import Dropdown from "@/components/Dropdown" import NumberInput from "@/components/NumberInput" -import WPILibBrain, { simMap } from "@/systems/simulation/wpilib_brain/WPILibBrain" +import WPILibBrain, { simMap, SimType } from "@/systems/simulation/wpilib_brain/WPILibBrain" import World from "@/systems/World" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import EncoderStimulus from "@/systems/simulation/stimulus/EncoderStimulus" @@ -30,7 +30,7 @@ const RCConfigEncoderModal: React.FC = ({ modalId }) => { let devices: [string, unknown][] = [] - const encoders = simMap.get("CANEncoder") + const encoders = simMap.get(SimType.CANEncoder) if (encoders) { devices = [...encoders.entries()] } From 14868c73c7c425c5e157dc29163075cd6506f4a9 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 7 Aug 2024 10:39:25 -0700 Subject: [PATCH 170/344] Format --- .../configuring/ConfigureSubsystemsPanel.tsx | 71 ++++++++++--------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx index 348d49aac2..b454da7bf9 100644 --- a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx @@ -43,46 +43,49 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { ((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 - }) + 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 { - ((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel - ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force + // Preferences + PreferencesSystem.getRobotPreferences(robot.assemblyName).driveVelocity = vel + PreferencesSystem.getRobotPreferences(robot.assemblyName).driveAcceleration = force + } else { + ((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel + ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force - // 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 - }) - : [] + // 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, - }) + removedMotor.push({ + name: driver.info?.name ?? "", + maxVelocity: vel, + maxForce: force, + }) - PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor + PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor + } } - } - PreferencesSystem.savePreferences() - }, [driver, robot.mechanism, robot.assemblyName]) + PreferencesSystem.savePreferences() + }, + [driver, robot.mechanism, robot.assemblyName] + ) return ( From c12f5e29769abd60d5a0120f816ad6ecf34eb8bc Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 7 Aug 2024 11:09:05 -0700 Subject: [PATCH 171/344] Playwright update --- fission/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/package.json b/fission/package.json index 6e1ae08b2b..0643c80a52 100644 --- a/fission/package.json +++ b/fission/package.json @@ -32,7 +32,7 @@ "colord": "^2.9.3", "framer-motion": "^10.13.1", "lygia": "^1.1.3", - "playwright": "^1.45.0", + "playwright": "^1.46.0", "postprocessing": "^6.35.6", "react": "^18.2.0", "react-colorful": "^5.6.1", From 83ed35c0e4cae031f86ece8be0615fd375921709 Mon Sep 17 00:00:00 2001 From: LucaHaverty Date: Wed, 7 Aug 2024 11:24:00 -0700 Subject: [PATCH 172/344] Delete, create, and modify input schemes in a way that is easy and integrated with the config panel --- fission/index.html | 32 +++++---- fission/src/systems/input/InputSystem.ts | 3 - fission/src/ui/components/SelectMenu.tsx | 16 ++++- .../theme-editor/AssignNewSchemeModal.tsx | 12 ++-- .../theme-editor/NewInputSchemeModal.tsx | 12 ++-- .../configuring/ChooseInputSchemePanel.tsx | 15 ++-- .../assembly-config/ConfigurePanel.tsx | 70 ++++++++++++------- .../inputs/ConfigureInputsInterface.tsx | 52 +++++++++++++- .../inputs/ConfigureSchemeInterface.tsx | 2 +- 9 files changed, 152 insertions(+), 62 deletions(-) diff --git a/fission/index.html b/fission/index.html index bf462bd999..5916fa6750 100644 --- a/fission/index.html +++ b/fission/index.html @@ -1,16 +1,22 @@ - - - - - - - - Fission | Synthesis - - -

- - + + + + + + + + Fission | Synthesis + + +
+ + diff --git a/fission/src/systems/input/InputSystem.ts b/fission/src/systems/input/InputSystem.ts index 3a396f8968..d56aadd692 100644 --- a/fission/src/systems/input/InputSystem.ts +++ b/fission/src/systems/input/InputSystem.ts @@ -141,9 +141,6 @@ class InputSystem extends WorldSystem { private static _gpIndex: number | null public static gamepad: Gamepad | null - // The scheme most recently selected in the controls modal - public static selectedScheme: InputScheme | undefined - // Maps a brain index to a certain input scheme public static brainIndexSchemeMap: Map = new Map() diff --git a/fission/src/ui/components/SelectMenu.tsx b/fission/src/ui/components/SelectMenu.tsx index 852bb9852f..c4c93b5fb4 100644 --- a/fission/src/ui/components/SelectMenu.tsx +++ b/fission/src/ui/components/SelectMenu.tsx @@ -54,9 +54,10 @@ interface OptionCardProps { index: number onSelected: (val: SelectMenuOption) => void onDelete?: () => void + includeDelete: boolean } -const OptionCard: React.FC = ({ value, index, onSelected, onDelete }) => { +const OptionCard: React.FC = ({ value, index, onSelected, onDelete, includeDelete }) => { return ( = ({ value, index, onSelected, onDel sx={{ borderColor: "#888888" }} /> {/** Delete button only if onDelete is defined */} - {onDelete && ( + {onDelete && includeDelete && ( <> {Spacer(0, 10)} {DeleteButton(onDelete != undefined ? onDelete : () => {})} @@ -102,23 +103,31 @@ const OptionCard: React.FC = ({ value, index, onSelected, onDel interface SelectMenuProps { options: SelectMenuOption[] onOptionSelected: (val: SelectMenuOption | undefined) => void + + // Function to return a default value + defaultSelectedOption?: (options: SelectMenuOption[]) => SelectMenuOption | undefined defaultHeaderText: string noOptionsText?: string indentation?: number onDelete?: (val: SelectMenuOption) => void | undefined + + // If false, this menu option will not have a delete button + deleteCondition?: (val: SelectMenuOption) => boolean onAddClicked?: () => void } const SelectMenu: React.FC = ({ options, onOptionSelected, + defaultSelectedOption, defaultHeaderText, noOptionsText, indentation, onDelete, + deleteCondition, onAddClicked, }) => { - const [selectedOption, setSelectedOption] = useState(undefined) + const [selectedOption, setSelectedOption] = useState(defaultSelectedOption?.(options)) // If the selected option no longer exists as an option, deselect it useEffect(() => { @@ -170,6 +179,7 @@ const SelectMenu: React.FC = ({ }} key={option.name + i} onDelete={onDelete ? () => onDelete(option) : undefined} + includeDelete={deleteCondition == undefined || deleteCondition(option)} /> ) }) diff --git a/fission/src/ui/modals/configuring/theme-editor/AssignNewSchemeModal.tsx b/fission/src/ui/modals/configuring/theme-editor/AssignNewSchemeModal.tsx index e55c885ff8..1f5c98bd2f 100644 --- a/fission/src/ui/modals/configuring/theme-editor/AssignNewSchemeModal.tsx +++ b/fission/src/ui/modals/configuring/theme-editor/AssignNewSchemeModal.tsx @@ -1,14 +1,16 @@ import React, { useState } from "react" import Input from "@/components/Input" import Modal, { ModalPropsImpl } from "@/components/Modal" -import { useModalControlContext } from "@/ui/ModalContext" import InputSchemeManager from "@/systems/input/InputSchemeManager" import InputSystem from "@/systems/input/InputSystem" import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain" import { SynthesisIcons } from "@/ui/components/StyledComponents" +import { usePanelControlContext } from "@/ui/PanelContext" +import { ConfigurationType, setSelectedConfigurationType } from "@/ui/panels/configuring/assembly-config/ConfigurePanel" +import { setSelectedScheme } from "@/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureInputsInterface" const AssignNewSchemeModal: React.FC = ({ modalId }) => { - const { openModal } = useModalControlContext() + const { openPanel } = usePanelControlContext() const [name, setName] = useState(InputSchemeManager.randomAvailableName) @@ -24,12 +26,12 @@ const AssignNewSchemeModal: React.FC = ({ modalId }) => { scheme.schemeName = name - InputSystem.selectedScheme = scheme InputSchemeManager.addCustomScheme(scheme) - InputSchemeManager.saveSchemes() - openModal("change-inputs") + setSelectedConfigurationType(ConfigurationType.INPUTS) + setSelectedScheme(scheme) + openPanel("configure") }} cancelEnabled={false} > diff --git a/fission/src/ui/modals/configuring/theme-editor/NewInputSchemeModal.tsx b/fission/src/ui/modals/configuring/theme-editor/NewInputSchemeModal.tsx index 403ad9588c..81c59769aa 100644 --- a/fission/src/ui/modals/configuring/theme-editor/NewInputSchemeModal.tsx +++ b/fission/src/ui/modals/configuring/theme-editor/NewInputSchemeModal.tsx @@ -1,14 +1,15 @@ import React, { useState } from "react" import Input from "@/components/Input" import Modal, { ModalPropsImpl } from "@/components/Modal" -import { useModalControlContext } from "@/ui/ModalContext" import InputSchemeManager from "@/systems/input/InputSchemeManager" -import InputSystem from "@/systems/input/InputSystem" import DefaultInputs from "@/systems/input/DefaultInputs" import { SynthesisIcons } from "@/ui/components/StyledComponents" +import { usePanelControlContext } from "@/ui/PanelContext" +import { ConfigurationType, setSelectedConfigurationType } from "@/ui/panels/configuring/assembly-config/ConfigurePanel" +import { setSelectedScheme } from "@/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureInputsInterface" const NewInputSchemeModal: React.FC = ({ modalId }) => { - const { openModal } = useModalControlContext() + const { openPanel } = usePanelControlContext() const [name, setName] = useState(InputSchemeManager.randomAvailableName) @@ -24,8 +25,9 @@ const NewInputSchemeModal: React.FC = ({ modalId }) => { InputSchemeManager.addCustomScheme(scheme) InputSchemeManager.saveSchemes() - InputSystem.selectedScheme = scheme - openModal("change-inputs") + setSelectedConfigurationType(ConfigurationType.INPUTS) + setSelectedScheme(scheme) + openPanel("configure") }} cancelEnabled={false} > diff --git a/fission/src/ui/panels/configuring/ChooseInputSchemePanel.tsx b/fission/src/ui/panels/configuring/ChooseInputSchemePanel.tsx index e1755c2500..2b61340430 100644 --- a/fission/src/ui/panels/configuring/ChooseInputSchemePanel.tsx +++ b/fission/src/ui/panels/configuring/ChooseInputSchemePanel.tsx @@ -18,6 +18,8 @@ import { useModalControlContext } from "@/ui/ModalContext" import { usePanelControlContext } from "@/ui/PanelContext" import { Box } from "@mui/material" import { useEffect, useReducer } from "react" +import { ConfigurationType, setSelectedConfigurationType } from "./assembly-config/ConfigurePanel" +import { setSelectedScheme } from "./assembly-config/interfaces/inputs/ConfigureInputsInterface" let selectedBrainIndexGlobal: number | undefined = undefined // eslint-disable-next-line react-refresh/only-export-components @@ -30,7 +32,7 @@ function getBrainIndex() { } const ChooseInputSchemePanel: React.FC = ({ panelId }) => { - const { closePanel } = usePanelControlContext() + const { closePanel, openPanel } = usePanelControlContext() const { openModal } = useModalControlContext() const [_, update] = useReducer(x => !x, false) @@ -47,9 +49,10 @@ const ChooseInputSchemePanel: React.FC = ({ panelId }) => { const scheme = InputSchemeManager.availableInputSchemes[0] InputSystem.brainIndexSchemeMap.set(brainIndex, scheme) - InputSystem.selectedScheme = scheme - openModal("change-inputs") + setSelectedConfigurationType(ConfigurationType.INPUTS) + setSelectedScheme(scheme) + openPanel("configure") } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -109,8 +112,10 @@ const ChooseInputSchemePanel: React.FC = ({ panelId }) => { {/** Edit button - same as select but opens the inputs modal */} {EditButton(() => { InputSystem.brainIndexSchemeMap.set(getBrainIndex(), scheme) - InputSystem.selectedScheme = scheme - openModal("change-inputs") + + setSelectedConfigurationType(ConfigurationType.INPUTS) + setSelectedScheme(scheme) + openPanel("configure") })} {/** Delete button (only if the scheme is customized) */} diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index e9b1dbc47c..a2cb525533 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -5,7 +5,7 @@ import Label from "@/ui/components/Label" import Panel, { PanelPropsImpl } from "@/ui/components/Panel" import SelectMenu, { SelectMenuOption } from "@/ui/components/SelectMenu" import { ToggleButton, ToggleButtonGroup } from "@/ui/components/ToggleButtonGroup" -import { useMemo, useReducer, useState } from "react" +import { useEffect, useMemo, useReducer, useState } from "react" import ConfigureGamepiecePickupInterface from "./interfaces/ConfigureGamepiecePickupInterface" import ConfigureShotTrajectoryInterface from "./interfaces/ConfigureShotTrajectoryInterface" import ConfigureScoringZonesInterface from "./interfaces/scoring/ConfigureScoringZonesInterface" @@ -19,6 +19,31 @@ import { setSelectedBrainIndexGlobal } from "../ChooseInputSchemePanel" import ConfigureSchemeInterface from "./interfaces/inputs/ConfigureSchemeInterface" import { SynthesisIcons } from "@/ui/components/StyledComponents" +enum ConfigMode { + INTAKE, + EJECTOR, + MOTORS, + CONTROLS, + SCORING_ZONES, +} + +// eslint-disable-next-line react-refresh/only-export-components +export enum ConfigurationType { + ROBOT, + FIELD, + INPUTS, +} + +let selectedConfigurationType: ConfigurationType = ConfigurationType.ROBOT +// eslint-disable-next-line react-refresh/only-export-components +export function setSelectedConfigurationType(type: ConfigurationType) { + selectedConfigurationType = type +} + +function getConfigurationType() { + return selectedConfigurationType +} + /** Option for selecting a robot of field */ class AssemblySelectionOption extends SelectMenuOption { assemblyObject: MirabufSceneObject @@ -90,14 +115,6 @@ const AssemblySelection: React.FC = ({ configuratio ) } -enum ConfigMode { - INTAKE, - EJECTOR, - MOTORS, - CONTROLS, - SCORING_ZONES, -} - class ConfigModeSelectionOption extends SelectMenuOption { configMode: ConfigMode @@ -195,17 +212,16 @@ export class ConfigurationSavedEvent extends Event { } } -enum ConfigurationType { - ROBOT, - FIELD, - INPUTS, -} - const ConfigurePanel: React.FC = ({ panelId }) => { - const { openPanel } = usePanelControlContext() - const [configurationType, setConfigurationType] = useState(ConfigurationType.ROBOT) + const { openPanel, closePanel } = usePanelControlContext() + const [configurationType, setConfigurationType] = useState(getConfigurationType()) const [selectedAssembly, setSelectedAssembly] = useState(undefined) - const [selectedConfigMode, setSelectedConfigMode] = useState(undefined) + const [configMode, setConfigMode] = useState(undefined) + + useEffect(() => { + closePanel("choose-scheme") + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) return ( = ({ panelId }) => { cancelEnabled={false} openLocation="right" onAccept={() => { + // Save the current panel state + setSelectedConfigurationType(configurationType) + new ConfigurationSavedEvent() }} acceptName="Close" @@ -226,9 +245,10 @@ const ConfigurePanel: React.FC = ({ panelId }) => { exclusive onChange={(_, v) => { v != null && setConfigurationType(v) + //console.log(v) setSelectedAssembly(undefined) new ConfigurationSavedEvent() - setSelectedConfigMode(undefined) + setConfigMode(undefined) }} sx={{ alignSelf: "center", @@ -246,10 +266,10 @@ const ConfigurePanel: React.FC = ({ panelId }) => { { - if (selectedConfigMode != undefined) { + if (configMode != undefined) { new ConfigurationSavedEvent() } - setSelectedConfigMode(undefined) + setConfigMode(undefined) setSelectedAssembly(a) }} /> @@ -258,15 +278,15 @@ const ConfigurePanel: React.FC = ({ panelId }) => { { - if (selectedConfigMode != undefined) new ConfigurationSavedEvent() - setSelectedConfigMode(mode) + if (configMode != undefined) new ConfigurationSavedEvent() + setConfigMode(mode) }} /> )} {/** The interface for the selected configuration mode */} - {selectedConfigMode != undefined && selectedAssembly != undefined && ( + {configMode != undefined && selectedAssembly != undefined && ( diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureInputsInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureInputsInterface.tsx index 6d466bc8ef..dd2fdfbeef 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureInputsInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureInputsInterface.tsx @@ -5,6 +5,18 @@ import InputSystem from "@/systems/input/InputSystem" import InputSchemeManager, { InputScheme } from "@/systems/input/InputSchemeManager" import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain" import ConfigureSchemeInterface from "./ConfigureSchemeInterface" +import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import { useModalControlContext } from "@/ui/ModalContext" + +let selectedScheme: InputScheme | undefined = undefined +// eslint-disable-next-line react-refresh/only-export-components +export function setSelectedScheme(scheme: InputScheme | undefined) { + selectedScheme = scheme +} + +function getSelectedScheme() { + return selectedScheme +} /** If a scheme is assigned to a robot, find the name of that robot */ const findSchemeRobotName = (scheme: InputScheme): string | undefined => { @@ -29,7 +41,10 @@ class SchemeSelectionOption extends SelectMenuOption { } const ConfigureInputsInterface = () => { - const [selectedScheme, setSelectedScheme] = useState(undefined) + const { openModal } = useModalControlContext() + + const [selectedScheme, setSelectedScheme] = useState(getSelectedScheme()) + const [schemes, setSchemes] = useState(InputSchemeManager.allInputSchemes) const saveEvent = useCallback(() => { InputSchemeManager.saveSchemes() @@ -39,6 +54,7 @@ const ConfigureInputsInterface = () => { ConfigurationSavedEvent.Listen(saveEvent) return () => { + setSelectedScheme(undefined) ConfigurationSavedEvent.RemoveListener(saveEvent) } }, [saveEvent]) @@ -47,7 +63,7 @@ const ConfigureInputsInterface = () => { <> {/** Select menu with input schemes */} new SchemeSelectionOption(s))} + options={schemes.map(s => new SchemeSelectionOption(s))} onOptionSelected={val => { setSelectedScheme((val as SchemeSelectionOption)?.scheme) if (val == undefined) { @@ -55,6 +71,38 @@ const ConfigureInputsInterface = () => { } }} defaultHeaderText={"Select an Input Scheme"} + onDelete={val => { + if (!(val instanceof SchemeSelectionOption)) return + + // Fetch current custom schemes + InputSchemeManager.saveSchemes() + InputSchemeManager.resetDefaultSchemes() + const schemes = PreferencesSystem.getGlobalPreference("InputSchemes") + + // Find and remove this input scheme + const index = schemes.indexOf(val.scheme) + schemes.splice(index, 1) + + // Save to preferences + PreferencesSystem.setGlobalPreference("InputSchemes", schemes) + PreferencesSystem.savePreferences() + + // Update UI with new schemes + setSchemes(InputSchemeManager.allInputSchemes) + }} + deleteCondition={val => { + if (!(val instanceof SchemeSelectionOption)) return false + + return val.scheme.customized + }} + onAddClicked={() => { + openModal("new-scheme") + }} + defaultSelectedOption={options => { + if (options.length < 0 || !(options[0] instanceof SchemeSelectionOption)) return undefined + + return options.find(o => (o as SchemeSelectionOption).scheme == getSelectedScheme()) + }} /> {selectedScheme && } diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx index 06887a84aa..996ef2f574 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx @@ -11,7 +11,6 @@ interface ConfigSchemeProps { /** Interface to configure a specific input scheme */ const ConfigureSchemeInterface: React.FC = ({ selectedScheme }) => { - //const [selectedInput, setSelectedInput] = useState(undefined) const [useGamepad, setUseGamepad] = useState(selectedScheme.usesGamepad) const saveEvent = useCallback(() => { @@ -44,6 +43,7 @@ const ConfigureSchemeInterface: React.FC = ({ selectedScheme {selectedScheme.inputs.map(i => { return ( { From 557475c0f134bb395d805331c1fd622573a8f9f3 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 7 Aug 2024 12:20:37 -0700 Subject: [PATCH 173/344] Velocity max and dividers --- .../configuring/ConfigureSubsystemsPanel.tsx | 67 ++++++++++--------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx index b454da7bf9..93a437ba01 100644 --- a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx @@ -13,6 +13,7 @@ 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" @@ -88,37 +89,41 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { ) return ( - - - - { - setVelocity(_velocity as number) - onChange(_velocity as number, force) - }} - step={0.01} - /> - { - setForce(_force as number) - onChange(velocity, _force as number) - }} - step={0.01} - /> - - + <> + + + + { + setVelocity(_velocity as number) + onChange(_velocity as number, force) + }} + step={0.01} + /> + { + setForce(_force as number) + onChange(velocity, _force as number) + }} + step={0.01} + /> + + + + + ) } From 3cfed80ce575e09aa398fa9873c9c6c6bc9dffac Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 7 Aug 2024 12:36:16 -0700 Subject: [PATCH 174/344] Format --- fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx index 93a437ba01..309442f817 100644 --- a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx @@ -123,7 +123,6 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { - ) } From d53a2f87def8566dfea1f03848a60be6ca8b74c9 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Wed, 7 Aug 2024 15:10:13 -0700 Subject: [PATCH 175/344] sparkmax absolute encoder support --- .../synthesis/revrobotics/CANSparkMax.java | 13 +-- .../revrobotics/SparkAbsoluteEncoder.java | 100 ++++++++++++------ 2 files changed, 72 insertions(+), 41 deletions(-) diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java index 5bfb81350f..6ac10f7424 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/CANSparkMax.java @@ -47,15 +47,10 @@ public REVLibError setIdleMode(com.revrobotics.CANSparkBase.IdleMode mode) { return super.setIdleMode(mode); } - // @Override - // public SparkAbsoluteEncoder getAbsoluteEncoder() throws Exception{ - // try { - // return new SparkAbsoluteEncoder(this.m_encoder, com.revrobotics.SparkAbsoluteEncoder.Type.kDutyCycle); - // } catch (Exception e) { - // // TODO Auto-generated catch block - // e.printStackTrace(); - // } - // } + /// Use instead on getAbsoluteEncoder(), everything else works exactly the same + public com.autodesk.synthesis.revrobotics.SparkAbsoluteEncoder getAbsoluteEncoderSim() { + return new SparkAbsoluteEncoder(super.getAbsoluteEncoder(), this.m_encoder); + } // @Override // public SparkAbsoluteEncoder getAbsoluteEncoder(com.revrobotics.SparkAbsoluteEncoder.Type type) throws Exception { diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/SparkAbsoluteEncoder.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/SparkAbsoluteEncoder.java index 276a08cb6a..e99a73a6d6 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/SparkAbsoluteEncoder.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/revrobotics/SparkAbsoluteEncoder.java @@ -1,32 +1,68 @@ -// package com.autodesk.synthesis.revrobotics; -// import java.lang.reflect.Constructor; - -// import com.autodesk.synthesis.CANEncoder; -// import com.revrobotics.CANSparkBase; - -// public class SparkAbsoluteEncoder extends com.revrobotics.SparkAbsoluteEncoder { -// private CANSparkBase base; - -// // We're prettys sure that it's impossible to make a child class of this parent class with a constructor, because the parent's constructor is private -// // Reflection didn't work since a child constructor __needs__ a super() call at the top of the body -// // Passing in the constuctor wouldn't work either, since it's just a function pointer and the child constructor would have no idea that it points to its super -// public SparkAbsoluteEncoder(CANSparkBase base, com.revrobotics.SparkAbsoluteEncoder.Type type) throws Exception { -// try { -// Constructor constructor = com.revrobotics.SparkAbsoluteEncoder.class.getDeclaredConstructor(com.revrobotics.CANSparkBase.class, com.revrobotics.SparkAbsoluteEncoder.Type.class); -// constructor.setAccessible(true); -// com.revrobotics.SparkAbsoluteEncoder parent = constructor.newInstance(base, type); -// } catch (Exception e) { -// e.printStackTrace(); -// } -// } - -// @Override -// public double getPosition() { -// return this.base.m_encoder.getPosition(); -// } - -// @Override -// public double getVelocity() { -// return this.base.m_encoder.getVelocity(); -// } -// } +package com.autodesk.synthesis.revrobotics; + +import com.autodesk.synthesis.CANEncoder; + +import com.revrobotics.AbsoluteEncoder; +import com.revrobotics.REVLibError; + +public class SparkAbsoluteEncoder implements AbsoluteEncoder { + private CANEncoder simEncoder; + private com.revrobotics.SparkAbsoluteEncoder realEncoder; + + // We're prettys sure that it's impossible to make a child class of this parent class with a constructor, because the parent's constructor is private + // Reflection didn't work since a child constructor __needs__ a super() call at the top of the body + // Passing in the constuctor wouldn't work either, since it's just a function pointer and the child constructor would have no idea that it points to its super + public SparkAbsoluteEncoder(com.revrobotics.SparkAbsoluteEncoder realEncoder, CANEncoder simEncoder) { + this.realEncoder = realEncoder; + this.simEncoder = simEncoder; + } + + public int getAverageDepth() { + return this.realEncoder.getAverageDepth(); + } + + public boolean getInverted() { + return this.realEncoder.getInverted(); + } + + public double getPosition() { + return this.simEncoder.getPosition() * this.realEncoder.getPositionConversionFactor(); + } + + // TODO: Remove conversion factors on the fission end + public double getPositionConversionFactor() { + return this.getPositionConversionFactor(); + } + + public double getVelocity() { + return this.simEncoder.getVelocity() * this.realEncoder.getVelocityConversionFactor(); + } + + public double getVelocityConversionFactor() { + return this.realEncoder.getVelocityConversionFactor(); + } + + public double getZeroOffset() { + return this.realEncoder.getZeroOffset(); + } + + public REVLibError setAverageDepth(int depth) { + return this.realEncoder.setAverageDepth(depth); + } + + public REVLibError setInverted(boolean inverted) { + return this.realEncoder.setInverted(inverted); + } + + public REVLibError setPositionConversionFactor(double factor) { + return this.realEncoder.setPositionConversionFactor(factor); + } + + public REVLibError setVelocityConversionFactor(double factor) { + return this.realEncoder.setVelocityConversionFactor(factor); + } + + public REVLibError setZeroOffset(double factor) { + return this.realEncoder.setZeroOffset(factor); + } +} From 44f2860b8466299a3383c13ededef1bd7757b6b4 Mon Sep 17 00:00:00 2001 From: LucaHaverty Date: Wed, 7 Aug 2024 15:39:51 -0700 Subject: [PATCH 176/344] Close config panel when you choose to spawn a new robot or field --- fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx index 0c5906fa78..1b0d050045 100644 --- a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx +++ b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx @@ -136,6 +136,11 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { } }, []) + useEffect(() => { + closePanel("configure") + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + // Get Default Mirabuf Data, Load into manifest. useEffect(() => { // To remove the prettier warning From 7fcf795d132c2a9ecea0f7b20a19784e36d8d9bc Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Thu, 8 Aug 2024 09:22:57 -0700 Subject: [PATCH 177/344] test proper codesim autos w/encoder --- .../simulation/wpilib_brain/SimInput.ts | 6 ++--- .../simulation/wpilib_brain/WPILibBrain.ts | 3 --- .../rio-config/RCConfigEncoderModal.tsx | 12 +--------- .../src/main/java/frc/robot/Robot.java | 24 ++++++++++++++----- 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimInput.ts b/fission/src/systems/simulation/wpilib_brain/SimInput.ts index ef66d049dc..a073e6c510 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimInput.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimInput.ts @@ -12,16 +12,14 @@ export interface SimInput { export class SimEncoderInput implements SimInput { private _device: string private _stimulus: EncoderStimulus - private _conversionFactor: number - constructor(device: string, stimulus: EncoderStimulus, conversionFactor: number) { + constructor(device: string, stimulus: EncoderStimulus) { this._device = device this._stimulus = stimulus - this._conversionFactor = conversionFactor } public Update(_deltaT: number) { - SimCANEncoder.SetPosition(`${this._device}`, this._stimulus.positionValue * this._conversionFactor) + SimCANEncoder.SetPosition(`${this._device}`, this._stimulus.positionValue) } } diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index 8557aea794..3d54c67caa 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -267,8 +267,6 @@ class WPILibBrain extends Brain { console.warn("SimulationLayer is undefined") return } - - this.addSimInput(new SimGyroInput("Gyro:ADXRS450[0]", mechanism)) } public addSimOutputGroup(device: SimOutputGroup) { @@ -282,7 +280,6 @@ class WPILibBrain extends Brain { public Update(deltaT: number): void { this._simOutputs.forEach(d => d.Update(deltaT)) this._simInputs.forEach(i => i.Update(deltaT)) - console.log(simMap) } public Enable(): void { diff --git a/fission/src/ui/modals/configuring/rio-config/RCConfigEncoderModal.tsx b/fission/src/ui/modals/configuring/rio-config/RCConfigEncoderModal.tsx index b3743b9c6a..e0f452cda7 100644 --- a/fission/src/ui/modals/configuring/rio-config/RCConfigEncoderModal.tsx +++ b/fission/src/ui/modals/configuring/rio-config/RCConfigEncoderModal.tsx @@ -4,7 +4,6 @@ import { useModalControlContext } from "@/ui/ModalContext" import Label, { LabelSize } from "@/components/Label" import Input from "@/components/Input" import Dropdown from "@/components/Dropdown" -import NumberInput from "@/components/NumberInput" import WPILibBrain, { simMap, SimType } from "@/systems/simulation/wpilib_brain/WPILibBrain" import World from "@/systems/World" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" @@ -44,7 +43,6 @@ const RCConfigEncoderModal: React.FC = ({ modalId }) => { const [selectedDevice, setSelectedDevice] = useState(devices[0] && devices[0][0]) const [selectedStimulus, setSelectedStimulus] = useState(stimuli[0]) - const [conversionFactor, setConversionFactor] = useState(1) return ( = ({ modalId }) => { acceptName="Done" onAccept={() => { if (selectedDevice && selectedStimulus) - brain.addSimInput(new SimEncoderInput(selectedDevice, selectedStimulus, conversionFactor)) + brain.addSimInput(new SimEncoderInput(selectedDevice, selectedStimulus)) }} onCancel={() => { openModal("roborio") @@ -64,14 +62,6 @@ const RCConfigEncoderModal: React.FC = ({ modalId }) => { n[0])} onSelect={s => setSelectedDevice(s)} /> setSelectedStimulus(stimMap[s])} /> - { - setConversionFactor(n || 0) - }} - /> ) } diff --git a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java index 92936dbd24..2369e7aea6 100644 --- a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java +++ b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java @@ -91,13 +91,25 @@ public void autonomousInit() { @Override public void autonomousPeriodic() { - m_SparkMax1.set(1.0); - m_SparkMax2.set(1.0); - m_SparkMax3.set(1.0); - m_SparkMax4.set(1.0); - m_SparkMax5.set(1.0); - m_SparkMax6.set(1.0); + double position = m_SparkMax1.getAbsoluteEncoderSim().getPosition(); + + if (position >= 20) { + m_SparkMax1.set(0.0); + m_SparkMax2.set(0.0); + m_SparkMax3.set(0.0); + m_SparkMax4.set(0.0); + m_SparkMax5.set(0.0); + m_SparkMax6.set(0.0); + } else { + m_SparkMax1.set(1.0); + m_SparkMax2.set(1.0); + m_SparkMax3.set(1.0); + m_SparkMax4.set(1.0); + m_SparkMax5.set(1.0); + m_SparkMax6.set(1.0); + } + switch (m_autoSelected) { case kCustomAuto: // Put custom auto code here From b787cd479dad2ca8f71aa0ec1f8398fc020c2f37 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Thu, 8 Aug 2024 10:48:13 -0700 Subject: [PATCH 178/344] disable CANMotor inputs --- .../src/systems/simulation/wpilib_brain/SimInput.ts | 1 + .../src/main/java/com/autodesk/synthesis/CANMotor.java | 10 ++-------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimInput.ts b/fission/src/systems/simulation/wpilib_brain/SimInput.ts index a073e6c510..79c74f39ec 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimInput.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimInput.ts @@ -20,6 +20,7 @@ export class SimEncoderInput implements SimInput { public Update(_deltaT: number) { SimCANEncoder.SetPosition(`${this._device}`, this._stimulus.positionValue) + SimCANEncoder.SetVelocity(`${this._device}`, this._stimulus.velocityValue) } } diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java index cd5e1d55d2..e4df23ba66 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CANMotor.java @@ -54,6 +54,7 @@ public CANMotor(String name, int deviceId, double defaultPercentOutput, boolean m_supplyCurrent = m_device.createDouble("supplyCurrent", Direction.kInput, 120.0); m_motorCurrent = m_device.createDouble("motorCurrent", Direction.kInput, 120.0); m_busVoltage = m_device.createDouble("busVoltage", Direction.kInput, 12.0); + m_busVoltage.set(0.0); // disable CANMotor inputs } /** @@ -91,14 +92,7 @@ public void setNeutralDeadband(double deadband) { m_neutralDeadband.set(Math.min(1.0, Math.max(0.0, deadband))); } - /** - * Sets the supply current, simulated. - * - */ - public void setSupplyCurrent(int current) { - m_supplyCurrent.set(current); - } - + /** * Get the supply current, simulated. * From b54cec933e94c9c18c86a1770d9fb9f205648557 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Thu, 8 Aug 2024 11:18:51 -0700 Subject: [PATCH 179/344] fix build --- .../simulation/wpilib_brain/WPILibBrain.ts | 2 +- fission/src/ui/panels/WSViewPanel.tsx | 37 +++++-------------- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index 3d54c67caa..d484a4bee9 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -7,7 +7,7 @@ import { SimulationLayer } from "../SimulationSystem" import World from "@/systems/World" import { SimOutputGroup } from "./SimOutput" -import { SimGyroInput, SimInput } from "./SimInput" +import { SimInput } from "./SimInput" const worker = new WPILibWSWorker() const PWM_SPEED = " - {simMap.has("PWM") ? ( - [...simMap.get("PWM")!.entries()] + {simMap.has(SimType.PWM) ? ( + [...simMap.get(SimType.PWM)!.entries()] .filter(x => x[1][" { return ( @@ -50,27 +50,8 @@ function generateTableBody() { ) : ( <> )} - {simMap.has("SimDevice") ? ( - [...simMap.get("SimDevice")!.entries()].map(x => { - return ( - - - SimDevice - - - {x[0]} - - - {JSON.stringify(x[1])} - - - ) - }) - ) : ( - <> - )} - {simMap.has("CANMotor") ? ( - [...simMap.get("CANMotor")!.entries()].map(x => { + {simMap.has(SimType.CANMotor) ? ( + [...simMap.get(SimType.CANMotor)!.entries()].map(x => { return ( @@ -88,8 +69,8 @@ function generateTableBody() { ) : ( <> )} - {simMap.has("CANEncoder") ? ( - [...simMap.get("CANEncoder")!.entries()].map(x => { + {simMap.has(SimType.CANEncoder) ? ( + [...simMap.get(SimType.CANEncoder)!.entries()].map(x => { return ( @@ -107,8 +88,8 @@ function generateTableBody() { ) : ( <> )} - {simMap.has("Gyro") ? ( - [...simMap.get("Gyro")!.entries()].map(x => { + {simMap.has(SimType.Gyro) ? ( + [...simMap.get(SimType.Gyro)!.entries()].map(x => { return ( @@ -228,7 +209,7 @@ const WSViewPanel: React.FC = ({ panelId }) => {
Date: Thu, 8 Aug 2024 15:36:56 -0700 Subject: [PATCH 186/344] ran formatter --- fission/src/ui/components/Modal.tsx | 20 +++++++++++-------- .../rio-config/RCConfigCANGroupModal.tsx | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/fission/src/ui/components/Modal.tsx b/fission/src/ui/components/Modal.tsx index b550d82bfd..6ccf23c837 100644 --- a/fission/src/ui/components/Modal.tsx +++ b/fission/src/ui/components/Modal.tsx @@ -68,8 +68,9 @@ const Modal: React.FC = ({ )}
{children}
@@ -82,8 +83,9 @@ const Modal: React.FC = ({ closeModal() if (!cancelBlocked && onCancel) onCancel() }} - className={`${cancelBlocked ? "bg-interactive-background" : "bg-cancel-button" - } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90`} + className={`${ + cancelBlocked ? "bg-interactive-background" : "bg-cancel-button" + } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90`} /> )} {middleEnabled && ( @@ -93,8 +95,9 @@ const Modal: React.FC = ({ onClick={() => { if (!middleBlocked && onMiddle) onMiddle() }} - className={`${middleBlocked ? "bg-interactive-background" : "bg-accept-button" - } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90`} + className={`${ + middleBlocked ? "bg-interactive-background" : "bg-accept-button" + } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90`} /> )} {acceptEnabled && ( @@ -105,8 +108,9 @@ const Modal: React.FC = ({ closeModal() if (!acceptBlocked && onAccept) onAccept() }} - className={`${acceptBlocked ? "bg-interactive-background" : "bg-accept-button" - } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90`} + className={`${ + acceptBlocked ? "bg-interactive-background" : "bg-accept-button" + } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90`} /> )}
diff --git a/fission/src/ui/modals/configuring/rio-config/RCConfigCANGroupModal.tsx b/fission/src/ui/modals/configuring/rio-config/RCConfigCANGroupModal.tsx index 83ee24046c..2f81e76dc4 100644 --- a/fission/src/ui/modals/configuring/rio-config/RCConfigCANGroupModal.tsx +++ b/fission/src/ui/modals/configuring/rio-config/RCConfigCANGroupModal.tsx @@ -34,8 +34,8 @@ const RCConfigCANGroupModal: React.FC = ({ modalId }) => { simLayer?.SetBrain(brain) } - const cans = simMap.get(SimType.CANMotor) ?? new Map - const devices: [string, any][] = [...cans.entries()].filter(([_, data]) => data[">() + const devices: [string, Map][] = [...cans.entries()].filter(([_, data]) => data[" Date: Thu, 8 Aug 2024 15:53:46 -0700 Subject: [PATCH 187/344] Version detection for protobuf fix --- exporter/SynthesisFusionAddin/Synthesis.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/exporter/SynthesisFusionAddin/Synthesis.py b/exporter/SynthesisFusionAddin/Synthesis.py index 06ead88431..5398577fde 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.py +++ b/exporter/SynthesisFusionAddin/Synthesis.py @@ -14,8 +14,15 @@ try: # Attempt to import required pip dependencies to verify their installation. - import google.protobuf import requests + from proto.proto_out import ( + assembly_pb2, + joint_pb2, + material_pb2, + motor_pb2, + signal_pb2, + types_pb2, + ) except (ImportError, ModuleNotFoundError) as error: logger.warn(f"Running resolve dependencies with error of:\n{error}") result = resolveDependencies() From db138f94aca32ee5149b9fcac6b437868de59a40 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:10:07 -0700 Subject: [PATCH 188/344] Added `BaseException` catch for google `VersionError` --- exporter/SynthesisFusionAddin/Synthesis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/SynthesisFusionAddin/Synthesis.py b/exporter/SynthesisFusionAddin/Synthesis.py index 5398577fde..deba3c9551 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.py +++ b/exporter/SynthesisFusionAddin/Synthesis.py @@ -23,7 +23,7 @@ signal_pb2, types_pb2, ) -except (ImportError, ModuleNotFoundError) as error: +except (ImportError, ModuleNotFoundError, BaseException) as error: # BaseException required to catch proto.VersionError logger.warn(f"Running resolve dependencies with error of:\n{error}") result = resolveDependencies() if result: From 9c3de89876fd1ead974fe1766c089d5b018267ab Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:48:06 -0700 Subject: [PATCH 189/344] Ensure browser cache key --- .github/workflows/FissionUnitTest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/FissionUnitTest.yml b/.github/workflows/FissionUnitTest.yml index 1a0389244b..b3dc4e6be2 100644 --- a/.github/workflows/FissionUnitTest.yml +++ b/.github/workflows/FissionUnitTest.yml @@ -44,7 +44,7 @@ jobs: with: path: | ~/.cache/ms-playwright/ - key: ${{ runner.os }}-assets-playwright-${{ env.PLAYWRIGHT_VERSION }} + key: ${{ runner.os }}-assets-playwright-${{ env.PLAYWRIGHT_VERSION }}-v2 - name: Install Dependencies run: | From 4514f7ec87300e961abdffd896e14185028f5ae7 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Thu, 8 Aug 2024 21:06:46 -0700 Subject: [PATCH 190/344] Import formatting fixes --- .../src/Parser/SynthesisParser/Components.py | 1 + .../src/Parser/SynthesisParser/JointHierarchy.py | 1 + .../SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py | 1 + .../SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py | 1 + .../src/Parser/SynthesisParser/PhysicalProperties.py | 1 + .../src/Parser/SynthesisParser/RigidGroup.py | 1 + 6 files changed, 6 insertions(+) diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py index 6f53db6eb1..aea709f04a 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py @@ -2,6 +2,7 @@ import adsk.core import adsk.fusion from proto.proto_out import assembly_pb2, joint_pb2, material_pb2, types_pb2 + from src.Logging import logFailure from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser import PhysicalProperties diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py index 811a39cc00..cf8c5e04b0 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py @@ -4,6 +4,7 @@ import adsk.core import adsk.fusion from proto.proto_out import joint_pb2, types_pb2 + from src import gm from src.Logging import getLogger, logFailure from src.Parser.ExporterOptions import ExporterOptions diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py index dbc996c08a..a077c764b7 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py @@ -1,5 +1,6 @@ import adsk from proto.proto_out import material_pb2 + from src.Logging import logFailure from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser.PDMessage import PDMessage diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py index f43add6642..338f5a300a 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py @@ -5,6 +5,7 @@ import adsk.fusion from google.protobuf.json_format import MessageToJson from proto.proto_out import assembly_pb2, types_pb2 + from src import gm from src.APS.APS import getAuth, upload_mirabuf from src.Logging import getLogger, logFailure, timed diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py index f9c5ef7249..b178bcb3bb 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py @@ -20,6 +20,7 @@ import adsk from proto.proto_out import types_pb2 + from src.Logging import logFailure diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py index bdc012dbd1..8516cefae6 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py @@ -17,6 +17,7 @@ import adsk.core import adsk.fusion from proto.proto_out import assembly_pb2 + from src.Logging import logFailure From cd60dcbac91f3fa9f81629fc48ff640c7a38d033 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Fri, 9 Aug 2024 09:49:04 -0700 Subject: [PATCH 191/344] Arm fix --- fission/src/systems/simulation/driver/HingeDriver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/src/systems/simulation/driver/HingeDriver.ts b/fission/src/systems/simulation/driver/HingeDriver.ts index 13e01d163a..e86a6e5b48 100644 --- a/fission/src/systems/simulation/driver/HingeDriver.ts +++ b/fission/src/systems/simulation/driver/HingeDriver.ts @@ -34,7 +34,7 @@ class HingeDriver extends Driver { public set maxForce(nm: number) { const motorSettings = this._constraint.GetMotorSettings() motorSettings.set_mMaxTorqueLimit(nm) - motorSettings.set_mMinTorqueLimit(nm) + motorSettings.set_mMinTorqueLimit(-nm) } public get controlMode(): DriverControlMode { From ff07568b4676c32e52b48eb5f62f18f8e72a3c84 Mon Sep 17 00:00:00 2001 From: LucaHaverty Date: Fri, 9 Aug 2024 10:17:50 -0700 Subject: [PATCH 192/344] merge fixes --- fission/src/systems/input/DefaultInputs.ts | 4 +-- fission/src/ui/components/MainHUD.tsx | 2 -- fission/src/ui/components/SelectMenu.tsx | 4 +-- .../assembly-config/ConfigurePanel.tsx | 6 ++--- .../ConfigureGamepiecePickupInterface.tsx | 2 +- .../ConfigureShotTrajectoryInterface.tsx | 2 +- .../inputs/ConfigureSchemeInterface.tsx | 25 +++++++++++++++++-- 7 files changed, 31 insertions(+), 14 deletions(-) diff --git a/fission/src/systems/input/DefaultInputs.ts b/fission/src/systems/input/DefaultInputs.ts index 2b9ac8d65a..e15fe82d44 100644 --- a/fission/src/systems/input/DefaultInputs.ts +++ b/fission/src/systems/input/DefaultInputs.ts @@ -91,8 +91,8 @@ class DefaultInputs { shift: false, meta: false, }), - new AxisInput("joint 5", "KeyN", "true", -1, false, false, -1, -1, EmptyModifierState, { - ctrl: false, + new AxisInput("joint 5", "KeyN", "KeyN", -1, false, false, -1, -1, EmptyModifierState, { + ctrl: true, alt: false, shift: false, meta: false, diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 34c53855d1..c2c9fea4df 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -100,8 +100,6 @@ const MainHUD: React.FC = () => { icon={SynthesisIcons.Import} onClick={() => openModal("import-local-mirabuf")} /> -
-
= ({ configuratio update() }} onAddClicked={() => { - console.log("Open spawn panel") openPanel("import-mirabuf") }} noOptionsText={`No ${configurationType == ConfigurationType.ROBOT ? "robots" : "fields"} spawned!`} @@ -127,7 +126,7 @@ class ConfigModeSelectionOption extends SelectMenuOption { const robotModes = [ new ConfigModeSelectionOption("Intake", ConfigMode.INTAKE), new ConfigModeSelectionOption("Ejector", ConfigMode.EJECTOR), - new ConfigModeSelectionOption("Joints", ConfigMode.MOTORS), + new ConfigModeSelectionOption("Sequential Joints", ConfigMode.MOTORS), new ConfigModeSelectionOption("Controls", ConfigMode.CONTROLS), ] const fieldModes = [new ConfigModeSelectionOption("Scoring Zones", ConfigMode.SCORING_ZONES)] @@ -225,7 +224,7 @@ const ConfigurePanel: React.FC = ({ panelId }) => { return ( = ({ panelId }) => { exclusive onChange={(_, v) => { v != null && setConfigurationType(v) - //console.log(v) setSelectedAssembly(undefined) new ConfigurationSavedEvent() setConfigMode(undefined) diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx index 62f4ae818d..4981082dfe 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx @@ -220,7 +220,7 @@ const ConfigureGamepiecePickupInterface: React.FC = ({ select transformGizmo.mesh.position.setFromMatrixPosition(robotTransformation) transformGizmo.mesh.rotation.setFromRotationMatrix(robotTransformation) } - setZoneSize((MIN_ZONE_SIZE + MAX_ZONE_SIZE) / 2.0) + setZoneSize(0.5) setSelectedNode(selectedRobot?.rootNodeId) }} /> diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureShotTrajectoryInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureShotTrajectoryInterface.tsx index 49e716b93e..f5293e93cf 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureShotTrajectoryInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureShotTrajectoryInterface.tsx @@ -215,7 +215,7 @@ const ConfigureShotTrajectoryInterface: React.FC = ({ select transformGizmo.mesh.position.setFromMatrixPosition(robotTransformation) transformGizmo.mesh.rotation.setFromRotationMatrix(robotTransformation) } - setEjectorVelocity((MIN_VELOCITY + MAX_VELOCITY) / 2.0) + setEjectorVelocity(1) setSelectedNode(selectedRobot?.rootNodeId) }} /> diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx index 996ef2f574..f2a3721704 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx @@ -1,7 +1,7 @@ import InputSchemeManager, { InputScheme } from "@/systems/input/InputSchemeManager" import Checkbox from "@/ui/components/Checkbox" import EditInputInterface from "./EditInputInterface" -import { useCallback, useEffect, useState } from "react" +import { useCallback, useEffect, useRef, useState } from "react" import { ConfigurationSavedEvent } from "../../ConfigurePanel" import { SectionDivider } from "@/ui/components/StyledComponents" @@ -12,6 +12,7 @@ interface ConfigSchemeProps { /** Interface to configure a specific input scheme */ const ConfigureSchemeInterface: React.FC = ({ selectedScheme }) => { const [useGamepad, setUseGamepad] = useState(selectedScheme.usesGamepad) + const scrollRef = useRef(null) const saveEvent = useCallback(() => { InputSchemeManager.saveSchemes() @@ -25,6 +26,26 @@ const ConfigureSchemeInterface: React.FC = ({ selectedScheme } }, [saveEvent]) + /** Disable scrolling with arrow keys to stop accidentally scrolling when binding keys */ + useEffect(() => { + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "ArrowUp" || event.key === "ArrowDown") { + event.preventDefault() + } + } + + const scrollElement = scrollRef.current + if (scrollElement) { + scrollElement.addEventListener("keydown", handleKeyDown as unknown as EventListener) + } + + return () => { + if (scrollElement) { + scrollElement.removeEventListener("keydown", handleKeyDown as unknown as EventListener) + } + } + }, []) + return ( <> {/** Toggle the input scheme between controller and keyboard mode */} @@ -39,7 +60,7 @@ const ConfigureSchemeInterface: React.FC = ({ selectedScheme {/* Scroll view for inputs */} -
+
{selectedScheme.inputs.map(i => { return ( Date: Fri, 9 Aug 2024 11:29:14 -0700 Subject: [PATCH 193/344] Merge conflict Co-authored-by: Brandon Pacewic <92102436+BrandonPacewic@users.noreply.github.com> --- fission/src/mirabuf/MirabufParser.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index ae1607b97c..7dab72156b 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -240,7 +240,6 @@ class MirabufParser { console.log("Failed to get part definitions") return } - console.debug(partDefinitions) } private NewRigidNode(suffix?: string): RigidNode { From 645ee2b135cc91ca865155e80b9642fbb2e14654 Mon Sep 17 00:00:00 2001 From: LucaHaverty Date: Fri, 9 Aug 2024 11:41:55 -0700 Subject: [PATCH 194/344] Forgot to add the button change :( --- fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx index fdc5a0efcb..aa3f37b2f8 100644 --- a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx +++ b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx @@ -372,10 +372,6 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { {cachedRobotElements} - {Spacer(5)} - -
) From b7e17fbae4d0a702cdc3652f62e9eddcb837a327 Mon Sep 17 00:00:00 2001 From: LucaHaverty Date: Fri, 9 Aug 2024 12:18:00 -0700 Subject: [PATCH 195/344] Tweaked colors and dynamic scaling ui components --- fission/src/main.tsx | 10 +- fission/src/ui/components/Button.tsx | 23 +++-- fission/src/ui/components/Checkbox.tsx | 4 +- fission/src/ui/components/Dropdown.tsx | 13 ++- fission/src/ui/components/MainHUD.tsx | 9 +- fission/src/ui/components/Modal.tsx | 99 ++++++++++--------- fission/src/ui/components/Panel.tsx | 12 ++- fission/src/ui/components/Slider.tsx | 3 +- .../src/ui/components/ToggleButtonGroup.tsx | 5 +- .../ui/modals/configuring/SettingsModal.tsx | 17 ++-- 10 files changed, 119 insertions(+), 76 deletions(-) diff --git a/fission/src/main.tsx b/fission/src/main.tsx index 3b761d8ab3..49f85cf2cb 100644 --- a/fission/src/main.tsx +++ b/fission/src/main.tsx @@ -11,15 +11,15 @@ const defaultColors: Theme = { above: [], }, InteractiveElementLeft: { - color: { r: 224, g: 130, b: 65, a: 1 }, + color: { r: 207, g: 114, b: 57, a: 1 }, above: ["Background", "BackgroundSecondary"], }, InteractiveElementRight: { - color: { r: 218, g: 102, b: 89, a: 1 }, + color: { r: 212, g: 75, b: 62, a: 1 }, above: ["Background", "BackgroundSecondary"], }, Background: { color: { r: 0, g: 0, b: 0, a: 1 }, above: [] }, - BackgroundSecondary: { color: { r: 30, g: 30, b: 30, a: 1 }, above: [] }, + BackgroundSecondary: { color: { r: 25, g: 25, b: 25, a: 1 }, above: [] }, InteractiveBackground: { color: { r: 52, g: 58, b: 64, a: 1 }, above: [] }, MainText: { color: { r: 255, g: 255, b: 255, a: 1 }, @@ -33,8 +33,8 @@ const defaultColors: Theme = { ], }, Scrollbar: { color: { r: 170, g: 170, b: 170, a: 1 }, above: [] }, - AcceptButton: { color: { r: 71, g: 138, b: 226, a: 1 }, above: [] }, - CancelButton: { color: { r: 231, g: 85, b: 81, a: 1 }, above: [] }, + AcceptButton: { color: { r: 33, g: 137, b: 228, a: 1 }, above: [] }, + CancelButton: { color: { r: 248, g: 78, b: 78, a: 1 }, above: [] }, InteractiveElementText: { color: { r: 255, g: 255, b: 255, a: 1 }, above: [], diff --git a/fission/src/ui/components/Button.tsx b/fission/src/ui/components/Button.tsx index 997a2081af..c129f49516 100644 --- a/fission/src/ui/components/Button.tsx +++ b/fission/src/ui/components/Button.tsx @@ -42,13 +42,22 @@ const Button: React.FC = ({ value, colorOverrideClass, sizeOverride return ( {value} diff --git a/fission/src/ui/components/Checkbox.tsx b/fission/src/ui/components/Checkbox.tsx index 451891073a..3c1551ab79 100644 --- a/fission/src/ui/components/Checkbox.tsx +++ b/fission/src/ui/components/Checkbox.tsx @@ -22,14 +22,14 @@ const Checkbox: React.FC = ({ label, className, defaultState, sta onChange={(e: React.ChangeEvent) => onClick && onClick(e.target.checked)} slotProps={{ root: { - className: `group relative inline-block w-[24px] h-[24px] m-2.5 cursor-pointer`, + className: `group relative inline-block w-[24px] h-[24px] m-2.5 cursor-pointer transform transition-transform hover:scale-[1.03] active:scale-[1.06]`, }, input: { className: `cursor-inherit absolute w-full h-full top-0 left-0 opacity-0 z-10 border-none`, }, track: ownerState => { return { - className: `absolute block w-full h-full transition rounded-full border border-solid outline-none border-interactive-element-right dark:border-interactive-element-right group-[.base--focusVisible]:shadow-outline-switch ${ownerState.checked ? "bg-gradient-to-br from-interactive-element-left to-interactive-element-right" : "bg-background-secondary"}`, + className: `absolute block w-full h-full transition rounded-full border border-solid outline-none border-interactive-element-right dark:border-interactive-element-right group-[.base--focusVisible]:shadow-outline-switch ${ownerState.checked ? "bg-gradient-to-br from-interactive-element-left to-interactive-element-right" : "bg-background-secondary"} transform transition-transform group-hover:scale-[1.03] group-active:scale-[1.06]`, } }, thumb: { diff --git a/fission/src/ui/components/Dropdown.tsx b/fission/src/ui/components/Dropdown.tsx index d5ba480505..e85449e071 100644 --- a/fission/src/ui/components/Dropdown.tsx +++ b/fission/src/ui/components/Dropdown.tsx @@ -45,9 +45,14 @@ const CustomMenu = styled(Menu)({ minWidth: "unset", }, "& .MuiMenuItem-root": { - "transition": "background-color 0.3s ease, color 0.3s ease", + "transition": "background-color 0.3s ease, color 0.3s ease, transform 0.2s ease", + "transform": "scale(1.06)", "&:hover": { color: "#da6659", + transform: "scale(1.05)", + }, + "&:active": { + transform: "scale(1.03)", }, }, }) @@ -102,7 +107,11 @@ const Dropdown: React.FC = ({ options, defaultValue, onSelect, la )}
- + {selectedValue || "Select an option"}
diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 2395f26e0d..e748a7a829 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -23,7 +23,14 @@ const MainHUDButton: React.FC = ({ value, icon, onClick, larger }) return (
) diff --git a/fission/src/ui/components/Panel.tsx b/fission/src/ui/components/Panel.tsx index b8faee4977..212d0d1ce9 100644 --- a/fission/src/ui/components/Panel.tsx +++ b/fission/src/ui/components/Panel.tsx @@ -158,7 +158,9 @@ const Panel: React.FC = ({ }} className={`${ cancelBlocked ? "bg-interactive-background" : "bg-cancel-button" - } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90`} + } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90 + transform transition-transform hover:scale-[1.03] active:scale-[1.06]`} + style={{ fontWeight: "bold" }} /> )} {middleEnabled && ( @@ -170,7 +172,9 @@ const Panel: React.FC = ({ }} className={`${ middleBlocked ? "bg-interactive-background" : "bg-accept-button" - } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90`} + } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90 + transform transition-transform hover:scale-[1.03] active:scale-[1.06]`} + style={{ fontWeight: "bold" }} /> )} {acceptEnabled && ( @@ -183,7 +187,9 @@ const Panel: React.FC = ({ }} className={`${ acceptBlocked ? "bg-interactive-background" : "bg-accept-button" - } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90`} + } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90 + transform transition-transform hover:scale-[1.03] active:scale-[1.06]`} + style={{ fontWeight: "bold" }} /> )}
diff --git a/fission/src/ui/components/Slider.tsx b/fission/src/ui/components/Slider.tsx index b64b894d94..0c2d4b0f2b 100644 --- a/fission/src/ui/components/Slider.tsx +++ b/fission/src/ui/components/Slider.tsx @@ -48,7 +48,8 @@ const Slider: React.FC = ({ label, min, max, value, onChange, step, slotProps={{ root: ownerState => { return { - className: `h-1 w-full inline-flex items-center relative touch-none ${ownerState.disabled ? "text-slate-200 dark:text-slate-200" : "cursor-pointer text-[#343A40] dark:text-[#343A40]"}`, + className: `h-1 w-full inline-flex items-center relative touch-none ${ownerState.disabled ? "text-slate-200 dark:text-slate-200" : "cursor-pointer text-[#343A40] dark:text-[#343A40]"} + transform transition-transform hover:scale-[1.01] active:scale-[1.02]`, } }, rail: { diff --git a/fission/src/ui/components/ToggleButtonGroup.tsx b/fission/src/ui/components/ToggleButtonGroup.tsx index 8159c97b97..90ffb28f7f 100644 --- a/fission/src/ui/components/ToggleButtonGroup.tsx +++ b/fission/src/ui/components/ToggleButtonGroup.tsx @@ -3,11 +3,12 @@ import { styled } from "@mui/system" import { colorNameToVar } from "../ThemeContext" export const ToggleButton = styled(ToggleButtonMUI)({ - // backgroundColor: "white" "borderColor": "transparent", "fontFamily": "Artifakt", "fontWeight": 700, "color": "white", + "transition": "transform 0.2s ease", + "transform": "scale(1)", "&.Mui-selected": { color: "white", backgroundImage: `linear-gradient(to right, ${colorNameToVar("InteractiveElementLeft")}, ${colorNameToVar("InteractiveElementRight")})`, @@ -24,6 +25,7 @@ export const ToggleButton = styled(ToggleButtonMUI)({ "&:hover": { outline: "none", borderColor: "transparent", + transform: "scale(1.03)", }, "&:focus-visible": { outline: "none", @@ -32,6 +34,7 @@ export const ToggleButton = styled(ToggleButtonMUI)({ "&:active": { outline: "none", borderColor: "transparent", + transform: "scale(1.06)", }, "&::-moz-focus-inner": { outline: "none", diff --git a/fission/src/ui/modals/configuring/SettingsModal.tsx b/fission/src/ui/modals/configuring/SettingsModal.tsx index 9e2ab7e340..3aa7751331 100644 --- a/fission/src/ui/modals/configuring/SettingsModal.tsx +++ b/fission/src/ui/modals/configuring/SettingsModal.tsx @@ -1,10 +1,8 @@ import React, { useState } from "react" -import { useModalControlContext } from "@/ui/ModalContext" import Modal, { ModalPropsImpl } from "@/components/Modal" import { FaGear } from "react-icons/fa6" import Label, { LabelSize } from "@/components/Label" import Dropdown from "@/components/Dropdown" -import Button from "@/components/Button" import Slider from "@/components/Slider" import Checkbox from "@/components/Checkbox" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" @@ -14,8 +12,6 @@ import { Box } from "@mui/material" import { Spacer } from "@/ui/components/StyledComponents" const SettingsModal: React.FC = ({ modalId }) => { - const { openModal } = useModalControlContext() - const [qualitySettings, setQualitySettings] = useState( PreferencesSystem.getGlobalPreference("QualitySettings") ) @@ -75,32 +71,35 @@ const SettingsModal: React.FC = ({ modalId }) => { setQualitySettings(selected) }} /> - {Spacer(5)} + {/** Theme editor disabled for now */} + {/* {Spacer(5)} + value={} + /> )} {
} + value={} onClick={() => setIsOpen(false)} />
@@ -97,7 +96,7 @@ const MainHUD: React.FC = () => { icon={SynthesisIcons.Gear} onClick={() => openModal("settings")} /> - {/* openModal("view")} @@ -108,8 +107,8 @@ const MainHUD: React.FC = () => { onClick={() => openModal("import-local-mirabuf")} /> openPanel("configure")} /> = ({ colorClass, size, value, pl return ( - + {LabelWithTooltip( + "Select parent node", + "Select the parent node for this object to follow. Click the button below, then click a part of the robot or field." + )} ) } @@ -98,7 +98,7 @@ const MainHUD: React.FC = () => {
} + value={} onClick={() => setIsOpen(false)} />
@@ -108,7 +108,11 @@ const MainHUD: React.FC = () => { larger={true} onClick={() => openPanel("import-mirabuf")} /> -
+ { openPanel("debug") }} /> -
+
{userInfo ? ( Date: Wed, 14 Aug 2024 11:05:02 -0700 Subject: [PATCH 254/344] removed vendordeps from git ignore --- simulation/SyntheSimJava/.gitignore | 4 +- .../SyntheSimJava/vendordeps/Phoenix6.json | 339 ++++++++++++++++++ .../SyntheSimJava/vendordeps/REVLib.json | 74 ++++ simulation/samples/JavaSample/.gitignore | 5 +- .../JavaSample/vendordeps/Phoenix6.json | 339 ++++++++++++++++++ .../samples/JavaSample/vendordeps/REVLib.json | 74 ++++ 6 files changed, 831 insertions(+), 4 deletions(-) create mode 100644 simulation/SyntheSimJava/vendordeps/Phoenix6.json create mode 100644 simulation/SyntheSimJava/vendordeps/REVLib.json create mode 100644 simulation/samples/JavaSample/vendordeps/Phoenix6.json create mode 100644 simulation/samples/JavaSample/vendordeps/REVLib.json diff --git a/simulation/SyntheSimJava/.gitignore b/simulation/SyntheSimJava/.gitignore index b7691f9c47..c73e0b2d0f 100644 --- a/simulation/SyntheSimJava/.gitignore +++ b/simulation/SyntheSimJava/.gitignore @@ -1,4 +1,3 @@ -*.json # Ignore Gradle project-specific cache directory .gradle .vscode @@ -9,5 +8,4 @@ ctre-sil/ bin/ .settings/ -.classpath - +.classpath \ No newline at end of file diff --git a/simulation/SyntheSimJava/vendordeps/Phoenix6.json b/simulation/SyntheSimJava/vendordeps/Phoenix6.json new file mode 100644 index 0000000000..032238505f --- /dev/null +++ b/simulation/SyntheSimJava/vendordeps/Phoenix6.json @@ -0,0 +1,339 @@ +{ + "fileName": "Phoenix6.json", + "name": "CTRE-Phoenix (v6)", + "version": "24.3.0", + "frcYear": 2024, + "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", + "mavenUrls": [ + "https://maven.ctr-electronics.com/release/" + ], + "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2024-latest.json", + "conflictsWith": [ + { + "uuid": "3fcf3402-e646-4fa6-971e-18afe8173b1a", + "errorMessage": "The combined Phoenix-6-And-5 vendordep is no longer supported. Please remove the vendordep and instead add both the latest Phoenix 6 vendordep and Phoenix 5 vendordep.", + "offlineFileName": "Phoenix6And5.json" + } + ], + "javaDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "wpiapi-java", + "version": "24.3.0" + } + ], + "jniDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "tools", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "tools-sim", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonSRX", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonFX", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simVictorSPX", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simPigeonIMU", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simCANCoder", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFX", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANcoder", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProPigeon2", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + } + ], + "cppDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "wpiapi-cpp", + "version": "24.3.0", + "libName": "CTRE_Phoenix6_WPI", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix6", + "artifactId": "tools", + "version": "24.3.0", + "libName": "CTRE_PhoenixTools", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "wpiapi-cpp-sim", + "version": "24.3.0", + "libName": "CTRE_Phoenix6_WPISim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "tools-sim", + "version": "24.3.0", + "libName": "CTRE_PhoenixTools_Sim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonSRX", + "version": "24.3.0", + "libName": "CTRE_SimTalonSRX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonFX", + "version": "24.3.0", + "libName": "CTRE_SimTalonFX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simVictorSPX", + "version": "24.3.0", + "libName": "CTRE_SimVictorSPX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simPigeonIMU", + "version": "24.3.0", + "libName": "CTRE_SimPigeonIMU", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simCANCoder", + "version": "24.3.0", + "libName": "CTRE_SimCANCoder", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFX", + "version": "24.3.0", + "libName": "CTRE_SimProTalonFX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANcoder", + "version": "24.3.0", + "libName": "CTRE_SimProCANcoder", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProPigeon2", + "version": "24.3.0", + "libName": "CTRE_SimProPigeon2", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + } + ] +} \ No newline at end of file diff --git a/simulation/SyntheSimJava/vendordeps/REVLib.json b/simulation/SyntheSimJava/vendordeps/REVLib.json new file mode 100644 index 0000000000..f85acd4054 --- /dev/null +++ b/simulation/SyntheSimJava/vendordeps/REVLib.json @@ -0,0 +1,74 @@ +{ + "fileName": "REVLib.json", + "name": "REVLib", + "version": "2024.2.4", + "frcYear": "2024", + "uuid": "3f48eb8c-50fe-43a6-9cb7-44c86353c4cb", + "mavenUrls": [ + "https://maven.revrobotics.com/" + ], + "jsonUrl": "https://software-metadata.revrobotics.com/REVLib-2024.json", + "javaDependencies": [ + { + "groupId": "com.revrobotics.frc", + "artifactId": "REVLib-java", + "version": "2024.2.4" + } + ], + "jniDependencies": [ + { + "groupId": "com.revrobotics.frc", + "artifactId": "REVLib-driver", + "version": "2024.2.4", + "skipInvalidPlatforms": true, + "isJar": false, + "validPlatforms": [ + "windowsx86-64", + "windowsx86", + "linuxarm64", + "linuxx86-64", + "linuxathena", + "linuxarm32", + "osxuniversal" + ] + } + ], + "cppDependencies": [ + { + "groupId": "com.revrobotics.frc", + "artifactId": "REVLib-cpp", + "version": "2024.2.4", + "libName": "REVLib", + "headerClassifier": "headers", + "sharedLibrary": false, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "windowsx86", + "linuxarm64", + "linuxx86-64", + "linuxathena", + "linuxarm32", + "osxuniversal" + ] + }, + { + "groupId": "com.revrobotics.frc", + "artifactId": "REVLib-driver", + "version": "2024.2.4", + "libName": "REVLibDriver", + "headerClassifier": "headers", + "sharedLibrary": false, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "windowsx86", + "linuxarm64", + "linuxx86-64", + "linuxathena", + "linuxarm32", + "osxuniversal" + ] + } + ] +} \ No newline at end of file diff --git a/simulation/samples/JavaSample/.gitignore b/simulation/samples/JavaSample/.gitignore index 0a7cba8930..4a2a0df950 100644 --- a/simulation/samples/JavaSample/.gitignore +++ b/simulation/samples/JavaSample/.gitignore @@ -1,4 +1,7 @@ -*.json +networktables.json +simgui-ds.json +simgui-window.json +simgui.json .gradle/ .vscode/ diff --git a/simulation/samples/JavaSample/vendordeps/Phoenix6.json b/simulation/samples/JavaSample/vendordeps/Phoenix6.json new file mode 100644 index 0000000000..032238505f --- /dev/null +++ b/simulation/samples/JavaSample/vendordeps/Phoenix6.json @@ -0,0 +1,339 @@ +{ + "fileName": "Phoenix6.json", + "name": "CTRE-Phoenix (v6)", + "version": "24.3.0", + "frcYear": 2024, + "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", + "mavenUrls": [ + "https://maven.ctr-electronics.com/release/" + ], + "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2024-latest.json", + "conflictsWith": [ + { + "uuid": "3fcf3402-e646-4fa6-971e-18afe8173b1a", + "errorMessage": "The combined Phoenix-6-And-5 vendordep is no longer supported. Please remove the vendordep and instead add both the latest Phoenix 6 vendordep and Phoenix 5 vendordep.", + "offlineFileName": "Phoenix6And5.json" + } + ], + "javaDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "wpiapi-java", + "version": "24.3.0" + } + ], + "jniDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "tools", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "tools-sim", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonSRX", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonFX", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simVictorSPX", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simPigeonIMU", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simCANCoder", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFX", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANcoder", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProPigeon2", + "version": "24.3.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + } + ], + "cppDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "wpiapi-cpp", + "version": "24.3.0", + "libName": "CTRE_Phoenix6_WPI", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix6", + "artifactId": "tools", + "version": "24.3.0", + "libName": "CTRE_PhoenixTools", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "wpiapi-cpp-sim", + "version": "24.3.0", + "libName": "CTRE_Phoenix6_WPISim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "tools-sim", + "version": "24.3.0", + "libName": "CTRE_PhoenixTools_Sim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonSRX", + "version": "24.3.0", + "libName": "CTRE_SimTalonSRX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonFX", + "version": "24.3.0", + "libName": "CTRE_SimTalonFX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simVictorSPX", + "version": "24.3.0", + "libName": "CTRE_SimVictorSPX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simPigeonIMU", + "version": "24.3.0", + "libName": "CTRE_SimPigeonIMU", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simCANCoder", + "version": "24.3.0", + "libName": "CTRE_SimCANCoder", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFX", + "version": "24.3.0", + "libName": "CTRE_SimProTalonFX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANcoder", + "version": "24.3.0", + "libName": "CTRE_SimProCANcoder", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProPigeon2", + "version": "24.3.0", + "libName": "CTRE_SimProPigeon2", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + } + ] +} \ No newline at end of file diff --git a/simulation/samples/JavaSample/vendordeps/REVLib.json b/simulation/samples/JavaSample/vendordeps/REVLib.json new file mode 100644 index 0000000000..f85acd4054 --- /dev/null +++ b/simulation/samples/JavaSample/vendordeps/REVLib.json @@ -0,0 +1,74 @@ +{ + "fileName": "REVLib.json", + "name": "REVLib", + "version": "2024.2.4", + "frcYear": "2024", + "uuid": "3f48eb8c-50fe-43a6-9cb7-44c86353c4cb", + "mavenUrls": [ + "https://maven.revrobotics.com/" + ], + "jsonUrl": "https://software-metadata.revrobotics.com/REVLib-2024.json", + "javaDependencies": [ + { + "groupId": "com.revrobotics.frc", + "artifactId": "REVLib-java", + "version": "2024.2.4" + } + ], + "jniDependencies": [ + { + "groupId": "com.revrobotics.frc", + "artifactId": "REVLib-driver", + "version": "2024.2.4", + "skipInvalidPlatforms": true, + "isJar": false, + "validPlatforms": [ + "windowsx86-64", + "windowsx86", + "linuxarm64", + "linuxx86-64", + "linuxathena", + "linuxarm32", + "osxuniversal" + ] + } + ], + "cppDependencies": [ + { + "groupId": "com.revrobotics.frc", + "artifactId": "REVLib-cpp", + "version": "2024.2.4", + "libName": "REVLib", + "headerClassifier": "headers", + "sharedLibrary": false, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "windowsx86", + "linuxarm64", + "linuxx86-64", + "linuxathena", + "linuxarm32", + "osxuniversal" + ] + }, + { + "groupId": "com.revrobotics.frc", + "artifactId": "REVLib-driver", + "version": "2024.2.4", + "libName": "REVLibDriver", + "headerClassifier": "headers", + "sharedLibrary": false, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "windowsx86", + "linuxarm64", + "linuxx86-64", + "linuxathena", + "linuxarm32", + "osxuniversal" + ] + } + ] +} \ No newline at end of file From 9627c8aa765e0d443555cfce12db579e11999718 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 14 Aug 2024 11:57:28 -0700 Subject: [PATCH 255/344] Formatting? --- fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx index 7e56fd27a0..b62db8debb 100644 --- a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx @@ -61,7 +61,7 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { PreferencesSystem.getRobotPreferences(robot.assemblyName).driveVelocity = vel PreferencesSystem.getRobotPreferences(robot.assemblyName).driveAcceleration = force } else { - ((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel + ;((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force // Preferences From e4997bc5ed852a7566da00b67a6b54a58a21694d Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 14 Aug 2024 12:02:47 -0700 Subject: [PATCH 256/344] Formatting? pt. 2 --- fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx index b62db8debb..f633b2e83f 100644 --- a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx @@ -61,7 +61,8 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { PreferencesSystem.getRobotPreferences(robot.assemblyName).driveVelocity = vel PreferencesSystem.getRobotPreferences(robot.assemblyName).driveAcceleration = force } else { - ;((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel + // prettier-ignore + ((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force // Preferences From 3f4ba0ba11333b5f6ee0ab9d1761c9a640dbc0c9 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 14 Aug 2024 12:35:53 -0700 Subject: [PATCH 257/344] Formatting :/ pt. 3 --- .../ui/panels/configuring/ConfigureSubsystemsPanel.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx index f633b2e83f..2b44012616 100644 --- a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx @@ -61,10 +61,6 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { PreferencesSystem.getRobotPreferences(robot.assemblyName).driveVelocity = vel PreferencesSystem.getRobotPreferences(robot.assemblyName).driveAcceleration = force } else { - // prettier-ignore - ((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel - ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force - // Preferences if (driver.info && driver.info.name) { const removedMotor = PreferencesSystem.getRobotPreferences(robot.assemblyName).motors @@ -82,6 +78,10 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor } + + // Edit subsystems + ((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel + ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force } PreferencesSystem.savePreferences() From eea49af801a025fc6c8c8610d9415f8f6321c774 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 14 Aug 2024 12:39:39 -0700 Subject: [PATCH 258/344] Formatting -.- pt. 4 --- .../panels/configuring/ConfigureSubsystemsPanel.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx index 2b44012616..6efdeff732 100644 --- a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx @@ -61,6 +61,14 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { PreferencesSystem.getRobotPreferences(robot.assemblyName).driveVelocity = vel PreferencesSystem.getRobotPreferences(robot.assemblyName).driveAcceleration = force } else { + // A function because ES Lint and Prettier are fighting over semicolon formatting + const editMotors = () => { + ((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel + ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force + } + + editMotors() + // Preferences if (driver.info && driver.info.name) { const removedMotor = PreferencesSystem.getRobotPreferences(robot.assemblyName).motors @@ -79,9 +87,7 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor } - // Edit subsystems - ((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel - ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force + } PreferencesSystem.savePreferences() From 48c23f0e9501170dc3caf472ae53e01f50eb1b87 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 14 Aug 2024 12:45:51 -0700 Subject: [PATCH 259/344] Formatting :D pt. 5 --- .../panels/configuring/ConfigureSubsystemsPanel.tsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx index 6efdeff732..18848c4a9d 100644 --- a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx @@ -61,14 +61,6 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { PreferencesSystem.getRobotPreferences(robot.assemblyName).driveVelocity = vel PreferencesSystem.getRobotPreferences(robot.assemblyName).driveAcceleration = force } else { - // A function because ES Lint and Prettier are fighting over semicolon formatting - const editMotors = () => { - ((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel - ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force - } - - editMotors() - // Preferences if (driver.info && driver.info.name) { const removedMotor = PreferencesSystem.getRobotPreferences(robot.assemblyName).motors @@ -87,7 +79,9 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { 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() From 84039e0f1f4d2997acac757ee78641cff0c0b1d9 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 14 Aug 2024 12:49:17 -0700 Subject: [PATCH 260/344] Formatting? pt. 6 --- fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx index 18848c4a9d..12e3f032ce 100644 --- a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx @@ -79,7 +79,7 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor } - // eslint-disable-next-line no-extra-semi + // eslint-disable-next-line ;((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force } From ef86b555a4a249da337d0e2b92e3f98412419334 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 14 Aug 2024 15:32:51 -0700 Subject: [PATCH 261/344] Formatting... pt. 7 --- .../src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx index 12e3f032ce..8ef674b3cf 100644 --- a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx @@ -79,7 +79,9 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor } - // eslint-disable-next-line + // This line is purely for ES Lint and Prettier to agree on formatting the semicolon below. + PreferencesSystem.savePreferences() + ;((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force } From 6360df0e6c182d0be0cf49aec232eabdcefaa1ca Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 14 Aug 2024 15:38:33 -0700 Subject: [PATCH 262/344] Formatting :D pt. 8 --- .../configuring/ConfigureSubsystemsPanel.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx index 8ef674b3cf..afa71cd9c0 100644 --- a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx @@ -79,9 +79,7 @@ const SubsystemRow: React.FC = ({ robot, driver }) => { PreferencesSystem.getRobotPreferences(robot.assemblyName).motors = removedMotor } - // This line is purely for ES Lint and Prettier to agree on formatting the semicolon below. - PreferencesSystem.savePreferences() - + // eslint-disable-next-line no-extra-semi ;((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = vel ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = force } @@ -192,13 +190,14 @@ const ConfigureSubsystemsPanel: React.FC = ({ panelId, openLocat return false })[0] if (motor) { - ((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = motor.maxVelocity - ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = - PreferencesSystem.getGlobalPreference("SubsystemGravity") - ? motor.maxForce - : driver instanceof SliderDriver - ? 500 - : 100 + // 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 } } } From a14ab94c51e44de1955d7fae4ac28d4327f551de Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 14 Aug 2024 15:40:55 -0700 Subject: [PATCH 263/344] Formatting ._. pt. 9 --- .../src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx index afa71cd9c0..cf343f012f 100644 --- a/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureSubsystemsPanel.tsx @@ -194,8 +194,8 @@ const ConfigureSubsystemsPanel: React.FC = ({ panelId, openLocat const forcePref = PreferencesSystem.getGlobalPreference("SubsystemGravity") ? motor.maxForce : driver instanceof SliderDriver - ? 500 - : 100 + ? 500 + : 100 ;((driver as SliderDriver) || (driver as HingeDriver)).maxVelocity = motor.maxVelocity ;((driver as SliderDriver) || (driver as HingeDriver)).maxForce = forcePref } From f5f0d92beb9ea6d9d4b3c3bee5bcf6f24778b8e2 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:23:39 -0700 Subject: [PATCH 264/344] Moved Mac install location into `ApplicationPlugins` --- exporter/SynthesisFusionAddin/Synthesis.py | 4 ++ exporter/SynthesisFusionAddin/src/Logging.py | 25 ++++++---- .../src/Parser/SynthesisParser/Components.py | 2 +- .../Parser/SynthesisParser/JointHierarchy.py | 2 +- .../src/Parser/SynthesisParser/Joints.py | 2 +- .../src/Parser/SynthesisParser/Materials.py | 2 +- .../src/Parser/SynthesisParser/Parser.py | 2 +- .../SynthesisParser/PhysicalProperties.py | 2 +- .../src/Parser/SynthesisParser/RigidGroup.py | 2 +- exporter/SynthesisFusionAddin/src/Types.py | 4 +- .../SynthesisFusionAddin/src/UI/Camera.py | 9 ++-- .../src/UI/ConfigCommand.py | 10 ++-- .../src/UI/Configuration/SerialCommand.py | 7 ++- exporter/SynthesisFusionAddin/src/Util.py | 7 +++ exporter/SynthesisFusionAddin/src/__init__.py | 24 +++++++++- installer/OSX/Resources/LICENSE.txt | 1 - installer/OSX/build.sh | 5 +- .../OSX/synthesis.bundle/Contents/Info.plist | 46 +++++++++++++++++++ .../OSX/synthesis.bundle/PackageContents.xml | 24 ++++++++++ 19 files changed, 151 insertions(+), 29 deletions(-) create mode 100644 exporter/SynthesisFusionAddin/src/Util.py delete mode 100644 installer/OSX/Resources/LICENSE.txt create mode 100644 installer/OSX/synthesis.bundle/Contents/Info.plist create mode 100644 installer/OSX/synthesis.bundle/PackageContents.xml diff --git a/exporter/SynthesisFusionAddin/Synthesis.py b/exporter/SynthesisFusionAddin/Synthesis.py index deba3c9551..80e43b4e3e 100644 --- a/exporter/SynthesisFusionAddin/Synthesis.py +++ b/exporter/SynthesisFusionAddin/Synthesis.py @@ -15,6 +15,7 @@ try: # Attempt to import required pip dependencies to verify their installation. import requests + from proto.proto_out import ( assembly_pb2, joint_pb2, @@ -69,6 +70,9 @@ def stop(_): Arguments: **context** *context* -- Fusion Data. """ + sys.path.remove(os.path.dirname(os.path.abspath(__file__))) + sys.path.remove(os.path.abspath(os.path.join(os.path.dirname(__file__), "proto", "proto_out"))) + unregister_all() app = adsk.core.Application.get() diff --git a/exporter/SynthesisFusionAddin/src/Logging.py b/exporter/SynthesisFusionAddin/src/Logging.py index e5f352f480..c2fbad7b9d 100644 --- a/exporter/SynthesisFusionAddin/src/Logging.py +++ b/exporter/SynthesisFusionAddin/src/Logging.py @@ -11,8 +11,9 @@ import adsk.core -from src import INTERNAL_ID +from src import INTERNAL_ID, IS_RELEASE, SUPPORT_PATH from src.UI.OsHelper import getOSPath +from src.Util import makeDirectories MAX_LOG_FILES_TO_KEEP = 10 TIMING_LEVEL = 25 @@ -30,14 +31,19 @@ def cleanupHandlers(self) -> None: def setupLogger() -> SynthesisLogger: now = datetime.now().strftime("%H-%M-%S") today = date.today() - logFileFolder = getOSPath(f"{pathlib.Path(__file__).parent.parent}", "logs") - logFiles = [os.path.join(logFileFolder, file) for file in os.listdir(logFileFolder) if file.endswith(".log")] - logFiles.sort() - if len(logFiles) >= MAX_LOG_FILES_TO_KEEP: - for file in logFiles[: len(logFiles) - MAX_LOG_FILES_TO_KEEP]: - os.remove(file) - - logFileName = f"{logFileFolder}{getOSPath(f'{INTERNAL_ID}-{today}-{now}.log')}" + if not IS_RELEASE: + logFileFolder = makeDirectories(getOSPath(f"{pathlib.Path(__file__).parent.parent}", "logs")) + else: + logFileFolder = makeDirectories(f"{SUPPORT_PATH}/Logs/") + + if not IS_RELEASE: # TODO: Decide if this is what I want to do or not + logFiles = [os.path.join(logFileFolder, file) for file in os.listdir(logFileFolder) if file.endswith(".log")] + logFiles.sort() + if len(logFiles) >= MAX_LOG_FILES_TO_KEEP: + for file in logFiles[: len(logFiles) - MAX_LOG_FILES_TO_KEEP]: + os.remove(file) + + logFileName = f"{logFileFolder}{getOSPath(f'{today}-{now}.log')}" logHandler = logging.handlers.WatchedFileHandler(logFileName, mode="w") logHandler.setFormatter(logging.Formatter("%(name)s - %(levelname)s - %(message)s")) @@ -46,6 +52,7 @@ def setupLogger() -> SynthesisLogger: logger = getLogger(INTERNAL_ID) logger.setLevel(10) # Debug logger.addHandler(logHandler) + return cast(SynthesisLogger, logger) diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py index aea709f04a..fbdba1d891 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py @@ -1,8 +1,8 @@ # Contains all of the logic for mapping the Components / Occurrences import adsk.core import adsk.fusion -from proto.proto_out import assembly_pb2, joint_pb2, material_pb2, types_pb2 +from proto.proto_out import assembly_pb2, joint_pb2, material_pb2, types_pb2 from src.Logging import logFailure from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser import PhysicalProperties diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py index cf8c5e04b0..2bd563b489 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py @@ -3,8 +3,8 @@ import adsk.core import adsk.fusion -from proto.proto_out import joint_pb2, types_pb2 +from proto.proto_out import joint_pb2, types_pb2 from src import gm from src.Logging import getLogger, logFailure from src.Parser.ExporterOptions import ExporterOptions diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py index 6b1650b3f9..0d0cdea910 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py @@ -28,8 +28,8 @@ import adsk.core import adsk.fusion -from proto.proto_out import assembly_pb2, joint_pb2, signal_pb2, types_pb2 +from proto.proto_out import assembly_pb2, joint_pb2, signal_pb2, types_pb2 from src.Logging import getLogger from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser.PDMessage import PDMessage diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py index a077c764b7..d8538b5d9b 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py @@ -1,6 +1,6 @@ import adsk -from proto.proto_out import material_pb2 +from proto.proto_out import material_pb2 from src.Logging import logFailure from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser.PDMessage import PDMessage diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py index 338f5a300a..37148fb622 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py @@ -4,8 +4,8 @@ import adsk.core import adsk.fusion from google.protobuf.json_format import MessageToJson -from proto.proto_out import assembly_pb2, types_pb2 +from proto.proto_out import assembly_pb2, types_pb2 from src import gm from src.APS.APS import getAuth, upload_mirabuf from src.Logging import getLogger, logFailure, timed diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py index b178bcb3bb..15e025f4c7 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py @@ -19,8 +19,8 @@ from typing import Union import adsk -from proto.proto_out import types_pb2 +from proto.proto_out import types_pb2 from src.Logging import logFailure diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py index 8516cefae6..b90a2167fe 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py @@ -16,8 +16,8 @@ import adsk.core import adsk.fusion -from proto.proto_out import assembly_pb2 +from proto.proto_out import assembly_pb2 from src.Logging import logFailure diff --git a/exporter/SynthesisFusionAddin/src/Types.py b/exporter/SynthesisFusionAddin/src/Types.py index 1aca3a5162..d8215d33b2 100644 --- a/exporter/SynthesisFusionAddin/src/Types.py +++ b/exporter/SynthesisFusionAddin/src/Types.py @@ -3,7 +3,9 @@ import platform from dataclasses import dataclass, field, fields, is_dataclass from enum import Enum, EnumType -from typing import Union, get_origin +from typing import Literal, TypeAlias, Union, get_origin + +OperatingSystemString: TypeAlias = Literal["Windows", "Darwin", "Linux"] # Not 100% sure what this is for - Brandon JointParentType = Enum("JointParentType", ["ROOT", "END"]) diff --git a/exporter/SynthesisFusionAddin/src/UI/Camera.py b/exporter/SynthesisFusionAddin/src/UI/Camera.py index 02de04083a..d59b1c6e28 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Camera.py +++ b/exporter/SynthesisFusionAddin/src/UI/Camera.py @@ -2,8 +2,10 @@ import adsk.core +from src import SUPPORT_PATH from src.Logging import logFailure from src.Types import OString +from src.Util import makeDirectories @logFailure @@ -21,9 +23,10 @@ def captureThumbnail(size=250): ) # remove whitespace from just the filename ) - path = OString.ThumbnailPath(name) + path = makeDirectories(f"{SUPPORT_PATH}/Resources/Icons/") + path += name - saveOptions = adsk.core.SaveImageFileOptions.create(str(path.getPath())) + saveOptions = adsk.core.SaveImageFileOptions.create(path) saveOptions.height = size saveOptions.width = size saveOptions.isAntiAliased = True @@ -36,7 +39,7 @@ def captureThumbnail(size=250): app.activeViewport.saveAsImageFileWithOptions(saveOptions) app.activeViewport.camera = originalCamera - return str(path.getPath()) + return path def clearIconCache() -> None: diff --git a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py index c50c7feed2..8cd9d31bad 100644 --- a/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py @@ -305,9 +305,13 @@ def notify(self, args): # save was canceled return - updatedPath = pathlib.Path(savepath).parent - if updatedPath != self.current.filePath: - self.current.filePath = str(updatedPath) + # Transition: AARD-1742 + # With the addition of a 'release' build the fusion exporter will not have permissions within the sourced + # folder. Because of this we cannot use this kind of tmp path anymore. This code was already unused and + # should be removed. + # updatedPath = pathlib.Path(savepath).parent + # if updatedPath != self.current.filePath: + # self.current.filePath = str(updatedPath) else: savepath = processedFileName diff --git a/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py b/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py index 663afe9337..3163dc03b2 100644 --- a/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py +++ b/exporter/SynthesisFusionAddin/src/UI/Configuration/SerialCommand.py @@ -36,7 +36,12 @@ class SerialCommand: def __init__(self): self.general = General() self.advanced = Advanced() - self.filePath = generateFilePath() + + # Transition: AARD-1742 + # With the addition of a 'release' build the fusion exporter will not have permissions within the sourced + # folder. Because of this we cannot use this kind of tmp path anymore. This code was already unused and + # should be removed. + # self.filePath = generateFilePath() def toJSON(self) -> str: """Converts this class into a json object that can be written to the object data diff --git a/exporter/SynthesisFusionAddin/src/Util.py b/exporter/SynthesisFusionAddin/src/Util.py new file mode 100644 index 0000000000..3980e85975 --- /dev/null +++ b/exporter/SynthesisFusionAddin/src/Util.py @@ -0,0 +1,7 @@ +import os + + +def makeDirectories(directory: str | os.PathLike[str]) -> str | os.PathLike[str]: + """Ensures than an input directory exists and attempts to create it if it doesn't.""" + os.makedirs(directory, exist_ok=True) + return directory diff --git a/exporter/SynthesisFusionAddin/src/__init__.py b/exporter/SynthesisFusionAddin/src/__init__.py index 1e426279bb..ecfe620c72 100644 --- a/exporter/SynthesisFusionAddin/src/__init__.py +++ b/exporter/SynthesisFusionAddin/src/__init__.py @@ -1,17 +1,37 @@ import os import platform +from pathlib import Path from src.GlobalManager import GlobalManager +from src.Types import OperatingSystemString +from src.Util import makeDirectories APP_NAME = "Synthesis" APP_TITLE = "Synthesis Robot Exporter" DESCRIPTION = "Exports files from Fusion into the Synthesis Format" INTERNAL_ID = "Synthesis" ADDIN_PATH = os.path.dirname(os.path.realpath(__file__)) +IS_RELEASE = str(Path(os.path.abspath(__file__)).parent.parent.parent.parent).split("/")[-1] == "ApplicationPlugins" -SYSTEM = platform.system() +SYSTEM: OperatingSystemString = platform.system() assert SYSTEM != "Linux" +if SYSTEM == "Windows": + SUPPORT_PATH = makeDirectories(f"{os.getenv('APPDATA')}\\Autodesk\\Synthesis\\") +else: + assert SYSTEM == "Darwin" + SUPPORT_PATH = makeDirectories(f"{os.path.expanduser('~')}/.config/Autodesk/Synthesis/") + gm = GlobalManager() -__all__ = ["APP_NAME", "APP_TITLE", "DESCRIPTION", "INTERNAL_ID", "ADDIN_PATH", "SYSTEM", "gm"] +__all__ = [ + "APP_NAME", + "APP_TITLE", + "DESCRIPTION", + "INTERNAL_ID", + "ADDIN_PATH", + "IS_RELEASE", + "SYSTEM", + "SUPPORT_PATH", + "gm", +] diff --git a/installer/OSX/Resources/LICENSE.txt b/installer/OSX/Resources/LICENSE.txt deleted file mode 100644 index b10760de2f..0000000000 --- a/installer/OSX/Resources/LICENSE.txt +++ /dev/null @@ -1 +0,0 @@ -../../../LICENSE.txt diff --git a/installer/OSX/build.sh b/installer/OSX/build.sh index 673c7c80a7..3be9807fa7 100755 --- a/installer/OSX/build.sh +++ b/installer/OSX/build.sh @@ -1,10 +1,11 @@ #!/bin/bash -FUSION_ADDIN_LOCATION=~/Library/Application\ Support/Autodesk/Autodesk\ Fusion\ 360/API/AddIns/ +FUSION_ADDIN_LOCATION=~/Library/Application\ Support/Autodesk/ApplicationPlugins/ EXPORTER_SOURCE_DIR=../../exporter/SynthesisFusionAddin/ mkdir -p tmp/ -cp -r "$EXPORTER_SOURCE_DIR"/* tmp/ +cp -r synthesis.bundle tmp/ +cp -r "$EXPORTER_SOURCE_DIR"/* tmp/synthesis.bundle/Contents/ pkgbuild --root tmp/ --identifier com.Autodesk.Synthesis --version 2.0.0 --install-location "$FUSION_ADDIN_LOCATION" SynthesisExporter.pkg productbuild --distribution distribution.xml --package-path . SynthesisExporterInstaller.pkg diff --git a/installer/OSX/synthesis.bundle/Contents/Info.plist b/installer/OSX/synthesis.bundle/Contents/Info.plist new file mode 100644 index 0000000000..5c6c161002 --- /dev/null +++ b/installer/OSX/synthesis.bundle/Contents/Info.plist @@ -0,0 +1,46 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleGetInfoString + Synthesis addin for Fusion. + CFBundleIdentifier + Synthesis Exporter for Autodesk Fusion 360 + CFBundleShortVersionString + 2.0.0 + IFMajorVersion + 1 + IFMinorVersion + 0 + IFPkgFlagAllowBackRev + + IFPkgFlagAuthorizationAction + AdminAuthorization + IFPkgFlagBackgroundAlignment + topleft + IFPkgFlagBackgroundScaling + none + IFPkgFlagDefaultLocation + / + IFPkgFlagFollowLinks + + IFPkgFlagInstallFat + + IFPkgFlagIsRequired + + IFPkgFlagOverwritePermissions + + IFPkgFlagRelocatable + + IFPkgFlagRestartAction + NoRestart + IFPkgFlagRootVolumeOnly + + IFPkgFlagUpdateInstalledLanguages + + IFPkgFormatVersion + 0.1000000014901161 + + diff --git a/installer/OSX/synthesis.bundle/PackageContents.xml b/installer/OSX/synthesis.bundle/PackageContents.xml new file mode 100644 index 0000000000..7e3ffb9484 --- /dev/null +++ b/installer/OSX/synthesis.bundle/PackageContents.xml @@ -0,0 +1,24 @@ + + + + + + + + + + From b801c9b7eb9d33038838231a7419946fb0b6e65c Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:28:29 -0700 Subject: [PATCH 265/344] Resolve merge conflicts --- .../src/Parser/SynthesisParser/Components.py | 1 - .../src/Parser/SynthesisParser/JointHierarchy.py | 1 - .../SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py | 1 - .../SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py | 1 - .../SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py | 1 - .../src/Parser/SynthesisParser/PhysicalProperties.py | 1 - .../src/Parser/SynthesisParser/RigidGroup.py | 1 - 7 files changed, 7 deletions(-) diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py index ba1212ca48..6190d55a99 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Components.py @@ -2,7 +2,6 @@ import adsk.core import adsk.fusion -from proto.proto_out import assembly_pb2, joint_pb2, material_pb2, types_pb2 from src.Logging import logFailure from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser import PhysicalProperties diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py index 61e2bf3a5e..26dae4f2a4 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/JointHierarchy.py @@ -4,7 +4,6 @@ import adsk.core import adsk.fusion -from proto.proto_out import joint_pb2, types_pb2 from src import gm from src.Logging import getLogger, logFailure from src.Parser.ExporterOptions import ExporterOptions diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py index b10e23b2a0..7a3e0da3b9 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Joints.py @@ -29,7 +29,6 @@ import adsk.core import adsk.fusion -from proto.proto_out import assembly_pb2, joint_pb2, signal_pb2, types_pb2 from src.Logging import getLogger from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser.PDMessage import PDMessage diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py index c3ce4d5229..22b42180a2 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Materials.py @@ -1,6 +1,5 @@ import adsk -from proto.proto_out import material_pb2 from src.Logging import logFailure from src.Parser.ExporterOptions import ExporterOptions from src.Parser.SynthesisParser.PDMessage import PDMessage diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py index 7a3a3ecf37..c44fd50fdb 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/Parser.py @@ -5,7 +5,6 @@ import adsk.fusion from google.protobuf.json_format import MessageToJson -from proto.proto_out import assembly_pb2, types_pb2 from src import gm from src.APS.APS import getAuth, upload_mirabuf from src.Logging import getLogger, logFailure, timed diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py index 23634cc156..c19b0bd6b3 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/PhysicalProperties.py @@ -20,7 +20,6 @@ import adsk -from proto.proto_out import types_pb2 from src.Logging import logFailure from src.Proto import types_pb2 diff --git a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py index c59aab9f34..78da93ffec 100644 --- a/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py +++ b/exporter/SynthesisFusionAddin/src/Parser/SynthesisParser/RigidGroup.py @@ -17,7 +17,6 @@ import adsk.core import adsk.fusion -from proto.proto_out import assembly_pb2 from src.Logging import logFailure from src.Proto import assembly_pb2 From 6d3dfedaabb2622d90376556d55052bee87cd928 Mon Sep 17 00:00:00 2001 From: BrandonPacewic <92102436+BrandonPacewic@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:32:48 -0700 Subject: [PATCH 266/344] Added a step to make the application plugins folder if it does not already exist --- installer/OSX/Scripts/preinstall | 7 +++++++ installer/OSX/build.sh | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100755 installer/OSX/Scripts/preinstall diff --git a/installer/OSX/Scripts/preinstall b/installer/OSX/Scripts/preinstall new file mode 100755 index 0000000000..014f372637 --- /dev/null +++ b/installer/OSX/Scripts/preinstall @@ -0,0 +1,7 @@ +#!/bin/bash + +FUSION_ADDIN_LOCATION=~/Library/Application\ Support/Autodesk/ApplicationPlugins/ + +if [ ! -d "$FUSION_ADDIN_LOCATION" ]; then + mkdir -p "$FUSION_ADDIN_LOCATION" +fi diff --git a/installer/OSX/build.sh b/installer/OSX/build.sh index 3be9807fa7..9c1b704fbd 100755 --- a/installer/OSX/build.sh +++ b/installer/OSX/build.sh @@ -7,7 +7,7 @@ mkdir -p tmp/ cp -r synthesis.bundle tmp/ cp -r "$EXPORTER_SOURCE_DIR"/* tmp/synthesis.bundle/Contents/ -pkgbuild --root tmp/ --identifier com.Autodesk.Synthesis --version 2.0.0 --install-location "$FUSION_ADDIN_LOCATION" SynthesisExporter.pkg +pkgbuild --root tmp/ --identifier com.Autodesk.Synthesis --scripts Scripts/ --version 2.0.0 --install-location "$FUSION_ADDIN_LOCATION" SynthesisExporter.pkg productbuild --distribution distribution.xml --package-path . SynthesisExporterInstaller.pkg rm SynthesisExporter.pkg From 44a2b135bfbed9fe634aed8a3c5be5c679565063 Mon Sep 17 00:00:00 2001 From: KyroVibe Date: Thu, 15 Aug 2024 11:45:39 -0600 Subject: [PATCH 267/344] Remove stats in top-left corner when built. --- fission/src/Synthesis.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 0e2fe2a29b..62c055c101 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -165,7 +165,7 @@ function Synthesis() { closeAllPanels={closeAllPanels} > - + {panelElements.length > 0 && panelElements} From fcac0cbd1b6ce3db07c11b18c8aa4c2f0237bf00 Mon Sep 17 00:00:00 2001 From: LucaHaverty Date: Thu, 15 Aug 2024 10:51:51 -0700 Subject: [PATCH 268/344] Input system, prefs system, select menu, and button unit tests --- fission/package.json | 3 + fission/src/systems/input/InputSystem.ts | 2 +- fission/src/test/InputSystem.test.ts | 42 +++++++--- fission/src/test/PreferencesSystem.test.ts | 34 ++++++++ fission/src/test/ui/Button.test.tsx | 29 +++++++ fission/src/test/ui/SelectMenu.test.tsx | 81 +++++++++++++++++++ fission/src/ui/components/Button.tsx | 15 +++- fission/src/ui/components/Checkbox.tsx | 1 + fission/src/ui/components/SelectMenu.tsx | 7 +- .../src/ui/components/StyledComponents.tsx | 14 ++-- 10 files changed, 209 insertions(+), 19 deletions(-) create mode 100644 fission/src/test/PreferencesSystem.test.ts create mode 100644 fission/src/test/ui/Button.test.tsx create mode 100644 fission/src/test/ui/SelectMenu.test.tsx diff --git a/fission/package.json b/fission/package.json index 6e1ae08b2b..e305e855a6 100644 --- a/fission/package.json +++ b/fission/package.json @@ -45,6 +45,9 @@ "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", "@mui/material": "^5.15.6", + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.0.0", + "@testing-library/user-event": "^14.5.2", "@types/node": "^20.4.4", "@types/pako": "^2.0.3", "@types/react": "^18.2.47", diff --git a/fission/src/systems/input/InputSystem.ts b/fission/src/systems/input/InputSystem.ts index d56aadd692..3be39b564a 100644 --- a/fission/src/systems/input/InputSystem.ts +++ b/fission/src/systems/input/InputSystem.ts @@ -241,7 +241,7 @@ class InputSystem extends WorldSystem { } // Returns true if two modifier states are identical - private static compareModifiers(state1: ModifierState, state2: ModifierState): boolean { + public static compareModifiers(state1: ModifierState, state2: ModifierState): boolean { if (!state1 || !state2) return false return ( diff --git a/fission/src/test/InputSystem.test.ts b/fission/src/test/InputSystem.test.ts index 768ec5b930..fc63f5e829 100644 --- a/fission/src/test/InputSystem.test.ts +++ b/fission/src/test/InputSystem.test.ts @@ -1,10 +1,10 @@ import { test, describe, assert, expect } from "vitest" -import InputSystem from "@/systems/input/InputSystem" +import InputSystem, { EmptyModifierState, ModifierState } from "@/systems/input/InputSystem" import InputSchemeManager from "@/systems/input/InputSchemeManager" import DefaultInputs from "@/systems/input/DefaultInputs" -describe("Input scheme manager checks", () => { - test("Available schemes", () => { +describe("Input Scheme Manager Checks", () => { + test("Available Schemes", () => { assert(InputSchemeManager.availableInputSchemes[0].schemeName == DefaultInputs.ernie().schemeName) assert(InputSchemeManager.defaultInputSchemes.length >= 1) @@ -13,13 +13,13 @@ describe("Input scheme manager checks", () => { expect(InputSchemeManager.availableInputSchemes.length).toBe(startingLength + 1) }) - test("Add a custom scheme", () => { + test("Add a Custom Scheme", () => { const startingLength = InputSchemeManager.availableInputSchemes.length InputSchemeManager.addCustomScheme(DefaultInputs.newBlankScheme) assert((InputSchemeManager.availableInputSchemes.length = startingLength + 1)) }) - test("Get random names", () => { + test("Get Random Names", () => { const names: string[] = [] for (let i = 0; i < 20; i++) { const name = InputSchemeManager.randomAvailableName @@ -37,14 +37,14 @@ describe("Input scheme manager checks", () => { }) }) -describe("Input system checks", () => { - new InputSystem() +describe("Input System Checks", () => { + const inputSystem = new InputSystem() - test("Brain map exists", () => { + test("Brain Map Exists?", () => { assert(InputSystem.brainIndexSchemeMap != undefined) }) - test("Inputs are zero", () => { + test("Inputs are Zero", () => { expect(InputSystem.getInput("arcadeDrive", 0)).toBe(0) expect(InputSystem.getGamepadAxis(0)).toBe(0) expect(InputSystem.getInput("randomInputThatDoesNotExist", 1273)).toBe(0) @@ -52,4 +52,28 @@ describe("Input system checks", () => { expect(InputSystem.isKeyPressed("ajhsekff")).toBe(false) expect(InputSystem.isGamepadButtonPressed(1)).toBe(false) }) + + test("Modifier State Comparison", () => { + const allFalse: ModifierState = { + alt: false, + ctrl: false, + shift: false, + meta: false, + } + + const differentState: ModifierState = { + alt: false, + ctrl: true, + shift: false, + meta: true, + } + + inputSystem.Update(-1) + + expect(InputSystem.compareModifiers(allFalse, EmptyModifierState)).toBe(true) + expect(InputSystem.compareModifiers(allFalse, InputSystem.currentModifierState)).toBe(true) + expect(InputSystem.compareModifiers(differentState, InputSystem.currentModifierState)).toBe(false) + expect(InputSystem.compareModifiers(differentState, differentState)).toBe(true) + expect(InputSystem.compareModifiers(differentState, allFalse)).toBe(false) + }) }) diff --git a/fission/src/test/PreferencesSystem.test.ts b/fission/src/test/PreferencesSystem.test.ts new file mode 100644 index 0000000000..34f8d73661 --- /dev/null +++ b/fission/src/test/PreferencesSystem.test.ts @@ -0,0 +1,34 @@ +import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import { test, describe, expect } from "vitest" + +describe("Preferences System", () => { + test("Settings without saving", () => { + PreferencesSystem.setGlobalPreference("ZoomSensitivity", 15) + PreferencesSystem.setGlobalPreference("RenderSceneTags", true) + PreferencesSystem.setGlobalPreference("RenderScoreboard", false) + + expect(PreferencesSystem.getGlobalPreference("ZoomSensitivity")).toBe(15) + expect(PreferencesSystem.getGlobalPreference("RenderSceneTags")).toBe(true) + expect(PreferencesSystem.getGlobalPreference("RenderScoreboard")).toBe(false) + }) + test("Reset to default if undefined", () => { + PreferencesSystem.setGlobalPreference("ZoomSensitivity", undefined) + PreferencesSystem.setGlobalPreference("RenderSceneTags", undefined) + PreferencesSystem.setGlobalPreference("RenderScoreboard", undefined) + + expect(PreferencesSystem.getGlobalPreference("ZoomSensitivity")).toBe(15) + expect(PreferencesSystem.getGlobalPreference("RenderSceneTags")).toBe(true) + expect(PreferencesSystem.getGlobalPreference("RenderScoreboard")).toBe(true) + }) + test("Settings then saving", () => { + PreferencesSystem.setGlobalPreference("ZoomSensitivity", 13) + PreferencesSystem.setGlobalPreference("RenderSceneTags", true) + PreferencesSystem.setGlobalPreference("RenderScoreboard", false) + + PreferencesSystem.savePreferences() + + expect(PreferencesSystem.getGlobalPreference("ZoomSensitivity")).toBe(13) + expect(PreferencesSystem.getGlobalPreference("RenderSceneTags")).toBe(true) + expect(PreferencesSystem.getGlobalPreference("RenderScoreboard")).toBe(false) + }) +}) diff --git a/fission/src/test/ui/Button.test.tsx b/fission/src/test/ui/Button.test.tsx new file mode 100644 index 0000000000..6c831f313b --- /dev/null +++ b/fission/src/test/ui/Button.test.tsx @@ -0,0 +1,29 @@ +import { render, fireEvent, getByText } from "@testing-library/react" +import { assert, describe, expect, test } from "vitest" +import Button from "@/ui/components/Button" + +describe("Button", () => { + test("Click Enabled Button", () => { + let buttonClicked = false + let container = render( ) } @@ -74,7 +79,7 @@ const MainHUD: React.FC = () => { > { borderBottomLeftRadius: "0", }} > - + setIsOpen(!isOpen)} value={SynthesisIcons.OpenHudIcon} /> @@ -96,7 +101,16 @@ const MainHUD: React.FC = () => { className="fixed flex flex-col gap-2 bg-gradient-to-b from-interactive-element-right to-interactive-element-left w-min p-4 rounded-3xl ml-4 top-1/2 -translate-y-1/2" >
- + } onClick={() => setIsOpen(false)} diff --git a/fission/src/ui/components/Modal.tsx b/fission/src/ui/components/Modal.tsx index 72457b6994..3b2041904e 100644 --- a/fission/src/ui/components/Modal.tsx +++ b/fission/src/ui/components/Modal.tsx @@ -61,7 +61,17 @@ const Modal: React.FC = ({ {name && ( )}
= ({ {name && ( )}
= ({ modalId }) => { icon={SynthesisIcons.Import} modalId={modalId} acceptEnabled={selectedFile !== undefined && miraType !== undefined} + onCancel={() => openPanel("import-mirabuf")} onAccept={async () => { if (selectedFile && miraType != undefined) { showTooltip("controls", [ diff --git a/fission/src/ui/modals/spawning/SpawningModals.tsx b/fission/src/ui/modals/spawning/SpawningModals.tsx deleted file mode 100644 index 158268e77f..0000000000 --- a/fission/src/ui/modals/spawning/SpawningModals.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import React, { useEffect, useState } from "react" -import Modal, { ModalPropsImpl } from "../../components/Modal" -import Stack, { StackDirection } from "../../components/Stack" -import Button, { ButtonSize } from "../../components/Button" -import { useModalControlContext } from "@/ui/ModalContext" -import Label, { LabelSize } from "@/components/Label" -import World from "@/systems/World" -import { useTooltipControlContext } from "@/ui/TooltipContext" -import MirabufCachingService, { MirabufCacheInfo, MiraType } from "@/mirabuf/MirabufLoader" -import { CreateMirabuf } from "@/mirabuf/MirabufSceneObject" -import { SynthesisIcons } from "@/ui/components/StyledComponents" - -interface MirabufRemoteInfo { - displayName: string - src: string -} - -interface MirabufRemoteCardProps { - info: MirabufRemoteInfo - select: (info: MirabufRemoteInfo) => void -} - -const MirabufRemoteCard: React.FC = ({ info, select }) => { - return ( -
- -
- ) -} - -interface MirabufCacheCardProps { - info: MirabufCacheInfo - select: (info: MirabufCacheInfo) => void -} - -const MirabufCacheCard: React.FC = ({ info, select }) => { - return ( -
- -
- ) -} - -export const AddRobotsModal: React.FC = ({ modalId }) => { - const { showTooltip } = useTooltipControlContext() - const { closeModal } = useModalControlContext() - - const [cachedRobots, setCachedRobots] = useState(undefined) - - // prettier-ignore - useEffect(() => { - (async () => { - const map = MirabufCachingService.GetCacheMap(MiraType.ROBOT) - setCachedRobots(Object.values(map)) - })() - }, []) - - const [remoteRobots, setRemoteRobots] = useState(undefined) - - // prettier-ignore - useEffect(() => { - (async () => { - fetch("/api/mira/manifest.json") - .then(x => x.json()) - .then(x => { - const map = MirabufCachingService.GetCacheMap(MiraType.ROBOT) - const robots: MirabufRemoteInfo[] = [] - for (const src of x["robots"]) { - if (typeof src == "string") { - const str = `/api/mira/Robots/${src}` - if (!map[str]) robots.push({ displayName: src, src: str }) - } else { - if (!map[src["src"]]) robots.push({ displayName: src["displayName"], src: src["src"] }) - } - } - setRemoteRobots(robots) - }) - })() - }, []) - - const selectCache = async (info: MirabufCacheInfo) => { - const assembly = await MirabufCachingService.Get(info.id, MiraType.ROBOT) - - if (assembly) { - showTooltip("controls", [ - { control: "WASD", description: "Drive" }, - { control: "E", description: "Intake" }, - { control: "Q", description: "Dispense" }, - ]) - - CreateMirabuf(assembly).then(x => { - if (x) { - World.SceneRenderer.RegisterSceneObject(x) - } - }) - - if (!info.name) - MirabufCachingService.CacheInfo(info.cacheKey, MiraType.ROBOT, assembly.info?.name ?? undefined) - } else { - console.error("Failed to spawn robot") - } - - closeModal() - } - - const selectRemote = async (info: MirabufRemoteInfo) => { - const cacheInfo = await MirabufCachingService.CacheRemote(info.src, MiraType.ROBOT) - - if (!cacheInfo) { - console.error("Failed to cache robot") - closeModal() - } else { - selectCache(cacheInfo) - } - } - - return ( - -
- - {cachedRobots ? cachedRobots!.map(x => MirabufCacheCard({ info: x, select: selectCache })) : <>} - - {remoteRobots ? remoteRobots!.map(x => MirabufRemoteCard({ info: x, select: selectRemote })) : <>} -
-
- ) -} - -export const AddFieldsModal: React.FC = ({ modalId }) => { - const { closeModal } = useModalControlContext() - - const [cachedFields, setCachedFields] = useState(undefined) - - // prettier-ignore - useEffect(() => { - (async () => { - const map = MirabufCachingService.GetCacheMap(MiraType.FIELD) - setCachedFields(Object.values(map)) - })() - }, []) - - const [remoteFields, setRemoteFields] = useState(undefined) - - // prettier-ignore - useEffect(() => { - (async () => { - fetch("/api/mira/manifest.json") - .then(x => x.json()) - .then(x => { - // TODO: Skip already cached fields - const map = MirabufCachingService.GetCacheMap(MiraType.FIELD) - const fields: MirabufRemoteInfo[] = [] - for (const src of x["fields"]) { - if (typeof src == "string") { - const newSrc = `/api/mira/Fields/${src}` - if (!map[newSrc]) fields.push({ displayName: src, src: newSrc }) - } else { - if (!map[src["src"]]) - fields.push({ displayName: src["displayName"], src: src["src"] }) - } - } - setRemoteFields(fields) - }) - })() - }, []) - - const selectCache = async (info: MirabufCacheInfo) => { - const assembly = await MirabufCachingService.Get(info.id, MiraType.FIELD) - - if (assembly) { - CreateMirabuf(assembly).then(x => { - if (x) { - World.SceneRenderer.RegisterSceneObject(x) - } - }) - - if (!info.name) - MirabufCachingService.CacheInfo(info.cacheKey, MiraType.FIELD, assembly.info?.name ?? undefined) - } else { - console.error("Failed to spawn field") - } - - closeModal() - } - - const selectRemote = async (info: MirabufRemoteInfo) => { - const cacheInfo = await MirabufCachingService.CacheRemote(info.src, MiraType.FIELD) - - if (!cacheInfo) { - console.error("Failed to cache field") - closeModal() - } else { - selectCache(cacheInfo) - } - } - - return ( - -
- - {cachedFields ? cachedFields!.map(x => MirabufCacheCard({ info: x, select: selectCache })) : <>} - - {remoteFields ? remoteFields!.map(x => MirabufRemoteCard({ info: x, select: selectRemote })) : <>} -
-
- ) -} - -export const SpawningModal: React.FC = ({ modalId }) => { - const { openModal } = useModalControlContext() - - return ( - - - ) } @@ -58,12 +70,29 @@ const MainHUD: React.FC = () => { return ( <> {!isOpen && ( - + + + setIsOpen(!isOpen)} value={SynthesisIcons.OpenHudIcon} /> + + + )} { className="fixed flex flex-col gap-2 bg-gradient-to-b from-interactive-element-right to-interactive-element-left w-min p-4 rounded-3xl ml-4 top-1/2 -translate-y-1/2" >
- + } + value={} onClick={() => setIsOpen(false)} />
@@ -84,35 +122,34 @@ const MainHUD: React.FC = () => { larger={true} onClick={() => openPanel("import-mirabuf")} /> -
+ openModal("settings")} /> - {/* openModal("view")} /> */} openPanel("subsystem-config")} - /> - openPanel("configure")} /> { openPanel("debug") }} /> -
+ {userInfo ? ( = ({ {name && ( )}
{children}
- + {(cancelEnabled || middleEnabled || acceptEnabled) && ( + + )}
) diff --git a/fission/src/ui/components/Panel.tsx b/fission/src/ui/components/Panel.tsx index b8faee4977..bed0ec4fa6 100644 --- a/fission/src/ui/components/Panel.tsx +++ b/fission/src/ui/components/Panel.tsx @@ -132,7 +132,17 @@ const Panel: React.FC = ({ {name && ( )}
= ({ }} className={`${ cancelBlocked ? "bg-interactive-background" : "bg-cancel-button" - } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90`} + } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90 + transform transition-transform hover:scale-[1.03] active:scale-[1.06]`} + style={{ fontWeight: "bold" }} /> )} {middleEnabled && ( @@ -170,7 +182,9 @@ const Panel: React.FC = ({ }} className={`${ middleBlocked ? "bg-interactive-background" : "bg-accept-button" - } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90`} + } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90 + transform transition-transform hover:scale-[1.03] active:scale-[1.06]`} + style={{ fontWeight: "bold" }} /> )} {acceptEnabled && ( @@ -183,7 +197,9 @@ const Panel: React.FC = ({ }} className={`${ acceptBlocked ? "bg-interactive-background" : "bg-accept-button" - } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90`} + } rounded-md cursor-pointer px-4 py-1 font-bold duration-100 hover:brightness-90 + transform transition-transform hover:scale-[1.03] active:scale-[1.06]`} + style={{ fontWeight: "bold" }} /> )}
diff --git a/fission/src/ui/components/SelectButton.tsx b/fission/src/ui/components/SelectButton.tsx index 3d7e018669..65e277dbb5 100644 --- a/fission/src/ui/components/SelectButton.tsx +++ b/fission/src/ui/components/SelectButton.tsx @@ -1,10 +1,10 @@ import React, { useCallback, useEffect, useRef, useState } from "react" import Button, { ButtonSize } from "./Button" -import Label, { LabelSize } from "./Label" import Stack, { StackDirection } from "./Stack" import World from "@/systems/World" import { ThreeVector3_JoltVec3 } from "@/util/TypeConversions" import Jolt from "@barclah/jolt-physics" +import { LabelWithTooltip } from "./StyledComponents" // raycasting constants const RAY_MAX_LENGTH = 20.0 @@ -70,7 +70,10 @@ const SelectButton: React.FC = ({ colorClass, size, value, pl return ( - + {LabelWithTooltip( + "Select parent node", + "Select the parent node for this object to follow. Click the button below, then click a part of the robot or field." + )}
) diff --git a/fission/src/ui/modals/spawning/SpawningModals.tsx b/fission/src/ui/modals/spawning/SpawningModals.tsx deleted file mode 100644 index 158268e77f..0000000000 --- a/fission/src/ui/modals/spawning/SpawningModals.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import React, { useEffect, useState } from "react" -import Modal, { ModalPropsImpl } from "../../components/Modal" -import Stack, { StackDirection } from "../../components/Stack" -import Button, { ButtonSize } from "../../components/Button" -import { useModalControlContext } from "@/ui/ModalContext" -import Label, { LabelSize } from "@/components/Label" -import World from "@/systems/World" -import { useTooltipControlContext } from "@/ui/TooltipContext" -import MirabufCachingService, { MirabufCacheInfo, MiraType } from "@/mirabuf/MirabufLoader" -import { CreateMirabuf } from "@/mirabuf/MirabufSceneObject" -import { SynthesisIcons } from "@/ui/components/StyledComponents" - -interface MirabufRemoteInfo { - displayName: string - src: string -} - -interface MirabufRemoteCardProps { - info: MirabufRemoteInfo - select: (info: MirabufRemoteInfo) => void -} - -const MirabufRemoteCard: React.FC = ({ info, select }) => { - return ( -
- -
- ) -} - -interface MirabufCacheCardProps { - info: MirabufCacheInfo - select: (info: MirabufCacheInfo) => void -} - -const MirabufCacheCard: React.FC = ({ info, select }) => { - return ( -
- -
- ) -} - -export const AddRobotsModal: React.FC = ({ modalId }) => { - const { showTooltip } = useTooltipControlContext() - const { closeModal } = useModalControlContext() - - const [cachedRobots, setCachedRobots] = useState(undefined) - - // prettier-ignore - useEffect(() => { - (async () => { - const map = MirabufCachingService.GetCacheMap(MiraType.ROBOT) - setCachedRobots(Object.values(map)) - })() - }, []) - - const [remoteRobots, setRemoteRobots] = useState(undefined) - - // prettier-ignore - useEffect(() => { - (async () => { - fetch("/api/mira/manifest.json") - .then(x => x.json()) - .then(x => { - const map = MirabufCachingService.GetCacheMap(MiraType.ROBOT) - const robots: MirabufRemoteInfo[] = [] - for (const src of x["robots"]) { - if (typeof src == "string") { - const str = `/api/mira/Robots/${src}` - if (!map[str]) robots.push({ displayName: src, src: str }) - } else { - if (!map[src["src"]]) robots.push({ displayName: src["displayName"], src: src["src"] }) - } - } - setRemoteRobots(robots) - }) - })() - }, []) - - const selectCache = async (info: MirabufCacheInfo) => { - const assembly = await MirabufCachingService.Get(info.id, MiraType.ROBOT) - - if (assembly) { - showTooltip("controls", [ - { control: "WASD", description: "Drive" }, - { control: "E", description: "Intake" }, - { control: "Q", description: "Dispense" }, - ]) - - CreateMirabuf(assembly).then(x => { - if (x) { - World.SceneRenderer.RegisterSceneObject(x) - } - }) - - if (!info.name) - MirabufCachingService.CacheInfo(info.cacheKey, MiraType.ROBOT, assembly.info?.name ?? undefined) - } else { - console.error("Failed to spawn robot") - } - - closeModal() - } - - const selectRemote = async (info: MirabufRemoteInfo) => { - const cacheInfo = await MirabufCachingService.CacheRemote(info.src, MiraType.ROBOT) - - if (!cacheInfo) { - console.error("Failed to cache robot") - closeModal() - } else { - selectCache(cacheInfo) - } - } - - return ( - -
- - {cachedRobots ? cachedRobots!.map(x => MirabufCacheCard({ info: x, select: selectCache })) : <>} - - {remoteRobots ? remoteRobots!.map(x => MirabufRemoteCard({ info: x, select: selectRemote })) : <>} -
-
- ) -} - -export const AddFieldsModal: React.FC = ({ modalId }) => { - const { closeModal } = useModalControlContext() - - const [cachedFields, setCachedFields] = useState(undefined) - - // prettier-ignore - useEffect(() => { - (async () => { - const map = MirabufCachingService.GetCacheMap(MiraType.FIELD) - setCachedFields(Object.values(map)) - })() - }, []) - - const [remoteFields, setRemoteFields] = useState(undefined) - - // prettier-ignore - useEffect(() => { - (async () => { - fetch("/api/mira/manifest.json") - .then(x => x.json()) - .then(x => { - // TODO: Skip already cached fields - const map = MirabufCachingService.GetCacheMap(MiraType.FIELD) - const fields: MirabufRemoteInfo[] = [] - for (const src of x["fields"]) { - if (typeof src == "string") { - const newSrc = `/api/mira/Fields/${src}` - if (!map[newSrc]) fields.push({ displayName: src, src: newSrc }) - } else { - if (!map[src["src"]]) - fields.push({ displayName: src["displayName"], src: src["src"] }) - } - } - setRemoteFields(fields) - }) - })() - }, []) - - const selectCache = async (info: MirabufCacheInfo) => { - const assembly = await MirabufCachingService.Get(info.id, MiraType.FIELD) - - if (assembly) { - CreateMirabuf(assembly).then(x => { - if (x) { - World.SceneRenderer.RegisterSceneObject(x) - } - }) - - if (!info.name) - MirabufCachingService.CacheInfo(info.cacheKey, MiraType.FIELD, assembly.info?.name ?? undefined) - } else { - console.error("Failed to spawn field") - } - - closeModal() - } - - const selectRemote = async (info: MirabufRemoteInfo) => { - const cacheInfo = await MirabufCachingService.CacheRemote(info.src, MiraType.FIELD) - - if (!cacheInfo) { - console.error("Failed to cache field") - closeModal() - } else { - selectCache(cacheInfo) - } - } - - return ( - -
- - {cachedFields ? cachedFields!.map(x => MirabufCacheCard({ info: x, select: selectCache })) : <>} - - {remoteFields ? remoteFields!.map(x => MirabufRemoteCard({ info: x, select: selectRemote })) : <>} -
-
- ) -} - -export const SpawningModal: React.FC = ({ modalId }) => { - const { openModal } = useModalControlContext() - - return ( - - - - ) - })} -
- - ) : ( - <> - {drivers ? ( - - {/** Drivetrain row. Then other SliderDrivers and HingeDrivers */} + <> + {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 drivers.filter(x => x instanceof WheelDriver)[0] + return driver })()} /> - {drivers - .filter(x => x instanceof SliderDriver || x instanceof HingeDriver) - .map((driver: Driver, i: number) => ( - { - return selectedRobot - })()} - driver={(() => { - return driver - })()} - /> - ))} - - ) : ( - - )} - + ))} + + ) : ( + )} - + ) } -export default ConfigureSubsystemsPanel +export default ConfigureSubsystemsInterface diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index 245ad48311..ec527ac26d 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -18,11 +18,13 @@ 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 "../ConfigureSubsystemsInterface" enum ConfigMode { INTAKE, EJECTOR, MOTORS, + SUBSYSTEMS, CONTROLS, SCORING_ZONES, } @@ -127,6 +129,7 @@ const robotModes = [ new ConfigModeSelectionOption("Intake", ConfigMode.INTAKE), new ConfigModeSelectionOption("Ejector", ConfigMode.EJECTOR), new ConfigModeSelectionOption("Sequential Joints", ConfigMode.MOTORS), + new ConfigModeSelectionOption("Subsystems", ConfigMode.SUBSYSTEMS), new ConfigModeSelectionOption("Controls", ConfigMode.CONTROLS), ] const fieldModes = [new ConfigModeSelectionOption("Scoring Zones", ConfigMode.SCORING_ZONES)] @@ -164,6 +167,8 @@ const ConfigInterface: React.FC = ({ configMode, assembly, 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) @@ -224,8 +229,8 @@ const ConfigurePanel: React.FC = ({ panelId }) => { return ( = ({ select min={MIN_ZONE_SIZE} max={MAX_ZONE_SIZE} value={zoneSize} - label="Size" + label="Zone Size" format={{ minimumFractionDigits: 2, maximumFractionDigits: 2 }} onChange={(_, vel: number | number[]) => { setZoneSize(vel as number) diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx index f2a3721704..220191cab3 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx @@ -56,6 +56,7 @@ const ConfigureSchemeInterface: React.FC = ({ selectedScheme setUseGamepad(val) selectedScheme.usesGamepad = val }} + tooltipText="Supported controllers: Xbox one, Xbox 360." /> diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/EditInputInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/EditInputInterface.tsx index d351551a86..33ef12b7ab 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/EditInputInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/EditInputInterface.tsx @@ -112,20 +112,23 @@ const EditInputInterface: React.FC = ({ input, useGamepad, onInp gap="10px" alignItems={"center"} justifyContent={"space-between"} + width={"98%"} > -
- ) - })} - - {/* 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/mirabuf/ImportMirabufPanel.tsx b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx index aa3f37b2f8..2fbd12231c 100644 --- a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx +++ b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx @@ -8,7 +8,7 @@ import { MirabufFilesUpdateEvent, RequestMirabufFiles, } from "@/aps/APSDataManagement" -import MirabufCachingService, { MirabufCacheInfo, MirabufRemoteInfo, MiraType } from "@/mirabuf/MirabufLoader" +import MirabufCachingService, { backUpFields, backUpRobots, MirabufCacheInfo, MirabufRemoteInfo, MiraType } from "@/mirabuf/MirabufLoader" import World from "@/systems/World" import { useTooltipControlContext } from "@/ui/TooltipContext" import { CreateMirabuf } from "@/mirabuf/MirabufSceneObject" @@ -71,7 +71,8 @@ export type MiraManifest = { } function GetCacheInfo(miraType: MiraType): MirabufCacheInfo[] { - return Object.values(MirabufCachingService.GetCacheMap(miraType)) + // return canOPFS ? + return Object.values(MirabufCachingService.GetCacheMap(miraType))// : (miraType == MiraType.ROBOT ? backUpRobots : backUpFields) } function SpawnCachedMira(info: MirabufCacheInfo, type: MiraType, progressHandle?: ProgressHandle) { From 7cfae204589ed4086754bdca8261357b40ae5581 Mon Sep 17 00:00:00 2001 From: LucaHaverty Date: Mon, 19 Aug 2024 14:38:04 -0700 Subject: [PATCH 294/344] full subsystem config integration with the main config menu --- .../src/ui/components/StyledComponents.tsx | 1 + .../assembly-config/ConfigurePanel.tsx | 5 + .../ConfigureSubsystemsInterface.tsx | 78 ++++++++-- .../SequentialBehaviorsInterface.tsx | 27 +--- ...rfaceOLD.tsx => SubsystemRowInterface.tsx} | 142 +++--------------- 5 files changed, 91 insertions(+), 162 deletions(-) rename fission/src/ui/panels/configuring/assembly-config/interfaces/{ConfigureSubsystemsInterfaceOLD.tsx => SubsystemRowInterface.tsx} (51%) diff --git a/fission/src/ui/components/StyledComponents.tsx b/fission/src/ui/components/StyledComponents.tsx index 9b63b2ddfd..c8c23f7ea3 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 = () } diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index 3bcc27676f..27c9d7faea 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -16,10 +16,12 @@ 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" enum ConfigMode { SUBSYSTEMS, CONTROLS, + SEQUENTIAL, SCORING_ZONES, } @@ -122,6 +124,7 @@ class ConfigModeSelectionOption extends SelectMenuOption { const robotModes = [ new ConfigModeSelectionOption("Subsystems", ConfigMode.SUBSYSTEMS), new ConfigModeSelectionOption("Controls", ConfigMode.CONTROLS), + new ConfigModeSelectionOption("Sequence Joints", ConfigMode.SEQUENTIAL), ] const fieldModes = [new ConfigModeSelectionOption("Scoring Zones", ConfigMode.SCORING_ZONES)] @@ -171,6 +174,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 index 6779e5d61c..b7269f55e7 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureSubsystemsInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureSubsystemsInterface.tsx @@ -3,31 +3,35 @@ import SelectMenu, { SelectMenuOption } from "@/ui/components/SelectMenu" import React, { useMemo, useState } from "react" import ConfigureGamepiecePickupInterface from "./ConfigureGamepiecePickupInterface" import ConfigureShotTrajectoryInterface from "./ConfigureShotTrajectoryInterface" -import SequentialBehaviorsInterface from "./SequentialBehaviorsInterface" 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 { SubsystemRow } from "./ConfigureSubsystemsInterfaceOLD" +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" enum ConfigMode { NONE, INTAKE, EJECTOR, - SUBSYSTEMS, - SEQUENTIAL, } class ConfigModeSelectionOption extends SelectMenuOption { configMode: ConfigMode driver?: Driver + sequential?: SequentialBehaviorPreferences - constructor(name: string, configMode: ConfigMode, driver?: Driver) { + constructor(name: string, configMode: ConfigMode, driver?: Driver, sequential?: SequentialBehaviorPreferences) { super(name) this.configMode = configMode this.driver = driver + this.sequential = sequential } } @@ -38,20 +42,24 @@ interface ConfigSubsystemProps { interface ConfigInterfaceProps { configModeOption: ConfigModeSelectionOption selectedRobot: MirabufSceneObject + saveBehaviors: () => void } -const ConfigInterface: React.FC = ({ configModeOption, selectedRobot }) => { +const ConfigInterface: React.FC = ({ configModeOption, selectedRobot, saveBehaviors }) => { switch (configModeOption.configMode) { case ConfigMode.INTAKE: return case ConfigMode.EJECTOR: return - case ConfigMode.SEQUENTIAL: - return - case ConfigMode.SUBSYSTEMS: - return case ConfigMode.NONE: - return + return ( + + ) default: throw new Error(`Config mode ${configModeOption.configMode} has no associated interface`) } @@ -60,6 +68,15 @@ const ConfigInterface: React.FC = ({ configModeOption, sel 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]) @@ -68,7 +85,6 @@ const ConfigureSubsystemsInterface: React.FC = ({ selected const modes = [ new ConfigModeSelectionOption("Intake", ConfigMode.INTAKE), new ConfigModeSelectionOption("Ejector", ConfigMode.EJECTOR), - //new ConfigModeSelectionOption("Subsystems", ConfigMode.SUBSYSTEMS), ] if (drivers == undefined) return modes @@ -81,13 +97,35 @@ const ConfigureSubsystemsInterface: React.FC = ({ selected ) ) + let jointIndex = 0 + drivers - .filter(x => x instanceof SliderDriver || x instanceof HingeDriver) + .filter(x => x instanceof HingeDriver) .forEach(d => { - modes.push(new ConfigModeSelectionOption(d.info?.name ?? "UnnamedMotor", ConfigMode.NONE, d)) + modes.push( + new ConfigModeSelectionOption( + d.info?.name ?? "UnnamedMotor", + ConfigMode.NONE, + d, + behaviors[jointIndex] + ) + ) + jointIndex++ }) - modes.push(new ConfigModeSelectionOption("Sequence Joints", ConfigMode.SEQUENTIAL)) + drivers + .filter(x => x instanceof SliderDriver) + .forEach(d => { + modes.push( + new ConfigModeSelectionOption( + d.info?.name ?? "UnnamedMotor", + ConfigMode.NONE, + d, + behaviors[jointIndex] + ) + ) + jointIndex++ + }) return modes } @@ -104,7 +142,15 @@ const ConfigureSubsystemsInterface: React.FC = ({ selected indentation={2} /> {selectedConfigMode != undefined && ( - + { + PreferencesSystem.getRobotPreferences(selectedRobot.assemblyName).sequentialConfig = behaviors + PreferencesSystem.savePreferences() + console.log("behaviors saved!") + }} + /> )} ) 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 0b97b14d38..e22a8e141f 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 } from "@/ui/components/StyledComponents" - -const UnselectParentIcon = -const InvertIcon = +import { SectionLabel, 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 */}