diff --git a/app/src/lib/graph-interface/graph-manager.ts b/app/src/lib/graph-interface/graph-manager.ts index 92befcf..1764979 100644 --- a/app/src/lib/graph-interface/graph-manager.ts +++ b/app/src/lib/graph-interface/graph-manager.ts @@ -5,6 +5,7 @@ import EventEmitter from "./helpers/EventEmitter.js"; import throttle from "./helpers/throttle.js"; import { createLogger } from "./helpers/index.js"; import type { NodeInput } from "@nodes/types"; +import { fastHashString } from "@nodes/utils"; const logger = createLogger("graph-manager"); @@ -70,7 +71,13 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, " } + private lastSettingsHash = 0; setSettings(settings: Record) { + + let hash = fastHashString(JSON.stringify(settings)); + if (hash === this.lastSettingsHash) return; + this.lastSettingsHash = hash; + this.settings = settings; this.save(); this.execute(); diff --git a/app/src/lib/graph-interface/graph/Graph.svelte b/app/src/lib/graph-interface/graph/Graph.svelte index 25537e0..56e34c5 100644 --- a/app/src/lib/graph-interface/graph/Graph.svelte +++ b/app/src/lib/graph-interface/graph/Graph.svelte @@ -39,10 +39,11 @@ const edges = graph.edges; let wrapper: HTMLDivElement; + let rect: DOMRect; $: rect = wrapper && width ? wrapper.getBoundingClientRect() - : { x: 0, y: 0, width: 0, height: 0 }; + : ({ x: 0, y: 0, width: 0, height: 0 } as DOMRect); let camera: OrthographicCamera; const minZoom = 1; @@ -121,10 +122,13 @@ const height = 5 + 10 * - Object.keys(node.inputs) - .filter((i) => i !== "seed") - .filter((p) => node?.inputs && !("setting" in node?.inputs?.[p])) - .length; + Object.keys(node.inputs).filter( + (p) => + p !== "seed" && + node?.inputs && + !("setting" in node?.inputs?.[p]) && + node.inputs[p].hidden !== true, + ).length; nodeHeightCache[nodeTypeId] = height; return height; } @@ -756,7 +760,8 @@ clickedNodeId === -1 && !boxSelection && cameraDown[0] === cameraPosition[0] && - cameraDown[1] === cameraPosition[1] + cameraDown[1] === cameraPosition[1] && + isBodyFocused() ) { $activeNodeId = -1; $selectedNodes?.clear(); @@ -817,7 +822,6 @@ function handleDragEnter(e: DragEvent) { e.preventDefault(); isDragging = true; - console.log(e); } function handlerDragOver(e: DragEvent) { @@ -840,7 +844,7 @@ }); - +
- import type { Graph, NodeRegistry } from "@nodes/types"; + import type { Graph, Node, NodeRegistry } from "@nodes/types"; import GraphEl from "./Graph.svelte"; import { GraphManager } from "../graph-manager.js"; import { createEventDispatcher, setContext } from "svelte"; - import type { Writable } from "svelte/store"; + import { type Writable } from "svelte/store"; import { debounce } from "$lib/helpers"; import { createKeyMap } from "$lib/helpers/createKeyMap"; + import { activeNodeId } from "./stores"; export let registry: NodeRegistry; export let graph: Graph; export let settings: Writable> | undefined; - export const manager = new GraphManager(registry); + export let activeNode: Node | undefined; + $: if ($activeNodeId !== -1) { + activeNode = manager.getNode($activeNodeId); + } else { + activeNode = undefined; + } export const status = manager.status; diff --git a/app/src/lib/graph-interface/node/NodeHTML.svelte b/app/src/lib/graph-interface/node/NodeHTML.svelte index 34f68a6..fd6002e 100644 --- a/app/src/lib/graph-interface/node/NodeHTML.svelte +++ b/app/src/lib/graph-interface/node/NodeHTML.svelte @@ -3,6 +3,7 @@ import NodeHeader from "./NodeHeader.svelte"; import NodeParameter from "./NodeParameter.svelte"; import { getContext, onMount } from "svelte"; + import Page from "../../../routes/+page.svelte"; export let isActive = false; export let isSelected = false; export let inView = true; @@ -17,9 +18,10 @@ const type = node?.tmp?.type; - const parameters = Object.entries(type?.inputs || {}) - .filter((p) => p[1].type !== "seed") - .filter((p) => !("setting" in p[1])); + const parameters = Object.entries(type?.inputs || {}).filter( + (p) => + p[1].type !== "seed" && !("setting" in p[1]) && p[1]?.hidden !== true, + ); const updateNodePosition = getContext<(n: Node) => void>("updateNodePosition"); diff --git a/app/src/lib/settings/ActiveNode.svelte b/app/src/lib/settings/ActiveNode.svelte new file mode 100644 index 0000000..0ff05ce --- /dev/null +++ b/app/src/lib/settings/ActiveNode.svelte @@ -0,0 +1,71 @@ + + +{#if node} + {#if nodeDefinition && store && Object.keys(nodeDefinition).length > 0} + + {:else} +

Active Node has no Settings

+ {/if} +{:else} +

No active node

+{/if} diff --git a/app/src/lib/settings/Settings.svelte b/app/src/lib/settings/Settings.svelte index b51de39..aa2ddc6 100644 --- a/app/src/lib/settings/Settings.svelte +++ b/app/src/lib/settings/Settings.svelte @@ -10,6 +10,8 @@ { icon: string; id: string; + hidden?: boolean; + props?: Record; component?: typeof SvelteComponent<{}, {}, {}>; definition: Record; settings: Writable>; @@ -22,7 +24,7 @@ ); $: keys = panels ? (Object.keys(panels) as unknown as (keyof typeof panels)[]).filter( - (key) => !!panels[key]?.id, + (key) => !!panels[key]?.id && panels[key]?.hidden !== true, ) : []; @@ -79,7 +81,7 @@ {/each}
- {#if $activePanel && panels[$activePanel]} + {#if $activePanel && panels[$activePanel] && panels[$activePanel].hidden !== true}

{panels[$activePanel].id}

{#key $activePanel} {#if panels[$activePanel]?.component} diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte index 32b8300..dc0634b 100644 --- a/app/src/routes/+page.svelte +++ b/app/src/routes/+page.svelte @@ -4,11 +4,11 @@ import { MemoryRuntimeExecutor } from "$lib/runtime-executor"; import { RemoteNodeRegistry } from "$lib/node-registry-client"; import * as templates from "$lib/graph-templates"; - import type { Graph } from "@nodes/types"; + import type { Graph, Node } from "@nodes/types"; import Viewer from "$lib/result-viewer/Viewer.svelte"; import Settings from "$lib/settings/Settings.svelte"; import { AppSettings, AppSettingTypes } from "$lib/settings/app-settings"; - import { get, writable, type Writable } from "svelte/store"; + import { get, writable, type Readable, type Writable } from "svelte/store"; import Keymap from "$lib/settings/Keymap.svelte"; import type { createKeyMap } from "$lib/helpers/createKeyMap"; import NodeStore from "$lib/node-store/NodeStore.svelte"; @@ -17,7 +17,7 @@ import { decodeNestedArray, encodeNestedArray } from "@nodes/utils"; import type { PerspectiveCamera, Vector3 } from "three"; import type { OrbitControls } from "three/examples/jsm/Addons.js"; - import GraphView from "$lib/graph-interface/graph/GraphView.svelte"; + import ActiveNode from "$lib/settings/ActiveNode.svelte"; const nodeRegistry = new RemoteNodeRegistry(""); const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry); @@ -29,6 +29,7 @@ let viewerCamera: PerspectiveCamera; let viewerControls: OrbitControls; let viewerCenter: Vector3; + let activeNode: Node | undefined; let graph = localStorage.getItem("graph") ? JSON.parse(localStorage.getItem("graph")!) @@ -65,6 +66,12 @@ shortcuts: {}, nodeStore: {}, graph: {}, + activeNode: { + id: "Active Node", + icon: "i-tabler-adjustments", + props: { node: undefined, manager }, + component: ActiveNode, + }, }; $: if (keymap) { @@ -79,6 +86,7 @@ } $: if (manager) { + settings.activeNode.props.manager = manager; settings.nodeStore = { id: "Node Store", icon: "i-tabler-database", @@ -88,6 +96,14 @@ settings = settings; } + $: if (activeNode) { + settings.activeNode.props.node = activeNode; + settings = settings; + } else { + settings.activeNode.props.node = undefined; + settings = settings; + } + function handleSettings( ev: CustomEvent<{ values: Record; @@ -107,7 +123,7 @@ }; settings.graph = { - icon: "i-tabler-chart-bar", + icon: "i-tabler-git-fork", id: "graph", settings: writable(ev.detail.values), definition: { @@ -139,6 +155,7 @@ {#key graph} f32 { + a + t * (b - a) +} + #[wasm_bindgen] pub fn execute(input: &[i32]) -> Vec { set_panic_hook(); @@ -16,10 +20,23 @@ pub fn execute(input: &[i32]) -> Vec { let plants = get_args(args[0]); let scale = evaluate_float(args[1]); let strength = evaluate_float(args[2]); - let seed = args[3][0]; + let _fix_bottom = evaluate_float(args[3]); + let fix_bottom = if _fix_bottom.is_finite() { + _fix_bottom + } else { + 0.0 + }; + + let seed = args[4][0]; let hasher = PermutationTable::new(seed as u32); - log!("scale: {}, strength: {}, seed: {}", scale, strength, seed); + log!( + "scale: {}, strength: {}, fix_bottom: {}, seed: {}", + scale, + strength, + fix_bottom, + seed + ); let output: Vec> = plants .iter() @@ -33,8 +50,14 @@ pub fn execute(input: &[i32]) -> Vec { let a = i as f64 / (points - 1) as f64; let px = Vector2::new(0.0, a * scale as f64); let pz = Vector2::new(a * scale as f64, 0.0); - let nx = open_simplex_2d(px, &hasher) as f32 * strength * 0.1 * a as f32; - let nz = open_simplex_2d(pz, &hasher) as f32 * strength * 0.1 * a as f32; + let nx = open_simplex_2d(px, &hasher) as f32 + * strength + * 0.1 + * lerp(1.0, a as f32, fix_bottom); + let nz = open_simplex_2d(pz, &hasher) as f32 + * strength + * 0.1 + * lerp(1.0, a as f32, fix_bottom); plant[3 + i * 4] = encode_float(decode_float(plant[3 + i * 4]) + nx); plant[5 + i * 4] = encode_float(decode_float(plant[5 + i * 4]) + nz); } diff --git a/nodes/max/plantarium/output/src/inputs.json b/nodes/max/plantarium/output/src/inputs.json index ade35a4..d515efc 100644 --- a/nodes/max/plantarium/output/src/inputs.json +++ b/nodes/max/plantarium/output/src/inputs.json @@ -3,10 +3,7 @@ "outputs": [], "inputs": { "input": { - "type": [ - "plant", - "model" - ], + "type": "plant", "external": true }, "resolution_circle": { diff --git a/packages/macros/src/lib.rs b/packages/macros/src/lib.rs index 1d787df..ac1d639 100644 --- a/packages/macros/src/lib.rs +++ b/packages/macros/src/lib.rs @@ -49,8 +49,12 @@ pub fn include_definition_file(input: TokenStream) -> TokenStream { }); // Optionally, validate that the content is valid JSON - let _: NodeDefinition = serde_json::from_str(&json_content) - .unwrap_or_else(|err| panic!("JSON file contains invalid JSON: {}", err)); + let _: NodeDefinition = serde_json::from_str(&json_content).unwrap_or_else(|err| { + panic!( + "JSON file contains invalid JSON: \n {} \n {}", + json_content, err + ) + }); // Generate the function that returns the JSON string let expanded = quote! { diff --git a/packages/types/src/inputs.ts b/packages/types/src/inputs.ts index 2da4652..66ed42d 100644 --- a/packages/types/src/inputs.ts +++ b/packages/types/src/inputs.ts @@ -7,6 +7,7 @@ const DefaultOptionsSchema = z.object({ label: z.string().optional(), description: z.string().optional(), accepts: z.array(z.string()).optional(), + hidden: z.boolean().optional(), }); @@ -38,6 +39,7 @@ export const NodeInputBooleanSchema = z.object({ export const NodeInputSelectSchema = z.object({ ...DefaultOptionsSchema.shape, type: z.literal("select"), + options: z.array(z.string()).optional(), value: z.number().optional(), }); @@ -75,4 +77,4 @@ export const NodeInputSchema = z.union([ NodeInputPlantSchema ]); -export type NodeInput = z.infer; +export type NodeInput = z.infer; diff --git a/packages/types/src/lib.rs b/packages/types/src/lib.rs index 24d2ab0..18a9fe1 100644 --- a/packages/types/src/lib.rs +++ b/packages/types/src/lib.rs @@ -1,135 +1,142 @@ -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize)] +pub struct DefaultOptions { + #[serde(skip_serializing_if = "Option::is_none")] + pub internal: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub external: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub setting: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub label: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub accepts: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub hidden: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct NodeInputFloat { + #[serde(flatten)] + pub default_options: DefaultOptions, + + #[serde(skip_serializing_if = "Option::is_none")] + pub element: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub min: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub max: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub step: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct NodeInputInteger { + #[serde(flatten)] + pub default_options: DefaultOptions, + + #[serde(skip_serializing_if = "Option::is_none")] + pub element: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub min: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub max: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct NodeInputBoolean { + #[serde(flatten)] + pub default_options: DefaultOptions, + + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct NodeInputSelect { + #[serde(flatten)] + pub default_options: DefaultOptions, + + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub options: Option>, +} + +#[derive(Serialize, Deserialize)] +pub struct NodeInputSeed { + #[serde(flatten)] + pub default_options: DefaultOptions, + + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct NodeInputVec3 { + #[serde(flatten)] + pub default_options: DefaultOptions, + + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option>, +} + +#[derive(Serialize, Deserialize)] +pub struct NodeInputModel { + #[serde(flatten)] + pub default_options: DefaultOptions, +} + +#[derive(Serialize, Deserialize)] +pub struct NodeInputPlant { + #[serde(flatten)] + pub default_options: DefaultOptions, +} + +#[derive(Serialize, Deserialize)] #[serde(tag = "type")] #[allow(non_camel_case_types)] -pub enum InputTypes { +pub enum NodeInput { float(NodeInputFloat), integer(NodeInputInteger), boolean(NodeInputBoolean), select(NodeInputSelect), seed(NodeInputSeed), + vec3(NodeInputVec3), model(NodeInputModel), plant(NodeInputPlant), - vec3(NodeInputVec3), } -#[derive(Serialize, Deserialize, Debug)] -pub struct NodeInputVec3 { - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option>, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct NodeInputFloat { - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub min: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub max: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub step: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct NodeInputInteger { - #[serde(skip_serializing_if = "Option::is_none")] - pub element: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub min: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub max: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct NodeInputBoolean { - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct NodeInputSelect { - pub options: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct NodeInputSeed { - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, -} - -// Assuming similar structure as other NodeInput types for Model and Plant -#[derive(Serialize, Deserialize, Debug)] -pub struct NodeInputModel { - // Model-specific fields can be added here -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct NodeInputPlant { - // Plant-specific fields can be added here -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct DefaultOptions { - #[serde(skip_serializing_if = "Option::is_none")] - pub internal: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub external: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub setting: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub label: Option, // To handle both String and false - #[serde(skip_serializing_if = "Option::is_none")] - pub hidden: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(untagged)] -pub enum NodeDefinitionOrArray { - Single(InputTypes), - Multiple(Vec), -} - -#[derive(Debug, Serialize)] -pub struct NodeInput { - pub types: Vec, - pub options: DefaultOptions, -} - -impl<'de> Deserialize<'de> for NodeInput { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let raw_input: Value = Deserialize::deserialize(deserializer)?; - let options: DefaultOptions = - DefaultOptions::deserialize(&raw_input).map_err(serde::de::Error::custom)?; // Maps deserialization errors appropriately - - let types: Vec = match raw_input.get("type") { - Some(Value::String(single_type)) => vec![single_type.clone()], - Some(Value::Array(types)) => types - .iter() - .map(|t| t.as_str().unwrap_or("").to_owned()) - .collect(), - _ => return Err(serde::de::Error::custom("Invalid or missing 'type' field")), - }; - - Ok(NodeInput { types, options }) - } -} - -#[derive(Deserialize, Debug, Serialize)] +#[derive(Serialize, Deserialize)] pub struct NodeDefinition { pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] pub inputs: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub outputs: Option>, } diff --git a/packages/ui/src/lib/elements/Float.svelte b/packages/ui/src/lib/elements/Float.svelte index 78f3e24..dee6ae6 100644 --- a/packages/ui/src/lib/elements/Float.svelte +++ b/packages/ui/src/lib/elements/Float.svelte @@ -42,6 +42,8 @@ function handleMouseDown(ev: MouseEvent) { ev.preventDefault(); + inputEl.focus(); + isMouseDown = true; downV = value;