Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5efed39
style(ruff): :art: global ruff pass - no functional change
philippe-ynput Dec 15, 2025
09d900f
feat(groups): add node groups for visual organization of related nodes
philippe-ynput Dec 15, 2025
cde5f69
fix(view): :zap: finish implementation: drop a node over a group to a…
philippe-ynput Dec 16, 2025
61145b6
fix(models): fix a recursion error in is_compatible_type().
philippe-ynput Dec 16, 2025
fcf1c6d
refactor(signals): rename group signals to remove redundant prefix.
philippe-ynput Dec 16, 2025
b9bfd9c
feat(signals): add group_created and group_membership_changed signals.
philippe-ynput Dec 16, 2025
dccd267
feat(groups): add clear_node_groups method to remove all groups
philippe-ynput Dec 17, 2025
f0ed85f
feat(api): add frame_all method to NodzAPI
philippe-ynput Dec 18, 2025
baad754
feat(api): add layout_graph method to NodzAPI.
philippe-ynput Dec 18, 2025
2625e15
refactor(groups): change color parameter from hex string to rgba tuple
philippe-ynput Dec 19, 2025
811fb12
feat(groups): add set_node_group_color method to NodeGroupController …
philippe-ynput Dec 19, 2025
bdffc5a
refactor(groups): update group view color from model
philippe-ynput Dec 19, 2025
5d7867e
feat(api): add selected_nodes method to NodzAPI
philippe-ynput Dec 19, 2025
1c0c938
feat(groups): emit group_deleted signal and handle group deletion in …
philippe-ynput Dec 19, 2025
bf313af
fix(api): prevent signal emission during API node group operations
philippe-ynput Dec 19, 2025
357d334
style: format code to follow PEP-8 line length guidelines
philippe-ynput Dec 22, 2025
3c49d0f
refactor(api): simplify signal blocking using decorator pattern.
philippe-ynput Dec 22, 2025
3e3a21e
fix(ui): disable help display by default
philippe-ynput Dec 22, 2025
6c95502
fix(ui): include NodeGroupView in framable nodes.
philippe-ynput Dec 22, 2025
e7e8d3e
refactor(ui): update node group view resize handles and selection style
philippe-ynput Dec 22, 2025
b8b2bb8
feat(ui): add visual feedback for node group dragging
philippe-ynput Dec 22, 2025
4093bf7
feat(api): add node and group label support
philippe-ynput Dec 22, 2025
e61f903
refactor(errors): add Nodz prefix to attribute error classes
philippe-ynput Jan 6, 2026
d90219d
refactor(errors): add Nodz prefix to node and connection errors
philippe-ynput Jan 6, 2026
8323fe9
refactor(controllers): move random import to module level and remove …
philippe-ynput Jan 6, 2026
b353bc6
refactor(error): improve connection cleanup error handling
philippe-ynput Jan 6, 2026
fea6aee
refactor(errors): add Nodz prefix to group error classes
philippe-ynput Jan 6, 2026
778b791
fix: random color generation was failing in ayon-workflow
philippe-ynput Jan 6, 2026
b880eba
refactor(controllers): add emit parameter to control signal emission
philippe-ynput Jan 8, 2026
23ceed6
refactor(controllers): make group signal emission optional
philippe-ynput Jan 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 79 additions & 8 deletions API_MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,78 @@ nodz.api.signals.connection_created.connect(on_connection_created)

## New Features

### 1. Enhanced Error Handling
### 1. Node Groups API

The new unified API introduces comprehensive support for node groups, allowing visual organization of related nodes:

#### Group Operations

```python
# Create a group
group_info = api.create_node_group(
name="Processing", # Unique group name
members=["nodeA", "nodeB"], # List of node names to include
color=(65, 105, 225, 80) # Optional RGBA color tuple
) # Returns: dict with group info

# Delete a group (nodes are preserved)
success = api.delete_node_group("Processing") # Returns: bool

# Rename a group
success = api.rename_node_group("Processing", "Data Processing") # Returns: bool

# Add nodes to an existing group
success = api.add_to_node_group("Processing", ["nodeC", "nodeD"]) # Returns: bool

# Remove nodes from a group
success = api.remove_from_node_group("Processing", ["nodeA"]) # Returns: bool

# Validate a group (check for missing nodes, etc.)
issues = api.validate_node_group("Processing") # Returns: list[str]

# List all groups
groups = api.list_node_groups() # Returns: list[str]
```

