mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-08 00:05:00 +00:00
Instance tables refactor part 8: Make repeater nodes use pivot not bbox and output instance type not group; rename 'Flatten Vector Elements' to 'Flatten Path' and add 'Flatten Vector' (#2697)
Make repeater nodes use pivot not bbox and output instance type not group; rename 'Flatten Vector Elements' to 'Flatten Path' and add 'Flatten Vector'
This commit is contained in:
parent
523cc27523
commit
cea1a1c6a8
26 changed files with 252 additions and 125 deletions
2
demo-artwork/changing-seasons.graphite
generated
2
demo-artwork/changing-seasons.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/isometric-fountain.graphite
generated
2
demo-artwork/isometric-fountain.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/marbled-mandelbrot.graphite
generated
2
demo-artwork/marbled-mandelbrot.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/painted-dreams.graphite
generated
2
demo-artwork/painted-dreams.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/parametric-dunescape.graphite
generated
2
demo-artwork/parametric-dunescape.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/procedural-string-lights.graphite
generated
2
demo-artwork/procedural-string-lights.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/red-dress.graphite
generated
2
demo-artwork/red-dress.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/valley-of-spires.graphite
generated
2
demo-artwork/valley-of-spires.graphite
generated
File diff suppressed because one or more lines are too long
|
@ -498,9 +498,14 @@ mod test {
|
|||
println!("-------------------------------------------------");
|
||||
println!("Failed test due to receiving a DisplayDialogError while loading a Graphite demo file.");
|
||||
println!();
|
||||
println!("NOTE:");
|
||||
println!("Document upgrading isn't performed in tests like when opening in the actual editor.");
|
||||
println!("You may need to open and re-save a document in the editor to apply its migrations.");
|
||||
println!();
|
||||
println!("DisplayDialogError details:");
|
||||
println!();
|
||||
println!("Description: {value}");
|
||||
println!("Description:");
|
||||
println!("{value}");
|
||||
println!("-------------------------------------------------");
|
||||
println!();
|
||||
|
||||
|
@ -538,7 +543,9 @@ mod test {
|
|||
});
|
||||
|
||||
// Check if the graph renders
|
||||
editor.eval_graph().await;
|
||||
if let Err(e) = editor.eval_graph().await {
|
||||
print_problem_to_terminal_on_failure(&format!("Failed to evaluate the graph for document '{document_name}':\n{e}"));
|
||||
}
|
||||
|
||||
for response in responses {
|
||||
// Check for the existence of the file format incompatibility warning dialog after opening the test file
|
||||
|
|
|
@ -289,17 +289,17 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
log::error!("Node type {} does not exist in ModifyInputsContext::existing_node_id", reference);
|
||||
return None;
|
||||
};
|
||||
// If inserting a path node, insert a flatten vector elements if the type is a graphic group.
|
||||
// If inserting a path node, insert a Flatten Path if the type is a graphic group.
|
||||
// TODO: Allow the path node to operate on Graphic Group data by utilizing the reference for each vector data in a group.
|
||||
if node_definition.identifier == "Path" {
|
||||
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type().clone();
|
||||
if layer_input_type == concrete!(GraphicGroupTable) {
|
||||
let Some(flatten_vector_elements_definition) = resolve_document_node_type("Flatten Vector Elements") else {
|
||||
log::error!("Flatten Vector Elements does not exist in ModifyInputsContext::existing_node_id");
|
||||
let Some(flatten_path_definition) = resolve_document_node_type("Flatten Path") else {
|
||||
log::error!("Flatten Path does not exist in ModifyInputsContext::existing_node_id");
|
||||
return None;
|
||||
};
|
||||
let node_id = NodeId::new();
|
||||
self.network_interface.insert_node(node_id, flatten_vector_elements_definition.default_node_template(), &[]);
|
||||
self.network_interface.insert_node(node_id, flatten_path_definition.default_node_template(), &[]);
|
||||
self.network_interface.move_node_to_chain_start(&node_id, output_layer, &[]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3805,6 +3805,15 @@ impl NodeNetworkInterface {
|
|||
node_metadata.persistent_metadata.network_metadata = metadata.network_metadata;
|
||||
}
|
||||
|
||||
/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts
|
||||
pub fn replace_reference_name(&mut self, node_id: &NodeId, network_path: &[NodeId], reference_name: String) {
|
||||
let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {
|
||||
log::error!("Could not get node metadata in replace_reference_name");
|
||||
return;
|
||||
};
|
||||
node_metadata.persistent_metadata.reference = Some(reference_name);
|
||||
}
|
||||
|
||||
/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts
|
||||
pub fn set_manual_compostion(&mut self, node_id: &NodeId, network_path: &[NodeId], manual_composition: Option<Type>) {
|
||||
let Some(network) = self.network_mut(network_path) else {
|
||||
|
|
|
@ -467,7 +467,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
}
|
||||
};
|
||||
|
||||
const REPLACEMENTS: [(&str, &str); 36] = [
|
||||
const REPLACEMENTS: [(&str, &str); 37] = [
|
||||
("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"),
|
||||
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
|
||||
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
|
||||
|
@ -507,6 +507,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
("graphene_std::raster::SampleNode", "graphene_std::raster::SampleImageNode"),
|
||||
("graphene_core::transform::CullNode", "graphene_core::ops::IdentityNode"),
|
||||
("graphene_std::raster::MaskImageNode", "graphene_std::raster::MaskNode"),
|
||||
("graphene_core::vector::FlattenVectorElementsNode", "graphene_core::vector::FlattenPathNode"),
|
||||
];
|
||||
let mut network = document.network_interface.document_network().clone();
|
||||
network.generate_node_paths(&[]);
|
||||
|
@ -946,6 +947,22 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path);
|
||||
}
|
||||
|
||||
if reference == "Flatten Vector Elements" {
|
||||
let node_definition = resolve_document_node_type("Flatten Path").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Flatten Path".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
|
|
|
@ -91,9 +91,9 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
|
|||
delete_children: false,
|
||||
});
|
||||
|
||||
// Add a flatten vector elements node after the merge
|
||||
// Add a Flatten Path node after the merge
|
||||
let flatten_node_id = NodeId::new();
|
||||
let flatten_node = document_node_definitions::resolve_document_node_type("Flatten Vector Elements")
|
||||
let flatten_node = document_node_definitions::resolve_document_node_type("Flatten Path")
|
||||
.expect("Failed to create flatten node")
|
||||
.default_node_template();
|
||||
responses.add(NodeGraphMessage::InsertNode {
|
||||
|
|
|
@ -563,7 +563,10 @@ mod test_artboard {
|
|||
pub use crate::test_utils::test_prelude::*;
|
||||
|
||||
async fn get_artboards(editor: &mut EditorTestUtils) -> Vec<graphene_core::Artboard> {
|
||||
let instrumented = editor.eval_graph().await;
|
||||
let instrumented = match editor.eval_graph().await {
|
||||
Ok(instrumented) => instrumented,
|
||||
Err(e) => panic!("Failed to evaluate graph: {}", e),
|
||||
};
|
||||
instrumented.grab_all_input::<graphene_core::append_artboard::ArtboardInput>(&editor.runtime).collect()
|
||||
}
|
||||
|
||||
|
|
|
@ -330,7 +330,10 @@ mod test_ellipse {
|
|||
}
|
||||
|
||||
async fn get_ellipse(editor: &mut EditorTestUtils) -> Vec<ResolvedEllipse> {
|
||||
let instrumented = editor.eval_graph().await;
|
||||
let instrumented = match editor.eval_graph().await {
|
||||
Ok(instrumented) => instrumented,
|
||||
Err(e) => panic!("Failed to evaluate graph: {e}"),
|
||||
};
|
||||
|
||||
let document = editor.active_document();
|
||||
let layers = document.metadata().all_layers();
|
||||
|
|
|
@ -161,7 +161,10 @@ mod test_fill {
|
|||
use graphene_std::vector::style::Fill;
|
||||
|
||||
async fn get_fills(editor: &mut EditorTestUtils) -> Vec<Fill> {
|
||||
let instrumented = editor.eval_graph().await;
|
||||
let instrumented = match editor.eval_graph().await {
|
||||
Ok(instrumented) => instrumented,
|
||||
Err(e) => panic!("Failed to evaluate graph: {e}"),
|
||||
};
|
||||
|
||||
instrumented.grab_all_input::<fill::FillInput<Fill>>(&editor.runtime).collect()
|
||||
}
|
||||
|
|
|
@ -545,7 +545,10 @@ mod test_gradient {
|
|||
use super::gradient_space_transform;
|
||||
|
||||
async fn get_fills(editor: &mut EditorTestUtils) -> Vec<(Fill, DAffine2)> {
|
||||
let instrumented = editor.eval_graph().await;
|
||||
let instrumented = match editor.eval_graph().await {
|
||||
Ok(instrumented) => instrumented,
|
||||
Err(e) => panic!("Failed to evaluate graph: {}", e),
|
||||
};
|
||||
|
||||
let document = editor.active_document();
|
||||
let layers = document.metadata().all_layers();
|
||||
|
|
|
@ -670,7 +670,9 @@ mod test_spline_tool {
|
|||
editor.handle_message(SplineToolMessage::Confirm).await;
|
||||
|
||||
// Evaluate the graph to ensure everything is processed
|
||||
editor.eval_graph().await;
|
||||
if let Err(e) = editor.eval_graph().await {
|
||||
panic!("Graph evaluation failed: {}", e);
|
||||
}
|
||||
|
||||
// Get the layer and vector data
|
||||
let document = editor.active_document();
|
||||
|
@ -708,7 +710,9 @@ mod test_spline_tool {
|
|||
editor.handle_message(SplineToolMessage::Confirm).await;
|
||||
|
||||
// Evaluating the graph to ensure everything is processed
|
||||
editor.eval_graph().await;
|
||||
if let Err(e) = editor.eval_graph().await {
|
||||
panic!("Graph evaluation failed: {}", e);
|
||||
}
|
||||
|
||||
// Get the layer and vector data
|
||||
let document = editor.active_document();
|
||||
|
@ -744,7 +748,9 @@ mod test_spline_tool {
|
|||
editor.handle_message(SplineToolMessage::Confirm).await;
|
||||
|
||||
// Evaluating the graph to ensure everything is processed
|
||||
editor.eval_graph().await;
|
||||
if let Err(e) = editor.eval_graph().await {
|
||||
panic!("Graph evaluation failed: {}", e);
|
||||
}
|
||||
|
||||
// Get the layer and vector data
|
||||
let document = editor.active_document();
|
||||
|
@ -781,7 +787,9 @@ mod test_spline_tool {
|
|||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150.0, 100.0), ModifierKeys::empty()).await;
|
||||
|
||||
editor.handle_message(SplineToolMessage::Confirm).await;
|
||||
editor.eval_graph().await;
|
||||
if let Err(e) = editor.eval_graph().await {
|
||||
panic!("Graph evaluation failed: {}", e);
|
||||
}
|
||||
|
||||
// Get the layer and vector data
|
||||
let document = editor.active_document();
|
||||
|
|
|
@ -36,30 +36,35 @@ impl EditorTestUtils {
|
|||
Self { editor, runtime }
|
||||
}
|
||||
|
||||
pub fn eval_graph<'a>(&'a mut self) -> impl std::future::Future<Output = Instrumented> + 'a {
|
||||
pub fn eval_graph<'a>(&'a mut self) -> impl std::future::Future<Output = Result<Instrumented, String>> + 'a {
|
||||
// An inner function is required since async functions in traits are a bit weird
|
||||
async fn run<'a>(editor: &'a mut Editor, runtime: &'a mut NodeRuntime) -> Instrumented {
|
||||
async fn run<'a>(editor: &'a mut Editor, runtime: &'a mut NodeRuntime) -> Result<Instrumented, String> {
|
||||
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
|
||||
let exector = &mut portfolio.executor;
|
||||
let document = portfolio.documents.get_mut(&portfolio.active_document_id.unwrap()).unwrap();
|
||||
|
||||
let instrumented = exector.update_node_graph_instrumented(document).expect("update_node_graph_instrumented failed");
|
||||
let instrumented = match exector.update_node_graph_instrumented(document) {
|
||||
Ok(instrumented) => instrumented,
|
||||
Err(e) => return Err(format!("update_node_graph_instrumented failed\n\n{e}")),
|
||||
};
|
||||
|
||||
let viewport_resolution = glam::UVec2::ONE;
|
||||
exector
|
||||
.submit_current_node_graph_evaluation(document, viewport_resolution, Default::default())
|
||||
.expect("submit_current_node_graph_evaluation failed");
|
||||
if let Err(e) = exector.submit_current_node_graph_evaluation(document, viewport_resolution, Default::default()) {
|
||||
return Err(format!("submit_current_node_graph_evaluation failed\n\n{e}"));
|
||||
}
|
||||
runtime.run().await;
|
||||
|
||||
let mut messages = VecDeque::new();
|
||||
editor.poll_node_graph_evaluation(&mut messages).expect("Graph should render");
|
||||
if let Err(e) = editor.poll_node_graph_evaluation(&mut messages) {
|
||||
return Err(format!("Graph should render\n\n{e}"));
|
||||
}
|
||||
let frontend_messages = messages.into_iter().flat_map(|message| editor.handle_message(message));
|
||||
|
||||
for message in frontend_messages {
|
||||
message.check_node_graph_error();
|
||||
}
|
||||
|
||||
instrumented
|
||||
Ok(instrumented)
|
||||
}
|
||||
|
||||
run(&mut self.editor, &mut self.runtime)
|
||||
|
@ -69,7 +74,9 @@ impl EditorTestUtils {
|
|||
self.editor.handle_message(message);
|
||||
|
||||
// Required to process any buffered messages
|
||||
self.eval_graph().await;
|
||||
if let Err(e) = self.eval_graph().await {
|
||||
panic!("Failed to evaluate graph: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new_document(&mut self) {
|
||||
|
|
|
@ -423,6 +423,49 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: boo
|
|||
output
|
||||
}
|
||||
|
||||
#[node_macro::node(category("General"))]
|
||||
async fn flatten_vector(_: impl Ctx, group: GraphicGroupTable) -> VectorDataTable {
|
||||
// TODO: Avoid mutable reference, instead return a new GraphicGroupTable?
|
||||
fn flatten_group(output_group_table: &mut VectorDataTable, current_group_table: GraphicGroupTable) {
|
||||
for current_instance in current_group_table.instance_ref_iter() {
|
||||
let current_element = current_instance.instance.clone();
|
||||
let reference = *current_instance.source_node_id;
|
||||
|
||||
match current_element {
|
||||
// If we're allowed to recurse, flatten any GraphicGroups we encounter
|
||||
GraphicElement::GraphicGroup(mut current_element) => {
|
||||
// Apply the parent group's transform to all child elements
|
||||
for graphic_element in current_element.instance_mut_iter() {
|
||||
*graphic_element.transform = *current_instance.transform * *graphic_element.transform;
|
||||
}
|
||||
|
||||
flatten_group(output_group_table, current_element);
|
||||
}
|
||||
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
|
||||
GraphicElement::VectorData(vector_instance) => {
|
||||
for current_element in vector_instance.instance_ref_iter() {
|
||||
output_group_table.push(Instance {
|
||||
instance: current_element.instance.clone(),
|
||||
transform: *current_instance.transform * *current_element.transform,
|
||||
alpha_blending: AlphaBlending {
|
||||
opacity: current_instance.alpha_blending.opacity * current_element.alpha_blending.opacity,
|
||||
blend_mode: current_element.alpha_blending.blend_mode,
|
||||
},
|
||||
source_node_id: reference,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut output = VectorDataTable::default();
|
||||
flatten_group(&mut output, group);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn to_artboard<Data: Into<GraphicGroupTable> + 'n>(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||
|
|
|
@ -921,6 +921,10 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
|
|||
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
click_targets.push(ClickTarget::new(subpath, 0.));
|
||||
}
|
||||
|
||||
fn to_graphic_element(&self) -> GraphicElement {
|
||||
GraphicElement::RasterDataCPU(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicElementRendered for RasterDataTable<GPU> {
|
||||
|
|
|
@ -33,6 +33,13 @@ impl<T> Instances<T> {
|
|||
self.source_node_id.push(instance.source_node_id);
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, instances: Instances<T>) {
|
||||
self.instance.extend(instances.instance);
|
||||
self.transform.extend(instances.transform);
|
||||
self.alpha_blending.extend(instances.alpha_blending);
|
||||
self.source_node_id.extend(instances.source_node_id);
|
||||
}
|
||||
|
||||
pub fn instance_iter(self) -> impl DoubleEndedIterator<Item = Instance<T>> {
|
||||
self.instance
|
||||
.into_iter()
|
||||
|
|
|
@ -44,12 +44,26 @@ async fn switch<T, C: Send + 'n + Clone>(
|
|||
condition: bool,
|
||||
#[expose]
|
||||
#[implementations(
|
||||
Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorDataTable, Context -> DAffine2,
|
||||
Context -> String,
|
||||
Context -> bool,
|
||||
Context -> f64,
|
||||
Context -> u32,
|
||||
Context -> u64,
|
||||
Context -> DVec2,
|
||||
Context -> VectorDataTable,
|
||||
Context -> DAffine2,
|
||||
)]
|
||||
if_true: impl Node<C, Output = T>,
|
||||
#[expose]
|
||||
#[implementations(
|
||||
Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorDataTable, Context -> DAffine2,
|
||||
Context -> String,
|
||||
Context -> bool,
|
||||
Context -> f64,
|
||||
Context -> u32,
|
||||
Context -> u64,
|
||||
Context -> DVec2,
|
||||
Context -> VectorDataTable,
|
||||
Context -> DAffine2,
|
||||
)]
|
||||
if_false: impl Node<C, Output = T>,
|
||||
) -> T {
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
use crate::instances::{InstanceRef, Instances};
|
||||
use crate::raster_types::{CPU, RasterDataTable};
|
||||
use crate::transform::TransformMut;
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, GraphicElement, GraphicGroupTable, OwnedContextImpl};
|
||||
use glam::DVec2;
|
||||
|
||||
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))]
|
||||
async fn instance_on_points<T: Into<GraphicElement> + Default + Clone + 'static>(
|
||||
async fn instance_on_points<T: Into<GraphicElement> + Default + Send + Clone + 'static>(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx,
|
||||
points: VectorDataTable,
|
||||
#[implementations(Context -> GraphicGroupTable, Context -> VectorDataTable, Context -> RasterDataTable<CPU>)] instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
|
||||
#[implementations(
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> VectorDataTable,
|
||||
Context -> RasterDataTable<CPU>
|
||||
)]
|
||||
instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
|
||||
reverse: bool,
|
||||
) -> GraphicGroupTable {
|
||||
let mut result_table = GraphicGroupTable::default();
|
||||
) -> Instances<T> {
|
||||
let mut result_table = Instances::<T>::default();
|
||||
|
||||
for InstanceRef { instance: points, transform, .. } in points.instance_ref_iter() {
|
||||
let mut iteration = async |index, point| {
|
||||
|
@ -22,8 +26,8 @@ async fn instance_on_points<T: Into<GraphicElement> + Default + Clone + 'static>
|
|||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||
|
||||
for mut instanced in generated_instance.instance_iter() {
|
||||
instanced.transform.translate(transformed_point);
|
||||
result_table.push(instanced.to_graphic_element());
|
||||
instanced.transform.translation = transformed_point;
|
||||
result_table.push(instanced);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -43,15 +47,20 @@ async fn instance_on_points<T: Into<GraphicElement> + Default + Clone + 'static>
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
|
||||
async fn instance_repeat<T: Into<GraphicElement> + Default + Clone + 'static>(
|
||||
async fn instance_repeat<T: Into<GraphicElement> + Default + Send + Clone + 'static>(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||
#[implementations(Context -> GraphicGroupTable, Context -> VectorDataTable, Context -> RasterDataTable<CPU>)] instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
|
||||
#[implementations(
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> VectorDataTable,
|
||||
Context -> RasterDataTable<CPU>
|
||||
)]
|
||||
instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
|
||||
#[default(1)] count: u64,
|
||||
reverse: bool,
|
||||
) -> GraphicGroupTable {
|
||||
) -> Instances<T> {
|
||||
let count = count.max(1) as usize;
|
||||
|
||||
let mut result_table = GraphicGroupTable::default();
|
||||
let mut result_table = Instances::<T>::default();
|
||||
|
||||
for index in 0..count {
|
||||
let index = if reverse { count - index - 1 } else { index };
|
||||
|
@ -60,7 +69,7 @@ async fn instance_repeat<T: Into<GraphicElement> + Default + Clone + 'static>(
|
|||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||
|
||||
for instanced in generated_instance.instance_iter() {
|
||||
result_table.push(instanced.to_graphic_element());
|
||||
result_table.push(instanced);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,16 +133,7 @@ mod test {
|
|||
let repeated = super::instance_on_points(owned, points, &rect, false).await;
|
||||
assert_eq!(repeated.len(), positions.len());
|
||||
for (position, instanced) in positions.into_iter().zip(repeated.instance_ref_iter()) {
|
||||
let bounds = instanced
|
||||
.instance
|
||||
.as_vector_data()
|
||||
.unwrap()
|
||||
.instance_ref_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.instance
|
||||
.bounding_box_with_transform(*instanced.transform)
|
||||
.unwrap();
|
||||
let bounds = instanced.instance.bounding_box_with_transform(*instanced.transform).unwrap();
|
||||
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
|
||||
assert_eq!((bounds[1] - bounds[0]).x, position.y);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ use glam::{DAffine2, DVec2};
|
|||
use kurbo::{Affine, BezPath, Shape};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
/// Implemented for types that can be converted to an iterator of vector data.
|
||||
/// Used for the fill and stroke node so they can be used on VectorData or GraphicGroup
|
||||
|
@ -201,7 +202,7 @@ where
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn repeat<I: 'n + Send>(
|
||||
async fn repeat<I: 'n + Send + Clone>(
|
||||
_: impl Ctx,
|
||||
// TODO: Implement other GraphicElementRendered types.
|
||||
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>,
|
||||
|
@ -210,78 +211,72 @@ async fn repeat<I: 'n + Send>(
|
|||
direction: PixelSize,
|
||||
angle: Angle,
|
||||
#[default(4)] instances: IntegerCount,
|
||||
) -> GraphicGroupTable
|
||||
) -> Instances<I>
|
||||
where
|
||||
Instances<I>: GraphicElementRendered,
|
||||
{
|
||||
let angle = angle.to_radians();
|
||||
let instances = instances.max(1);
|
||||
let total = (instances - 1) as f64;
|
||||
let count = instances.max(1);
|
||||
let total = (count - 1) as f64;
|
||||
|
||||
let mut result_table = GraphicGroupTable::default();
|
||||
let mut result_table = Instances::<I>::default();
|
||||
|
||||
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY, false) else {
|
||||
return result_table;
|
||||
};
|
||||
|
||||
let center = (bounding_box[0] + bounding_box[1]) / 2.;
|
||||
|
||||
for index in 0..instances {
|
||||
for index in 0..count {
|
||||
let angle = index as f64 * angle / total;
|
||||
let translation = index as f64 * direction / total;
|
||||
let transform = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
|
||||
let transform = DAffine2::from_angle(angle) * DAffine2::from_translation(translation);
|
||||
|
||||
result_table.push(Instance {
|
||||
instance: instance.to_graphic_element().clone(),
|
||||
transform,
|
||||
alpha_blending: Default::default(),
|
||||
source_node_id: None,
|
||||
});
|
||||
for instance in instance.instance_ref_iter() {
|
||||
let mut instance = instance.to_instance_cloned();
|
||||
|
||||
let local_translation = DAffine2::from_translation(instance.transform.translation);
|
||||
let local_matrix = DAffine2::from_mat2(instance.transform.matrix2);
|
||||
instance.transform = local_translation * transform * local_matrix;
|
||||
|
||||
result_table.push(instance);
|
||||
}
|
||||
}
|
||||
|
||||
result_table
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn circular_repeat<I: 'n + Send>(
|
||||
async fn circular_repeat<I: 'n + Send + Clone>(
|
||||
_: impl Ctx,
|
||||
// TODO: Implement other GraphicElementRendered types.
|
||||
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>,
|
||||
angle_offset: Angle,
|
||||
#[default(5)] radius: f64,
|
||||
#[default(5)] instances: IntegerCount,
|
||||
) -> GraphicGroupTable
|
||||
) -> Instances<I>
|
||||
where
|
||||
Instances<I>: GraphicElementRendered,
|
||||
{
|
||||
let instances = instances.max(1);
|
||||
let count = instances.max(1);
|
||||
|
||||
let mut result_table = GraphicGroupTable::default();
|
||||
let mut result_table = Instances::<I>::default();
|
||||
|
||||
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY, false) else {
|
||||
return result_table;
|
||||
};
|
||||
for index in 0..count {
|
||||
let angle = DAffine2::from_angle((TAU / count as f64) * index as f64 + angle_offset.to_radians());
|
||||
let translation = DAffine2::from_translation(radius * DVec2::Y);
|
||||
let transform = angle * translation;
|
||||
|
||||
let center = (bounding_box[0] + bounding_box[1]) / 2.;
|
||||
let base_transform = DVec2::new(0., radius) - center;
|
||||
for instance in instance.instance_ref_iter() {
|
||||
let mut instance = instance.to_instance_cloned();
|
||||
|
||||
for index in 0..instances {
|
||||
let rotation = DAffine2::from_angle((std::f64::consts::TAU / instances as f64) * index as f64 + angle_offset.to_radians());
|
||||
let transform = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
|
||||
let local_translation = DAffine2::from_translation(instance.transform.translation);
|
||||
let local_matrix = DAffine2::from_mat2(instance.transform.matrix2);
|
||||
instance.transform = local_translation * transform * local_matrix;
|
||||
|
||||
result_table.push(Instance {
|
||||
instance: instance.to_graphic_element().clone(),
|
||||
transform,
|
||||
alpha_blending: Default::default(),
|
||||
source_node_id: None,
|
||||
});
|
||||
result_table.push(instance);
|
||||
}
|
||||
}
|
||||
|
||||
result_table
|
||||
}
|
||||
|
||||
#[node_macro::node(name("Copy to Points"), category("Vector"), path(graphene_core::vector))]
|
||||
async fn copy_to_points<I: 'n + Send>(
|
||||
async fn copy_to_points<I: 'n + Send + Clone>(
|
||||
_: impl Ctx,
|
||||
points: VectorDataTable,
|
||||
#[expose]
|
||||
|
@ -308,17 +303,14 @@ async fn copy_to_points<I: 'n + Send>(
|
|||
random_rotation: Angle,
|
||||
/// Seed to determine unique variations on all the randomized instance angles.
|
||||
random_rotation_seed: SeedValue,
|
||||
) -> GraphicGroupTable
|
||||
) -> Instances<I>
|
||||
where
|
||||
Instances<I>: GraphicElementRendered,
|
||||
{
|
||||
let mut result_table = GraphicGroupTable::default();
|
||||
let mut result_table = Instances::<I>::default();
|
||||
|
||||
let random_scale_difference = random_scale_max - random_scale_min;
|
||||
|
||||
let instance_bounding_box = instance.bounding_box(DAffine2::IDENTITY, false).unwrap_or_default();
|
||||
let instance_center = -0.5 * (instance_bounding_box[0] + instance_bounding_box[1]);
|
||||
|
||||
for point_instance in points.instance_iter() {
|
||||
let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed.into());
|
||||
let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed.into());
|
||||
|
@ -328,13 +320,11 @@ where
|
|||
|
||||
let points_transform = point_instance.transform;
|
||||
for &point in point_instance.instance.point_domain.positions() {
|
||||
let center_transform = DAffine2::from_translation(instance_center);
|
||||
|
||||
let translation = points_transform.transform_point2(point);
|
||||
|
||||
let rotation = if do_rotation {
|
||||
let degrees = (rotation_rng.random::<f64>() - 0.5) * random_rotation;
|
||||
degrees / 360. * std::f64::consts::TAU
|
||||
degrees / 360. * TAU
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
|
@ -353,22 +343,23 @@ where
|
|||
random_scale_min
|
||||
};
|
||||
|
||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform;
|
||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation);
|
||||
|
||||
result_table.push(Instance {
|
||||
instance: instance.to_graphic_element().clone(),
|
||||
transform,
|
||||
alpha_blending: Default::default(),
|
||||
source_node_id: None,
|
||||
});
|
||||
for mut instance in instance.instance_ref_iter().map(|instance| instance.to_instance_cloned()) {
|
||||
let local_matrix = DAffine2::from_mat2(instance.transform.matrix2);
|
||||
instance.transform = transform * local_matrix;
|
||||
|
||||
result_table.push(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result_table
|
||||
}
|
||||
|
||||
// TODO: Make this node return Instances<I> instead of GraphicGroupTable, while preserving the current transform behavior as the `reference_point` and `offset` parameters are varied
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn mirror<I: 'n + Send>(
|
||||
async fn mirror<I: 'n + Send + Clone>(
|
||||
_: impl Ctx,
|
||||
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>,
|
||||
#[default(ReferencePoint::Center)] reference_point: ReferencePoint,
|
||||
|
@ -388,10 +379,13 @@ where
|
|||
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY, false) else {
|
||||
return result_table;
|
||||
};
|
||||
let mirror_reference_point = reference_point
|
||||
.point_in_bounding_box((bounding_box[0], bounding_box[1]).into())
|
||||
.unwrap_or_else(|| (bounding_box[0] + bounding_box[1]) / 2.)
|
||||
+ normal * offset;
|
||||
|
||||
// TODO: If the reference point is not None, use the current behavior but make it work correctly with local pivot origins of each Instances<I> row
|
||||
let reference_point_location = reference_point.point_in_bounding_box((bounding_box[0], bounding_box[1]).into()).unwrap_or_else(|| {
|
||||
// TODO: In this None case, use the input's local pivot origin point instead of a point relative to its bounding box
|
||||
(bounding_box[0] + bounding_box[1]) / 2.
|
||||
});
|
||||
let mirror_reference_point = reference_point_location + normal * offset;
|
||||
|
||||
// Create the reflection matrix
|
||||
let reflection = DAffine2::from_mat2_translation(
|
||||
|
@ -1164,9 +1158,12 @@ async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDat
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupTable) -> VectorDataTable {
|
||||
async fn flatten_path<I: 'n + Send>(_: impl Ctx, #[implementations(GraphicGroupTable, VectorDataTable)] graphic_group_input: Instances<I>) -> VectorDataTable
|
||||
where
|
||||
Instances<I>: GraphicElementRendered,
|
||||
{
|
||||
// A node based solution to support passing through vector data could be a network node with a cache node connected to
|
||||
// a flatten vector elements connected to an if else node, another connection from the cache directly
|
||||
// a Flatten Path connected to an if else node, another connection from the cache directly
|
||||
// To the if else node, and another connection from the cache to a matches type node connected to the if else node.
|
||||
fn flatten_group(graphic_group_table: &GraphicGroupTable, output: &mut InstanceMut<VectorData>) {
|
||||
for (group_index, current_element) in graphic_group_table.instance_ref_iter().enumerate() {
|
||||
|
@ -1208,7 +1205,8 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
|
|||
};
|
||||
|
||||
// Flatten the graphic group input into the output VectorData instance
|
||||
flatten_group(&graphic_group_input, &mut output);
|
||||
let base_graphic_group = GraphicGroupTable::new(graphic_group_input.to_graphic_element());
|
||||
flatten_group(&base_graphic_group, &mut output);
|
||||
|
||||
// Return the single-row VectorDataTable containing the flattened VectorData subpaths
|
||||
output_table
|
||||
|
@ -1485,7 +1483,7 @@ async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)]
|
|||
|
||||
let deltas = (0..vector_data_instance.instance.point_domain.positions().len())
|
||||
.map(|_| {
|
||||
let angle = rng.random::<f64>() * std::f64::consts::TAU;
|
||||
let angle = rng.random::<f64>() * TAU;
|
||||
|
||||
inverse_transform.transform_vector2(DVec2::from_angle(angle) * rng.random::<f64>() * amount)
|
||||
})
|
||||
|
@ -1859,7 +1857,7 @@ mod test {
|
|||
let direction = DVec2::X * 1.5;
|
||||
let instances = 3;
|
||||
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
|
||||
let vector_data = super::flatten_path(Footprint::default(), repeated).await;
|
||||
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(vector_data.region_bezier_paths().count(), 3);
|
||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||
|
@ -1871,7 +1869,7 @@ mod test {
|
|||
let direction = DVec2::new(12., 10.);
|
||||
let instances = 8;
|
||||
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
|
||||
let vector_data = super::flatten_path(Footprint::default(), repeated).await;
|
||||
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||
|
@ -1881,7 +1879,7 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn circular_repeat() {
|
||||
let repeated = super::circular_repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
|
||||
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
|
||||
let vector_data = super::flatten_path(Footprint::default(), repeated).await;
|
||||
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
||||
|
||||
|
@ -1927,8 +1925,8 @@ mod test {
|
|||
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
|
||||
|
||||
let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await;
|
||||
let flatten_vector_elements = super::flatten_vector_elements(Footprint::default(), copy_to_points).await;
|
||||
let flattened_copy_to_points = flatten_vector_elements.instance_ref_iter().next().unwrap().instance;
|
||||
let flatten_path = super::flatten_path(Footprint::default(), copy_to_points).await;
|
||||
let flattened_copy_to_points = flatten_path.instance_ref_iter().next().unwrap().instance;
|
||||
|
||||
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<graphene_core::uuid::NodeId>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::Color]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Box<graphene_core::vector::VectorModification>]),
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]),
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RasterDataTable<CPU>]),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue