#assets #stick #node #read-write

no-std sticknodes-rs

Unofficial Stick Nodes asset manipulation library. Currently only supports Stickfigures. Currently only supports assets up to version 4.1.0 build 21.

1 stable release

new 1.0.0 Apr 29, 2025

#648 in Parser implementations

MIT license

95KB
2K SLoC

Stick Nodes Asset Library

Crates.io Docs.rs License: MIT

A Rust library for reading, creating, and manipulating Stick Nodes assets.

Currently supports .nodes (stickfigure) files, with planned support for .stknds (projects) and .nodemc (movieclips) in the future.

Version: 1.0.0
Supported Stick Nodes Version: Up to 4.1.0 build 21
Note: This is my first serious Rust library—feedback is welcome!


Features

  • 📄 Read and write .nodes (stickfigure) files.
  • 🛠️ Create and modify stickfigures programmatically.
  • 🔜 Future support for .stknds (project) and .nodemc (movieclip) files.
  • 🧩 Node management: add, remove, update nodes easily.
  • 🧵 Polyfill support.
  • 🧹 Automatic handling of node draw indices when modifying stickfigures.
  • 🧠 Safe internal handling using RefCell and Rc for node references.

Installation

Add to your Cargo.toml:

[dependencies]
sticknodes-rs = "1.0.0"

Example Usage

Creating and Modifying a Stickfigure

use sticknodes_rs::{Stickfigure, Node, NodeOptions, DrawOrderIndex, Polyfill, PolyfillOptions, IWillNotAbuseUnlimitedNodes, LibraryError};
use std::rc::Rc;
use std::cell::RefCell;

fn stickfigure_examples() -> Result<(), LibraryError> {
    // Create a new Stickfigure with a single root node
    let mut stickfigure = Stickfigure::new();

    // Add a node as a child of the root node
    let node_a_index = stickfigure.add_node(
        Node::from_options(NodeOptions::default()), 
        DrawOrderIndex(0)
    )?;

    // Add a custom node as a child of the previous node
    let node_b_index = stickfigure.add_node(
        Node::from_options(NodeOptions {
            length: 100.0,
            local_angle: 90.0,
            ..Default::default()
        }), 
        node_a_index
    )?;

    // Remove a node (note: draw indices are compacted after removal)
    stickfigure.remove_node(node_a_index)?;

    // Access and modify a node
    if let Some(node) = stickfigure.get_node(DrawOrderIndex(1)) {
        node.borrow_mut().is_static = true;
    }

    // Working with children and search
    let _children = stickfigure.get_children(DrawOrderIndex(0));
    let _long_nodes = stickfigure.get_nodes_with_property(|node| node.borrow().length > 100.0);

    // Adding a polyfill
    let polyfill_index = stickfigure.add_polyfill(
        Polyfill::from_options(PolyfillOptions {
            anchor_node_draw_index: DrawOrderIndex(1),
            ..Default::default()
        }, stickfigure.clone())?
    );

    // Modifying a polyfill
    if let Some(polyfill) = stickfigure.get_polyfill(polyfill_index) {
        polyfill.borrow_mut().set_attached_node_draw_indices(vec![DrawOrderIndex(0)], stickfigure.clone())?;
    }

    // Removing a polyfill
    stickfigure.remove_polyfill(polyfill_index)?;

    // Disabling the node limit (use responsibly)
    stickfigure.set_is_node_limit_enabled(false, IWillNotAbuseUnlimitedNodes(true));

    Ok(())
}

Reading and Writing Stickfigure Files

use sticknodes_rs::{Stickfigure, LibraryError};
use std::fs::File;
use std::io::{Read, Write};

fn read_write_stickfigure_examples() -> Result<(), LibraryError> {
    // Read a stickfigure from file
    let mut file = File::open("stickfigure_to_read.nodes")
        .map_err(|err| LibraryError::AnyString(format!("Error: {err}")))?;
    
    let mut buffer = Vec::new();
    file.read_to_end(&mut buffer)
        .map_err(|err| LibraryError::AnyString(format!("Error: {err}")))?;
    
    let mut slice = buffer.as_slice();
    let stickfigure = Stickfigure::from_bytes(&mut slice)?;

    // Write the stickfigure to a new file
    let bytes = stickfigure.to_bytes()?;
    let mut output_file = File::create_new("stickfigure_to_write.nodes")
        .map_err(|err| LibraryError::AnyString(format!("Error: {err}")))?;
    output_file.write_all(&bytes)
        .map_err(|err| LibraryError::AnyString(format!("Error: {err}")))?;

    Ok(())
}

Planned Features

  • ✅ Support .nodes stickfigure files (Done)
  • 🔜 Read/write .stknds project files
  • 🔜 Read/write .nodemc movieclip files
  • 🧹 Further API ergonomics improvements
  • 📄 More documentation and examples

Feedback

Since this is my first major Rust project, feedback and suggestions are very much appreciated! Feel free to open issues, pull requests, or just share thoughts if you have any ideas to improve the library.

License

This project is licensed under the MIT License.

Dependencies

~4MB
~64K SLoC