= 2 &&
+ ((operations[0] === '"' && operations[operations.length - 1] === '"') ||
+ (operations[0] === "'" && operations[operations.length - 1] === "'"))) {
+ operations = operations.slice(1, -1);
+ }
+
+ useEffect(() => {
+ if (data.inputCount !== undefined && parseInt(data.inputCount) !== inputHandleCount) {
+ setInputHandleCount(parseInt(data.inputCount) || 2);
+ updateNodeInternals(id);
+ }
+ }, [data.inputCount, inputHandleCount, id, updateNodeInternals]);
+
+ // Calculate node size based on number of inputs
+ const nodeSize = Math.max(60, inputHandleCount * 15 + 30);
+
+ return (
+
+ );
+}
diff --git a/src/components/nodes/DynamicHandleNode.jsx b/src/components/nodes/DynamicHandleNode.jsx
index 11754f96..6b324a35 100644
--- a/src/components/nodes/DynamicHandleNode.jsx
+++ b/src/components/nodes/DynamicHandleNode.jsx
@@ -1,31 +1,31 @@
import React, { useCallback, useState, useEffect } from 'react';
import { Handle, useUpdateNodeInternals } from '@xyflow/react';
-
+
export function DynamicHandleNode({ id, data }) {
const updateNodeInternals = useUpdateNodeInternals();
const [inputHandleCount, setInputHandleCount] = useState(parseInt(data.inputCount) || 0);
const [outputHandleCount, setOutputHandleCount] = useState(parseInt(data.outputCount) || 0);
-
+
useEffect(() => {
let shouldUpdate = false;
-
+
if (data.inputCount !== undefined && parseInt(data.inputCount) !== inputHandleCount) {
setInputHandleCount(parseInt(data.inputCount) || 0);
shouldUpdate = true;
}
-
+
if (data.outputCount !== undefined && parseInt(data.outputCount) !== outputHandleCount) {
setOutputHandleCount(parseInt(data.outputCount) || 0);
shouldUpdate = true;
}
-
+
if (shouldUpdate) {
updateNodeInternals(id);
}
}, [data.inputCount, data.outputCount, inputHandleCount, outputHandleCount, id, updateNodeInternals]);
-
-
+
+
return (
1 && (
- {index + 1}
+ {index + 1}
)}
);
})}
-
+
{/* Output Handles (right side) */}
{Array.from({ length: outputHandleCount }).map((_, index) => {
const topPercentage = outputHandleCount === 1 ? 50 : ((index + 1) / (outputHandleCount + 1)) * 100;
@@ -89,7 +89,7 @@ export function DynamicHandleNode({ id, data }) {
type="source"
position="right"
id={`source-${index}`}
- style={{
+ style={{
background: '#555',
top: `${topPercentage}%`
}}
@@ -97,7 +97,7 @@ export function DynamicHandleNode({ id, data }) {
{/* Output label for multiple outputs */}
{outputHandleCount > 1 && (
- {index + 1}
+ {index + 1}
)}
@@ -116,9 +116,9 @@ export function DynamicHandleNode({ id, data }) {
})}
{/* Main content */}
-
{
}
});
-export const nodeDynamicHandles = ['ode', 'function', 'interface'];
+export const nodeDynamicHandles = ['ode', 'function', 'interface', 'addsub'];
// Node categories for better organization
export const nodeCategories = {
@@ -103,7 +105,7 @@ export const nodeCategories = {
description: 'Signal processing and transformation nodes'
},
'Math': {
- nodes: ['adder', 'multiplier', 'splitter2', 'splitter3'].concat(Object.keys(nodeMathTypes)),
+ nodes: ['adder', 'addsub', 'multiplier', 'splitter2', 'splitter3'].concat(Object.keys(nodeMathTypes)),
description: 'Mathematical operation nodes'
},
'Control': {
@@ -153,6 +155,7 @@ export const getNodeDisplayName = (nodeType) => {
'integrator': 'Integrator',
'function': 'Function',
'adder': 'Adder',
+ 'addsub': 'Adder/Subtractor',
'ode': 'ODE',
'multiplier': 'Multiplier',
'splitter2': 'Splitter (1→2)',
diff --git a/src/python/pathsim_utils.py b/src/python/pathsim_utils.py
index 7799beaa..c8ed2819 100644
--- a/src/python/pathsim_utils.py
+++ b/src/python/pathsim_utils.py
@@ -105,6 +105,7 @@
"splitter2": Splitter2,
"splitter3": Splitter3,
"adder": Adder,
+ "addsub": Adder,
"adder_reverse": Adder,
"multiplier": Multiplier,
"process": Process,
@@ -446,7 +447,7 @@ def get_parameters_for_block_class(block_class, node, eval_namespace):
continue
# Skip 'operations' for Adder, as it is handled separately
# https://github.com/festim-dev/pathview/issues/73
- if k in ["operations"]:
+ if k in ["operations"] and node["type"] != "addsub":
continue
user_input = node["data"][k]
if user_input == "":
@@ -518,13 +519,16 @@ def get_input_index(block: Block, edge: dict, block_to_input_index: dict) -> int
# TODO maybe we could directly use the targetHandle as a port alias for these:
if type(block) in (Function, ODE, pathsim.blocks.Switch):
return int(edge["targetHandle"].replace("target-", ""))
- else:
- # make sure that the target block has only one input port (ie. that targetHandle is None)
- assert edge["targetHandle"] is None, (
- f"Target block {block.id} has multiple input ports, "
- "but connection method hasn't been implemented."
- )
- return block_to_input_index[block]
+ if isinstance(block, Adder):
+ if block.operations:
+ return int(edge["targetHandle"].replace("target-", ""))
+
+ # make sure that the target block has only one input port (ie. that targetHandle is None)
+ assert edge["targetHandle"] is None, (
+ f"Target block {block.id} has multiple input ports, "
+ "but connection method hasn't been implemented."
+ )
+ return block_to_input_index[block]
# TODO here we could only pass edge and not block
@@ -562,13 +566,13 @@ def get_output_index(block: Block, edge: dict) -> int:
# Function and ODE outputs are always in order, so we can use the handle directly
assert edge["sourceHandle"], edge
return int(edge["sourceHandle"].replace("source-", ""))
- else:
- # make sure that the source block has only one output port (ie. that sourceHandle is None)
- assert edge["sourceHandle"] is None, (
- f"Source block {block.id} has multiple output ports, "
- "but connection method hasn't been implemented."
- )
- return 0
+
+ # make sure that the source block has only one output port (ie. that sourceHandle is None)
+ assert edge["sourceHandle"] is None, (
+ f"Source block {block.id} has multiple output ports, "
+ "but connection method hasn't been implemented."
+ )
+ return 0
def make_connections(nodes, edges, blocks) -> list[Connection]: