Instance tables refactor part 5: unwrap GraphicGroup as multi-row Instance<GraphicElement> tables and move up transforms (#2363)

* Just group

* Partly working but without transforms

* Remove Transform/TransformMut from GraphicElement and GraphicGroupTable

* Fix layers and flattening

* Fix transform group handling on the remaining nodes

* Change collect metadata

* Add transform on vector data. TODO: Remove duplicate transform

* Small code tidying-up

* Add concatenate node?

* Remove ignore_modifications which is always false

* Improve transforms

* Mostly fix the nested transform cage angle (except leaf layers and skew)

* WIP attempt to integrate skew

* Fix nesting bounding box

* Avoid setting the transform

* Fix stroke transforms

* Renderer cleanup

* Fix tests for repeated elements not given unique point IDs

* Suppress cargo-deny warning

* Fix upgrade code for graphic group data

* Work around rendering issue in Isometric Fountain

---------

Co-authored-by: Adam <adamgerhant@gmail.com>
Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
Keavon Chambers
2025-03-12 01:38:36 -07:00
committed by GitHub
parent d2fc919ba6
commit a696aae044
28 changed files with 856 additions and 930 deletions

View File

@@ -13,44 +13,53 @@ use std::ops::Mul;
#[node_macro::node(category(""))]
async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable {
fn vector_from_image<T: Transform>(image_frame: T) -> VectorDataTable {
let corner1 = DVec2::ZERO;
let corner2 = DVec2::new(1., 1.);
fn flatten_vector_data(graphic_group_table: &GraphicGroupTable) -> Vec<VectorDataTable> {
graphic_group_table
.instances()
.map(|element| match element.instance.clone() {
GraphicElement::VectorData(mut vector_data) => {
// Apply the parent group's transform to each element of vector data
for sub_vector_data in vector_data.instances_mut() {
*sub_vector_data.transform = *element.transform * *sub_vector_data.transform;
}
let mut subpath = Subpath::new_rect(corner1, corner2);
subpath.apply_transform(image_frame.transform());
vector_data
}
GraphicElement::RasterFrame(mut image) => {
// Apply the parent group's transform to each element of raster data
match &mut image {
graphene_core::RasterFrame::ImageFrame(image) => {
for instance in image.instances_mut() {
*instance.transform = *element.transform * *instance.transform;
}
}
graphene_core::RasterFrame::TextureFrame(image) => {
for instance in image.instances_mut() {
*instance.transform = *element.transform * *instance.transform;
}
}
}
let mut vector_data = VectorData::from_subpath(subpath);
vector_data.style.set_fill(Fill::Solid(Color::from_rgb_str("777777").unwrap().to_gamma_srgb()));
// Convert the image frame into a rectangular subpath with the image's transform
let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(image.transform());
VectorDataTable::new(vector_data)
}
// Create a vector data table from the rectangular subpath, with a default black fill
let mut vector_data = VectorData::from_subpath(subpath);
vector_data.style.set_fill(Fill::Solid(Color::BLACK));
VectorDataTable::new(vector_data)
}
GraphicElement::GraphicGroup(mut graphic_group) => {
// Apply the parent group's transform to each element of inner group
for sub_element in graphic_group.instances_mut() {
*sub_element.transform = *element.transform * *sub_element.transform;
}
fn union_vector_data(graphic_element: &GraphicElement) -> VectorDataTable {
match graphic_element {
GraphicElement::VectorData(vector_data) => vector_data.clone(),
// Union all vector data in the graphic group into a single vector
GraphicElement::GraphicGroup(graphic_group) => {
let vector_data = collect_vector_data(graphic_group);
boolean_operation_on_vector_data(&vector_data, BooleanOperation::Union)
}
GraphicElement::RasterFrame(image) => vector_from_image(image),
}
}
fn collect_vector_data(graphic_group_table: &GraphicGroupTable) -> Vec<VectorDataTable> {
let graphic_group = graphic_group_table.one_instance();
// Ensure all non vector data in the graphic group is converted to vector data
let vector_data_tables = graphic_group.instance.iter().map(|(element, _)| union_vector_data(element));
// Apply the transform from the parent graphic group
let transformed_vector_data = vector_data_tables.map(|mut vector_data_table| {
*vector_data_table.transform_mut() = graphic_group.transform() * vector_data_table.transform();
vector_data_table
});
transformed_vector_data.collect::<Vec<_>>()
// Recursively flatten the inner group into vector data
boolean_operation_on_vector_data(&flatten_vector_data(&graphic_group), BooleanOperation::Union)
}
})
.collect()
}
fn subtract<'a>(vector_data: impl Iterator<Item = &'a VectorDataTable>) -> VectorDataTable {
@@ -162,12 +171,12 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
#[allow(unused_unsafe)]
let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
let mut boolean_intersection_result = VectorDataTable::new(from_path(&boolean_intersection_string));
*boolean_intersection_result.transform_mut() = all_other_vector_data_instance.transform();
*boolean_intersection_result.transform_mut() = *all_other_vector_data_instance.transform;
boolean_intersection_result.one_instance_mut().instance.style = all_other_vector_data_instance.instance.style.clone();
*boolean_intersection_result.one_instance_mut().alpha_blending = *all_other_vector_data_instance.alpha_blending;
let transform_of_lower_into_space_of_upper = boolean_intersection_result.one_instance_mut().transform().inverse() * any_intersection.transform();
let transform_of_lower_into_space_of_upper = boolean_intersection_result.one_instance_mut().transform.inverse() * any_intersection.transform();
let upper_path_string = to_path(boolean_intersection_result.one_instance_mut().instance, DAffine2::IDENTITY);
let lower_path_string = to_path(any_intersection.one_instance_mut().instance, transform_of_lower_into_space_of_upper);
@@ -190,7 +199,7 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
}
// The first index is the bottom of the stack
let mut result_vector_data_table = boolean_operation_on_vector_data(&collect_vector_data(&group_of_paths), operation);
let mut result_vector_data_table = boolean_operation_on_vector_data(&flatten_vector_data(&group_of_paths), operation);
// Replace the transformation matrix with a mutation of the vector points themselves
let result_vector_data_table_transform = result_vector_data_table.transform();

View File

@@ -5,10 +5,12 @@ pub use graph_craft::wasm_application_io::*;
use graphene_core::application_io::SurfaceHandle;
use graphene_core::application_io::{ApplicationIo, ExportFormat, RenderConfig};
#[cfg(target_arch = "wasm32")]
use graphene_core::instances::Instances;
#[cfg(target_arch = "wasm32")]
use graphene_core::raster::bbox::Bbox;
use graphene_core::raster::image::{Image, ImageFrameTable};
use graphene_core::renderer::RenderMetadata;
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, RenderParams, RenderSvgSegmentList, SvgRender};
use graphene_core::transform::Footprint;
#[cfg(target_arch = "wasm32")]
use graphene_core::transform::TransformMut;
@@ -149,19 +151,22 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen
RenderOutputType::CanvasFrame(frame)
}
#[node_macro::node(category(""))]
#[cfg(target_arch = "wasm32")]
async fn rasterize<T: GraphicElementRendered + graphene_core::transform::TransformMut + WasmNotSend + 'n>(
#[node_macro::node(category(""))]
async fn rasterize<T: WasmNotSend + 'n>(
_: impl Ctx,
#[implementations(
VectorDataTable,
ImageFrameTable<Color>,
GraphicGroupTable,
)]
mut data: T,
mut data: Instances<T>,
footprint: Footprint,
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
) -> ImageFrameTable<Color> {
) -> ImageFrameTable<Color>
where
Instances<T>: GraphicElementRendered,
{
if footprint.transform.matrix2.determinant() == 0. {
log::trace!("Invalid footprint received for rasterization");
return ImageFrameTable::empty();
@@ -176,7 +181,9 @@ async fn rasterize<T: GraphicElementRendered + graphene_core::transform::Transfo
..Default::default()
};
*data.transform_mut() = DAffine2::from_translation(-aabb.start) * data.transform();
for instance in data.instances_mut() {
*instance.transform = DAffine2::from_translation(-aabb.start) * *instance.transform;
}
data.render_svg(&mut render, &render_params);
render.format_svg(glam::DVec2::ZERO, size);
let svg_string = render.svg.to_svg_string();
@@ -232,7 +239,7 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
ctx.footprint();
let RenderConfig { hide_artboards, for_export, .. } = render_config;
let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export);
let render_params = RenderParams::new(render_config.view_mode, None, false, hide_artboards, for_export);
let data = data.eval(ctx.clone()).await;
let editor_api = editor_api.eval(ctx.clone()).await;
@@ -245,7 +252,8 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
let use_vello = use_vello && surface_handle.is_some();
let mut metadata = RenderMetadata {
footprints: HashMap::new(),
upstream_footprints: HashMap::new(),
local_transforms: HashMap::new(),
click_targets: HashMap::new(),
clip_targets: HashSet::new(),
};