Improve older document upgrading compatibility and make node type errors clearer (#2201)

* Improve older document upgrading compatibility and make node type errors clearer

Misc.

* Fixes

* Avoid unwrap
This commit is contained in:
Keavon Chambers 2025-01-21 01:40:43 -08:00 committed by GitHub
parent eec0ef761c
commit 8505ed3f10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 154 additions and 74 deletions

View file

@ -6,7 +6,7 @@
},
"ghcr.io/devcontainers/features/node:1": {}
},
"onCreateCommand": "cargo install wasm-pack cargo-watch cargo-about",
"onCreateCommand": "cargo install cargo-watch wasm-pack cargo-about && cargo install -f wasm-bindgen-cli@0.2.99",
"customizations": {
"vscode": {
// NOTE: Keep this in sync with `.vscode/extensions.json`
@ -14,7 +14,6 @@
// Rust
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml",
"serayuzgur.crates",
// Web
"dbaeumer.vscode-eslint",
"svelte.svelte-vscode",

View file

@ -10,7 +10,8 @@ jobs:
name: Run Clippy
runs-on: ubuntu-latest
# TODO(Keavon): Find a workaround (passing the output text to a separate action with permission to read the secrets?) that allows this to work on fork PRs
if: ${{ !github.event.pull_request.draft && !github.event.pull_request.head.repo.fork }}
if: false
# if: ${{ !github.event.pull_request.draft && !github.event.pull_request.head.repo.fork }}
permissions:
contents: read
pull-requests: write

View file

@ -41,7 +41,7 @@ pub struct NodePropertiesContext<'a> {
impl NodePropertiesContext<'_> {
pub fn call_widget_override(&mut self, node_id: &NodeId, index: usize) -> Option<Vec<LayoutGroup>> {
let Some(input_properties_row) = self.network_interface.input_properties_row(node_id, index, self.selection_network_path) else {
log::error!("Could not get input properties row in call_widget_override");
log::error!("Could not get input properties row at the beginning of call_widget_override");
return None;
};
if let Some(widget_override) = &input_properties_row.widget_override {
@ -2466,7 +2466,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(concrete!(graphene_core::vector::VectorData), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SubpathSegmentLengthsNode")),
manual_composition: Some(generic!(T)),
..Default::default()
},
@ -2479,7 +2479,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(bool), 4), // From the document node's parameters
NodeInput::node(NodeId(0), 0), // From output 0 of SubpathSegmentLengthsNode
],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_nodes::SamplePointsNode")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SamplePointsNode")),
manual_composition: Some(generic!(T)),
..Default::default()
},

View file