#### Additional Group Helpers

```python
# Get members of a group
members = api.get_group_members("Processing") # Returns: list[str]

# Check which group contains a node
group_name = api.get_node_group("nodeA") # Returns: str or None

# Set group color
api.set_group_color("Processing", (255, 0, 0, 100))

# Check if group exists
exists = api.group_exists("Processing") # Returns: bool
```

#### Group Error Handling

```python
from nodz.controllers import (
NodeGroupError, # Base group exception
GroupNotFoundError, # Group doesn't exist
GroupExistsError, # Group already exists
NodeAlreadyInGroupError, # Node is already in another group
)

try:
api.create_node_group("MyGroup", ["nodeA"])
except GroupExistsError as e:
print(f"Group '{e.group_name}' already exists")
except NodeAlreadyInGroupError as e:
print(f"Node '{e.node_name}' is already in '{e.existing_group}'")
```

#### Keyboard Shortcut

- **Ctrl+G**: Create a group from currently selected nodes

### 2. Enhanced Error Handling

The new API provides comprehensive error handling with custom exceptions:

Expand All @@ -393,7 +464,7 @@ except ConnectionError as e:
print(f"Connection error: {e}")
```

### 2. Graph Analysis Tools
### 3. Graph Analysis Tools

```python
# Get graph statistics
Expand All @@ -417,7 +488,7 @@ upstream = api.get_upstream_nodes('myNode')
downstream = api.get_downstream_nodes('myNode')
```

### 3. Validation Tools
### 4. Validation Tools

```python
# Validate graph integrity
Expand All @@ -429,7 +500,7 @@ else:
print("Graph is valid")
```

### 4. Utility Methods
### 5. Utility Methods

```python
# Check existence
Expand All @@ -449,23 +520,23 @@ connections = api.get_connections()
node_connections = api.get_node_connections('myNode')
```

### 5. Position Management
### 6. Position Management

```python
# Get and set node positions
position = api.get_node_position('myNode')
api.set_node_position('myNode', QtCore.QPointF(200, 150))
```

### 6. Logging Control
### 7. Logging Control

```python
# Control logging levels
api.set_logging_level('DEBUG')
current_level = api.get_logging_level()
```

### 7. Viewport Control
### 8. Viewport Control

```python
# Save and restore viewport state
Expand Down Expand Up @@ -596,7 +667,7 @@ nodz.api.signals.node_created.connect(callback)
- [ ] Add error handling with new exception types
- [ ] Test all functionality with the new API
- [ ] Update documentation and comments
- [ ] Consider using new features (graph analysis, validation, etc.)
- [ ] Consider using new features (graph analysis, validation, node groups, etc.)

---

Expand Down
87 changes: 87 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ L : Auto-layout graph hierarchically
S (hold) : Snap selected nodes to grid
H : Toggle help overlay
Alt+Drag : Cut connections with line
Ctrl+G : Create group from selected nodes
Ctrl+Shift+G : Remove selected groups
```

### Advanced Features
Expand All @@ -191,6 +193,53 @@ Alt+Drag : Cut connections with line
- **Undo/Redo Ready**: Architecture supports command pattern implementation
- **Plugin System**: Extensible preset system for custom node types

### Node Groups

Node groups allow you to visually organize related nodes within colored, labeled containers. Groups help manage complex graphs by providing logical grouping and collective manipulation of nodes.

**Visual Representation:**
- Semi-transparent colored background box
- Title label displayed at the top of the group
- Automatically resizes to encompass all member nodes
- Groups are rendered behind nodes to keep nodes as primary interactive elements

**Creating Groups:**
- Select one or more nodes
- Press `Ctrl+G` to create a group from the selection
- Groups are automatically named and colored (customizable via API)

**Behavior:**
- Moving a group moves all member nodes together
- A node can only belong to one group at a time
- Deleting a group preserves the member nodes
- When a grouped node is deleted, it is automatically removed from its group

**Example Usage:**

```python
# Create nodes
api.create_node("Input", position=QtCore.QPointF(100, 100))
api.create_node("Process", position=QtCore.QPointF(300, 100))
api.create_node("Output", position=QtCore.QPointF(500, 100))

