Pyrox is a Python back-end engine and application framework built on PyQt6. It provides a rich set of interfaces, models, services, and abstractions for building industrial automation and desktop applications. Pyrox is designed to be used as a foundation β downstream projects like ControlRox build their entire application layer on top of it.
Pyrox enforces a strict separation of concerns through a dedicated interface layer (pyrox/interfaces/). Every major abstraction β applications, tasks, scenes, physics bodies, workspaces, services β is defined as a Protocol or ABC in the interface layer before any concrete implementation exists. This allows downstream consumers to depend on contracts, not implementations.
- Structural Protocols (
Protocol+@runtime_checkable):INameable,IDescribable,IRunnable,IConfigurable,IHasId,ISpatial2D,IKinematic2D,IPhysicsBody2D, and more - Application Interfaces:
IApplication,IApplicationTask,IWorkspace,IViewport - Scene Interfaces:
IScene,ISceneObject,ISceneGroup,ISceneBridge,ISceneBoundLayer,ICompositeSceneObject - Physics Interfaces:
IBasePhysicsBody,ICollider2D,IRigidBody2D,IMaterial,IPhysicsEngine - Service Interfaces:
IEnvironmentManager,ILoggingManager,ILogger
Pyrox is built exclusively on PyQt6. All GUI components, the workspace layout, and the application shell are PyQt6-native. There is no tkinter dependency.
- VSCode-style Workspace: Vertical icon sidebar (
QTabWidget), central workspace area forTaskFramepanels, an integrated log window, and a status bar β all managed byWorkspace - TaskFrame System: Custom widgets subclass
TaskFrame, expose a.root: QWidgetproperty, and are registered with the workspace for docking and visibility management - CommandBar: Composable toolbars built with
CommandButtonandCommandDropdownwidgets - AttributeTreeView: Lazy object introspection tree for exploring any Python object at runtime
- ObjectExplorer: Scene object list with add/remove and selection callbacks
- PropertyPanel: Auto-generated property editor for objects implementing
IHasProperties - SceneViewer: Full composited scene viewer including a canvas viewport, object explorer, property panel, scene bridge, and toolbar β all wired together
- YAML/Text Editors:
PyroxYamlEditorandTextEditorFramefor in-app editing - Dark Theme:
DefaultThemeprovides consistent color constants across all widgets
The ServicesRunnableMixin composes focused sub-mixins to give any class clean, property-based access to all platform services:
| Property | Service |
|---|---|
self.env / self.env_keys |
EnvManager + EnvironmentKeys |
self.gui |
GuiManager (PyQt6 root, menus, geometry) |
self.logging / self.log() |
LoggingManager |
self.directory |
PlatformDirectoryService |
self.root_window / self.file_menu β¦ |
Convenience accessors on GuiManager |
Static service classes (EnvManager, GuiManager, LoggingManager, MenuRegistry, GuiStateService) are never instantiated β they expose only class methods and properties.
Applications are extended through ApplicationTask subclasses. Each task:
- Is auto-registered with
ApplicationTaskFactoryvia theFactoryTypeABCmetaclass β no manual wiring required - Is instantiated automatically at application startup via
ApplicationTaskFactory.build_tasks(app) - Injects menu commands into the application's menu bar via
register_menu_command() - Creates and manages its own
TaskFramepanel viacreate_task_frame()andcreate_or_raise_frame()
from pyrox.models.task import ApplicationTask
from pyrox.models.gui.frame import TaskFrame
class MyTask(ApplicationTask):
def __init__(self, application):
super().__init__(application)
self.register_menu_command(
menu=self.file_menu,
registry_id='my_task.open',
registry_path='File/My Task',
index=0,
label='My Task...',
command=self.create_or_raise_frame,
)
def create_task_frame(self) -> TaskFrame:
return MyTaskFrame(self.application.workspace.workspace_area)Pyrox provides the foundational layer for building 2D scene-based emulation environments:
- Scene Graph:
ScenecontainsSceneObject,SceneGroup, andCompositeSceneObjectinstances; all implement interface contracts frompyrox.interfaces - Physics Engine:
PhysicsEngineServicedrives a 2D physics simulation. Built-in body types includeConveyorBody,CrateBody,FloorBody,SensorBody, andActorBody - Collision Service:
CollisionServicewith aSpatialGridfor broad-phase collision detection - Scene Runner:
SceneRunnerServiceandSceneEventBusmanage scene lifecycle and broadcast scene events - Scene Viewer Task:
SceneviewerApplicationTaskwires together the full interactive scene viewer UI - Scene Bridge:
ISceneBridge/ISceneBoundLayercontracts decouple the rendered canvas layer from the domain scene model
Typed, decoupled communication between components via EventBus:
from pyrox.services.bus import EventBus, Event, EventType
class MyEventType(EventType):
SOMETHING_HAPPENED = 'something_happened'
class MyEvent(Event[MyEventType]):
data: str
class MyEventBus(EventBus[MyEventType, MyEvent]):
pass
MyEventBus.subscribe(MyEventType.SOMETHING_HAPPENED, my_callback)
MyEventBus.publish(MyEvent(event_type=MyEventType.SOMETHING_HAPPENED, data='hello'))Each EventBus subclass gets its own isolated subscriber registry β no cross-bus leakage.
MetaFactory enables extensible type registries. FactoryTypeABC auto-registers subclasses with a target factory via __init_subclass__:
from pyrox.models.factory import MetaFactory, FactoryTypeABC
class MyFactory(MetaFactory):
pass # gets its own _registered_types dict automatically
class MyBase(FactoryTypeABC[MyFactory]):
pass # auto-registers with MyFactory
class MyConcrete(MyBase):
pass # auto-registered as 'MyConcrete' in MyFactory._registered_typesEnvManager:.env-based configuration viapython-dotenv; typedget()with defaultsGuiManager: Static PyQt6 root window, menu bar, and geometry management; event schedulingLoggingManager: Named loggers, stream capture, and callback routing for log outputPlatformDirectoryService: Cross-platform user data, log, and config directories viaplatformdirsGuiStateService: Persists GUI layout preferences (splitter ratios, sidebar widths, etc.) as JSON between sessionsMenuRegistry: Centralized registry for all menu items; supportsenable_item()andset_command()from anywhereSceneRunnerService/SceneEventBus: Scene lifecycle management and event broadcastingCollisionService/SpatialGrid: 2D spatial indexing and collision detectionPhysicsEngineService: 2D physics simulation driverStatusUpdateEventBus: Application-wide status bar messaging
- Python 3.13.9+ (Required)
- Cross-platform support (Windows, Linux, macOS)
# Clone the repository
git clone https://github.com/iroxusux/Pyrox.git
cd Pyrox
# Run the installation script (recommended β handles venv, deps, and git hooks)
./install.sh
# Or install manually:
pip install -e . --upgrade
python utils/setup_hooks.py # Set up pre-commit hooksKey runtime dependencies installed automatically:
| Package | Purpose |
|---|---|
PyQt6 |
GUI framework (required) |
python-dotenv |
.env configuration loading |
platformdirs |
Cross-platform directory resolution |
pyyaml |
YAML file support |
lxml |
XML processing |
pandas / openpyxl |
Data and Excel processing |
Pillow |
Image processing |
pdfplumber / PyMuPDF |
PDF processing |
pylogix |
Allen-Bradley PLC communication |
tomli |
TOML parsing |
# myapp/__main__.py
import pyrox
from pyrox.application import Application
app = Application()
app.run()Configuration is read from .env at startup:
# .env
APP_NAME=My Application
APP_DESCRIPTION=Built on Pyrox
APP_AUTHOR=Your Namefrom pyrox.models.task import ApplicationTask
from pyrox.models.gui.frame import TaskFrame
from pyrox.interfaces import IApplication
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QWidget
class MyTaskFrame(TaskFrame):
def __init__(self, parent: QWidget):
super().__init__()
layout = QVBoxLayout()
layout.addWidget(QLabel("Hello from MyTask!"))
self._root = QWidget(parent)
self._root.setLayout(layout)
@property
def root(self) -> QWidget:
return self._root
class MyTask(ApplicationTask):
def __init__(self, application: IApplication) -> None:
super().__init__(application)
self.register_menu_command(
menu=self.file_menu,
registry_id='my_task.open',
registry_path='File/My Task',
index=0,
label='My Task...',
command=self.create_or_raise_frame,
)
def create_task_frame(self) -> TaskFrame:
return MyTaskFrame(self.application.workspace.workspace_area)MyTask is automatically discovered and built at startup β no registration call needed.
from pyrox.services.logging import log
class MyClass:
def do_work(self):
log(self).info('Starting work...')
log(self).debug('Detail message')from pyrox.services.status import StatusUpdateEventBus, StatusUpdateEvent, StatusUpdateEventType
StatusUpdateEventBus.publish(
StatusUpdateEvent(
event_type=StatusUpdateEventType.UPDATE,
status_message='Processing complete'
)
)pyrox/
βββ interfaces/ # Abstract contracts only β no implementations
β βββ protocols/ # Structural Protocol definitions
β β βββ meta.py # INameable, IRunnable, IConfigurable, IHasId, β¦
β β βββ coord.py # ICoord2D, IArea2D
β β βββ spatial.py # ISpatial2D, IRotatable, IDirectional2D, IZoomable
β β βββ kinematic.py # IVelocity2D, IKinematic2D
β β βββ physics.py # IPhysicsBody2D, IRigidBody2D, IMaterial, ICollider2D
β β βββ property.py # IHasProperties
β β βββ connection.py # IConnectable, Connection
β β βββ gui.py # IHasCanvas
β βββ scene/ # IScene, ISceneObject, ISceneGroup, ISceneBridge, β¦
β βββ gui/ # IWorkspace, IViewport
β βββ physics.py # IBasePhysicsBody
β βββ application.py # IApplication, IApplicationTask
β βββ services.py # IEnvironmentManager, ILoggingManager, ILogger
β βββ constants.py # EnvironmentKeys
β
βββ models/ # Concrete implementations
β βββ factory.py # MetaFactory, FactoryTypeABC
β βββ task.py # ApplicationTask, ApplicationTaskFactory
β βββ services.py # ServicesRunnableMixin (Supports* sub-mixins)
β βββ meta.py # PyroxObject, SnowFlake, SliceableInt
β βββ list.py # HashList, SafeList, TrackedList, Subscribable
β βββ runtime.py # RuntimeDict
β βββ protocols/ # CoreMixin, Nameable, HasId, PhysicsBody2D, β¦
β βββ scene/ # Scene, SceneObject, SceneGroup, SceneBridge, β¦
β βββ physics/ # BasePhysicsBody, ConveyorBody, CrateBody, FloorBody, β¦
β βββ gui/ # All PyQt6 widgets
β βββ frame.py # TaskFrame base class
β βββ workspace.py # Workspace (VSCode-style layout)
β βββ commandbar.py # CommandBar, CommandButton, CommandDropdown
β βββ treeview.py # AttributeTreeView
β βββ objectexplorer.py # ObjectExplorer
β βββ propertypanel.py # PropertyPanel
β βββ logframe.py # LogFrame
β βββ theme.py # DefaultTheme
β βββ yamleditor.py # PyroxYamlEditor
β βββ texteditor.py # TextEditorFrame
β βββ contextmenu.py # PyroxContextMenu
β βββ sceneviewer/ # Full scene viewer composite widget
β
βββ services/ # Static-class utilities
β βββ logging.py # log(), LoggingManager, StreamCapture
β βββ env.py # EnvManager
β βββ gui.py # GuiManager (PyQt6)
β βββ gui_state.py # GuiStateService
β βββ menu_registry.py # MenuRegistry, MenuItemDescriptor
β βββ bus.py # EventBus, Event, EventType
β βββ status.py # StatusUpdateEventBus
β βββ scene.py # SceneRunnerService, SceneEventBus, HasSceneMixin
β βββ file.py # get_open_file, get_save_file, PlatformDirectoryService
β βββ theme.py # ThemeManager
β βββ collision.py # CollisionService, SpatialGrid
β βββ physics.py # PhysicsEngineService
β
βββ tasks/ # Built-in ApplicationTask implementations
β βββ builtin.py # FileTask, HelpTask, ToolsTask, ViewTask
β βββ sceneviewer.py # SceneviewerApplicationTask
β
βββ application.py # Application (concrete IApplication entry point)
βββ ui/ # Icons and splash assets
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Downstream Application β
β (e.g. ControlRox, custom apps) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β pyrox/tasks/ β
β ApplicationTask subclasses (built-in) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β pyrox/models/ β pyrox/services/ β
β Concrete impls β Static service classes β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β pyrox/interfaces/ β
β Protocols, ABCs β no implementation code β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Pattern | Where Used |
|---|---|
| Interface Segregation | pyrox/interfaces/ β one interface per concern |
| Mixin Composition | ServicesRunnableMixin composed of Supports* mixins |
| Factory + Auto-registration | MetaFactory + FactoryTypeABC.__init_subclass__ |
| Event Bus | EventBus[T, E] with per-subclass subscriber isolation |
| RuntimeDict | Dict with save/change callbacks for persistent state |
| Menu Registry | MenuRegistry centralises all menu items across tasks |
Pyrox provides the scaffolding β interface contracts, service infrastructure, GUI shell, scene engine, and physics layer β so that application developers can focus exclusively on domain logic.
- ControlRox: Industrial automation application built directly on Pyrox; uses
ApplicationTask,Scene,SceneObject, and the PyQt6 workspace - Custom desktop tools: Any Python desktop application that needs a structured, extensible PyQt6 shell
- Emulation environments: Applications that simulate physical processes using Pyrox's scene graph and physics engine
- New application: Subclass
Applicationfrompyrox.application - New feature/panel: Subclass
ApplicationTask; implementcreate_task_frame()and register menu commands in__init__ - New GUI widget: Subclass
TaskFrame; expose.root: QWidgetfor workspace integration - New service: Add a static class to
pyrox/services/; expose viapyrox/services/__init__.py - New interface: Add a
ProtocolorABCtopyrox/interfaces/with no implementation - New event: Subclass
EventType,Event, andEventBusβ one class per domain - New scene object type: Subclass
SceneObjectand implement theISceneObjectinterface - New physics body: Subclass
BasePhysicsBodyand register withPhysicsSceneFactory
# Install with venv and git hooks (recommended)
./install.sh
# Or manually
pip install -e . --upgrade
python utils/setup_hooks.pypytest pyrox/services/test/
pytest pyrox/models/test/
pytest pyrox/tasks/test/./build.sh| Script | Purpose |
|---|---|
utils/sync_readme.py |
Sync README version/status badges from pyproject.toml |
utils/check_version_increment.py |
Verify a version bump exists for code changes |
utils/setup_hooks.py |
Install pre-commit hooks |
utils/extract_todos.py |
Generate code_todos.md from source TODOs |
The pre-commit hook automatically:
- Checks for a version bump when Python source files change
- Syncs README badges from
pyproject.toml(version,requires-python, classifiers)
Files that do not require a version bump: README.md, .md files, docs/, .gitignore, LICENSE, .yml/.yaml, utils/, hooks/
Files that do require a version bump: pyrox/**/*.py, pyproject.toml
- ControlRox β Industrial automation application built on Pyrox
Contributions are welcome! Please maintain architectural consistency:
- New interfaces go in
pyrox/interfaces/with no implementation code - New concrete implementations go in
pyrox/models/ - New services go in
pyrox/services/as static classes - All code must target Python 3.13.9+ and use PyQt6 for any GUI work
- Use built-in generic types (
list[str],dict[str, int]) β nevertyping.List,typing.Dict, etc. - Use
X | Yunions β neverOptional[X]orUnion[X, Y]
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the GNU General Public License v3.0 β see the LICENSE file for details.
Brian LaFond
π§ Brian.L.LaFond@gmail.com
π GitHub
Pyrox β PyQt6-based back-end engine providing interfaces, models, services, and a scene/emulation environment for building industrial automation and desktop applications