@ -240,8 +240,7 @@ pub(crate) fn property_from_type(node_id: NodeId, index: usize, ty: &Type, conte
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextLabel::new("-")
.tooltip(format!(
"This data can only be supplied through the\n\
node graph because no widget exists for its type:\n\
"This data can only be supplied through the node graph because no widget exists for its type:\n\
{}",
concrete_type.name
))

View file

@ -14,7 +14,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
use graphene_core::text::{Font, TypesettingConfig};
use graphene_std::vector::style::{Fill, FillType, Gradient};
use interpreted_executor::dynamic_executor::IntrospectError;
@ -383,11 +383,25 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
document_is_saved,
document_serialized_content,
} => {
// TODO: Eventually remove this document upgrade code
// This big code block contains lots of hacky code for upgrading old documents to the new format
// It can be helpful to temporarily set `upgrade_from_before_editable_subgraphs` to true if it's desired to upgrade a piece of artwork to use fresh copies of all nodes
let replace_implementations_from_definition = document_serialized_content.contains("node_output_index");
// Upgrade layer implementation from https://github.com/GraphiteEditor/Graphite/pull/1946 (see also `fn fix_nodes()` in `main.rs` of Graphene CLI)
let upgrade_from_before_returning_nested_click_targets =
document_serialized_content.contains("graphene_core::ConstructLayerNode") || document_serialized_content.contains("graphene_core::AddArtboardNode");
let upgrade_vector_manipulation_format = document_serialized_content.contains("ManipulatorGroupIds") && !document_name.contains("__DO_NOT_UPGRADE__");
let document_name = document_name.replace("__DO_NOT_UPGRADE__", "");
const TEXT_REPLACEMENTS: [(&str, &str); 2] = [
("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePointsNode"),
("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"),
];
let document_serialized_content = TEXT_REPLACEMENTS
.iter()
.fold(document_serialized_content, |document_serialized_content, (old, new)| document_serialized_content.replace(old, new));
let document = DocumentMessageHandler::deserialize_document(&document_serialized_content).map(|mut document| {
document.name.clone_from(&document_name);
document
@ -407,9 +421,72 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}
};
// TODO: Eventually remove this document upgrade code
const REPLACEMENTS: [(&str, &str); 36] = [
("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"),
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
("graphene_core::ToGraphicGroupNode", "graphene_core::graphic_element::ToGroupNode"),
("graphene_core::logic::LogicAndNode", "graphene_core::ops::LogicAndNode"),
("graphene_core::logic::LogicNotNode", "graphene_core::ops::LogicNotNode"),
("graphene_core::logic::LogicOrNode", "graphene_core::ops::LogicOrNode"),
("graphene_core::ops::ConstructVector2", "graphene_core::ops::Vector2ValueNode"),
("graphene_core::raster::BlackAndWhiteNode", "graphene_core::raster::adjustments::BlackAndWhiteNode"),
("graphene_core::raster::BlendNode", "graphene_core::raster::adjustments::BlendNode"),
("graphene_core::raster::ChannelMixerNode", "graphene_core::raster::adjustments::ChannelMixerNode"),
("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_core::raster::adjustments::ColorOverlayNode"),
("graphene_core::raster::ExposureNode", "graphene_core::raster::adjustments::ExposureNode"),
("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"),
("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"),
("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"),
("graphene_core::raster::IndexNode", "graphene_core::raster::adjustments::IndexNode"),
("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"),
("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"),
("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"),
("graphene_core::raster::LuminanceNode", "graphene_core::raster::adjustments::LuminanceNode"),
("graphene_core::raster::ExtractOpaqueNode", "graphene_core::raster::adjustments::MakeOpaqueNode"),
("graphene_core::raster::PosterizeNode", "graphene_core::raster::adjustments::PosterizeNode"),
("graphene_core::raster::ThresholdNode", "graphene_core::raster::adjustments::ThresholdNode"),
("graphene_core::raster::VibranceNode", "graphene_core::raster::adjustments::VibranceNode"),
("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"),
("graphene_core::transform::SetTransformNode", "graphene_core::transform::ReplaceTransformNode"),
("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"),
("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"),
("graphene_core::vector::generator_nodes::PathGenerator", "graphene_core::vector::generator_nodes::PathNode"),
("graphene_core::vector::generator_nodes::RectangleGenerator", "graphene_core::vector::generator_nodes::RectangleNode"),
(
"graphene_core::vector::generator_nodes::RegularPolygonGenerator",
"graphene_core::vector::generator_nodes::RegularPolygonNode",
),
("graphene_core::vector::generator_nodes::SplineGenerator", "graphene_core::vector::generator_nodes::SplineNode"),
("graphene_core::vector::generator_nodes::StarGenerator", "graphene_core::vector::generator_nodes::StarNode"),
("graphene_std::executor::BlendGpuImageNode", "graphene_std::gpu_nodes::BlendGpuImageNode"),
("graphene_std::raster::SampleNode", "graphene_std::raster::SampleImageNode"),
];
for node_id in &document
.network_interface
.network_metadata(&[])
.unwrap()
.persistent_metadata
.node_metadata
.keys()
.cloned()
.collect::<Vec<NodeId>>()
{
if let Some(DocumentNodeImplementation::ProtoNode(protonode_id)) = document.network_interface.network(&[]).unwrap().nodes.get(node_id).map(|node| node.implementation.clone()) {
for (old, new) in REPLACEMENTS {
let node_path_without_type_args = protonode_id.name.split('<').next();
if node_path_without_type_args == Some(old) {
document
.network_interface
.replace_implementation(node_id, &[], DocumentNodeImplementation::ProtoNode(new.to_string().into()));
document.network_interface.set_manual_compostion(node_id, &[], Some(graph_craft::Type::Generic("T".into())));
}
}
}
}
// Upgrade all old nodes to support editable subgraphs introduced in #1750
if replace_implementations_from_definition {
if replace_implementations_from_definition || upgrade_from_before_returning_nested_click_targets {
// This can be used, if uncommented, to upgrade demo artwork with outdated document node internals from their definitions. Delete when it's no longer needed.
// Used for upgrading old internal networks for demo artwork nodes. Will reset all node internals for any opened file
for node_id in &document
@ -431,12 +508,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
.get(node_id)
.and_then(|node| node.persistent_metadata.reference.as_ref())
{
let node_definition = resolve_document_node_type(reference).unwrap();
let Some(node_definition) = resolve_document_node_type(reference) else { continue };
let default_definition_node = node_definition.default_node_template();
document.network_interface.replace_implementation(node_id, &[], default_definition_node.document_node.implementation);
document
.network_interface
.replace_implementation_metadata(node_id, &[], default_definition_node.persistent_node_metadata);
document.network_interface.set_manual_compostion(node_id, &[], default_definition_node.document_node.manual_composition);
}
}
}
@ -460,8 +538,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
continue;
};
// Upgrade Fill nodes to the format change in #1778
// TODO: Eventually remove this document upgrade code
let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else {
continue;
};
@ -472,6 +548,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
};
let inputs_count = node.inputs.len();
// Upgrade Fill nodes to the format change in #1778
if reference == "Fill" && inputs_count == 8 {
let node_definition = resolve_document_node_type(reference).unwrap();
let document_node = node_definition.default_node_template().document_node;
@ -600,15 +677,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), &[]);
}
// Upgrade layer implementation from https://github.com/GraphiteEditor/Graphite/pull/1946
if reference == "Merge" || reference == "Artboard" {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
let new_merge_node = node_definition.default_node_template();
document.network_interface.replace_implementation(node_id, &[], new_merge_node.document_node.implementation)
}
// Upgrade artboard name being passed as hidden value input to "To Artboard"
if reference == "Artboard" {
if reference == "Artboard" && upgrade_from_before_returning_nested_click_targets {
let label = document.network_interface.frontend_display_name(node_id, &[]);
document
.network_interface

View file

@ -327,7 +327,7 @@
}
function dataTypeTooltip(value: FrontendGraphInput | FrontendGraphOutput): string {
return value.resolvedType ? `Resolved Data: ${value.resolvedType}` : `Unresolved Data: ${value.dataType}`;
return value.resolvedType ? `Resolved Data:\n${value.resolvedType}` : `Unresolved Data ${value.dataType}`;
}
function validTypesText(value: FrontendGraphInput): string {
@ -502,7 +502,7 @@
style:--offset-top={position.y / 24}
bind:this={outputs[0][index]}
>
<title>{`${dataTypeTooltip(outputMetadata)}\n${outputConnectedToText(outputMetadata)}`}</title>
<title>{`${dataTypeTooltip(outputMetadata)}\n\n${outputConnectedToText(outputMetadata)}`}</title>
{#if outputMetadata.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@ -576,7 +576,7 @@
style:--offset-top={position.y / 24}
bind:this={inputs[0][index]}
>
<title>{`${dataTypeTooltip(inputMetadata)}\n${inputConnectedToText(inputMetadata)}`}</title>
<title>{`${dataTypeTooltip(inputMetadata)}\n\n${inputConnectedToText(inputMetadata)}`}</title>
{#if inputMetadata.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@ -670,8 +670,8 @@
bind:this={nodeElements[nodeIndex]}
>
{#if node.errors}
<span class="node-error faded" transition:fade={FADE_TRANSITION} data-node-error>{node.errors}</span>
<span class="node-error hover" transition:fade={FADE_TRANSITION} data-node-error>{node.errors}</span>
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
<span class="node-error hover" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
{/if}
<div class="thumbnail">
{#if $nodeGraph.thumbnails.has(node.id)}
@ -689,7 +689,7 @@
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex + 1][0]}
>
<title>{`${dataTypeTooltip(node.primaryOutput)}\n${outputConnectedToText(node.primaryOutput)}`}</title>
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connectedTo.length > 0}
<path d="M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z" fill="var(--data-color)" />
{#if primaryOutputConnectedToLayer(node)}
@ -712,7 +712,7 @@
bind:this={inputs[nodeIndex + 1][0]}
>
{#if node.primaryInput}
<title>{`${dataTypeTooltip(node.primaryInput)}\n${validTypesText(node.primaryInput)}\n${inputConnectedToText(node.primaryInput)}`}</title>
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
{/if}
{#if node.primaryInput?.connectedTo !== undefined}
<path d="M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z" fill="var(--data-color)" />
@ -737,7 +737,7 @@
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex + 1][1]}
>
<title>{`${dataTypeTooltip(stackDataInput)}\n${validTypesText(stackDataInput)}\n${inputConnectedToText(stackDataInput)}`}</title>
<title>{`${dataTypeTooltip(stackDataInput)}\n\n${validTypesText(stackDataInput)}\n\n${inputConnectedToText(stackDataInput)}`}</title>
{#if stackDataInput.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@ -810,8 +810,8 @@
bind:this={nodeElements[nodeIndex]}
>
{#if node.errors}
<span class="node-error faded" transition:fade={FADE_TRANSITION} data-node-error>{node.errors}</span>
<span class="node-error hover" transition:fade={FADE_TRANSITION} data-node-error>{node.errors}</span>
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
<span class="node-error hover" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
{/if}
<!-- Primary row -->
<div class="primary" class:in-selected-network={$nodeGraph.inSelectedNetwork} class:no-secondary-section={exposedInputsOutputs.length === 0}>
@ -844,7 +844,7 @@
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex + 1][0]}
>
<title>{`${dataTypeTooltip(node.primaryInput)}\n${validTypesText(node.primaryInput)}\n${inputConnectedToText(node.primaryInput)}`}</title>
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
{#if node.primaryInput.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@ -864,7 +864,7 @@
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex + 1][index + (node.primaryInput ? 1 : 0)]}
>
<title>{`${dataTypeTooltip(secondary)}\n${validTypesText(secondary)}\n${inputConnectedToText(secondary)}`}</title>
<title>{`${dataTypeTooltip(secondary)}\n\n${validTypesText(secondary)}\n\n${inputConnectedToText(secondary)}`}</title>
{#if secondary.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@ -887,7 +887,7 @@
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex + 1][0]}
>
<title>{`${dataTypeTooltip(node.primaryOutput)}\n${outputConnectedToText(node.primaryOutput)}`}</title>
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@ -906,7 +906,7 @@
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex + 1][outputIndex + (node.primaryOutput ? 1 : 0)]}
>
<title>{`${dataTypeTooltip(secondary)}\n${outputConnectedToText(secondary)}`}</title>
<title>{`${dataTypeTooltip(secondary)}\n\n${outputConnectedToText(secondary)}`}</title>
{#if secondary.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}

View file

@ -20,7 +20,7 @@ pub mod types {
pub type PixelLength = f64;
/// Non negative
pub type Length = f64;
/// 0.- 1.
/// 0 to 1
pub type Fraction = f64;
pub type IntegerCount = u32;
/// Int input with randomization button

View file

@ -110,7 +110,7 @@ impl NodeIOTypes {
impl core::fmt::Debug for NodeIOTypes {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_fmt(format_args!(
"node({}) -> {}",
"node({}) {}",
[&self.call_argument].into_iter().chain(&self.inputs).map(|input| input.to_string()).collect::<Vec<_>>().join(", "),
self.return_value
))
@ -292,13 +292,13 @@ fn format_type(ty: &str) -> String {
impl core::fmt::Debug for Type {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Generic(arg0) => write!(f, "Generic({arg0})"),
Self::Generic(arg0) => write!(f, "Generic<{arg0}>"),
#[cfg(feature = "type_id_logging")]
Self::Concrete(arg0) => write!(f, "Concrete({}, {:?})", arg0.name, arg0.id),
Self::Concrete(arg0) => write!(f, "Concrete<{}, {:?}>", arg0.name, arg0.id),
#[cfg(not(feature = "type_id_logging"))]
Self::Concrete(arg0) => write!(f, "Concrete({})", format_type(&arg0.name)),
Self::Fn(arg0, arg1) => write!(f, "({arg0:?} -> {arg1:?})"),
Self::Future(arg0) => write!(f, "Future({arg0:?})"),
Self::Concrete(arg0) => write!(f, "Concrete<{}>", format_type(&arg0.name)),
Self::Fn(arg0, arg1) => write!(f, "{arg0:?} → {arg1:?}"),
Self::Future(arg0) => write!(f, "Future<{arg0:?}>"),
}
}
}
@ -308,7 +308,7 @@ impl std::fmt::Display for Type {
match self {
Type::Generic(name) => write!(f, "{name}"),
Type::Concrete(ty) => write!(f, "{}", format_type(&ty.name)),
Type::Fn(input, output) => write!(f, "({input} -> {output})"),
Type::Fn(input, output) => write!(f, "{input} → {output}"),
Type::Future(ty) => write!(f, "Future<{ty}>"),
}
}

View file

@ -588,7 +588,7 @@ impl ConcatElement for GraphicGroup {
}
}
#[node_macro::node(category(""))]
#[node_macro::node(category(""), path(graphene_core::vector))]
async fn sample_points<F: 'n + Send + Copy>(
#[implementations(
(),
@ -815,7 +815,7 @@ async fn poisson_disk_points<F: 'n + Send>(
result
}
#[node_macro::node(category(""))]
#[node_macro::node(category(""), path(graphene_core::vector))]
async fn subpath_segment_lengths<F: 'n + Send>(
#[implementations(
(),
@ -979,6 +979,8 @@ async fn morph<F: 'n + Send + Copy>(
let target = target.eval(footprint).await;
let mut result = VectorData::empty();
let time = time.clamp(0., 1.);
// Lerp styles
result.alpha_blending = if time < 0.5 { source.alpha_blending } else { target.alpha_blending };
result.style = source.style.lerp(&target.style, time);

View file

@ -324,7 +324,7 @@ impl ProtoNetwork {
}
}
// TODO: Remsove
// TODO: Remove
/// Create a hashmap with the list of nodes this proto network depends on/uses as inputs.
pub fn collect_inwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
@ -552,27 +552,19 @@ impl core::fmt::Debug for GraphErrorType {
GraphErrorType::NoImplementations => write!(f, "No implementations found"),
GraphErrorType::NoConstructor => write!(f, "No construct found for node"),
GraphErrorType::InvalidImplementations { inputs, error_inputs } => {
let ordinal = |x: usize| match x.to_string().as_str() {
x if x.ends_with('1') && !x.ends_with("11") => format!("{x}st"),
x if x.ends_with('2') && !x.ends_with("12") => format!("{x}nd"),
x if x.ends_with('3') && !x.ends_with("13") => format!("{x}rd"),
x => format!("{x}th"),
};
let format_index = |index: usize| if index == 0 { "primary".to_string() } else { format!("{} secondary", ordinal(index)) };
let format_error = |(index, (real, expected)): &(usize, (Type, Type))| format!("• The {} input expected {} but found {}", format_index(*index), expected, real);
let format_error = |(index, (_found, expected)): &(usize, (Type, Type))| format!("• Input {}: {expected}", index + 1);
let format_error_list = |errors: &Vec<(usize, (Type, Type))>| errors.iter().map(format_error).collect::<Vec<_>>().join("\n");
let errors = error_inputs.iter().map(format_error_list).collect::<Vec<_>>();
let mut errors = error_inputs.iter().map(format_error_list).collect::<Vec<_>>();
errors.sort();
write!(
f,
"Node graph type error! If this just appeared while editing the graph,\n\
consider using undo to go back and try another way to connect the nodes.\n\
"This node isn't compatible with the com-\n\
bination of types for the data it is given:\n\
{inputs}\n\
\n\
No node implementation exists for type:\n\
({inputs})\n\
\n\
Caused by{}:\n\
Each invalid input should be replaced by\n\
data with one of these supported types:\n\
{}",
if errors.len() > 1 { " one of" } else { "" },
errors.join("\n")
)
}
@ -679,7 +671,8 @@ impl TypingContext {
};
// Get the node input type from the proto node declaration
let input = match node.input {
// TODO: When removing automatic composition, rename this to just `call_argument`
let primary_input_or_call_argument = match node.input {
ProtoNodeInput::None => concrete!(()),
ProtoNodeInput::ManualComposition(ref ty) => ty.clone(),
ProtoNodeInput::Node(id) | ProtoNodeInput::NodeLambda(id) => {
@ -687,6 +680,7 @@ impl TypingContext {
input.return_value.clone()
}
};
let using_manual_composition = matches!(node.input, ProtoNodeInput::ManualComposition(_) | ProtoNodeInput::None);
let impls = self.lookup.get(&node.identifier).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NoImplementations)])?;
if let Some(index) = inputs.iter().position(|p| {
@ -724,7 +718,7 @@ impl TypingContext {
// List of all implementations that match the input types
let valid_output_types = impls
.keys()
.filter(|node_io| valid_subtype(&node_io.call_argument, &input) && inputs.iter().zip(node_io.inputs.iter()).all(|(p1, p2)| valid_subtype(p1, p2)))
.filter(|node_io| valid_subtype(&node_io.call_argument, &primary_input_or_call_argument) && inputs.iter().zip(node_io.inputs.iter()).all(|(p1, p2)| valid_subtype(p1, p2)))
.collect::<Vec<_>>();
// Attempt to substitute generic types with concrete types and save the list of results
@ -733,10 +727,10 @@ impl TypingContext {
.map(|node_io| {
collect_generics(node_io)
.iter()
.try_for_each(|generic| check_generic(node_io, &input, &inputs, generic).map(|_| ()))
.try_for_each(|generic| check_generic(node_io, &primary_input_or_call_argument, &inputs, generic).map(|_| ()))
.map(|_| {
if let Type::Generic(out) = &node_io.return_value {
((*node_io).clone(), check_generic(node_io, &input, &inputs, out).unwrap())
((*node_io).clone(), check_generic(node_io, &primary_input_or_call_argument, &inputs, out).unwrap())
} else {
((*node_io).clone(), node_io.return_value.clone())
}
@ -752,14 +746,18 @@ impl TypingContext {
let mut best_errors = usize::MAX;
let mut error_inputs = Vec::new();
for node_io in impls.keys() {
let current_errors = [&input]
let current_errors = [&primary_input_or_call_argument]
.into_iter()
.chain(&inputs)
.cloned()
.zip([&node_io.call_argument].into_iter().chain(&node_io.inputs).cloned())
.enumerate()
.filter(|(_, (p1, p2))| !valid_subtype(p1, p2))
.map(|(index, ty)| (node.original_location.inputs(index).min_by_key(|s| s.node.len()).map(|s| s.index).unwrap_or(index), ty))
.map(|(index, ty)| {
let i = node.original_location.inputs(index).min_by_key(|s| s.node.len()).map(|s| s.index).unwrap_or(index);
let i = if using_manual_composition { i } else { i + 1 };
(i, ty)
})
.collect::<Vec<_>>();
if current_errors.len() < best_errors {
best_errors = current_errors.len();
@ -769,7 +767,17 @@ impl TypingContext {
error_inputs.push(current_errors);
}
}
let inputs = [&input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
let inputs = [&primary_input_or_call_argument]
.into_iter()
.chain(&inputs)
.enumerate()
// TODO: Make the following line's if statement conditional on being a call argument or primary input
.filter_map(|(i, t)| {
let i = if using_manual_composition { i } else { i + 1 };
if i == 0 { None } else { Some(format!("• Input {i}: {t}")) }
})
.collect::<Vec<_>>()
.join("\n");
Err(vec![GraphError::new(node, GraphErrorType::InvalidImplementations { inputs, error_inputs })])
}
[(org_nio, _)] => {
@ -794,13 +802,13 @@ impl TypingContext {
return Ok(org_nio.clone());
}
}
let inputs = [&input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
let inputs = [&primary_input_or_call_argument].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
let valid = valid_output_types.into_iter().cloned().collect();
Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })])
}
_ => {
let inputs = [&input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
let inputs = [&primary_input_or_call_argument].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
let valid = valid_output_types.into_iter().cloned().collect();
Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })])
}

View file

@ -79,7 +79,7 @@ fn init_logging() {
}
// Migrations are done in the editor which is unfortunately not available here.
// TODO: remove this and share migrations between the edtior and the CLI.
// TODO: remove this and share migrations between the editor and the CLI.
fn fix_nodes(network: &mut NodeNetwork) {
for node in network.nodes.values_mut() {
match &mut node.implementation {

View file

@ -222,6 +222,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
),
),
// Filters
// TODO: Move these filters to the new node macro and put them in `graphene_core::raster::adjustments`, then add them to the document upgrade script which moves many of the adjustment nodes from `graphene_core::raster` to `graphene_core::raster::adjustments`
(
ProtoNodeIdentifier::new("graphene_core::raster::BrightnessContrastNode"),
|args| {

View file

@ -79,7 +79,7 @@ Click a membership level below to pay directly (no account needed). A small fee
**$25 / month**
- Your **personal name** (or handle) **on the Graphite website and GitHub readme**
- Option to be mailed a personal **thank-you card with Graphite stickers** (US addresses only)
- Option to be mailed a personal **thank-you card with Graphite stickers** (in the US only)
- *Plus the lower-tier rewards*
</a>