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

@@ -2,14 +2,13 @@ use crate::application_io::{ImageTexture, TextureFrameTable};
use crate::instances::Instances;
use crate::raster::image::{Image, ImageFrameTable};
use crate::raster::BlendMode;
use crate::transform::{Transform, TransformMut};
use crate::transform::TransformMut;
use crate::uuid::NodeId;
use crate::vector::{VectorData, VectorDataTable};
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
use dyn_any::DynAny;
use core::ops::{Deref, DerefMut};
use glam::{DAffine2, IVec2};
use std::hash::Hash;
@@ -52,83 +51,84 @@ pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
transform: DAffine2,
alpha_blending: AlphaBlending,
}
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GraphicGroup {
elements: Vec<(GraphicElement, Option<NodeId>)>,
}
pub type OldGraphicGroupTable = Instances<GraphicGroup>;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
GraphicGroup(GraphicGroup),
OldGraphicGroup(OldGraphicGroup),
GraphicGroupTable(GraphicGroupTable),
InstanceTable(serde_json::Value),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::GraphicGroup(graphic_group) => GraphicGroupTable::new(graphic_group),
EitherFormat::OldGraphicGroup(old) => {
let mut graphic_group_table = GraphicGroupTable::new(GraphicGroup { elements: old.elements });
*graphic_group_table.one_instance_mut().transform = old.transform;
*graphic_group_table.one_instance_mut().alpha_blending = old.alpha_blending;
let mut graphic_group_table = GraphicGroupTable::empty();
for (graphic_element, source_node_id) in old.elements {
let last = graphic_group_table.push(graphic_element);
*last.source_node_id = source_node_id;
*last.transform = old.transform;
*last.alpha_blending = old.alpha_blending;
}
graphic_group_table
}
EitherFormat::GraphicGroupTable(graphic_group_table) => graphic_group_table,
EitherFormat::InstanceTable(value) => {
// Try to deserialize as either table format
if let Ok(old_table) = serde_json::from_value::<OldGraphicGroupTable>(value.clone()) {
let mut graphic_group_table = GraphicGroupTable::empty();
for instance in old_table.instances() {
for (graphic_element, source_node_id) in &instance.instance.elements {
let new_row = graphic_group_table.push(graphic_element.clone());
*new_row.source_node_id = *source_node_id;
*new_row.transform = *instance.transform;
*new_row.alpha_blending = *instance.alpha_blending;
}
}
graphic_group_table
} else if let Ok(new_table) = serde_json::from_value::<GraphicGroupTable>(value) {
new_table
} else {
return Err(serde::de::Error::custom("Failed to deserialize GraphicGroupTable"));
}
}
})
}
pub type GraphicGroupTable = Instances<GraphicGroup>;
// TODO: Rename to GraphicElementTable
pub type GraphicGroupTable = Instances<GraphicElement>;
/// A list of [`GraphicElement`]s
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GraphicGroup {
elements: Vec<(GraphicElement, Option<NodeId>)>,
}
impl core::hash::Hash for GraphicGroup {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.elements.hash(state);
}
}
impl GraphicGroup {
pub fn new(elements: Vec<GraphicElement>) -> Self {
Self {
elements: elements.into_iter().map(|element| (element, None)).collect(),
}
}
}
impl From<GraphicGroup> for GraphicGroupTable {
fn from(graphic_group: GraphicGroup) -> Self {
Self::new(graphic_group)
}
}
impl From<VectorData> for GraphicGroupTable {
fn from(vector_data: VectorData) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::VectorData(VectorDataTable::new(vector_data))]))
Self::new(GraphicElement::VectorData(VectorDataTable::new(vector_data)))
}
}
impl From<VectorDataTable> for GraphicGroupTable {
fn from(vector_data: VectorDataTable) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::VectorData(vector_data)]))
Self::new(GraphicElement::VectorData(vector_data))
}
}
impl From<Image<Color>> for GraphicGroupTable {
fn from(image: Image<Color>) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image)))]))
Self::new(GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image))))
}
}
impl From<ImageFrameTable<Color>> for GraphicGroupTable {
fn from(image_frame: ImageFrameTable<Color>) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::ImageFrame(image_frame))]))
Self::new(GraphicElement::RasterFrame(RasterFrame::ImageFrame(image_frame)))
}
}
impl From<ImageTexture> for GraphicGroupTable {
fn from(image_texture: ImageTexture) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::TextureFrame(TextureFrameTable::new(image_texture)))]))
Self::new(GraphicElement::RasterFrame(RasterFrame::TextureFrame(TextureFrameTable::new(image_texture))))
}
}
impl From<TextureFrameTable> for GraphicGroupTable {
fn from(texture_frame: TextureFrameTable) -> Self {
Self::new(GraphicGroup::new(vec![GraphicElement::RasterFrame(RasterFrame::TextureFrame(texture_frame))]))
Self::new(GraphicElement::RasterFrame(RasterFrame::TextureFrame(texture_frame)))
}
}
@@ -278,8 +278,8 @@ pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
EitherFormat::ArtboardGroup(artboard_group) => {
let mut table = ArtboardGroupTable::empty();
for (artboard, source_node_id) in artboard_group.artboards {
table.push(artboard);
*table.instances_mut().last().unwrap().source_node_id = source_node_id;
let pushed = table.push(artboard);
*pushed.source_node_id = source_node_id;
}
table
}
@@ -290,23 +290,40 @@ pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
pub type ArtboardGroupTable = Instances<Artboard>;
#[node_macro::node(category(""))]
async fn layer(_: impl Ctx, stack: GraphicGroupTable, mut element: GraphicElement, node_path: Vec<NodeId>) -> GraphicGroupTable {
let mut stack = stack;
if stack.transform().matrix2.determinant() != 0. {
*element.transform_mut() = stack.transform().inverse() * element.transform();
} else {
stack.one_instance_mut().instance.clear();
*stack.transform_mut() = DAffine2::IDENTITY;
}
async fn layer(_: impl Ctx, mut stack: GraphicGroupTable, element: GraphicElement, node_path: Vec<NodeId>) -> GraphicGroupTable {
// Get the penultimate element of the node path, or None if the path is too short
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
stack.one_instance_mut().instance.push((element, encapsulating_node_id));
let pushed = stack.push(element);
*pushed.source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
stack
}
// TODO: Once we have nicely working spreadsheet tables, test this and make it nicely user-facing and move it from "Debug" to "General"
#[node_macro::node(category("Debug"))]
async fn concatenate<T: Clone>(
_: impl Ctx,
#[implementations(
GraphicGroupTable,
VectorDataTable,
ImageFrameTable<Color>,
TextureFrameTable,
)]
from: Instances<T>,
#[expose]
#[implementations(
GraphicGroupTable,
VectorDataTable,
ImageFrameTable<Color>,
TextureFrameTable,
)]
mut to: Instances<T>,
) -> Instances<T> {
for instance in from.instances() {
to.push_instance(instance);
}
to
}
#[node_macro::node(category("Debug"))]
async fn to_element<Data: Into<GraphicElement> + 'n>(
_: impl Ctx,
@@ -337,38 +354,39 @@ async fn to_group<Data: Into<GraphicGroupTable> + 'n>(
#[node_macro::node(category("General"))]
async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable {
fn flatten_group(result_group: &mut GraphicGroupTable, current_group_table: GraphicGroupTable, fully_flatten: bool) {
let mut collection_group = GraphicGroup::default();
let current_group_elements = current_group_table.one_instance().instance.elements.clone();
// TODO: Avoid mutable reference, instead return a new GraphicGroupTable?
fn flatten_group(output_group_table: &mut GraphicGroupTable, current_group_table: GraphicGroupTable, fully_flatten: bool, recursion_depth: usize) {
for current_instance in current_group_table.instances() {
let current_element = current_instance.instance.clone();
let reference = *current_instance.source_node_id;
for (element, reference) in current_group_elements {
if let GraphicElement::GraphicGroup(mut nested_group_table) = element {
*nested_group_table.transform_mut() = nested_group_table.transform() * current_group_table.transform();
let recurse = fully_flatten || recursion_depth == 0;
let mut sub_group_table = GraphicGroupTable::default();
if fully_flatten {
flatten_group(&mut sub_group_table, nested_group_table, fully_flatten);
} else {
let nested_group_table_transform = nested_group_table.transform();
for (collection_element, _) in &mut nested_group_table.one_instance_mut().instance.elements {
*collection_element.transform_mut() = nested_group_table_transform * collection_element.transform();
match current_element {
// If we're allowed to recurse, flatten any GraphicGroups we encounter
GraphicElement::GraphicGroup(mut current_element) if recurse => {
// Apply the parent group's transform to all child elements
for graphic_element in current_element.instances_mut() {
*graphic_element.transform = *current_instance.transform * *graphic_element.transform;
}
sub_group_table = nested_group_table;
}
collection_group.append(&mut sub_group_table.one_instance_mut().instance.elements);
} else {
collection_group.push((element, reference));
flatten_group(output_group_table, current_element, fully_flatten, recursion_depth + 1);
}
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
_ => {
let pushed = output_group_table.push(current_element);
*pushed.source_node_id = reference;
// Apply the parent group's transform to the leaf element
*pushed.transform = *current_instance.transform * *pushed.transform;
}
}
}
result_group.one_instance_mut().instance.append(&mut collection_group.elements);
}
let mut flat_group = GraphicGroupTable::default();
flatten_group(&mut flat_group, group, fully_flatten);
let mut output = GraphicGroupTable::default();
flatten_group(&mut output, group, fully_flatten, 0);
flat_group
output
}
#[node_macro::node(category(""))]
@@ -411,8 +429,8 @@ async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artb
// This is used to get the ID of the user-facing "Artboard" node (which encapsulates this internal "Append Artboard" node).
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
artboards.push(artboard);
*artboards.instances_mut().last().unwrap().source_node_id = encapsulating_node_id;
let pushed = artboards.push(artboard);
*pushed.source_node_id = encapsulating_node_id;
artboards
}
@@ -450,46 +468,8 @@ impl From<VectorDataTable> for GraphicElement {
GraphicElement::VectorData(vector_data)
}
}
// TODO: Remove this one
impl From<GraphicGroup> for GraphicElement {
fn from(graphic_group: GraphicGroup) -> Self {
GraphicElement::GraphicGroup(GraphicGroupTable::new(graphic_group))
}
}
impl From<GraphicGroupTable> for GraphicElement {
fn from(graphic_group: GraphicGroupTable) -> Self {
GraphicElement::GraphicGroup(graphic_group)
}
}
impl Deref for GraphicGroup {
type Target = Vec<(GraphicElement, Option<NodeId>)>;
fn deref(&self) -> &Self::Target {
&self.elements
}
}
impl DerefMut for GraphicGroup {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.elements
}
}
/// This is a helper trait used for the Into Implementation.
/// We can't just implement this for all for which from is implemented
/// as that would conflict with the implementation for `Self`
trait ToGraphicElement: Into<GraphicElement> {}
impl ToGraphicElement for VectorDataTable {}
impl ToGraphicElement for ImageFrameTable<Color> {}
impl ToGraphicElement for ImageTexture {}
impl<T> From<T> for GraphicGroup
where
T: ToGraphicElement,
{
fn from(value: T) -> Self {
Self {
elements: (vec![(value.into(), None)]),
}
}
}