From d08655e6c35f2d450560c3d52eea4f1b9b3bfca3 Mon Sep 17 00:00:00 2001 From: Ange-Marie MAURIN Date: Sun, 11 Jan 2026 22:44:45 +0100 Subject: [PATCH] adding module dev section in readme.md --- README.md | 343 +++++++++++++++++++++++++++++++++++++++++++++++++++++- prompt.md | 6 +- 2 files changed, 339 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5703b61..49fd398 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,345 @@ Modular audiovisual synoptic node editor. -## installation +## Installation Just git clone the project. Open a terminal in the cloned folder. -``` shell - - - +```shell +$ python -m venv .venv +$ pip install -r requirements.txt +$ python synodetik.py ``` -## module development \ No newline at end of file +--- + +## Module Development + +Synodetik supports a modular architecture that allows developers to extend functionality through custom modules. Modules can add menu items, subscribe to events, and integrate with the node editor. + +### Module Locations + +Modules can be placed in two locations: + +| Location | Path | Description | +|----------|------|-------------| +| **Built-in** | `synodetik/modules/builtins/` | Shipped with the application | +| **User modules** | `~/.synodetik/modules/` | Custom user modules | + +### Creating a Module + +Create a new Python file in one of the module directories. Your module must: + +1. Subclass the `Module` base class +2. Implement the `info` property returning a `ModuleInfo` +3. Implement `on_load()` and `on_unload()` lifecycle methods + +### Basic Module Template + +```python +""" +My Custom Module - Brief description of what it does. +""" +from synodetik.modules import Module, ModuleInfo, MenuItem +from synodetik.events import EventType + + +class MyCustomModule(Module): + """ + Description of your module's functionality. + """ + + @property + def info(self) -> ModuleInfo: + """Return module metadata.""" + return ModuleInfo( + id="my_custom_module", # Unique identifier (lowercase, no spaces) + name="My Custom Module", # Display name + version="1.0.0", # Semantic version + author="Your Name", # Author name + description="Does something useful", # Brief description + dependencies=[], # List of required module IDs + website="" # Optional documentation URL + ) + + def on_load(self) -> bool: + """ + Called when the module is activated. + + Use this to: + - Subscribe to events + - Initialize resources + - Set up UI elements + + Returns: + True if loading succeeded, False to abort + """ + print(f"{self.info.name} loaded!") + return True + + def on_unload(self) -> None: + """ + Called when the module is deactivated. + + Use this to: + - Unsubscribe from events + - Clean up resources + - Remove UI elements + """ + print(f"{self.info.name} unloaded!") +``` + +### Adding Menu Items + +Override `get_menu_items()` to add entries to the application menu: + +```python +def get_menu_items(self) -> list[MenuItem]: + """Return menu items to add to the application.""" + return [ + MenuItem( + label="My Action", # Menu item text + callback=self._my_callback, # Function to call + shortcut="Ctrl+M", # Optional keyboard shortcut display + parent_menu="View", # Target menu: "File", "Edit", "View", "Modules" + separator_before=False # Add separator before this item + ) + ] + +def _my_callback(self, sender=None, app_data=None): + """Handle menu item click.""" + print("Menu item clicked!") +``` + +### Subscribing to Events + +Use the event system to react to application changes: + +```python +def on_load(self) -> bool: + # Subscribe to events + self.events.on(EventType.MACHINE_ADDED, self._on_machine_added) + self.events.on(EventType.CONNECTION_CREATED, self._on_connection_created) + return True + +def on_unload(self) -> None: + # Always unsubscribe when unloading + self.events.off(EventType.MACHINE_ADDED, self._on_machine_added) + self.events.off(EventType.CONNECTION_CREATED, self._on_connection_created) + +def _on_machine_added(self, event): + """Handle machine added event.""" + machine = event.data.get('machine') + print(f"Machine added: {machine.name}") + +def _on_connection_created(self, event): + """Handle connection created event.""" + connection = event.data.get('connection') + print(f"Connection created: {connection.id}") +``` + +### Available Event Types + +| Event | Description | Data | +|-------|-------------|------| +| `APP_STARTED` | Application has started | - | +| `APP_CLOSING` | Application is closing | - | +| `SYNOPTIK_CREATED` | New synoptik created | `synoptik` | +| `SYNOPTIK_LOADED` | Synoptik loaded from file | `synoptik` | +| `SYNOPTIK_SAVED` | Synoptik saved to file | `filepath` | +| `SYNOPTIK_MODIFIED` | Synoptik was modified | `machine_id`, `property`, `value` | +| `MACHINE_ADDED` | Machine added to synoptik | `machine` | +| `MACHINE_REMOVED` | Machine removed from synoptik | `machine_id` | +| `CONNECTION_CREATED` | Connection created | `connection` | +| `CONNECTION_REMOVED` | Connection removed | `connection_id` | +| `TEMPLATE_CREATED` | Machine template created | `machine` | +| `TEMPLATE_DELETED` | Machine template deleted | `machine_id` | + +### Accessing Application Services + +Modules have access to core services through properties: + +```python +def on_load(self) -> bool: + # Access the main application + app = self.app + + # Access the current synoptik (diagram) + if self.app and self.app.synoptik: + machines = self.app.synoptik.machines + connections = self.app.synoptik.connections + + # Access the event dispatcher + dispatcher = self.events + + return True +``` + +### Adding Context Menu Items + +Override `on_context_menu()` to add items to the right-click menu: + +```python +def on_context_menu(self, menu_parent: int, context: dict) -> None: + """ + Called when building the context menu. + + Args: + menu_parent: DearPyGui parent ID for adding items + context: Dict containing: + - 'selected_machines': List of selected machine IDs + - 'selected_connections': List of selected connection IDs + - 'position': Mouse position (x, y) + """ + import dearpygui.dearpygui as dpg + + selected = context.get('selected_machines', []) + if selected: + dpg.add_menu_item( + label="My Custom Action", + callback=self._custom_action, + user_data=selected, + parent=menu_parent + ) + +def _custom_action(self, sender, app_data, user_data): + """Handle context menu action.""" + selected_machines = user_data + print(f"Action on: {selected_machines}") +``` + +### Adding to Properties Panel + +Override `on_properties_panel()` to add custom properties: + +```python +def on_properties_panel(self, panel_parent: int, selection: dict) -> None: + """ + Called when properties panel needs custom content. + + Args: + panel_parent: DearPyGui parent ID for adding items + selection: Dict with selected machine/connection info + """ + import dearpygui.dearpygui as dpg + + dpg.add_separator(parent=panel_parent) + dpg.add_text("My Module Properties", parent=panel_parent) + dpg.add_button( + label="Custom Action", + callback=self._panel_action, + parent=panel_parent + ) +``` + +### Complete Example: Statistics Module + +Here's a complete example that displays synoptik statistics: + +```python +""" +Statistics module - Displays synoptik metrics. +""" +import dearpygui.dearpygui as dpg + +from synodetik.modules import Module, ModuleInfo, MenuItem +from synodetik.events import EventType + + +class StatisticsModule(Module): + """Displays statistics about the current synoptik.""" + + @property + def info(self) -> ModuleInfo: + return ModuleInfo( + id="statistics", + name="Statistics", + version="1.0.0", + author="Synodetik", + description="Display synoptik statistics" + ) + + def on_load(self) -> bool: + # Subscribe to relevant events + self.events.on(EventType.MACHINE_ADDED, self._update_stats) + self.events.on(EventType.MACHINE_REMOVED, self._update_stats) + self.events.on(EventType.CONNECTION_CREATED, self._update_stats) + self.events.on(EventType.CONNECTION_REMOVED, self._update_stats) + return True + + def on_unload(self) -> None: + # Unsubscribe from events + self.events.off(EventType.MACHINE_ADDED, self._update_stats) + self.events.off(EventType.MACHINE_REMOVED, self._update_stats) + self.events.off(EventType.CONNECTION_CREATED, self._update_stats) + self.events.off(EventType.CONNECTION_REMOVED, self._update_stats) + + # Clean up UI + if dpg.does_item_exist("stats_window"): + dpg.delete_item("stats_window") + + def get_menu_items(self) -> list[MenuItem]: + return [ + MenuItem( + label="Show Statistics", + callback=self._show_window, + parent_menu="View" + ) + ] + + def _show_window(self, sender=None, app_data=None): + """Create or focus the statistics window.""" + if dpg.does_item_exist("stats_window"): + dpg.focus_item("stats_window") + return + + with dpg.window( + label="Statistics", + tag="stats_window", + width=250, + height=150, + pos=(100, 100) + ): + dpg.add_text("Machines: 0", tag="stats_machines") + dpg.add_text("Connections: 0", tag="stats_connections") + + self._update_stats(None) + + def _update_stats(self, event): + """Update statistics display.""" + if not self.app or not self.app.synoptik: + return + + synoptik = self.app.synoptik + + if dpg.does_item_exist("stats_machines"): + dpg.set_value("stats_machines", f"Machines: {len(synoptik.machines)}") + if dpg.does_item_exist("stats_connections"): + dpg.set_value("stats_connections", f"Connections: {len(synoptik.connections)}") +``` + +### Best Practices + +1. **Always clean up** - Unsubscribe from events and delete UI elements in `on_unload()` +2. **Check for None** - Always verify `self.app` and `self.app.synoptik` exist before accessing +3. **Use unique IDs** - Prefix DearPyGui tags with your module ID to avoid conflicts +4. **Handle errors gracefully** - Wrap callbacks in try/except to prevent crashes +5. **Document your module** - Include docstrings explaining functionality +6. **Test thoroughly** - Test loading, unloading, and all event handlers + +### Module Dependencies + +If your module requires other modules, list them in dependencies: + +```python +@property +def info(self) -> ModuleInfo: + return ModuleInfo( + id="my_advanced_module", + name="Advanced Module", + version="1.0.0", + dependencies=["statistics", "other_module"] # Required modules + ) +``` + +The module manager will automatically load dependencies before your module. diff --git a/prompt.md b/prompt.md index 05598f6..3e4ffc2 100644 --- a/prompt.md +++ b/prompt.md @@ -1,12 +1,10 @@ # création modules - ``` -Je souhaite rendre le code un peu plus clair. Peux tu me proposer des actions de refactoring, sachant que je m'oriente vers un systeme de modules (addon). Je souhaite qu'un developpeur puisse ajouter des fonctionalités au programme en créant une structure de class particuliere et en placant sont code dans le dossier modules du projet. Comment faire ? - - +Je souhaite maintenant que les connexions inter-node aient la même couleurs que la couleurs des flux. Comment faire cela. Explique moi d'abord je veux valider avant de faire les modifications. ``` + ``` Je souhaite mettre en place une logique de module (sortes d'addons).