mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Finalize and unify the design of the 'Morph' and 'Blend' nodes (#3974)
* Fix Morph node transform interpolation and preservation in the table * Fix click target positions for Morph's nested layers by pre-compensating upstream_data transforms * Redesign Morph node (v3) with control path input and uniformly spaced progression, and fix Stroke::lerp interpolation weights * Add migration from Morph node v2 to v3 * Redesign the 'Blend Shapes' node behavior and subgraph definition * Add the Layer > Blend menu entry to easily set up a blend * Optimize the Morph node * Refactor the Morph node to remove the roundtrip through BezPath * Fine-tune Morph node Bezier order promotion and handle interpolation * Add the Layer > Morph menu bar entry * Fix NaN and guard against other potential NaN bugs breaking the editor * Add InterpolationDistribution parameter to Morph with weighted progression, swap parameter orders, and rename shear to skew * Add the Reverse parameter to the Morph node * Update the order of the inputs to Blend Shapes for consistency with Morph * Make Layer > Morph create the Morph Path control layer * Fix migrations * Move 10 to a constant * Avoid division by 0 in the Blend Shapes node internals * Rename nodes 'Blend' -> 'Mix' and 'Blend Shapes' to 'Blend' * Fix a crash encountered while testing * Final code review * Make domain push dupe checks debug-only and use push_unchecked in the Morph node * Pre-allocate for pushes to the vector domains * Add fast path at t=0 * Inline reserve() * Set up the control path layer above not below, and starting collapsed * Review fixes --------- Co-authored-by: Timon <me@timon.zip>
This commit is contained in:
@@ -186,3 +186,6 @@ pub const DOUBLE_CLICK_MILLISECONDS: u64 = 500;
|
||||
pub const UI_SCALE_DEFAULT: f64 = 1.;
|
||||
pub const UI_SCALE_MIN: f64 = 0.5;
|
||||
pub const UI_SCALE_MAX: f64 = 3.;
|
||||
|
||||
// ACTIONS
|
||||
pub const BLEND_COUNT_PER_LAYER: usize = 10;
|
||||
|
||||
@@ -357,6 +357,8 @@ pub fn input_mappings(zoom_with_scroll: bool) -> Mapping {
|
||||
entry!(KeyDown(KeyS); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::SaveDocumentAs),
|
||||
entry!(KeyDown(KeyD); modifiers=[Accel], canonical, action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||
entry!(KeyDown(KeyJ); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||
entry!(KeyDown(KeyB); modifiers=[Accel, Alt], action_dispatch=DocumentMessage::BlendSelectedLayers),
|
||||
entry!(KeyDown(KeyM); modifiers=[Accel, Alt], action_dispatch=DocumentMessage::MorphSelectedLayers), // Might get eaten by the GeForce Experience overlay for some Windows users
|
||||
entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers { group_folder_type: GroupFolderType::Layer }),
|
||||
entry!(KeyDown(KeyG); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
|
||||
entry!(KeyDown(KeyN); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::CreateEmptyFolder),
|
||||
|
||||
@@ -495,6 +495,18 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||
})
|
||||
.disabled(no_active_document || !has_selected_layers),
|
||||
]]),
|
||||
MenuListEntry::new("Blend")
|
||||
.label("Blend")
|
||||
.icon("InterpolationBlend")
|
||||
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::BlendSelectedLayers))
|
||||
.on_commit(|_| DocumentMessage::BlendSelectedLayers.into())
|
||||
.disabled(no_active_document || !has_selected_layers),
|
||||
MenuListEntry::new("Morph")
|
||||
.label("Morph")
|
||||
.icon("InterpolationMorph")
|
||||
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::MorphSelectedLayers))
|
||||
.on_commit(|_| DocumentMessage::MorphSelectedLayers.into())
|
||||
.disabled(no_active_document || !has_selected_layers),
|
||||
],
|
||||
vec![
|
||||
MenuListEntry::new("Make Path Editable")
|
||||
|
||||
@@ -84,6 +84,8 @@ pub enum DocumentMessage {
|
||||
GridVisibility {
|
||||
visible: bool,
|
||||
},
|
||||
BlendSelectedLayers,
|
||||
MorphSelectedLayers,
|
||||
GroupSelectedLayers {
|
||||
group_folder_type: GroupFolderType,
|
||||
},
|
||||
|
||||
@@ -5,7 +5,8 @@ use super::utility_types::network_interface::{self, NodeNetworkInterface, Transa
|
||||
use super::utility_types::nodes::{CollapsedLayers, LayerStructureEntry, SelectedNodes};
|
||||
use crate::application::{GRAPHITE_GIT_COMMIT_HASH, generate_uuid};
|
||||
use crate::consts::{
|
||||
ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_EXTENSION, LAYER_INDENT_OFFSET, NODE_CHAIN_WIDTH, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL,
|
||||
ASYMPTOTIC_EFFECT, BLEND_COUNT_PER_LAYER, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_EXTENSION, LAYER_INDENT_OFFSET, NODE_CHAIN_WIDTH, SCALE_EFFECT, SCROLLBAR_SPACING,
|
||||
VIEWPORT_ROTATE_SNAP_INTERVAL,
|
||||
};
|
||||
use crate::messages::input_mapper::utility_types::macros::action_shortcut;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
@@ -625,6 +626,12 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
self.snapping_state.grid_snapping = visible;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
DocumentMessage::BlendSelectedLayers => {
|
||||
self.handle_group_selected_layers(GroupFolderType::Blend, responses);
|
||||
}
|
||||
DocumentMessage::MorphSelectedLayers => {
|
||||
self.handle_group_selected_layers(GroupFolderType::Morph, responses);
|
||||
}
|
||||
DocumentMessage::GroupSelectedLayers { group_folder_type } => {
|
||||
self.handle_group_selected_layers(group_folder_type, responses);
|
||||
}
|
||||
@@ -1485,6 +1492,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
DeleteSelectedLayers,
|
||||
DuplicateSelectedLayers,
|
||||
GroupSelectedLayers,
|
||||
BlendSelectedLayers,
|
||||
MorphSelectedLayers,
|
||||
SelectedLayersLower,
|
||||
SelectedLayersLowerToBack,
|
||||
SelectedLayersRaise,
|
||||
@@ -2160,6 +2169,57 @@ impl DocumentMessageHandler {
|
||||
});
|
||||
}
|
||||
}
|
||||
GroupFolderType::Blend | GroupFolderType::Morph => {
|
||||
let control_path_id = NodeId(generate_uuid());
|
||||
let all_layers_to_group = network_interface.shallowest_unique_layers_sorted(&[]);
|
||||
let blend_count = matches!(group_folder_type, GroupFolderType::Blend).then(|| all_layers_to_group.len() * BLEND_COUNT_PER_LAYER);
|
||||
|
||||
responses.add(GraphOperationMessage::NewInterpolationLayer {
|
||||
id: folder_id,
|
||||
control_path_id,
|
||||
parent,
|
||||
insert_index,
|
||||
blend_count,
|
||||
});
|
||||
|
||||
let new_group_folder = LayerNodeIdentifier::new_unchecked(folder_id);
|
||||
|
||||
// Move selected layers into the group as children
|
||||
for layer_to_group in all_layers_to_group.into_iter().rev() {
|
||||
responses.add(NodeGraphMessage::MoveLayerToStack {
|
||||
layer: layer_to_group,
|
||||
parent: new_group_folder,
|
||||
insert_index: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Connect the child stack to the control path layer as a co-parent
|
||||
responses.add(GraphOperationMessage::ConnectInterpolationControlPathToChildren {
|
||||
interpolation_layer_id: folder_id,
|
||||
control_path_id,
|
||||
});
|
||||
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![folder_id] });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
|
||||
// The control path layer (Blend Path / Morph Path) should start collapsed.
|
||||
let instance_path = {
|
||||
// Build instance path from root down to the control path layer, which is a sibling of the main layer under `parent`.
|
||||
let mut instance_path: Vec<NodeId> = parent
|
||||
.ancestors(network_interface.document_metadata())
|
||||
.take_while(|&ancestor| ancestor != LayerNodeIdentifier::ROOT_PARENT)
|
||||
.map(LayerNodeIdentifier::to_node)
|
||||
.collect();
|
||||
instance_path.reverse();
|
||||
instance_path.push(control_path_id);
|
||||
instance_path
|
||||
};
|
||||
responses.add(DocumentMessage::ToggleLayerExpansion { instance_path, recursive: false });
|
||||
|
||||
return folder_id;
|
||||
}
|
||||
}
|
||||
|
||||
let new_group_folder = LayerNodeIdentifier::new_unchecked(folder_id);
|
||||
|
||||
@@ -74,6 +74,17 @@ pub enum GraphOperationMessage {
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: usize,
|
||||
},
|
||||
NewInterpolationLayer {
|
||||
id: NodeId,
|
||||
control_path_id: NodeId,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: usize,
|
||||
blend_count: Option<usize>,
|
||||
},
|
||||
ConnectInterpolationControlPathToChildren {
|
||||
interpolation_layer_id: NodeId,
|
||||
control_path_id: NodeId,
|
||||
},
|
||||
NewBooleanOperationLayer {
|
||||
id: NodeId,
|
||||
operation: graphene_std::vector::misc::BooleanOperation,
|
||||
|
||||
@@ -172,6 +172,77 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
GraphOperationMessage::NewInterpolationLayer {
|
||||
id,
|
||||
control_path_id,
|
||||
parent,
|
||||
insert_index,
|
||||
blend_count,
|
||||
} => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
|
||||
let layer = modify_inputs.create_layer(id);
|
||||
|
||||
// Insert the main chain node (Blend or Morph) depending on whether a blend count is provided
|
||||
let (chain_node_id, layer_alias, path_alias) = if let Some(count) = blend_count {
|
||||
(modify_inputs.insert_blend_data(layer, count as f64), "Blend", "Blend Path")
|
||||
} else {
|
||||
(modify_inputs.insert_morph_data(layer), "Morph", "Morph Path")
|
||||
};
|
||||
|
||||
// Create the control path layer (Path → Auto-Tangents → Origins to Polyline)
|
||||
let control_path_layer = modify_inputs.create_layer(control_path_id);
|
||||
let path_node_id = modify_inputs.insert_control_path_data(control_path_layer);
|
||||
|
||||
network_interface.move_layer_to_stack(control_path_layer, parent, insert_index, &[]);
|
||||
network_interface.move_layer_to_stack(layer, parent, insert_index + 1, &[]);
|
||||
|
||||
// Connect the Path node's output to the chain node's path parameter input (input 4 for both Morph and Blend).
|
||||
// Done after move_layer_to_stack so chain nodes have correct positions when converted to absolute.
|
||||
network_interface.set_input(&InputConnector::node(chain_node_id, 4), NodeInput::node(path_node_id, 0), &[]);
|
||||
|
||||
responses.add(NodeGraphMessage::SetDisplayNameImpl {
|
||||
node_id: id,
|
||||
alias: layer_alias.to_string(),
|
||||
});
|
||||
responses.add(NodeGraphMessage::SetDisplayNameImpl {
|
||||
node_id: control_path_id,
|
||||
alias: path_alias.to_string(),
|
||||
});
|
||||
}
|
||||
GraphOperationMessage::ConnectInterpolationControlPathToChildren {
|
||||
interpolation_layer_id,
|
||||
control_path_id,
|
||||
} => {
|
||||
// Find the chain node (Blend or Morph, first in chain of the layer)
|
||||
let Some(OutputConnector::Node { node_id: chain_node, .. }) = network_interface.upstream_output_connector(&InputConnector::node(interpolation_layer_id, 1), &[]) else {
|
||||
log::error!("Could not find chain node for layer {interpolation_layer_id}");
|
||||
return;
|
||||
};
|
||||
|
||||
// Get what feeds into the chain node's primary input (the children stack)
|
||||
let Some(OutputConnector::Node { node_id: children_id, output_index }) = network_interface.upstream_output_connector(&InputConnector::node(chain_node, 0), &[]) else {
|
||||
log::error!("Could not find children stack feeding chain node {chain_node}");
|
||||
return;
|
||||
};
|
||||
|
||||
// Find the deepest node in the control path layer's chain (Origins to Polyline)
|
||||
let mut deepest_chain_node = None;
|
||||
let mut current_connector = InputConnector::node(control_path_id, 1);
|
||||
while let Some(OutputConnector::Node { node_id, .. }) = network_interface.upstream_output_connector(¤t_connector, &[]) {
|
||||
deepest_chain_node = Some(node_id);
|
||||
current_connector = InputConnector::node(node_id, 0);
|
||||
}
|
||||
|
||||
// Connect children to the deepest chain node's input 0 (or the layer's input 1 if no chain)
|
||||
let target_connector = match deepest_chain_node {
|
||||
Some(node_id) => InputConnector::node(node_id, 0),
|
||||
None => InputConnector::node(control_path_id, 1),
|
||||
};
|
||||
network_interface.set_input(&target_connector, NodeInput::node(children_id, output_index), &[]);
|
||||
|
||||
// Shift the child stack (topmost child only, the rest follow) down 3 and left 10
|
||||
network_interface.shift_node(&children_id, IVec2::new(-10, 3), &[]);
|
||||
}
|
||||
GraphOperationMessage::NewBooleanOperationLayer { id, operation, parent, insert_index } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
|
||||
let layer = modify_inputs.create_layer(id);
|
||||
|
||||
@@ -156,6 +156,61 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
self.network_interface.move_node_to_chain_start(&boolean_id, layer, &[], self.import);
|
||||
}
|
||||
|
||||
pub fn insert_blend_data(&mut self, layer: LayerNodeIdentifier, count: f64) -> NodeId {
|
||||
let blend = resolve_network_node_type("Blend").expect("Blend node does not exist").node_template_input_override([
|
||||
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::F64(count), false)),
|
||||
]);
|
||||
|
||||
let blend_id = NodeId::new();
|
||||
self.network_interface.insert_node(blend_id, blend, &[]);
|
||||
self.network_interface.move_node_to_chain_start(&blend_id, layer, &[], self.import);
|
||||
|
||||
blend_id
|
||||
}
|
||||
|
||||
pub fn insert_morph_data(&mut self, layer: LayerNodeIdentifier) -> NodeId {
|
||||
let morph = resolve_proto_node_type(graphene_std::vector::morph::IDENTIFIER)
|
||||
.expect("Morph node does not exist")
|
||||
.node_template_input_override([
|
||||
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::F64(0.5), false)),
|
||||
]);
|
||||
|
||||
let morph_id = NodeId::new();
|
||||
self.network_interface.insert_node(morph_id, morph, &[]);
|
||||
self.network_interface.move_node_to_chain_start(&morph_id, layer, &[], self.import);
|
||||
|
||||
morph_id
|
||||
}
|
||||
|
||||
/// Returns the Path node ID (the node closest to the layer's merge node in the chain).
|
||||
pub fn insert_control_path_data(&mut self, layer: LayerNodeIdentifier) -> NodeId {
|
||||
// Add Origins to Polyline node first (will be pushed deepest in the chain)
|
||||
let origins_to_polyline = resolve_network_node_type("Origins to Polyline")
|
||||
.expect("Origins to Polyline node does not exist")
|
||||
.default_node_template();
|
||||
let origins_to_polyline_id = NodeId::new();
|
||||
self.network_interface.insert_node(origins_to_polyline_id, origins_to_polyline, &[]);
|
||||
self.network_interface.move_node_to_chain_start(&origins_to_polyline_id, layer, &[], self.import);
|
||||
|
||||
// Add Auto-Tangents node (between Origins to Polyline and Path), with spread=1 and preserve_existing=false
|
||||
let auto_tangents = resolve_proto_node_type(graphene_std::vector::auto_tangents::IDENTIFIER)
|
||||
.expect("Auto-Tangents node does not exist")
|
||||
.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(1.), false)), Some(NodeInput::value(TaggedValue::Bool(false), false))]);
|
||||
let auto_tangents_id = NodeId::new();
|
||||
self.network_interface.insert_node(auto_tangents_id, auto_tangents, &[]);
|
||||
self.network_interface.move_node_to_chain_start(&auto_tangents_id, layer, &[], self.import);
|
||||
|
||||
// Add Path node to chain start (closest to the Merge node)
|
||||
let path = resolve_network_node_type("Path").expect("Path node does not exist").default_node_template();
|
||||
let path_id = NodeId::new();
|
||||
self.network_interface.insert_node(path_id, path, &[]);
|
||||
self.network_interface.move_node_to_chain_start(&path_id, layer, &[], self.import);
|
||||
|
||||
path_id
|
||||
}
|
||||
|
||||
pub fn insert_vector(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
|
||||
let vector = Table::new_from_element(Vector::from_subpaths(subpaths, true));
|
||||
|
||||
|
||||
@@ -343,8 +343,8 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
let (pos1, pos2) = (pos1.min(pos2), pos1.max(pos2));
|
||||
let diagonal = pos2 - pos1;
|
||||
|
||||
if diagonal.length() < f64::EPSILON * 1000. || viewport.size().into_dvec2() == DVec2::ZERO {
|
||||
warn!("Cannot center since the viewport size is 0");
|
||||
if !diagonal.is_finite() || diagonal.length() < f64::EPSILON * 1000. || viewport.size().into_dvec2() == DVec2::ZERO {
|
||||
warn!("Cannot center since the viewport size is 0 or the bounds are non-finite");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -462,184 +462,131 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Blend Shapes",
|
||||
identifier: "Blend",
|
||||
category: "Vector",
|
||||
// [IMPORTS]2 -> 0[0:Floor]
|
||||
// [0:Floor]0 -> 0[1:Subtract]
|
||||
// "1: f64" -> 1[1:Subtract]
|
||||
// "(): ()" -> 0[2:Read Index]
|
||||
// "0: u32" -> 1[2:Read Index]
|
||||
// [2:Read Index]0 -> 0[3:Divide]
|
||||
// [1:Subtract]0 -> 1[3:Divide]
|
||||
// [IMPORTS]1 -> 0[4:Position on Path]
|
||||
// [3:Divide]0 -> 1[4:Position on Path]
|
||||
// "false: bool" -> 2[4:Position on Path]
|
||||
// "false: bool" -> 3[4:Position on Path]
|
||||
// "(): ()" -> 0[5:Read Vector]
|
||||
// [5:Read Vector]0 -> 0[6:Reset Transform]
|
||||
// "true: bool" -> 1[6:Reset Transform]
|
||||
// "false: bool" -> 2[6:Reset Transform]
|
||||
// "false: bool" -> 3[6:Reset Transform]
|
||||
// [12:Flatten Vector]0 -> 0[7:Map]
|
||||
// [6:Reset Transform]0 -> 1[7:Map]
|
||||
// [7:Map]0 -> 0[8:Morph]
|
||||
// [15:Multiply]0 -> 1[8:Morph]
|
||||
// [8:Morph]0 -> 0[9:Transform]
|
||||
// [4:Position on Path]0 -> 1[9:Transform]
|
||||
// "0: f64" -> 2[9:Transform]
|
||||
// "(0, 0): DVec2" -> 3[9:Transform]
|
||||
// "(0, 0): DVec2" -> 4[9:Transform]
|
||||
// [IMPORTS]1 -> 0[10:Count Points]
|
||||
// [10:Count Points]0 -> 0[11:Equals]
|
||||
// [13:Count Elements]0 -> 1[11:Equals]
|
||||
// [IMPORTS]0 -> 0[12:Flatten Vector]
|
||||
// [12:Flatten Vector]0 -> 0[13:Count Elements]
|
||||
// [13:Count Elements]0 -> 0[14:Subtract]
|
||||
// "1: f64" -> 1[14:Subtract]
|
||||
// [3:Divide]0 -> 0[15:Multiply]
|
||||
// [14:Subtract]0 -> 1[15:Multiply]
|
||||
// [12:Flatten Vector]0 -> 0[16:Morph]
|
||||
// [15:Multiply]0 -> 1[16:Morph]
|
||||
// [11:Equals]0 -> 0[17:Switch]
|
||||
// [9:Transform]0 -> 1[17:Switch]
|
||||
// [16:Morph]0 -> 2[17:Switch]
|
||||
// [17:Switch]0 -> 0[18:Repeat]
|
||||
// [0:Floor]0 -> 1[18:Repeat]
|
||||
// [IMPORTS]3 -> 2[18:Repeat]
|
||||
// [18:Repeat]0 -> 0[EXPORTS]
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(18), 0)],
|
||||
exports: vec![NodeInput::node(NodeId(16), 0)],
|
||||
nodes: [
|
||||
// 0: Floor
|
||||
// 0: Separate Subpaths (split path into individual subpaths)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::separate_subpaths::IDENTIFIER),
|
||||
inputs: vec![NodeInput::import(generic!(T), 4)],
|
||||
..Default::default()
|
||||
},
|
||||
// 1: Count Elements (number of subpaths)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::count_elements::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 2: Max (clamp subpath count to at least 1 for empty path case)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::max::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::value(TaggedValue::F64(1.), false)],
|
||||
..Default::default()
|
||||
},
|
||||
// 3: Floor (integer count per subpath)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::floor::IDENTIFIER),
|
||||
inputs: vec![NodeInput::import(concrete!(f64), 2)],
|
||||
inputs: vec![NodeInput::import(concrete!(f64), 1)],
|
||||
..Default::default()
|
||||
},
|
||||
// 1: Subtract
|
||||
// 4: Multiply (total_instances = count × subpath_count)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::multiply::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(17), 0), NodeInput::node(NodeId(2), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 5: Subtract (count - 1, open subpath denominator)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::subtract::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(1.), false)],
|
||||
inputs: vec![NodeInput::node(NodeId(17), 0), NodeInput::value(TaggedValue::F64(1.), false)],
|
||||
..Default::default()
|
||||
},
|
||||
// 2: Read Index
|
||||
// 6: Read Index (current repetition index)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(context::read_index::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::U32(0), false)],
|
||||
..Default::default()
|
||||
},
|
||||
// 3: Divide
|
||||
// 7: Divide (index / count)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::divide::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0), NodeInput::node(NodeId(1), 0)],
|
||||
inputs: vec![NodeInput::node(NodeId(6), 0), NodeInput::node(NodeId(17), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 4: Position on Path
|
||||
// 8: Floor (floor(index / count) = subpath index)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::position_on_path::IDENTIFIER),
|
||||
inputs: vec![
|
||||
NodeInput::import(generic!(T), 1),
|
||||
NodeInput::node(NodeId(3), 0),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::floor::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(7), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 5: Read Vector
|
||||
// 9: Modulo (index % count = local index within subpath)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(context::read_vector::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::None, false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::modulo::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(6), 0), NodeInput::node(NodeId(17), 0), NodeInput::value(TaggedValue::Bool(true), false)],
|
||||
..Default::default()
|
||||
},
|
||||
// 6: Reset Transform
|
||||
// 10: Path Is Closed (check if current subpath is closed)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::reset_transform::IDENTIFIER),
|
||||
inputs: vec![
|
||||
NodeInput::node(NodeId(5), 0),
|
||||
NodeInput::value(TaggedValue::Bool(true), false),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::path_is_closed::IDENTIFIER),
|
||||
inputs: vec![NodeInput::import(generic!(T), 4), NodeInput::node(NodeId(8), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 7: Map
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::map::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(12), 0), NodeInput::node(NodeId(6), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 8: Morph
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::morph::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(7), 0), NodeInput::node(NodeId(15), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 9: Transform
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
|
||||
inputs: vec![
|
||||
NodeInput::node(NodeId(8), 0),
|
||||
NodeInput::node(NodeId(4), 0),
|
||||
NodeInput::value(TaggedValue::F64(0.), false),
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
// 10: Count Points
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::count_points::IDENTIFIER),
|
||||
inputs: vec![NodeInput::import(generic!(T), 1)],
|
||||
..Default::default()
|
||||
},
|
||||
// 11: Equals
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::equals::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(10), 0), NodeInput::node(NodeId(13), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 12: Flatten Vector
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_nodes::graphic::flatten_vector::IDENTIFIER),
|
||||
inputs: vec![NodeInput::import(generic!(T), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 13: Count Elements
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::count_elements::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(12), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 14: Subtract
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::subtract::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(13), 0), NodeInput::value(TaggedValue::F64(1.), false)],
|
||||
..Default::default()
|
||||
},
|
||||
// 15: Multiply
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::multiply::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(3), 0), NodeInput::node(NodeId(14), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 16: Morph
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::morph::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(12), 0), NodeInput::node(NodeId(15), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 17: Switch
|
||||
// 11: Switch (closed → count, open → max(count - 1, 1) as denominator)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(logic::switch::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(11), 0), NodeInput::node(NodeId(9), 0), NodeInput::node(NodeId(16), 0)],
|
||||
inputs: vec![NodeInput::node(NodeId(10), 0), NodeInput::node(NodeId(17), 0), NodeInput::node(NodeId(18), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 18: Repeat
|
||||
// 12: Divide (local_index / denominator = within-subpath fraction)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::divide::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(9), 0), NodeInput::node(NodeId(11), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 13: Multiply (fraction × 0.9999999999 to avoid overflowing to the next subpath)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::multiply::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(12), 0), NodeInput::value(TaggedValue::F64(0.9999999999), false)],
|
||||
..Default::default()
|
||||
},
|
||||
// 14: Add (subpath_index + clamped fraction = progression)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::add::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(8), 0), NodeInput::node(NodeId(13), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 15: Morph (content, progression, reverse, distribution, path)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::morph::IDENTIFIER),
|
||||
inputs: vec![
|
||||
NodeInput::import(generic!(T), 0),
|
||||
NodeInput::node(NodeId(14), 0),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
NodeInput::import(concrete!(vector::misc::InterpolationDistribution), 3),
|
||||
NodeInput::import(generic!(T), 4),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
// 16: Repeat
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(repeat_nodes::repeat::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(17), 0), NodeInput::node(NodeId(0), 0), NodeInput::import(generic!(T), 3)],
|
||||
inputs: vec![NodeInput::node(NodeId(15), 0), NodeInput::node(NodeId(4), 0), NodeInput::import(generic!(T), 2)],
|
||||
..Default::default()
|
||||
},
|
||||
// 17: Max (clamp count to at least 1)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::max::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(3), 0), NodeInput::value(TaggedValue::F64(1.), false)],
|
||||
..Default::default()
|
||||
},
|
||||
// 18: Max (clamp open-path denominator to at least 1 to avoid division by zero when count = 1)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::max::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(5), 0), NodeInput::value(TaggedValue::F64(1.), false)],
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -650,168 +597,175 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::F64(10.), false),
|
||||
NodeInput::value(TaggedValue::Bool(Default::default()), false),
|
||||
NodeInput::value(TaggedValue::InterpolationDistribution(Default::default()), false),
|
||||
NodeInput::value(TaggedValue::Vector(Default::default()), false),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![("Content", "TODO").into(), ("Path", "TODO").into(), ("Count", "TODO").into(), ("Reverse", "TODO").into()],
|
||||
input_metadata: vec![
|
||||
("Content", "TODO").into(),
|
||||
("Count", "TODO").into(),
|
||||
("Reverse", "TODO").into(),
|
||||
("Distribution", "TODO").into(),
|
||||
("Path", "TODO").into(),
|
||||
],
|
||||
output_names: vec!["Out".to_string()],
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
node_metadata: [
|
||||
// 0: Floor
|
||||
// 0: Separate Subpaths
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(2, 0)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 1: Subtract
|
||||
// 1: Count Elements
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, -1)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(2, 2)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 2: Read Index
|
||||
// 2: Max (subpath count)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, -2)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(9, 2)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 3: Divide
|
||||
// 3: Floor (count)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, -2)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(2, 13)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 4: Position on Path
|
||||
// 4: Multiply (total instances)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, -3)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(16, 1)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 5: Read Vector
|
||||
// 5: Subtract (count - 1)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 2)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(16, 14)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 6: Reset Transform
|
||||
// 6: Read Index
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 2)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(2, 7)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 7: Map
|
||||
// 7: Divide (index / count)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 1)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(16, 10)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 8: Morph
|
||||
// 8: Floor (subpath index)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 1)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(23, 4)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 9: Transform
|
||||
// 9: Modulo (local index)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(35, 1)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(16, 7)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 10: Count Points
|
||||
// 10: Path Is Closed
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 4)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(30, 2)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 11: Equals
|
||||
// 11: Switch (denominator)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 4)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(30, 12)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 12: Flatten Vector
|
||||
// 12: Divide (within-subpath fraction)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 6)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(23, 7)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 13: Count Elements
|
||||
// 13: Multiply (clamp fraction)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 8)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(30, 7)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 14: Subtract
|
||||
// 14: Add (progression)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 8)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(37, 4)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 15: Multiply
|
||||
// 15: Morph
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 7)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(44, 3)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 16: Morph
|
||||
// 16: Repeat
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 6)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(51, 0)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 17: Switch
|
||||
// 17: Max (clamp count)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(42, 4)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(9, 13)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 18: Repeat
|
||||
// 18: Max (clamp open-path denominator)
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(49, -1)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(23, 14)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -834,17 +788,6 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Origins to Polyline",
|
||||
category: "Vector",
|
||||
// "(): ()" -> 0[0:Read Vector]
|
||||
// [0:Read Vector]0 -> 0[1:Extract Transform]
|
||||
// [1:Extract Transform]0 -> 0[2:Decompose Translation]
|
||||
// [2:Decompose Translation]0 -> 0[3:Vec2 to Point]
|
||||
// [IMPORTS]0 -> 0[4:Flatten Vector]
|
||||
// [4:Flatten Vector]0 -> 0[5:Map]
|
||||
// [3:Vec2 to Point]0 -> 1[5:Map]
|
||||
// [5:Map]0 -> 0[6: Flatten Path]
|
||||
// [6:Flatten Path]0 -> 0[7:Points to Polyline]
|
||||
// "false: bool" -> 1[7:Points to Polyline]
|
||||
// [7:Points to Polyline]0 -> 0[EXPORTS]
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
|
||||
@@ -26,7 +26,7 @@ use graphene_std::text::{Font, TextAlign};
|
||||
use graphene_std::transform::{Footprint, ReferencePoint, ScaleType, Transform};
|
||||
use graphene_std::vector::QRCodeErrorCorrectionLevel;
|
||||
use graphene_std::vector::misc::BooleanOperation;
|
||||
use graphene_std::vector::misc::{ArcType, CentroidType, ExtrudeJoiningAlgorithm, GridType, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType};
|
||||
use graphene_std::vector::misc::{ArcType, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType};
|
||||
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
|
||||
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
||||
@@ -266,6 +266,7 @@ pub(crate) fn property_from_type(
|
||||
Some(x) if x == TypeId::of::<LuminanceCalculation>() => enum_choice::<LuminanceCalculation>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<QRCodeErrorCorrectionLevel>() => enum_choice::<QRCodeErrorCorrectionLevel>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<ScaleType>() => enum_choice::<ScaleType>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<InterpolationDistribution>() => enum_choice::<InterpolationDistribution>().for_socket(default_info).property_row(),
|
||||
// =====
|
||||
// OTHER
|
||||
// =====
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::consts::COLOR_OVERLAY_GRAY;
|
||||
use glam::DVec2;
|
||||
use graphene_std::vector::misc::BooleanOperation;
|
||||
use std::fmt;
|
||||
|
||||
#[repr(transparent)]
|
||||
@@ -710,5 +711,7 @@ impl PTZ {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum GroupFolderType {
|
||||
Layer,
|
||||
BooleanOperation(graphene_std::vector::misc::BooleanOperation),
|
||||
BooleanOperation(BooleanOperation),
|
||||
Blend,
|
||||
Morph,
|
||||
}
|
||||
|
||||
@@ -1209,6 +1209,8 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
self.document_metadata.bounding_box_document(layer)
|
||||
})
|
||||
// Skip any layer bounds containing NaN to avoid poisoning the combined result
|
||||
.filter(|[min, max]| min.is_finite() && max.is_finite())
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
|
||||
@@ -474,13 +474,14 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::blending_nodes::blend::IDENTIFIER,
|
||||
node: graphene_std::raster_nodes::blending_nodes::mix::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_raster_nodes::adjustments::BlendNode",
|
||||
"raster_nodes::adjustments::BlendNode",
|
||||
"graphene_core::raster::adjustments::BlendNode",
|
||||
"graphene_core::raster::BlendNode",
|
||||
"graphene_raster_nodes::blending_nodes::BlendNode",
|
||||
"raster_nodes::blending_nodes::BlendNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
@@ -1664,7 +1665,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::U32(0), false), network_path);
|
||||
}
|
||||
|
||||
// Migrate from the old source/target "Morph" node to the new vector table based "Morph" node.
|
||||
// Migrate from the old source/target v1 "Morph" node to the new vector table based v2 "Morph" node.
|
||||
// This doesn't produce exactly equivalent results in cases involving input vector tables with multiple rows.
|
||||
// The old version would zip the source and target table rows, interpoleating each pair together.
|
||||
// The migrated version will instead deeply flatten both merged tables and morph sequentially between all source vectors and all target vector elements.
|
||||
@@ -1676,7 +1677,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
// 4 inputs - even older signature (commit 80b8df8d4298b6669f124b929ce61bfabfc44e41):
|
||||
// async fn morph(_: impl Ctx, source: Table<Vector>, #[expose] target: Table<Vector>, #[default(0.5)] time: Fraction, #[min(0.)] start_index: IntegerCount) -> Table<Vector> { ... }
|
||||
//
|
||||
// New signature:
|
||||
// v2 signature:
|
||||
// async fn morph<I: IntoGraphicTable>(_: impl Ctx, #[implementations(Table<Graphic>, Table<Vector>)] content: I, progression: Progression) -> Table<Vector> { ... }
|
||||
|
||||
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
||||
@@ -1712,6 +1713,84 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
.set_input(&InputConnector::node(*node_id, 0), NodeInput::node(merge_node_id, 0), network_path);
|
||||
// Connect the old 'progression' input to the new 'progression' input of the Morph node
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path);
|
||||
|
||||
inputs_count = 2;
|
||||
}
|
||||
|
||||
// Migrate from the v2 "Morph" node (2 inputs: content, progression) to the v3 "Morph" node (5 inputs: content, progression, reverse, distribution, path).
|
||||
// The old progression used integer part for pair selection (range 0..N-1 where N is the number of content objects).
|
||||
// The new progression uses fractional 0..1 for euclidean traversal through all objects.
|
||||
// We insert Count Elements → Subtract 1 → Divide to remap: new_progression = old_progression / (N - 1).
|
||||
// For the common 2-object case (N=2), this divides by 1 which is a no-op, preserving identical behavior.
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::morph::IDENTIFIER) && inputs_count == 2 {
|
||||
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
// Reconnect content (input 0) and leave path (input 4) as default
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
|
||||
let Some(morph_position) = document.network_interface.position_from_downstream_node(node_id, network_path) else {
|
||||
log::error!("Could not get position for morph node {node_id}");
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
return None;
|
||||
};
|
||||
|
||||
// Create Count Elements node: counts content table rows → N
|
||||
let Some(count_elements_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::count_elements::IDENTIFIER)) else {
|
||||
log::error!("Could not get count_elements node from definition when upgrading morph");
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
return None;
|
||||
};
|
||||
let count_elements_template = count_elements_def.default_node_template();
|
||||
let count_elements_id = NodeId::new();
|
||||
|
||||
// Create Subtract node: N → N-1
|
||||
let Some(subtract_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::subtract::IDENTIFIER)) else {
|
||||
log::error!("Could not get subtract node from definition when upgrading morph");
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
return None;
|
||||
};
|
||||
let mut subtract_template = subtract_def.default_node_template();
|
||||
subtract_template.document_node.inputs[1] = NodeInput::value(TaggedValue::F64(1.), false);
|
||||
let subtract_id = NodeId::new();
|
||||
|
||||
// Create Divide node: old_progression / (N-1) → new progression
|
||||
let Some(divide_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::divide::IDENTIFIER)) else {
|
||||
log::error!("Could not get divide node from definition when upgrading morph");
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
return None;
|
||||
};
|
||||
let divide_template = divide_def.default_node_template();
|
||||
let divide_id = NodeId::new();
|
||||
|
||||
// Insert and position nodes
|
||||
document.network_interface.insert_node(count_elements_id, count_elements_template, network_path);
|
||||
document
|
||||
.network_interface
|
||||
.shift_absolute_node_position(&count_elements_id, morph_position + IVec2::new(-21, 2), network_path);
|
||||
|
||||
document.network_interface.insert_node(subtract_id, subtract_template, network_path);
|
||||
document.network_interface.shift_absolute_node_position(&subtract_id, morph_position + IVec2::new(-14, 2), network_path);
|
||||
|
||||
document.network_interface.insert_node(divide_id, divide_template, network_path);
|
||||
document.network_interface.shift_absolute_node_position(÷_id, morph_position + IVec2::new(-7, 1), network_path);
|
||||
|
||||
// Wire: content source → Count Elements input 0
|
||||
document.network_interface.set_input(&InputConnector::node(count_elements_id, 0), old_inputs[0].clone(), network_path);
|
||||
|
||||
// Wire: Count Elements output → Subtract input 0 (minuend)
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(subtract_id, 0), NodeInput::node(count_elements_id, 0), network_path);
|
||||
|
||||
// Wire: old progression → Divide input 0 (numerator)
|
||||
document.network_interface.set_input(&InputConnector::node(divide_id, 0), old_inputs[1].clone(), network_path);
|
||||
|
||||
// Wire: Subtract output → Divide input 1 (denominator)
|
||||
document.network_interface.set_input(&InputConnector::node(divide_id, 1), NodeInput::node(subtract_id, 0), network_path);
|
||||
|
||||
// Wire: Divide output → Morph progression input
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), NodeInput::node(divide_id, 0), network_path);
|
||||
}
|
||||
|
||||
// Migrate old Arrow node from (start, end, shaft_width, head_width, head_length) to (arrow_to, shaft_width, head_width, head_length) with a Transform node for positioning
|
||||
|
||||
@@ -1822,10 +1822,7 @@ impl ShapeState {
|
||||
/// Find the `t` value along the path segment we have clicked upon, together with that segment ID.
|
||||
fn closest_segment(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> {
|
||||
let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||
if transform.matrix2.determinant() == 0. {
|
||||
return None;
|
||||
}
|
||||
let layer_pos = transform.inverse().transform_point2(position);
|
||||
let layer_pos = (transform.matrix2.determinant().abs() >= f64::EPSILON).then(|| transform.inverse().transform_point2(position))?;
|
||||
|
||||
let tolerance = tolerance + 0.5;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user