# Group related nodes
api.create_node_group("Processing Pipeline", ["Input", "Process"])

# Add another node to existing group
api.add_to_node_group("Processing Pipeline", "Output")

# Customize group color (RGBA)
api.set_group_color("Processing Pipeline", (65, 105, 225, 80))

# List all groups
groups = api.list_node_groups()
print(f"Groups: {groups}")

# Get nodes in a group
members = api.get_group_members("Processing Pipeline")
print(f"Members: {members}")
```

## Unified API Reference

Nodz provides a comprehensive unified API through the `NodzAPI` class that handles all graph operations:
Expand Down Expand Up @@ -281,6 +330,31 @@ except ValueError:
pass
```

### Group Operations

```python
# Create a group from selected nodes
group_info = api.create_node_group(name, members=None, color=None) # Returns dict with group info

# Manage groups
api.delete_node_group(group_name) # Returns bool
api.rename_node_group(group_name, new_name) # Returns bool

# Manage group membership
api.add_to_node_group(group_name, node_names) # Returns bool
api.remove_from_node_group(group_name, node_names) # Returns bool

# Query groups
groups = api.list_node_groups() # Returns list of group names
issues = api.validate_node_group(group_name) # Returns list of validation issues

# Additional helpers
members = api.get_group_members(group_name) # Get nodes in a group
group = api.get_node_group(node_name) # Get group containing a node (or None)
api.set_group_color(group_name, (r, g, b, a)) # Set group RGBA color
exists = api.group_exists(group_name) # Check if group exists
```

### Utility Methods

```python
Expand All @@ -307,6 +381,11 @@ from nodz.controllers import (
AttributeNotFoundError, # Attribute doesn't exist
ConnectionError, # Connection-related errors
IncompatibleTypesError, # Type mismatch in connections
# Group-related errors
NodeGroupError, # Base group exception
GroupNotFoundError, # Group doesn't exist
GroupExistsError, # Group already exists
NodeAlreadyInGroupError,# Node is already in another group
)

try:
Expand All @@ -317,6 +396,14 @@ except IncompatibleTypesError as e:
print(f"Type mismatch: {e.source_type} -> {e.target_type}")
except NodzError as e:
print(f"General error: {e}")

# Group error handling example
try:
api.create_node_group("MyGroup", ["NodeA", "NodeB"])
except GroupExistsError as e:
print(f"Group already exists: {e.group_name}")
except NodeAlreadyInGroupError as e:
print(f"Node '{e.node_name}' is already in group '{e.existing_group}'")
```

## Examples
Expand Down
28 changes: 14 additions & 14 deletions nodz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@
ConnectionController,
GraphController,
NodzError,
NodeError,
NodeNotFoundError,
NodeExistsError,
AttributeError,
AttributeNotFoundError,
ConnectionError,
IncompatibleTypesError,
NodzNodeError,
NodzNodeNotFoundError,
NodzNodeExistsError,
NodzAttributeError,
NodzAttributeNotFoundError,
NodzConnectionError,
NodzIncompatibleTypesError,
)

from .main import NodzView, NodzScene, create_nodz_view
Expand All @@ -56,13 +56,13 @@
"ConnectionController",
"GraphController",
"NodzError",
"NodeError",
"NodeNotFoundError",
"NodeExistsError",
"AttributeError",
"AttributeNotFoundError",
"ConnectionError",
"IncompatibleTypesError",
"NodzNodeError",
"NodzNodeNotFoundError",
"NodzNodeExistsError",
"NodzAttributeError",
"NodzAttributeNotFoundError",
"NodzConnectionError",
"NodzIncompatibleTypesError",
"NodzView",
"NodzScene",
"create_nodz_view",
Expand Down
Loading