mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-07 21:18:12 +08:00
Make Copy to Points and (Circular) Repeat and nodes output group data, and add flattening nodes (#2011)
* Output group from repeat, add flatten vector elements * Fix tests * Fix demo artwork * Output group from copy to points, add repeat for graphic groups, fix editor freeze on render fail * Restore painted dreams * WIP: Fix demo artwork * Fix demo artwork, add ungroup node * Incorrect scaling * fix test * Fix demo art --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
b028bbb8cc
commit
e09f5ecaec
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
@@ -239,18 +239,28 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
// Gets the node id of a node with a specific reference that is upstream from the layer node, and creates it if it does not exist
|
// Gets the node id of a node with a specific reference that is upstream from the layer node, and creates it if it does not exist
|
||||||
pub fn existing_node_id(&mut self, reference: &'static str) -> Option<NodeId> {
|
pub fn existing_node_id(&mut self, reference: &'static str) -> Option<NodeId> {
|
||||||
// Start from the layer node or export
|
// Start from the layer node or export
|
||||||
let node_id = self.get_output_layer().map_or(Vec::new(), |output_layer| vec![output_layer.to_node()]);
|
let output_layer = self.get_output_layer()?;
|
||||||
|
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type();
|
||||||
|
|
||||||
let upstream = self.network_interface.upstream_flow_back_from_nodes(node_id, &[], network_interface::FlowType::HorizontalFlow);
|
let upstream = self
|
||||||
|
.network_interface
|
||||||
|
.upstream_flow_back_from_nodes(vec![output_layer.to_node()], &[], network_interface::FlowType::HorizontalFlow);
|
||||||
|
|
||||||
|
// Take until another layer node is found (but not the first layer node)
|
||||||
|
let mut existing_node_id = None;
|
||||||
|
for upstream_node in upstream.collect::<Vec<_>>() {
|
||||||
|
let upstream_node_input_type = self.network_interface.input_type(&InputConnector::node(upstream_node, 0), &[]).0.nested_type();
|
||||||
let is_traversal_start = |node_id: NodeId| {
|
let is_traversal_start = |node_id: NodeId| {
|
||||||
self.layer_node.map(|layer| layer.to_node()) == Some(node_id) || self.network_interface.network(&[]).unwrap().exports.iter().any(|export| export.as_node() == Some(node_id))
|
self.layer_node.map(|layer| layer.to_node()) == Some(node_id) || self.network_interface.network(&[]).unwrap().exports.iter().any(|export| export.as_node() == Some(node_id))
|
||||||
};
|
};
|
||||||
|
if !is_traversal_start(upstream_node) && (self.network_interface.is_layer(&upstream_node, &[]) || upstream_node_input_type != layer_input_type) {
|
||||||
// Take until another layer node is found (but not the first layer node)
|
break;
|
||||||
let existing_node_id = upstream
|
}
|
||||||
.take_while(|node_id| is_traversal_start(*node_id) || !self.network_interface.is_layer(node_id, &[]))
|
if self.network_interface.reference(&upstream_node, &[]).is_some_and(|node_reference| node_reference == reference) {
|
||||||
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|node_reference| node_reference == reference));
|
existing_node_id = Some(upstream_node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new node if the node does not exist and update its inputs
|
// Create a new node if the node does not exist and update its inputs
|
||||||
existing_node_id.or_else(|| {
|
existing_node_id.or_else(|| {
|
||||||
|
|||||||
@@ -752,8 +752,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
responses.add(DocumentMessage::EndTransaction);
|
|
||||||
|
|
||||||
if let Some(preview_node) = self.preview_on_mouse_up {
|
if let Some(preview_node) = self.preview_on_mouse_up {
|
||||||
responses.add(NodeGraphMessage::TogglePreview { node_id: preview_node });
|
responses.add(NodeGraphMessage::TogglePreview { node_id: preview_node });
|
||||||
self.preview_on_mouse_up = None;
|
self.preview_on_mouse_up = None;
|
||||||
@@ -992,6 +990,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
self.box_selection_start = None;
|
self.box_selection_start = None;
|
||||||
self.wire_in_progress_from_connector = None;
|
self.wire_in_progress_from_connector = None;
|
||||||
self.wire_in_progress_to_connector = None;
|
self.wire_in_progress_to_connector = None;
|
||||||
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
||||||
responses.add(FrontendMessage::UpdateBox { box_selection: None })
|
responses.add(FrontendMessage::UpdateBox { box_selection: None })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3603,7 +3603,7 @@ impl NodeNetworkInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Check whether the being-deleted node's first (primary) input is a node
|
// Check whether the being-deleted node's first (primary) input is a node
|
||||||
let mut reconnect_to_input = network.nodes.get(node_id).and_then(|node| {
|
let reconnect_to_input = network.nodes.get(node_id).and_then(|node| {
|
||||||
node.inputs
|
node.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.find(|input| input.is_exposed_to_frontend(network_path.is_empty()))
|
.find(|input| input.is_exposed_to_frontend(network_path.is_empty()))
|
||||||
@@ -3625,15 +3625,22 @@ impl NodeNetworkInterface {
|
|||||||
|
|
||||||
let mut reconnect_node = None;
|
let mut reconnect_node = None;
|
||||||
|
|
||||||
|
// Don't reconnect if the upstream node is a layer and there are multiple downstream inputs to reconnect
|
||||||
|
let multiple_disconnect_and_upstream_is_layer = downstream_inputs_to_disconnect.len() > 1
|
||||||
|
&& reconnect_to_input
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|upstream_input| upstream_input.as_node().map_or(false, |node_id| self.is_layer(&node_id, network_path)));
|
||||||
|
|
||||||
for downstream_input in &downstream_inputs_to_disconnect {
|
for downstream_input in &downstream_inputs_to_disconnect {
|
||||||
self.disconnect_input(downstream_input, network_path);
|
self.disconnect_input(downstream_input, network_path);
|
||||||
// Prevent reconnecting export to import until https://github.com/GraphiteEditor/Graphite/issues/1762 is solved
|
// Prevent reconnecting export to import until https://github.com/GraphiteEditor/Graphite/issues/1762 is solved
|
||||||
if !(matches!(reconnect_to_input, Some(NodeInput::Network { .. })) && matches!(downstream_input, InputConnector::Export(_))) {
|
if !(matches!(reconnect_to_input, Some(NodeInput::Network { .. })) && matches!(downstream_input, InputConnector::Export(_))) {
|
||||||
if let Some(reconnect_input) = reconnect_to_input.take() {
|
if let Some(reconnect_input) = &reconnect_to_input {
|
||||||
// Get the reconnect node position only if it is in a stack
|
|
||||||
reconnect_node = reconnect_input.as_node().and_then(|node_id| if self.is_stack(&node_id, network_path) { Some(node_id) } else { None });
|
reconnect_node = reconnect_input.as_node().and_then(|node_id| if self.is_stack(&node_id, network_path) { Some(node_id) } else { None });
|
||||||
self.disconnect_input(&InputConnector::node(*node_id, 0), network_path);
|
self.disconnect_input(&InputConnector::node(*node_id, 0), network_path);
|
||||||
self.set_input(downstream_input, reconnect_input, network_path);
|
if !multiple_disconnect_and_upstream_is_layer {
|
||||||
|
self.set_input(downstream_input, reconnect_input.clone(), network_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -963,6 +963,7 @@ impl PortfolioMessageHandler {
|
|||||||
/text>"#
|
/text>"#
|
||||||
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
|
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
|
||||||
.to_string();
|
.to_string();
|
||||||
|
responses.add(Message::EndBuffer(graphene_std::renderer::RenderMetadata::default()));
|
||||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
|
responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use graphene_core::renderer::{RenderSvgSegmentList, SvgSegment};
|
|||||||
use graphene_core::text::FontCache;
|
use graphene_core::text::FontCache;
|
||||||
use graphene_core::transform::Footprint;
|
use graphene_core::transform::Footprint;
|
||||||
use graphene_core::vector::style::ViewMode;
|
use graphene_core::vector::style::ViewMode;
|
||||||
use graphene_std::renderer::format_transform_matrix;
|
use graphene_std::renderer::{format_transform_matrix, RenderMetadata};
|
||||||
use graphene_std::vector::VectorData;
|
use graphene_std::vector::VectorData;
|
||||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
||||||
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
|
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
|
||||||
@@ -630,6 +630,7 @@ impl NodeGraphExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
||||||
|
let mut render_output_metadata = RenderMetadata::default();
|
||||||
match node_graph_output {
|
match node_graph_output {
|
||||||
TaggedValue::RenderOutput(render_output) => {
|
TaggedValue::RenderOutput(render_output) => {
|
||||||
match render_output.data {
|
match render_output.data {
|
||||||
@@ -651,10 +652,7 @@ impl NodeGraphExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(Message::EndBuffer(render_output.metadata));
|
render_output_metadata = render_output.metadata;
|
||||||
responses.add(DocumentMessage::RenderScrollbars);
|
|
||||||
responses.add(DocumentMessage::RenderRulers);
|
|
||||||
responses.add(OverlaysMessage::Draw);
|
|
||||||
}
|
}
|
||||||
TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses),
|
TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses),
|
||||||
TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses),
|
TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses),
|
||||||
@@ -669,6 +667,10 @@ impl NodeGraphExecutor {
|
|||||||
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
|
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
responses.add(Message::EndBuffer(render_output_metadata));
|
||||||
|
responses.add(DocumentMessage::RenderScrollbars);
|
||||||
|
responses.add(DocumentMessage::RenderRulers);
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -315,6 +315,48 @@ async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>(
|
|||||||
element.eval(footprint).await.into()
|
element.eval(footprint).await.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("General"))]
|
||||||
|
async fn flatten_group<F: 'n + Send>(
|
||||||
|
#[implementations(
|
||||||
|
(),
|
||||||
|
Footprint,
|
||||||
|
)]
|
||||||
|
footprint: F,
|
||||||
|
#[implementations(
|
||||||
|
() -> GraphicGroup,
|
||||||
|
Footprint -> GraphicGroup,
|
||||||
|
)]
|
||||||
|
group: impl Node<F, Output = GraphicGroup>,
|
||||||
|
fully_flatten: bool,
|
||||||
|
) -> GraphicGroup {
|
||||||
|
let nested_group = group.eval(footprint).await;
|
||||||
|
let mut flat_group = GraphicGroup::EMPTY;
|
||||||
|
fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) {
|
||||||
|
let mut collection_group = GraphicGroup::EMPTY;
|
||||||
|
for (element, reference) in current_group.elements {
|
||||||
|
if let GraphicElement::GraphicGroup(mut nested_group) = element {
|
||||||
|
nested_group.transform *= current_group.transform;
|
||||||
|
let mut sub_group = GraphicGroup::EMPTY;
|
||||||
|
if fully_flatten {
|
||||||
|
flatten_group(&mut sub_group, nested_group, fully_flatten);
|
||||||
|
} else {
|
||||||
|
for (collection_element, _) in &mut nested_group.elements {
|
||||||
|
*collection_element.transform_mut() = nested_group.transform * collection_element.transform();
|
||||||
|
}
|
||||||
|
sub_group = nested_group;
|
||||||
|
}
|
||||||
|
collection_group.append(&mut sub_group.elements);
|
||||||
|
} else {
|
||||||
|
collection_group.push((element, reference));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result_group.append(&mut collection_group.elements);
|
||||||
|
}
|
||||||
|
flatten_group(&mut flat_group, nested_group, fully_flatten);
|
||||||
|
flat_group
|
||||||
|
}
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroup> + 'n>(
|
async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroup> + 'n>(
|
||||||
#[implementations(
|
#[implementations(
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform {
|
|||||||
|
|
||||||
// TODO: Click targets can be removed from the render output, since the vector data is available in the vector modify data from Monitor nodes.
|
// TODO: Click targets can be removed from the render output, since the vector data is available in the vector modify data from Monitor nodes.
|
||||||
// This will require that the transform for child layers into that layer space be calculated, or it could be returned from the RenderOutput instead of click targets.
|
// This will require that the transform for child layers into that layer space be calculated, or it could be returned from the RenderOutput instead of click targets.
|
||||||
#[derive(Debug, Clone, PartialEq, DynAny)]
|
#[derive(Debug, Default, Clone, PartialEq, DynAny)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct RenderMetadata {
|
pub struct RenderMetadata {
|
||||||
pub footprints: HashMap<NodeId, (Footprint, DAffine2)>,
|
pub footprints: HashMap<NodeId, (Footprint, DAffine2)>,
|
||||||
@@ -301,6 +301,12 @@ pub trait GraphicElementRendered {
|
|||||||
fn contains_artboard(&self) -> bool {
|
fn contains_artboard(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {}
|
||||||
|
|
||||||
|
fn to_graphic_element(&self) -> GraphicElement {
|
||||||
|
GraphicElement::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphicElementRendered for GraphicGroup {
|
impl GraphicElementRendered for GraphicGroup {
|
||||||
@@ -393,6 +399,16 @@ impl GraphicElementRendered for GraphicGroup {
|
|||||||
fn contains_artboard(&self) -> bool {
|
fn contains_artboard(&self) -> bool {
|
||||||
self.iter().any(|(element, _)| element.contains_artboard())
|
self.iter().any(|(element, _)| element.contains_artboard())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
||||||
|
for (element, node_id) in self.elements.iter_mut() {
|
||||||
|
element.new_ids_from_hash(*node_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_graphic_element(&self) -> GraphicElement {
|
||||||
|
GraphicElement::GraphicGroup(self.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphicElementRendered for VectorData {
|
impl GraphicElementRendered for VectorData {
|
||||||
@@ -582,6 +598,14 @@ impl GraphicElementRendered for VectorData {
|
|||||||
scene.pop_layer();
|
scene.pop_layer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
|
||||||
|
self.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_graphic_element(&self) -> GraphicElement {
|
||||||
|
GraphicElement::VectorData(Box::new(self.clone()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphicElementRendered for Artboard {
|
impl GraphicElementRendered for Artboard {
|
||||||
@@ -948,6 +972,14 @@ impl GraphicElementRendered for GraphicElement {
|
|||||||
GraphicElement::Raster(raster) => raster.contains_artboard(),
|
GraphicElement::Raster(raster) => raster.contains_artboard(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
|
||||||
|
match self {
|
||||||
|
GraphicElement::VectorData(vector_data) => vector_data.new_ids_from_hash(reference),
|
||||||
|
GraphicElement::GraphicGroup(graphic_group) => graphic_group.new_ids_from_hash(reference),
|
||||||
|
GraphicElement::Raster(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to stop rust complaining about upstream traits adding display implementations to `Option<Color>`. This would not be an issue as we control that crate.
|
/// Used to stop rust complaining about upstream traits adding display implementations to `Option<Color>`. This would not be an issue as we control that crate.
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ fn spline<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primar
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
|
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
|
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
|
||||||
let mut vector_data = super::VectorData::from_subpaths(path_data, false);
|
let mut vector_data = super::VectorData::from_subpaths(path_data, false);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use dyn_any::DynAny;
|
|||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
/// A simple macro for creating strongly typed ids (to avoid confusion when passing around ids).
|
/// A simple macro for creating strongly typed ids (to avoid confusion when passing around ids).
|
||||||
macro_rules! create_ids {
|
macro_rules! create_ids {
|
||||||
@@ -22,6 +23,14 @@ macro_rules! create_ids {
|
|||||||
Self(crate::uuid::generate_uuid())
|
Self(crate::uuid::generate_uuid())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_from_hash(self, node_id: u64) -> Self {
|
||||||
|
let mut hasher = std::hash::DefaultHasher::new();
|
||||||
|
node_id.hash(&mut hasher);
|
||||||
|
self.hash(&mut hasher);
|
||||||
|
let hash_value = hasher.finish();
|
||||||
|
Self(hash_value)
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the inner raw value.
|
/// Gets the inner raw value.
|
||||||
pub fn inner(self) -> u64 {
|
pub fn inner(self) -> u64 {
|
||||||
self.0
|
self.0
|
||||||
@@ -161,6 +170,10 @@ impl PointDomain {
|
|||||||
self.positions.extend(other.positions.iter().map(|&pos| transform.transform_point2(pos)));
|
self.positions.extend(other.positions.iter().map(|&pos| transform.transform_point2(pos)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_ids(&mut self, id_map: &IdMap) {
|
||||||
|
self.id.iter_mut().for_each(|id| *id = *id_map.point_map.get(id).unwrap_or(id));
|
||||||
|
}
|
||||||
|
|
||||||
fn transform(&mut self, transform: DAffine2) {
|
fn transform(&mut self, transform: DAffine2) {
|
||||||
for pos in &mut self.positions {
|
for pos in &mut self.positions {
|
||||||
*pos = transform.transform_point2(*pos);
|
*pos = transform.transform_point2(*pos);
|
||||||
@@ -353,6 +366,10 @@ impl SegmentDomain {
|
|||||||
self.stroke.extend(&other.stroke);
|
self.stroke.extend(&other.stroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_ids(&mut self, id_map: &IdMap) {
|
||||||
|
self.ids.iter_mut().for_each(|id| *id = *id_map.segment_map.get(id).unwrap_or(id));
|
||||||
|
}
|
||||||
|
|
||||||
fn transform(&mut self, transform: DAffine2) {
|
fn transform(&mut self, transform: DAffine2) {
|
||||||
for handles in &mut self.handles {
|
for handles in &mut self.handles {
|
||||||
*handles = handles.apply_transformation(|p| transform.transform_point2(p));
|
*handles = handles.apply_transformation(|p| transform.transform_point2(p));
|
||||||
@@ -460,6 +477,13 @@ impl RegionDomain {
|
|||||||
);
|
);
|
||||||
self.fill.extend(&other.fill);
|
self.fill.extend(&other.fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_ids(&mut self, id_map: &IdMap) {
|
||||||
|
self.ids.iter_mut().for_each(|id| *id = *id_map.region_map.get(id).unwrap_or(id));
|
||||||
|
self.segment_range
|
||||||
|
.iter_mut()
|
||||||
|
.for_each(|range| *range = *id_map.segment_map.get(range.start()).unwrap_or(range.start())..=*id_map.segment_map.get(range.end()).unwrap_or(range.end()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::VectorData {
|
impl super::VectorData {
|
||||||
@@ -590,6 +614,23 @@ impl super::VectorData {
|
|||||||
self.point_domain.transform(transform);
|
self.point_domain.transform(transform);
|
||||||
self.segment_domain.transform(transform);
|
self.segment_domain.transform(transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn vector_new_ids_from_hash(&mut self, node_id: u64) {
|
||||||
|
let point_map = self.point_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::<HashMap<_, _>>();
|
||||||
|
let segment_map = self.segment_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::<HashMap<_, _>>();
|
||||||
|
let region_map = self.region_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
|
let id_map = IdMap {
|
||||||
|
point_offset: self.point_domain.ids().len(),
|
||||||
|
point_map,
|
||||||
|
segment_map,
|
||||||
|
region_map,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.point_domain.map_ids(&id_map);
|
||||||
|
self.segment_domain.map_ids(&id_map);
|
||||||
|
self.region_domain.map_ids(&id_map);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue};
|
|||||||
use crate::renderer::GraphicElementRendered;
|
use crate::renderer::GraphicElementRendered;
|
||||||
use crate::transform::{Footprint, Transform, TransformMut};
|
use crate::transform::{Footprint, Transform, TransformMut};
|
||||||
use crate::vector::style::LineJoin;
|
use crate::vector::style::LineJoin;
|
||||||
use crate::{Color, GraphicGroup};
|
use crate::{Color, GraphicElement, GraphicGroup};
|
||||||
|
|
||||||
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
|
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
@@ -164,94 +164,187 @@ async fn stroke<F: 'n + Send, T: Into<Option<Color>> + 'n + Send>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn repeat<F: 'n + Send>(
|
async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
(),
|
||||||
|
(),
|
||||||
|
Footprint,
|
||||||
Footprint,
|
Footprint,
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
footprint: F,
|
||||||
|
// TODO: Implement other GraphicElementRendered types.
|
||||||
#[implementations(
|
#[implementations(
|
||||||
() -> VectorData,
|
() -> VectorData,
|
||||||
|
() -> GraphicGroup,
|
||||||
Footprint -> VectorData,
|
Footprint -> VectorData,
|
||||||
|
Footprint -> GraphicGroup,
|
||||||
)]
|
)]
|
||||||
instance: impl Node<F, Output = VectorData>,
|
instance: impl Node<F, Output = I>,
|
||||||
#[default(100., 100.)]
|
#[default(100., 100.)]
|
||||||
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
|
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
|
||||||
direction: DVec2,
|
direction: DVec2,
|
||||||
angle: Angle,
|
angle: Angle,
|
||||||
#[default(4)] instances: IntegerCount,
|
#[default(4)] instances: IntegerCount,
|
||||||
) -> VectorData {
|
) -> GraphicGroup {
|
||||||
let instance = instance.eval(footprint).await;
|
let instance = instance.eval(footprint).await;
|
||||||
|
let first_vector_transform = instance.transform();
|
||||||
|
|
||||||
let angle = angle.to_radians();
|
let angle = angle.to_radians();
|
||||||
let instances = instances.max(1);
|
let instances = instances.max(1);
|
||||||
let total = (instances - 1) as f64;
|
let total = (instances - 1) as f64;
|
||||||
|
|
||||||
if instances == 1 {
|
let mut result = GraphicGroup::EMPTY;
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repeat the vector data
|
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else {
|
||||||
let mut result = VectorData::empty();
|
return result;
|
||||||
|
|
||||||
let Some(bounding_box) = instance.bounding_box_with_transform(instance.transform) else {
|
|
||||||
return instance;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let center = (bounding_box[0] + bounding_box[1]) / 2.;
|
let center = (bounding_box[0] + bounding_box[1]) / 2.;
|
||||||
|
|
||||||
for i in 0..instances {
|
for i in 0..instances {
|
||||||
let translation = i as f64 * direction / total;
|
let translation = i as f64 * direction / total;
|
||||||
let angle = i as f64 * angle / total;
|
let angle = i as f64 * angle / total;
|
||||||
|
let mut new_instance = result.last().map(|(element, _)| element.clone()).unwrap_or(instance.to_graphic_element());
|
||||||
|
new_instance.new_ids_from_hash(None);
|
||||||
|
let modification = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
|
||||||
|
|
||||||
let transform = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
|
let data_transform = new_instance.transform_mut();
|
||||||
|
*data_transform = modification * first_vector_transform;
|
||||||
result.concat(&instance, transform);
|
result.push((new_instance, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn circular_repeat<F: 'n + Send>(
|
async fn circular_repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform + TransformMut + Send>(
|
||||||
#[implementations(
|
#[implementations(
|
||||||
|
(),
|
||||||
|
(),
|
||||||
|
Footprint,
|
||||||
|
Footprint,
|
||||||
|
)]
|
||||||
|
footprint: F,
|
||||||
|
// TODO: Implement other GraphicElementRendered types.
|
||||||
|
#[implementations(
|
||||||
|
() -> VectorData,
|
||||||
|
() -> GraphicGroup,
|
||||||
|
Footprint -> VectorData,
|
||||||
|
Footprint -> GraphicGroup,
|
||||||
|
)]
|
||||||
|
instance: impl Node<F, Output = I>,
|
||||||
|
angle_offset: Angle,
|
||||||
|
#[default(5)] radius: Length,
|
||||||
|
#[default(5)] instances: IntegerCount,
|
||||||
|
) -> GraphicGroup {
|
||||||
|
let instance = instance.eval(footprint).await;
|
||||||
|
let first_vector_transform = instance.transform();
|
||||||
|
let instances = instances.max(1);
|
||||||
|
|
||||||
|
let mut result = GraphicGroup::EMPTY;
|
||||||
|
|
||||||
|
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else {
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
let center = (bounding_box[0] + bounding_box[1]) / 2.;
|
||||||
|
let base_transform = DVec2::new(0., radius) - center;
|
||||||
|
|
||||||
|
for i in 0..instances {
|
||||||
|
let angle = (std::f64::consts::TAU / instances as f64) * i as f64 + angle_offset.to_radians();
|
||||||
|
let rotation = DAffine2::from_angle(angle);
|
||||||
|
let modification = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
|
||||||
|
let mut new_instance = result.last().map(|(element, _)| element.clone()).unwrap_or(instance.to_graphic_element());
|
||||||
|
new_instance.new_ids_from_hash(None);
|
||||||
|
|
||||||
|
let data_transform = new_instance.transform_mut();
|
||||||
|
*data_transform = modification * first_vector_transform;
|
||||||
|
result.push((new_instance, None));
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
|
async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatElement + TransformMut + Send + 'n>(
|
||||||
|
#[implementations(
|
||||||
|
(),
|
||||||
(),
|
(),
|
||||||
Footprint,
|
Footprint,
|
||||||
)]
|
)]
|
||||||
footprint: F,
|
footprint: F,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
|
() -> VectorData,
|
||||||
() -> VectorData,
|
() -> VectorData,
|
||||||
Footprint -> VectorData,
|
Footprint -> VectorData,
|
||||||
)]
|
)]
|
||||||
instance: impl Node<F, Output = VectorData>,
|
points: impl Node<F, Output = VectorData>,
|
||||||
angle_offset: Angle,
|
#[expose]
|
||||||
#[default(5)] radius: Length,
|
#[implementations(
|
||||||
#[default(5)] instances: IntegerCount,
|
() -> VectorData,
|
||||||
) -> VectorData {
|
() -> GraphicGroup,
|
||||||
|
Footprint -> VectorData,
|
||||||
|
Footprint -> GraphicGroup,
|
||||||
|
)]
|
||||||
|
instance: impl Node<F, Output = I>,
|
||||||
|
#[default(1)] random_scale_min: f64,
|
||||||
|
#[default(1)] random_scale_max: f64,
|
||||||
|
random_scale_bias: f64,
|
||||||
|
random_scale_seed: SeedValue,
|
||||||
|
random_rotation: Angle,
|
||||||
|
random_rotation_seed: SeedValue,
|
||||||
|
) -> GraphicGroup {
|
||||||
|
let points = points.eval(footprint).await;
|
||||||
let instance = instance.eval(footprint).await;
|
let instance = instance.eval(footprint).await;
|
||||||
let instances = instances.max(1);
|
let instance_transform = instance.transform();
|
||||||
|
|
||||||
if instances == 1 {
|
let random_scale_difference = random_scale_max - random_scale_min;
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut result = VectorData::empty();
|
let points_list = points.point_domain.positions();
|
||||||
|
|
||||||
let Some(bounding_box) = instance.bounding_box_with_transform(instance.transform) else {
|
let instance_bounding_box = instance.bounding_box(DAffine2::IDENTITY).unwrap_or_default();
|
||||||
return instance;
|
let instance_center = -0.5 * (instance_bounding_box[0] + instance_bounding_box[1]);
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
let do_scale = random_scale_difference.abs() > 1e-6;
|
||||||
|
let do_rotation = random_rotation.abs() > 1e-6;
|
||||||
|
|
||||||
|
let mut result = GraphicGroup::default();
|
||||||
|
for &point in points_list {
|
||||||
|
let center_transform = DAffine2::from_translation(instance_center);
|
||||||
|
|
||||||
|
let translation = points.transform.transform_point2(point);
|
||||||
|
|
||||||
|
let rotation = if do_rotation {
|
||||||
|
let degrees = (rotation_rng.gen::<f64>() - 0.5) * random_rotation;
|
||||||
|
degrees / 360. * std::f64::consts::TAU
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
};
|
};
|
||||||
let center = (bounding_box[0] + bounding_box[1]) / 2.;
|
|
||||||
|
|
||||||
let base_transform = DVec2::new(0., radius) - center;
|
let scale = if do_scale {
|
||||||
|
if random_scale_bias.abs() < 1e-6 {
|
||||||
for i in 0..instances {
|
// Linear
|
||||||
let angle = (std::f64::consts::TAU / instances as f64) * i as f64 + angle_offset.to_radians();
|
random_scale_min + scale_rng.gen::<f64>() * random_scale_difference
|
||||||
let rotation = DAffine2::from_angle(angle);
|
} else {
|
||||||
let transform = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
|
// Weighted (see <https://www.desmos.com/calculator/gmavd3m9bd>)
|
||||||
result.concat(&instance, transform);
|
let horizontal_scale_factor = 1. - 2_f64.powf(random_scale_bias);
|
||||||
|
let scale_factor = (1. - scale_rng.gen::<f64>() * horizontal_scale_factor).log2() / random_scale_bias;
|
||||||
|
random_scale_min + scale_factor * random_scale_difference
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
random_scale_min
|
||||||
|
};
|
||||||
|
|
||||||
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
let mut new_instance = result.last().map(|(element, _)| element.clone()).unwrap_or(instance.to_graphic_element());
|
||||||
|
new_instance.new_ids_from_hash(None);
|
||||||
|
|
||||||
|
let data_transform = new_instance.transform_mut();
|
||||||
|
*data_transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform * instance_transform;
|
||||||
|
result.push((new_instance, None));
|
||||||
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@@ -381,6 +474,42 @@ async fn solidify_stroke<F: 'n + Send>(
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
|
async fn flatten_vector_elements<F: 'n + Send>(
|
||||||
|
#[implementations(
|
||||||
|
(),
|
||||||
|
Footprint,
|
||||||
|
)]
|
||||||
|
footprint: F,
|
||||||
|
#[implementations(
|
||||||
|
() -> GraphicGroup,
|
||||||
|
Footprint -> GraphicGroup,
|
||||||
|
)]
|
||||||
|
graphic_group_input: impl Node<F, Output = GraphicGroup>,
|
||||||
|
) -> VectorData {
|
||||||
|
let graphic_group = graphic_group_input.eval(footprint).await;
|
||||||
|
|
||||||
|
fn concat_group(graphic_group: &GraphicGroup, current_transform: DAffine2, result: &mut VectorData) {
|
||||||
|
for (element, _) in graphic_group.iter() {
|
||||||
|
match element {
|
||||||
|
GraphicElement::VectorData(vector_data) => {
|
||||||
|
result.concat(vector_data, current_transform);
|
||||||
|
}
|
||||||
|
GraphicElement::GraphicGroup(graphic_group) => {
|
||||||
|
concat_group(graphic_group, current_transform * graphic_group.transform, result);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = VectorData::empty();
|
||||||
|
concat_group(&graphic_group, DAffine2::IDENTITY, &mut result);
|
||||||
|
// TODO: This leads to incorrect stroke widths when flattening groups with different transforms.
|
||||||
|
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ConcatElement {
|
pub trait ConcatElement {
|
||||||
fn concat(&mut self, other: &Self, transform: DAffine2);
|
fn concat(&mut self, other: &Self, transform: DAffine2);
|
||||||
}
|
}
|
||||||
@@ -396,84 +525,6 @@ impl ConcatElement for GraphicGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
|
||||||
async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + Default + ConcatElement + TransformMut + Send>(
|
|
||||||
#[implementations(
|
|
||||||
(),
|
|
||||||
(),
|
|
||||||
Footprint,
|
|
||||||
)]
|
|
||||||
footprint: F,
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorData,
|
|
||||||
() -> VectorData,
|
|
||||||
Footprint -> VectorData,
|
|
||||||
)]
|
|
||||||
points: impl Node<F, Output = VectorData>,
|
|
||||||
#[expose]
|
|
||||||
#[implementations(
|
|
||||||
() -> VectorData,
|
|
||||||
() -> GraphicGroup,
|
|
||||||
Footprint -> VectorData,
|
|
||||||
Footprint -> GraphicGroup,
|
|
||||||
)]
|
|
||||||
instance: impl Node<F, Output = I>,
|
|
||||||
#[default(1)] random_scale_min: f64,
|
|
||||||
#[default(1)] random_scale_max: f64,
|
|
||||||
random_scale_bias: f64,
|
|
||||||
random_scale_seed: SeedValue,
|
|
||||||
random_rotation: Angle,
|
|
||||||
random_rotation_seed: SeedValue,
|
|
||||||
) -> I {
|
|
||||||
let points = points.eval(footprint).await;
|
|
||||||
let instance = instance.eval(footprint).await;
|
|
||||||
|
|
||||||
let random_scale_difference = random_scale_max - random_scale_min;
|
|
||||||
|
|
||||||
let points_list = points.point_domain.positions();
|
|
||||||
|
|
||||||
let instance_bounding_box = instance.bounding_box(DAffine2::IDENTITY).unwrap_or_default();
|
|
||||||
let instance_center = -0.5 * (instance_bounding_box[0] + instance_bounding_box[1]);
|
|
||||||
|
|
||||||
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());
|
|
||||||
|
|
||||||
let do_scale = random_scale_difference.abs() > 1e-6;
|
|
||||||
let do_rotation = random_rotation.abs() > 1e-6;
|
|
||||||
|
|
||||||
let mut result = I::default();
|
|
||||||
for &point in points_list {
|
|
||||||
let center_transform = DAffine2::from_translation(instance_center);
|
|
||||||
|
|
||||||
let translation = points.transform.transform_point2(point);
|
|
||||||
|
|
||||||
let rotation = if do_rotation {
|
|
||||||
let degrees = (rotation_rng.gen::<f64>() - 0.5) * random_rotation;
|
|
||||||
degrees / 360. * std::f64::consts::TAU
|
|
||||||
} else {
|
|
||||||
0.
|
|
||||||
};
|
|
||||||
|
|
||||||
let scale = if do_scale {
|
|
||||||
if random_scale_bias.abs() < 1e-6 {
|
|
||||||
// Linear
|
|
||||||
random_scale_min + scale_rng.gen::<f64>() * random_scale_difference
|
|
||||||
} else {
|
|
||||||
// Weighted (see <https://www.desmos.com/calculator/gmavd3m9bd>)
|
|
||||||
let horizontal_scale_factor = 1. - 2_f64.powf(random_scale_bias);
|
|
||||||
let scale_factor = (1. - scale_rng.gen::<f64>() * horizontal_scale_factor).log2() / random_scale_bias;
|
|
||||||
random_scale_min + scale_factor * random_scale_difference
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
random_scale_min
|
|
||||||
};
|
|
||||||
|
|
||||||
result.concat(&instance, DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn sample_points<F: 'n + Send + Copy>(
|
async fn sample_points<F: 'n + Send + Copy>(
|
||||||
#[implementations(
|
#[implementations(
|
||||||
@@ -622,7 +673,7 @@ async fn subpath_segment_lengths<F: 'n + Send>(
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(name("Splines from Points"), category(""), path(graphene_core::vector))]
|
#[node_macro::node(name("Splines from Points"), category("Vector"), path(graphene_core::vector))]
|
||||||
fn splines_from_points(_: (), mut vector_data: VectorData) -> VectorData {
|
fn splines_from_points(_: (), mut vector_data: VectorData) -> VectorData {
|
||||||
let points = &vector_data.point_domain;
|
let points = &vector_data.point_domain;
|
||||||
|
|
||||||
@@ -838,8 +889,9 @@ mod test {
|
|||||||
let direction = DVec2::X * 1.5;
|
let direction = DVec2::X * 1.5;
|
||||||
let instances = 3;
|
let instances = 3;
|
||||||
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||||
assert_eq!(repeated.region_bezier_paths().count(), 3);
|
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
|
||||||
for (index, (_, subpath)) in repeated.region_bezier_paths().enumerate() {
|
assert_eq!(vector_data.region_bezier_paths().count(), 3);
|
||||||
|
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||||
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
|
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -848,16 +900,18 @@ mod test {
|
|||||||
let direction = DVec2::new(12., 10.);
|
let direction = DVec2::new(12., 10.);
|
||||||
let instances = 8;
|
let instances = 8;
|
||||||
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||||
assert_eq!(repeated.region_bezier_paths().count(), 8);
|
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
|
||||||
for (index, (_, subpath)) in repeated.region_bezier_paths().enumerate() {
|
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
||||||
|
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||||
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
|
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn circle_repeat() {
|
async fn circle_repeat() {
|
||||||
let repeated = super::circular_repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
|
let repeated = super::circular_repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
|
||||||
assert_eq!(repeated.region_bezier_paths().count(), 8);
|
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
|
||||||
for (index, (_, subpath)) in repeated.region_bezier_paths().enumerate() {
|
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
||||||
|
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||||
let expected_angle = (index as f64 + 1.) * 45.;
|
let expected_angle = (index as f64 + 1.) * 45.;
|
||||||
let center = (subpath.manipulator_groups()[0].anchor + subpath.manipulator_groups()[2].anchor) / 2.;
|
let center = (subpath.manipulator_groups()[0].anchor + subpath.manipulator_groups()[2].anchor) / 2.;
|
||||||
let actual_angle = DVec2::Y.angle_to(center).to_degrees();
|
let actual_angle = DVec2::Y.angle_to(center).to_degrees();
|
||||||
@@ -893,9 +947,10 @@ mod test {
|
|||||||
let points = Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.);
|
let points = Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.);
|
||||||
let instance = Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE);
|
let instance = Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE);
|
||||||
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
|
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
|
||||||
let bounding_box = super::copy_to_points(Footprint::default(), &vector_node(points), &vector_node(instance), 1., 1., 0., 0, 0., 0).await;
|
let copy_to_points = super::copy_to_points(Footprint::default(), &vector_node(points), &vector_node(instance), 1., 1., 0., 0, 0., 0).await;
|
||||||
assert_eq!(bounding_box.region_bezier_paths().count(), expected_points.len());
|
let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(copy_to_points)).await;
|
||||||
for (index, (_, subpath)) in bounding_box.region_bezier_paths().enumerate() {
|
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
|
||||||
|
for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() {
|
||||||
let offset = expected_points[index];
|
let offset = expected_points[index];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&subpath.anchors()[..4],
|
&subpath.anchors()[..4],
|
||||||
|
|||||||
Reference in New Issue
Block a user