IS-Matrix-Forge 1.0.0.dev27__tar.gz → 1.0.0.dev28__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/PKG-INFO +3 -3
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/font_map/base.py +2 -2
- is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/Scripts/led_matrix/__init__.py +531 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/led_matrix/arguments/commands/display_text.py +7 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/led_matrix/arguments/commands/scroll_text.py +14 -0
- is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/__init__.py +41 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/base.py +30 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/drawing.py +1 -1
- is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/controller/components/history/__init__.py +1 -0
- is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/controller/components/history/event.py +24 -0
- is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/controller/components/history/manager.py +185 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/controller.py +79 -71
- is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/controller/helpers/__init__.py +131 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/audio_visualizer.py +6 -3
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/text_scroller.py +6 -4
- is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/display/grid/composite/utils.py +258 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/hardware.py +1 -1
- is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/utils/__init__.py +107 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/pyproject.toml +3 -3
- is_matrix_forge-1.0.0.dev27/is_matrix_forge/led_matrix/Scripts/led_matrix/__init__.py +0 -210
- is_matrix_forge-1.0.0.dev27/is_matrix_forge/led_matrix/__init__.py +0 -96
- is_matrix_forge-1.0.0.dev27/is_matrix_forge/led_matrix/controller/helpers/__init__.py +0 -67
- is_matrix_forge-1.0.0.dev27/is_matrix_forge/led_matrix/utils/__init__.py +0 -41
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/LICENSE.md +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/README.md +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/Untitled.ipynb +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/audio/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/audio/files/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/audio/files/plugged.wav +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/audio/files/unplugged.wav +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/char_map.json +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/digit_map.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/font_map/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/font_map/builders.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/font_map/helpers.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/font_map/models/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/font_map/models/glyph.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/decorators/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/decorators/freeze_setter.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/dirs.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/helpers/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/helpers/errors.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/helpers/github_api.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/logging/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/logging/log_exceptions.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/layout/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/layout/button_grid/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/layout/button_grid/column.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/layout/button_grid/grid.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/layout/menu_bar.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/streamlit_app.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/dev_tools/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/dev_tools/debug.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/dev_tools/presets.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/inputmodule/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/inputmodule/b1display.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/inputmodule/c1minimal.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/inputmodule/font.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/inputmodule/helpers/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/inputmodule/ledmatrix.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/battery_monitor.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/identify_matrices.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/install_presets/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/install_presets/arguments.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/install_presets/main.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/led_matrix/arguments/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/led_matrix/arguments/commands/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/led_matrix/arguments/commands/identify_matrices.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/led_matrix/guards.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/pixel_grid.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/commands/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/commands/map.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/constants.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/animation.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/breather.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/breather_killer.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/easing.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/fade_planner.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/fade_spec.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/levels.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/percent.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/safe_ops.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/manager.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/example_template.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/identify.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/keep_alive.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/helpers/threading.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/legacy.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/multiton.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/threaded.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/animation.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/effects/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/errors.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/font/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/font/font.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/font/map_builder.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/frame/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/frame/base.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/frame/helpers.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/keep_awake.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/plugged.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/text/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/text/glyph_normalizer.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations.zip +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/assets/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/assets/fonts.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/effects/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/effects/breather.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/effects/fade.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/effects/scroller/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/effects/scroller/base.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/effects/scroller/helpers.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/base.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/composite/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/composite/background.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/composite/composite.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/composite/foreground.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/helpers.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/presets/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/presets/manifest.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/helpers/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/helpers/columns.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/media.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/patterns/built_in/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/patterns/built_in/commands.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/patterns/built_in/constants.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/patterns/built_in/stencils/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/patterns/built_in/stencils/res.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/scene/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/scene/errors.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/scene/scene.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/text/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/text/scroller.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/text/text.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/errors/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/errors/base.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/errors/composite_grid.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/errors/grid.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/errors/matrix.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/errors/misc.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/device/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/device/bootloader/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/device/bootloader/constants.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/device/bootloader/unix.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/device/bootloader/win.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/status_handler.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/is_matrix_forge.led_matrix.display.zip +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/status.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/tools/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/tools/identify.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/log_engine.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/errors.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/events.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/errors.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/event/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/event/collection.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/event/errors.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/event/event.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/layout/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/layout/base.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/metaclasses/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/windows/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/windows/base.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/windows/main/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/windows/main/layout.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/helpers.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/monitor.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/notify/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/notify/sounds/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/notify/sounds/base.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/progress.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/scripts/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/scripts/led-matrix.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/scripts/preset_installer.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/serial_com/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/serial_com/base/__init__.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/serial_com/base/answer.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/serial_com/base/message.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/serial_com/base/request.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/serial_com/exchange.py +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/blank.json +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/manifest.json +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/shock_face.json +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test1.json +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test2.json +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test3.json +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test4.json +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test5.json +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test6.json +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test7.json +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/x_animation II.json +0 -0
- {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/x_animation.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: IS-Matrix-Forge
|
|
3
|
-
Version: 1.0.0.
|
|
3
|
+
Version: 1.0.0.dev28
|
|
4
4
|
Summary:
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: led,matrix,framework,hardware
|
|
@@ -22,8 +22,8 @@ Requires-Dist: inspyre-toolbox (>=1.6.0.dev23)
|
|
|
22
22
|
Requires-Dist: ipywidgets (==8.1.7)
|
|
23
23
|
Requires-Dist: notebook (>=7.4.4,<8.0.0)
|
|
24
24
|
Requires-Dist: opencv-python (>=4.11.0.86,<5.0.0.0)
|
|
25
|
-
Requires-Dist: pillow (>=
|
|
26
|
-
Requires-Dist:
|
|
25
|
+
Requires-Dist: pillow (>=12.0.0,<13.0.0)
|
|
26
|
+
Requires-Dist: platformdirs (>=4.5.0,<5.0.0)
|
|
27
27
|
Requires-Dist: pyserial (>=3.5,<4.0)
|
|
28
28
|
Requires-Dist: pysimplegui-4-foss (==4.60.4.1)
|
|
29
29
|
Requires-Dist: pysimpleguiweb (==0.39.0)
|
{is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/font_map/base.py
RENAMED
|
@@ -20,8 +20,8 @@ from dataclasses import dataclass
|
|
|
20
20
|
from typing import Any, Dict, Iterable, Optional, Tuple, Callable
|
|
21
21
|
|
|
22
22
|
from inspyre_toolbox.syntactic_sweets.classes import validate_type
|
|
23
|
-
from .models.glyph import Glyph
|
|
24
|
-
from .helpers import load_builtin_char_map, get_ignore_case, has_key_ignore_case
|
|
23
|
+
from is_matrix_forge.assets.font_map.models.glyph import Glyph
|
|
24
|
+
from is_matrix_forge.assets.font_map.helpers import load_builtin_char_map, get_ignore_case, has_key_ignore_case
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
@dataclass(init=False, slots=True)
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Iterable, Optional, Tuple
|
|
3
|
+
|
|
4
|
+
import threading
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
|
|
7
|
+
from is_matrix_forge.led_matrix.Scripts.led_matrix.arguments import Arguments
|
|
8
|
+
from is_matrix_forge.led_matrix.Scripts.led_matrix.guards import run_with_guard
|
|
9
|
+
# from is_matrix_forge.led_matrix.Scripts.identify_matrices import
|
|
10
|
+
|
|
11
|
+
ARGUMENTS = Arguments()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _normalize_side(value):
|
|
15
|
+
if isinstance(value, str):
|
|
16
|
+
return value.strip().lower()
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
def _desired_side(cli_args):
|
|
20
|
+
if cli_args is None:
|
|
21
|
+
return None
|
|
22
|
+
if getattr(cli_args, 'only_left', False):
|
|
23
|
+
return 'left'
|
|
24
|
+
if getattr(cli_args, 'only_right', False):
|
|
25
|
+
return 'right'
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
def _resolve_location(controller) -> Tuple[Optional[str], Optional[int]]:
|
|
29
|
+
"""
|
|
30
|
+
Return a normalized (side, slot) tuple from a controller.
|
|
31
|
+
|
|
32
|
+
Looks in this order:
|
|
33
|
+
1) Explicit attributes: controller.side_of_keyboard, controller.slot
|
|
34
|
+
2) controller.location if it's a dict with 'side'/'slot'
|
|
35
|
+
3) controller.location if it's a dict with 'abbrev' (e.g. 'L0', 'R2')
|
|
36
|
+
4) controller.location if it's a str, resolved via SLOT_MAP
|
|
37
|
+
"""
|
|
38
|
+
# 1) explicit attributes
|
|
39
|
+
side = _normalize_side(getattr(controller, 'side_of_keyboard', None))
|
|
40
|
+
slot = getattr(controller, 'slot', None)
|
|
41
|
+
|
|
42
|
+
location = getattr(controller, 'location', None)
|
|
43
|
+
|
|
44
|
+
# 2) dict with 'side'/'slot'
|
|
45
|
+
if isinstance(location, dict):
|
|
46
|
+
if side is None:
|
|
47
|
+
side = _normalize_side(location.get('side'))
|
|
48
|
+
if slot is None:
|
|
49
|
+
slot = location.get('slot')
|
|
50
|
+
|
|
51
|
+
# 3) dict with 'abbrev' like 'L0', 'R1'
|
|
52
|
+
if (side is None or slot is None) and 'abbrev' in location and isinstance(location['abbrev'], str):
|
|
53
|
+
abbrev = location['abbrev'].strip().upper()
|
|
54
|
+
# Try SLOT_MAP first, then parse fallback
|
|
55
|
+
try:
|
|
56
|
+
from is_matrix_forge.led_matrix.constants import SLOT_MAP
|
|
57
|
+
except Exception:
|
|
58
|
+
SLOT_MAP = {}
|
|
59
|
+
|
|
60
|
+
entry = SLOT_MAP.get(abbrev) or {}
|
|
61
|
+
side = _normalize_side(side or entry.get('side'))
|
|
62
|
+
slot = slot if slot is not None else entry.get('slot')
|
|
63
|
+
|
|
64
|
+
if (side is None or slot is None):
|
|
65
|
+
# Parse e.g. 'L0'/'R12' -> ('left'/'right', 0/12)
|
|
66
|
+
if len(abbrev) >= 2 and abbrev[0] in ('L', 'R') and abbrev[1:].isdigit():
|
|
67
|
+
side = _normalize_side(side or ('left' if abbrev[0] == 'L' else 'right'))
|
|
68
|
+
slot = slot if slot is not None else int(abbrev[1:])
|
|
69
|
+
|
|
70
|
+
# 4) location is a string -> SLOT_MAP lookup
|
|
71
|
+
if (side is None or slot is None) and isinstance(location, str):
|
|
72
|
+
try:
|
|
73
|
+
from is_matrix_forge.led_matrix.constants import SLOT_MAP
|
|
74
|
+
except Exception:
|
|
75
|
+
SLOT_MAP = {}
|
|
76
|
+
entry = SLOT_MAP.get(location, {})
|
|
77
|
+
side = _normalize_side(side or entry.get('side'))
|
|
78
|
+
slot = slot if slot is not None else entry.get('slot')
|
|
79
|
+
|
|
80
|
+
# normalize slot
|
|
81
|
+
try:
|
|
82
|
+
slot = int(slot) if slot is not None else None
|
|
83
|
+
except (TypeError, ValueError):
|
|
84
|
+
slot = None
|
|
85
|
+
|
|
86
|
+
return side, slot
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _controller_side(controller) -> Optional[str]:
|
|
90
|
+
side, _ = _resolve_location(controller)
|
|
91
|
+
return side
|
|
92
|
+
|
|
93
|
+
def _slot_rank(controller) -> int:
|
|
94
|
+
"""
|
|
95
|
+
Returns an integer for sorting. Unknown becomes 0 (neutral).
|
|
96
|
+
"""
|
|
97
|
+
_, slot = _resolve_location(controller)
|
|
98
|
+
try:
|
|
99
|
+
return int(slot)
|
|
100
|
+
except (TypeError, ValueError):
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
def find_leftmost_matrix(controllers: Iterable):
|
|
104
|
+
"""Return the controller that is physically positioned furthest left.
|
|
105
|
+
|
|
106
|
+
Ordering:
|
|
107
|
+
1) left side first, unknown middle, right side last
|
|
108
|
+
2) within same side, lower slot = further left
|
|
109
|
+
3) stable tiebreaker by original index
|
|
110
|
+
"""
|
|
111
|
+
ranked = []
|
|
112
|
+
for index, controller in enumerate(controllers):
|
|
113
|
+
side = _controller_side(controller)
|
|
114
|
+
if side == 'left':
|
|
115
|
+
side_rank = 0
|
|
116
|
+
elif side == 'right':
|
|
117
|
+
side_rank = 2
|
|
118
|
+
else:
|
|
119
|
+
side_rank = 1 # unknowns in the middle
|
|
120
|
+
|
|
121
|
+
rank_tuple = (side_rank, _slot_rank(controller), index)
|
|
122
|
+
print(f'Ranks: {rank_tuple[0]}, {rank_tuple[1]}, {rank_tuple[2]}')
|
|
123
|
+
ranked.append((rank_tuple, controller))
|
|
124
|
+
print(ranked)
|
|
125
|
+
|
|
126
|
+
if not ranked:
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
return min(ranked, key=lambda item: item[0])[1]
|
|
130
|
+
|
|
131
|
+
def find_rightmost_matrix(controllers: Iterable):
|
|
132
|
+
"""Return the controller that is physically positioned furthest right.
|
|
133
|
+
|
|
134
|
+
Ordering:
|
|
135
|
+
1) right side first, unknown middle, left side last
|
|
136
|
+
2) within same side, higher slot = further right
|
|
137
|
+
3) stable tiebreaker by original index
|
|
138
|
+
"""
|
|
139
|
+
ranked = []
|
|
140
|
+
for index, controller in enumerate(controllers):
|
|
141
|
+
side = _controller_side(controller)
|
|
142
|
+
if side == 'right':
|
|
143
|
+
side_rank = 0
|
|
144
|
+
elif side == 'left':
|
|
145
|
+
side_rank = 2
|
|
146
|
+
else:
|
|
147
|
+
side_rank = 1 # unknowns in the middle
|
|
148
|
+
|
|
149
|
+
# negate slot so larger slot numbers sort earlier for "rightmost"
|
|
150
|
+
rank_tuple = (side_rank, -_slot_rank(controller), index)
|
|
151
|
+
print(f'Ranks: {rank_tuple[0]}, {-rank_tuple[1]}, {rank_tuple[2]}')
|
|
152
|
+
ranked.append((rank_tuple, controller))
|
|
153
|
+
print(ranked)
|
|
154
|
+
|
|
155
|
+
if not ranked:
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
return min(ranked, key=lambda item: item[0])[1]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _filter_controllers_by_side(controllers, cli_args):
|
|
162
|
+
"""Return the controllers that match the requested keyboard side."""
|
|
163
|
+
|
|
164
|
+
desired = _desired_side(cli_args)
|
|
165
|
+
|
|
166
|
+
if desired is None:
|
|
167
|
+
return list(controllers)
|
|
168
|
+
|
|
169
|
+
return [
|
|
170
|
+
controller for controller in controllers
|
|
171
|
+
if _controller_side(controller) == desired
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _describe_selection(cli_args):
|
|
176
|
+
desired = _desired_side(cli_args)
|
|
177
|
+
|
|
178
|
+
if desired == 'left':
|
|
179
|
+
return 'the leftmost LED matrix'
|
|
180
|
+
|
|
181
|
+
if desired == 'right':
|
|
182
|
+
return 'the rightmost LED matrix'
|
|
183
|
+
|
|
184
|
+
return 'any LED matrix'
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _order_controllers_for_span(controllers: Iterable):
|
|
188
|
+
controllers = list(controllers)
|
|
189
|
+
|
|
190
|
+
if len(controllers) <= 1:
|
|
191
|
+
return controllers
|
|
192
|
+
|
|
193
|
+
origin = find_rightmost_matrix(controllers)
|
|
194
|
+
|
|
195
|
+
if origin is None:
|
|
196
|
+
return controllers
|
|
197
|
+
|
|
198
|
+
ordered = [origin]
|
|
199
|
+
remaining = [controller for controller in controllers if controller is not origin]
|
|
200
|
+
|
|
201
|
+
while remaining:
|
|
202
|
+
next_controller = find_leftmost_matrix(remaining)
|
|
203
|
+
|
|
204
|
+
if next_controller is None:
|
|
205
|
+
ordered.extend(remaining)
|
|
206
|
+
break
|
|
207
|
+
|
|
208
|
+
ordered.append(next_controller)
|
|
209
|
+
remaining = [controller for controller in remaining if controller is not next_controller]
|
|
210
|
+
|
|
211
|
+
return ordered
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _build_horizontal_span_animations(text: str, controllers: Iterable):
|
|
215
|
+
controllers = list(controllers)
|
|
216
|
+
|
|
217
|
+
if not controllers:
|
|
218
|
+
return {}
|
|
219
|
+
|
|
220
|
+
from is_matrix_forge.assets.font_map.base import FontMap
|
|
221
|
+
from is_matrix_forge.led_matrix.display.animations.animation import Animation, Frame
|
|
222
|
+
from is_matrix_forge.led_matrix.display.animations.text.glyph_normalizer import GlyphNormalizer
|
|
223
|
+
from is_matrix_forge.led_matrix.display.grid.base import MATRIX_HEIGHT, MATRIX_WIDTH, Grid
|
|
224
|
+
|
|
225
|
+
font_map = FontMap(case_sensitive=False)
|
|
226
|
+
case_sensitive = font_map.is_case_sensitive
|
|
227
|
+
normalized_text = text if case_sensitive else text.upper()
|
|
228
|
+
|
|
229
|
+
glyphs = []
|
|
230
|
+
normalizer = GlyphNormalizer(MATRIX_WIDTH, MATRIX_HEIGHT)
|
|
231
|
+
for character in normalized_text:
|
|
232
|
+
glyph_rows = font_map.lookup(character)
|
|
233
|
+
normalized_rows = normalizer.normalize(glyph_rows)
|
|
234
|
+
glyphs.append([row[:] for row in normalized_rows])
|
|
235
|
+
|
|
236
|
+
spacing = 1
|
|
237
|
+
glyph_widths = [len(glyph[0]) if glyph and glyph[0] else 0 for glyph in glyphs]
|
|
238
|
+
total_glyph_width = sum(glyph_widths) + spacing * (len(glyph_widths) - 1 if glyph_widths else 0)
|
|
239
|
+
|
|
240
|
+
segment_width = MATRIX_WIDTH
|
|
241
|
+
total_width = segment_width * len(controllers)
|
|
242
|
+
frame_duration = 0.05
|
|
243
|
+
|
|
244
|
+
if total_glyph_width == 0:
|
|
245
|
+
animations = {}
|
|
246
|
+
|
|
247
|
+
for controller in controllers:
|
|
248
|
+
blank_columns = [[0] * MATRIX_HEIGHT for _ in range(segment_width)]
|
|
249
|
+
grid = Grid(
|
|
250
|
+
width=segment_width,
|
|
251
|
+
height=MATRIX_HEIGHT,
|
|
252
|
+
init_grid=[column[:] for column in blank_columns],
|
|
253
|
+
align_x='left',
|
|
254
|
+
)
|
|
255
|
+
frame_one = Frame(grid=grid)
|
|
256
|
+
frame_two = Frame(grid=Grid(
|
|
257
|
+
width=segment_width,
|
|
258
|
+
height=MATRIX_HEIGHT,
|
|
259
|
+
init_grid=[column[:] for column in blank_columns],
|
|
260
|
+
align_x='left',
|
|
261
|
+
))
|
|
262
|
+
animation = Animation(frame_data=[frame_one, frame_two])
|
|
263
|
+
animation.set_all_frame_durations(frame_duration)
|
|
264
|
+
animations[controller] = animation
|
|
265
|
+
|
|
266
|
+
return animations
|
|
267
|
+
|
|
268
|
+
canvas = [[0] * max(total_glyph_width, 1) for _ in range(MATRIX_HEIGHT)]
|
|
269
|
+
x_cursor = 0
|
|
270
|
+
|
|
271
|
+
for glyph, width in zip(glyphs, glyph_widths):
|
|
272
|
+
glyph_height = len(glyph)
|
|
273
|
+
vertical_padding = max((MATRIX_HEIGHT - glyph_height) // 2, 0)
|
|
274
|
+
|
|
275
|
+
for row_index in range(glyph_height):
|
|
276
|
+
if width:
|
|
277
|
+
canvas_row = canvas[vertical_padding + row_index]
|
|
278
|
+
canvas_row[x_cursor:x_cursor + width] = glyph[row_index]
|
|
279
|
+
|
|
280
|
+
x_cursor += width + spacing
|
|
281
|
+
|
|
282
|
+
offsets = range(-total_width, total_glyph_width)
|
|
283
|
+
frame_columns_by_controller = {controller: [] for controller in controllers}
|
|
284
|
+
|
|
285
|
+
for offset in offsets:
|
|
286
|
+
window_rows = [
|
|
287
|
+
[canvas[row][offset + column] if 0 <= offset + column < total_glyph_width else 0 for column in range(total_width)]
|
|
288
|
+
for row in range(MATRIX_HEIGHT)
|
|
289
|
+
]
|
|
290
|
+
|
|
291
|
+
window_columns = [
|
|
292
|
+
[window_rows[row][column] for row in range(MATRIX_HEIGHT)]
|
|
293
|
+
for column in range(total_width)
|
|
294
|
+
]
|
|
295
|
+
|
|
296
|
+
for index, controller in enumerate(controllers):
|
|
297
|
+
start = index * segment_width
|
|
298
|
+
end = start + segment_width
|
|
299
|
+
segment = [column[:] for column in window_columns[start:end]]
|
|
300
|
+
|
|
301
|
+
if len(segment) < segment_width:
|
|
302
|
+
segment.extend([[0] * MATRIX_HEIGHT for _ in range(segment_width - len(segment))])
|
|
303
|
+
|
|
304
|
+
grid = Grid(
|
|
305
|
+
width=segment_width,
|
|
306
|
+
height=MATRIX_HEIGHT,
|
|
307
|
+
init_grid=segment,
|
|
308
|
+
align_x='left',
|
|
309
|
+
)
|
|
310
|
+
frame_columns_by_controller[controller].append(Frame(grid=grid))
|
|
311
|
+
|
|
312
|
+
animations = {}
|
|
313
|
+
|
|
314
|
+
for controller, frames in frame_columns_by_controller.items():
|
|
315
|
+
animation = Animation(frame_data=frames or [Frame(width=segment_width, height=MATRIX_HEIGHT)])
|
|
316
|
+
animation.set_all_frame_durations(frame_duration)
|
|
317
|
+
animations[controller] = animation
|
|
318
|
+
|
|
319
|
+
return animations
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _run_operation(controllers: Iterable, operation: Callable, *, concurrent: bool) -> None:
|
|
323
|
+
"""Run an operation against one or more controllers, optionally in parallel."""
|
|
324
|
+
|
|
325
|
+
if concurrent and len(controllers) > 1:
|
|
326
|
+
threads = []
|
|
327
|
+
|
|
328
|
+
for controller in controllers:
|
|
329
|
+
thread = threading.Thread(target=operation, args=(controller,), daemon=True)
|
|
330
|
+
thread.start()
|
|
331
|
+
threads.append(thread)
|
|
332
|
+
|
|
333
|
+
for thread in threads:
|
|
334
|
+
thread.join()
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
for controller in controllers:
|
|
338
|
+
operation(controller)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def execute_get_controllers(cli_args=None):
|
|
342
|
+
"""Return the available controllers honoring any CLI matrix selection.
|
|
343
|
+
|
|
344
|
+
Parameters:
|
|
345
|
+
cli_args (Optional[argparse.Namespace]):
|
|
346
|
+
The parsed command-line arguments. When provided, any matrix
|
|
347
|
+
selection flags (``--only-left`` / ``--only-right``) are applied to
|
|
348
|
+
the available controllers.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
List[LEDMatrixController]:
|
|
352
|
+
A list of controller objects, each representing an available LED matrix.
|
|
353
|
+
"""
|
|
354
|
+
from is_matrix_forge.led_matrix.controller import get_controllers
|
|
355
|
+
controllers = get_controllers(
|
|
356
|
+
threaded = True,
|
|
357
|
+
skip_all_init_animations = True,
|
|
358
|
+
clear_on_init = True
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
if not controllers:
|
|
362
|
+
raise SystemExit('No LED matrices are available.')
|
|
363
|
+
|
|
364
|
+
if filtered := _filter_controllers_by_side(controllers, cli_args):
|
|
365
|
+
return filtered
|
|
366
|
+
|
|
367
|
+
raise SystemExit(f'No LED matrices matched the requested selection ({_describe_selection(cli_args)}).')
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def scroll_text_command(cli_args=ARGUMENTS):
|
|
371
|
+
"""
|
|
372
|
+
Invokes the command to scroll text on the LED matrix.
|
|
373
|
+
|
|
374
|
+
Parameters:
|
|
375
|
+
cli_args (Optional[Arguments]):
|
|
376
|
+
The object that holds the command-line arguments. This is a subclass of `argparse.ArgumentParser`.
|
|
377
|
+
(Defaults to `ARGUMENTS`)
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
None
|
|
381
|
+
"""
|
|
382
|
+
from .arguments.commands.scroll_text import DIRECTION_MAP
|
|
383
|
+
controllers = execute_get_controllers(cli_args)
|
|
384
|
+
|
|
385
|
+
direction = DIRECTION_MAP[cli_args.direction.strip().lower()]
|
|
386
|
+
text = cli_args.input
|
|
387
|
+
|
|
388
|
+
sequential_requested = getattr(cli_args, 'sequential', False) and len(controllers) > 1
|
|
389
|
+
span_requested = getattr(cli_args, 'span_matrices', False) and len(controllers) > 1
|
|
390
|
+
|
|
391
|
+
span_animations = None
|
|
392
|
+
|
|
393
|
+
if span_requested:
|
|
394
|
+
if cli_args.direction.strip().lower() != 'h':
|
|
395
|
+
raise SystemExit('--span-matrices requires --direction h.')
|
|
396
|
+
|
|
397
|
+
if sequential_requested:
|
|
398
|
+
raise SystemExit('--span-matrices cannot be combined with --sequential.')
|
|
399
|
+
|
|
400
|
+
controllers = _order_controllers_for_span(controllers)
|
|
401
|
+
span_animations = _build_horizontal_span_animations(text, controllers)
|
|
402
|
+
sequential = False
|
|
403
|
+
concurrent = True
|
|
404
|
+
else:
|
|
405
|
+
sequential = sequential_requested
|
|
406
|
+
concurrent = not sequential
|
|
407
|
+
|
|
408
|
+
def activator(devices, _stop_event):
|
|
409
|
+
def operation(controller):
|
|
410
|
+
controller.keep_alive = True
|
|
411
|
+
if span_animations is not None:
|
|
412
|
+
animation = span_animations.get(controller)
|
|
413
|
+
if animation is None:
|
|
414
|
+
return
|
|
415
|
+
controller.play_animation(animation)
|
|
416
|
+
else:
|
|
417
|
+
controller.scroll_text(text, direction=direction)
|
|
418
|
+
|
|
419
|
+
_run_operation(devices, operation, concurrent=concurrent)
|
|
420
|
+
|
|
421
|
+
def invoke(targets, index=None):
|
|
422
|
+
run_with_guard(
|
|
423
|
+
targets,
|
|
424
|
+
run_for=None,
|
|
425
|
+
clear_after=False,
|
|
426
|
+
activator=activator,
|
|
427
|
+
thread_name='scroll-text-guard' if index is None else f'scroll-text-guard-{index}',
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
if sequential:
|
|
431
|
+
for index, controller in enumerate(controllers, start=1):
|
|
432
|
+
invoke([controller], index)
|
|
433
|
+
else:
|
|
434
|
+
invoke(controllers)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def display_text_command(cli_args):
|
|
438
|
+
"""Display static text on the selected LED matrices until interrupted."""
|
|
439
|
+
controllers = execute_get_controllers(cli_args)
|
|
440
|
+
|
|
441
|
+
clear_after = not cli_args.skip_clear
|
|
442
|
+
sequential = getattr(cli_args, 'sequential', False) and len(controllers) > 1
|
|
443
|
+
|
|
444
|
+
if sequential and cli_args.run_for is None:
|
|
445
|
+
raise SystemExit('--sequential requires --run-for when multiple matrices are targeted.')
|
|
446
|
+
|
|
447
|
+
wait_for_interrupt = cli_args.run_for is None
|
|
448
|
+
text = cli_args.text
|
|
449
|
+
|
|
450
|
+
concurrent = not sequential
|
|
451
|
+
|
|
452
|
+
def activator(devices, _stop_event):
|
|
453
|
+
def operation(controller):
|
|
454
|
+
controller.keep_alive = True
|
|
455
|
+
controller.show_text(text)
|
|
456
|
+
|
|
457
|
+
_run_operation(devices, operation, concurrent=concurrent)
|
|
458
|
+
|
|
459
|
+
def invoke(targets, index=None):
|
|
460
|
+
run_with_guard(
|
|
461
|
+
targets,
|
|
462
|
+
run_for=cli_args.run_for,
|
|
463
|
+
clear_after=clear_after,
|
|
464
|
+
activator=activator,
|
|
465
|
+
thread_name='display-text-guard' if index is None else f'display-text-guard-{index}',
|
|
466
|
+
wait_for_interrupt=wait_for_interrupt,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
if sequential:
|
|
470
|
+
for index, controller in enumerate(controllers, start=1):
|
|
471
|
+
invoke([controller], index)
|
|
472
|
+
else:
|
|
473
|
+
invoke(controllers)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def identify_matrices_command(cli_args):
|
|
477
|
+
"""Run the identification routine on the selected LED matrices.
|
|
478
|
+
|
|
479
|
+
Parameters:
|
|
480
|
+
cli_args: argparse.Namespace
|
|
481
|
+
The parsed arguments for the ``identify-matrices`` sub-command.
|
|
482
|
+
"""
|
|
483
|
+
controllers = execute_get_controllers(cli_args)
|
|
484
|
+
|
|
485
|
+
for controller in controllers:
|
|
486
|
+
controller.identify(
|
|
487
|
+
skip_clear=cli_args.skip_clear,
|
|
488
|
+
duration=float(cli_args.runtime),
|
|
489
|
+
cycles=int(cli_args.cycle_count),
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def main(cli_args=ARGUMENTS):
|
|
494
|
+
"""
|
|
495
|
+
Parses and handles command-line arguments, registering specific subcommands
|
|
496
|
+
and executing associated functions.
|
|
497
|
+
|
|
498
|
+
This function sets up a command-line interface (CLI) parser, registers a
|
|
499
|
+
specific subcommand (`scroll-text`), and associates a function (`scroll_text_command`)
|
|
500
|
+
with this subcommand. After parsing the arguments, it executes the appropriate
|
|
501
|
+
function based on the parsed subcommand.
|
|
502
|
+
|
|
503
|
+
Parameters:
|
|
504
|
+
cli_args (Optional[Arguments]):
|
|
505
|
+
An object containing the command-line parser and associated
|
|
506
|
+
subcommand configurations. It must have attributes `scroll_parser`
|
|
507
|
+
and a callable `parse` function. (Defaults to `ARGUMENTS`)
|
|
508
|
+
"""
|
|
509
|
+
parser_bindings = (
|
|
510
|
+
('scroll_parser', 'Scroll text command parser was not initialized.', scroll_text_command),
|
|
511
|
+
('identify_parser', 'Identify matrices command parser was not initialized.', identify_matrices_command),
|
|
512
|
+
('display_parser', 'Display text command parser was not initialized.', display_text_command),
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
for attr_name, error_message, handler in parser_bindings:
|
|
516
|
+
parser = getattr(cli_args, attr_name)
|
|
517
|
+
if parser is None:
|
|
518
|
+
raise RuntimeError(error_message)
|
|
519
|
+
parser.set_defaults(func=handler)
|
|
520
|
+
|
|
521
|
+
# Parse command-line arguments;
|
|
522
|
+
parsed = cli_args.parse()
|
|
523
|
+
|
|
524
|
+
# Run the function associated with the parsed command;
|
|
525
|
+
parsed.func(parsed)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
if __name__ == '__main__':
|
|
529
|
+
main()
|
|
530
|
+
|
|
531
|
+
|
|
@@ -34,4 +34,11 @@ def register_command(parser: ArgumentParser):
|
|
|
34
34
|
help='Do not clear the matrix after the display stops.',
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
+
display_parser.add_argument(
|
|
38
|
+
'--sequential',
|
|
39
|
+
action='store_true',
|
|
40
|
+
default=False,
|
|
41
|
+
help='When multiple matrices are active, present the text on one matrix at a time.',
|
|
42
|
+
)
|
|
43
|
+
|
|
37
44
|
return display_parser
|
|
@@ -32,4 +32,18 @@ def register_command(parser: ArgumentParser):
|
|
|
32
32
|
help='The direction to scroll the text in. Default is up.'
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
+
scroll_parser.add_argument(
|
|
36
|
+
'--sequential',
|
|
37
|
+
action='store_true',
|
|
38
|
+
default=False,
|
|
39
|
+
help='Scroll across one matrix at a time when multiple are selected. Cannot be used with --span-matrices.',
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
scroll_parser.add_argument(
|
|
43
|
+
'--span-matrices',
|
|
44
|
+
action='store_true',
|
|
45
|
+
default=False,
|
|
46
|
+
help='Treat multiple matrices as a single wide canvas when scrolling horizontally. Cannot be used with --sequential.',
|
|
47
|
+
)
|
|
48
|
+
|
|
35
49
|
return scroll_parser
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LED Matrix package initialization.
|
|
3
|
+
|
|
4
|
+
Keep this module light: no hardware access, no file IO, no heavy imports.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from .utils import (
|
|
10
|
+
get_first_run,
|
|
11
|
+
set_first_run,
|
|
12
|
+
process_first_run,
|
|
13
|
+
initialize,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
'get_first_run',
|
|
18
|
+
'set_first_run',
|
|
19
|
+
'process_first_run',
|
|
20
|
+
'initialize',
|
|
21
|
+
'LEDMatrixController',
|
|
22
|
+
'get_controllers',
|
|
23
|
+
'scroll_text_on_multiple_matrices',
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def __getattr__(name: str):
|
|
28
|
+
if name in ('LEDMatrixController', 'get_controllers'):
|
|
29
|
+
from .controller import LEDMatrixController, get_controllers
|
|
30
|
+
globals().update({
|
|
31
|
+
'LEDMatrixController': LEDMatrixController,
|
|
32
|
+
'get_controllers': get_controllers,
|
|
33
|
+
})
|
|
34
|
+
return globals()[name]
|
|
35
|
+
|
|
36
|
+
if name == 'scroll_text_on_multiple_matrices':
|
|
37
|
+
from .display.text.scroller import scroll_text_on_multiple_matrices
|
|
38
|
+
globals()['scroll_text_on_multiple_matrices'] = scroll_text_on_multiple_matrices
|
|
39
|
+
return scroll_text_on_multiple_matrices
|
|
40
|
+
|
|
41
|
+
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
|
|
@@ -22,6 +22,14 @@ class DeviceBase(Aliases):
|
|
|
22
22
|
device (ListPortInfo): The serial device to control.
|
|
23
23
|
thread_safe (bool): Enables use of an internal RLock for synchronized ops.
|
|
24
24
|
cmd_lock (Optional[threading.RLock]): Lazily created lock when thread_safe.
|
|
25
|
+
|
|
26
|
+
Properties:
|
|
27
|
+
location (Dict[str, Any]): Physical location information for the device.
|
|
28
|
+
location_abbrev (str): Abbreviated location identifier (e.g., 'R1', 'L2').
|
|
29
|
+
name (str): Device name.
|
|
30
|
+
serial_number (str): Device serial number.
|
|
31
|
+
side_of_keyboard (str): Which side of the keyboard the device is on ('left' or 'right').
|
|
32
|
+
slot (int): The slot number of the device (1 or 2).
|
|
25
33
|
"""
|
|
26
34
|
def __init__(self, *, device: ListPortInfo, thread_safe: bool = False, **kwargs: Any) -> None:
|
|
27
35
|
# Initialize core device/threading state BEFORE forwarding to super(), so
|
|
@@ -73,6 +81,28 @@ class DeviceBase(Aliases):
|
|
|
73
81
|
def serial_number(self) -> str:
|
|
74
82
|
return self.device.serial_number
|
|
75
83
|
|
|
84
|
+
@property
|
|
85
|
+
def side_of_keyboard(self) -> str:
|
|
86
|
+
"""
|
|
87
|
+
Get which side of the keyboard the device is on.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
str:
|
|
91
|
+
The side of the keyboard ('left' or 'right').
|
|
92
|
+
"""
|
|
93
|
+
return self.location['side']
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def slot(self) -> int:
|
|
97
|
+
"""
|
|
98
|
+
Get the slot number of the device.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
int:
|
|
102
|
+
The slot number (1 or 2).
|
|
103
|
+
"""
|
|
104
|
+
return self.location['slot']
|
|
105
|
+
|
|
76
106
|
def _ping(self) -> None:
|
|
77
107
|
try:
|
|
78
108
|
_ = self.animating
|
is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/controller/components/history/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .manager import DisplayHistoryManager
|