rename to tono

This commit is contained in:
2026-03-29 22:51:58 -07:00
parent 961b5d08c8
commit 52da360804
33 changed files with 82 additions and 82 deletions

View File

@@ -1,4 +1,4 @@
# Gwyddion Features Not Yet in argonode
# Gwyddion Features Not Yet in tono
Reference for future implementation. Grouped by value to typical SPM workflows.
@@ -61,11 +61,11 @@ Reference for future implementation. Grouped by value to typical SPM workflows.
---
## Already Implemented in argonode
## Already Implemented in tono
For reference, these Gwyddion equivalents are already covered:
| argonode Node | Category | Gwyddion Equivalent |
| tono Node | Category | Gwyddion Equivalent |
|--------------|----------|-------------------|
| Load Image / Load SPM File | io | File import (gwy, sxm, ibw) |
| Save Image | io | File export |

View File

@@ -1,6 +1,6 @@
# argonode
# tono
argonode is a node-based image analysis application with:
tono is a node-based image analysis application with:
- a Python backend built on `aiohttp`
- a React + Vite frontend
@@ -11,7 +11,7 @@ The backend serves node definitions, runs workflows, manages file I/O, and strea
## Project Layout
```text
argonode/
tono/
backend/ Python server, execution engine, nodes
frontend/ React/Vite app
tests/ Python tests
@@ -86,7 +86,7 @@ Notes:
- The frontend dev server proxies API and WebSocket requests to the backend.
- `npm run dev` now clears Vite's local cache and stale Python bytecode first, then starts Vite with `--force`.
- If you open the backend directly in a browser instead of the Vite dev server, argonode now refreshes `frontend/dist` automatically when checked-out frontend sources are newer, such as after a `git pull`.
- If you open the backend directly in a browser instead of the Vite dev server, tono now refreshes `frontend/dist` automatically when checked-out frontend sources are newer, such as after a `git pull`.
- If you want the frontend accessible from other devices on your LAN, run:
```powershell
@@ -131,13 +131,13 @@ powershell -ExecutionPolicy Bypass -File scripts\build-desktop.ps1
The packaged app is written to:
```text
desktop-dist/argonode/
desktop-dist/tono/
```
Main executable:
```text
desktop-dist/argonode/argonode.exe
desktop-dist/tono/tono.exe
```
### One-File Build
@@ -157,20 +157,20 @@ During normal source-based development, input/output folders live under the repo
In the packaged desktop app, writable data is stored under:
```text
%LOCALAPPDATA%\argonode\
%LOCALAPPDATA%\tono\
```
Specifically:
```text
%LOCALAPPDATA%\argonode\input
%LOCALAPPDATA%\argonode\output
%LOCALAPPDATA%\tono\input
%LOCALAPPDATA%\tono\output
```
You can override the packaged app data directory with:
```powershell
$env:ARGONODE_APPDATA="C:\path\to\custom\data"
$env:TONO_APPDATA="C:\path\to\custom\data"
```
## Useful Commands

View File

@@ -1,5 +1,5 @@
"""
Core data types for argonode.
Core data types for tono.
DataField mirrors Gwyddion's GwyDataField structure:
xres, yres pixel dimensions

View File

@@ -1,5 +1,5 @@
"""
Graph execution engine for argonode.
Graph execution engine for tono.
Prompt format (same as ComfyUI):
{

View File

@@ -8,10 +8,10 @@ from typing import Any, Callable
Callback = Callable[[str, Any], None]
_callbacks_var: ContextVar[dict[str, Callback | None]] = ContextVar(
"argonode_execution_callbacks",
"tono_execution_callbacks",
default={},
)
_node_id_var: ContextVar[str | None] = ContextVar("argonode_execution_node_id", default=None)
_node_id_var: ContextVar[str | None] = ContextVar("tono_execution_node_id", default=None)
_LEGACY_CALLBACK_ATTRS = {
"preview": "_broadcast_fn",

View File

@@ -1,11 +1,11 @@
"""
Entry point for argonode.
Entry point for tono.
Run with:
python -m backend.main
or simply:
python backend/main.py
from the argonode/ directory.
from the tono/ directory.
"""
import asyncio
@@ -36,7 +36,7 @@ def main() -> None:
app = create_app(loop)
log.info("=" * 60)
log.info(" argonode — Node-based image analysis")
log.info(" tono — Node-based image analysis")
log.info(" Open your browser at http://%s:%d", HOST, PORT)
log.info("=" * 60)

View File

@@ -1,5 +1,5 @@
"""
Node registry for argonode.
Node registry for tono.
Nodes are plain Python classes decorated with @register_node.
NODE_CLASS_MAPPINGS is the single source of truth consumed by

View File

@@ -1,5 +1,5 @@
"""
Shared helper functions for argonode nodes.
Shared helper functions for tono nodes.
"""
from __future__ import annotations

View File

@@ -251,7 +251,7 @@ class Save:
length = float(np.linalg.norm(n))
return n / length if length > 0 else np.array([0.0, 1.0, 0.0], dtype=np.float32)
lines = ["solid argonode"]
lines = ["solid tono"]
vertices = np.asarray(mesh.vertices, dtype=np.float32)
for face in np.asarray(mesh.faces, dtype=np.int32):
a, b, c = vertices[int(face[0])], vertices[int(face[1])], vertices[int(face[2])]
@@ -263,7 +263,7 @@ class Save:
lines.append(f" vertex {c[0]} {c[1]} {c[2]}")
lines.append(" endloop")
lines.append(" endfacet")
lines.append("endsolid argonode")
lines.append("endsolid tono")
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def _send_warning(self, message: str):

View File

@@ -1,5 +1,5 @@
"""
Plugin loader for argonode.
Plugin loader for tono.
Scans a plugins directory for .py files and packages (directories containing
__init__.py), imports each one, and lets their @register_node decorators
@@ -96,17 +96,17 @@ def _import_plugin(name: str, path: Path) -> None:
"""
Import a single plugin file (or package ``__init__.py``) via importlib.
The module is registered under ``argonode_plugins.<name>`` in
The module is registered under ``tono_plugins.<name>`` in
``sys.modules``. This namespace:
- avoids collisions with any PyPI package of the same name, and
- makes package-style plugins (with sub-modules) work correctly, because
their relative imports resolve against the ``argonode_plugins.*`` parent.
their relative imports resolve against the ``tono_plugins.*`` parent.
If the module was previously imported (e.g. on a hot-reload call after an
upload), it is deleted from ``sys.modules`` first so the file is re-executed
and any updated ``@register_node`` decorators take effect.
"""
module_name = f"argonode_plugins.{name}"
module_name = f"tono_plugins.{name}"
# Remove stale module to support hot-reload after /upload-plugin.
if module_name in sys.modules:

View File

@@ -4,7 +4,7 @@ import os
import sys
from pathlib import Path
APP_NAME = "argonode"
APP_NAME = "tono"
def project_root() -> Path:
@@ -29,7 +29,7 @@ def frontend_dist_dir() -> Path:
def app_data_dir() -> Path:
override = os.getenv("ARGONODE_APPDATA")
override = os.getenv("TONO_APPDATA")
if override:
return Path(override).expanduser().resolve()
@@ -71,11 +71,11 @@ def plugins_enabled(*, native: bool) -> bool:
Return True when the plugin system should be active.
Default behaviour: enabled on native/desktop builds, disabled for web.
Override with the ARGONODE_PLUGINS environment variable:
ARGONODE_PLUGINS=1 force on (useful for testing plugins via main.py)
ARGONODE_PLUGINS=0 force off (disable even on native builds)
Override with the TONO_PLUGINS environment variable:
TONO_PLUGINS=1 force on (useful for testing plugins via main.py)
TONO_PLUGINS=0 force off (disable even on native builds)
"""
env = os.getenv("ARGONODE_PLUGINS", "").strip().lower()
env = os.getenv("TONO_PLUGINS", "").strip().lower()
if env in ("1", "true", "yes"):
return True
if env in ("0", "false", "no"):

View File

@@ -1,5 +1,5 @@
"""
aiohttp web server for argonode.
aiohttp web server for tono.
Routes
------
@@ -355,7 +355,7 @@ def create_app(
plugins, and notify every connected WebSocket client to refresh /nodes.
Warning: uploading Python files is equivalent to remote code execution.
This endpoint is intentionally unrestricted because argonode is a
This endpoint is intentionally unrestricted because tono is a
local-first application; do not expose it on a public network.
"""
reader = await request.multipart()

View File

@@ -15,7 +15,7 @@ from backend.runtime_paths import app_data_dir, ensure_runtime_dirs
from backend.server import create_app
HOST = "127.0.0.1"
WINDOW_TITLE = "argonode"
WINDOW_TITLE = "tono"
class _Api:
@@ -141,14 +141,14 @@ def main() -> None:
server_thread = threading.Thread(
target=_run_server,
args=(HOST, port, ready, state),
name="argonode-server",
name="tono-server",
daemon=True,
)
server_thread.start()
ready.wait(timeout=15.0)
if "error" in state:
raise RuntimeError("argonode server failed to start") from state["error"]
raise RuntimeError("tono server failed to start") from state["error"]
_wait_for_server(f"{base_url}/nodes")

View File

@@ -705,8 +705,8 @@
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { sortNodesForParentOrder } from './nodeHierarchy.js';
&nbsp;
export const NODE_CLIPBOARD_KIND = 'argonode/node-selection';
export const NODE_CLIPBOARD_MIME = 'application/x-argonode-node-selection';
export const NODE_CLIPBOARD_KIND = 'tono/node-selection';
export const NODE_CLIPBOARD_MIME = 'application/x-tono-node-selection';
&nbsp;
function cloneValue(value) {
if (value == null) <span class="branch-0 cbranch-no" title="branch not covered" >return value;</span>

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>argonode — Image Analysis</title>
<title>tono — Image Analysis</title>
</head>
<body>
<div id="root"></div>

View File

@@ -1,10 +1,10 @@
{
"name": "argonode-frontend",
"name": "tono-frontend",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "argonode-frontend",
"name": "tono-frontend",
"dependencies": {
"@xyflow/react": "^12.0.0",
"html-to-image": "^1.11.13",

View File

@@ -1,5 +1,5 @@
{
"name": "argonode-frontend",
"name": "tono-frontend",
"private": true,
"type": "module",
"engines": {

View File

@@ -1278,7 +1278,7 @@ function Flow() {
useEffect(() => {
api.setMessageHandler((msg) => {
console.log('[argonode] WS:', msg.type, msg.data?.node_id || msg.data?.node || '');
console.log('[tono] WS:', msg.type, msg.data?.node_id || msg.data?.node || '');
switch (msg.type) {
case 'execution_start':
setNodes((ns) => ns.map((n) => ({
@@ -1295,7 +1295,7 @@ function Flow() {
break;
case 'execution_error':
setStatus({ text: 'Error: ' + msg.data.message, level: 'error' });
console.error('[argonode] execution error', msg.data);
console.error('[tono] execution error', msg.data);
break;
case 'preview':
updateNodeData(msg.data.node_id, { previewImage: msg.data.image });
@@ -2799,7 +2799,7 @@ function Flow() {
<div className="app-container">
{/* Toolbar */}
<div id="toolbar">
<span id="app-title">argonode</span>
<span id="app-title">tono</span>
<div className="toolbar-group">
<button className="btn btn-primary" onClick={runWorkflow} title="Run workflow (Ctrl+Enter)">

View File

@@ -224,7 +224,7 @@ class PreviewBoundary extends React.Component {
}
componentDidCatch(error) {
console.error('[argonode] preview render failed', error);
console.error('[tono] preview render failed', error);
}
componentDidUpdate(prevProps) {

View File

@@ -2,7 +2,7 @@ import React, { useRef, useEffect, useCallback, useState } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
const VIEW3D_DIAGNOSTICS_STORAGE_KEY = 'argonode:view3d-diagnostics';
const VIEW3D_DIAGNOSTICS_STORAGE_KEY = 'tono:view3d-diagnostics';
const DEFAULT_CAMERA_STATE = {
// A diagonal home view avoids edge-on framing when one lateral axis is much smaller.
azimuth: Math.PI / 4,
@@ -109,7 +109,7 @@ export default function SurfaceView({ meshData, nodeId, widgetValues, runtimeVal
try {
return canvas.toDataURL('image/png');
} catch (error) {
console.warn('[argonode] Failed to capture View3D viewport snapshot', error);
console.warn('[tono] Failed to capture View3D viewport snapshot', error);
updateDiagnostics({
status: 'snapshot error',
error: error?.message || String(error),
@@ -454,7 +454,7 @@ export default function SurfaceView({ meshData, nodeId, widgetValues, runtimeVal
}
scheduleViewportSync(0, false);
} catch (error) {
console.error('[argonode] View3D mesh build failed', error);
console.error('[tono] View3D mesh build failed', error);
updateDiagnostics({
status: 'mesh build error',
error: error?.message || String(error),

View File

@@ -1,11 +1,11 @@
/**
* api.js — REST + WebSocket client for argonode backend.
* api.js — REST + WebSocket client for tono backend.
*
* Uses relative URLs so the Vite dev proxy (port 5173 → 8188)
* and production same-origin serving both work transparently.
*/
const SESSION_STORAGE_KEY = 'argonode-session-id';
const SESSION_STORAGE_KEY = 'tono-session-id';
let _sessionId = null;
let _ws = null;
@@ -127,17 +127,17 @@ export function initWS() {
_ws = new WebSocket(`${protocol}//${window.location.host}/ws?session=${session}`);
_ws.onopen = () => {
console.log('[argonode] WebSocket connected');
console.log('[tono] WebSocket connected');
};
_ws.onclose = () => {
console.log('[argonode] WebSocket closed, reconnecting in 3s…');
console.log('[tono] WebSocket closed, reconnecting in 3s…');
clearTimeout(_reconnectTimer);
_reconnectTimer = setTimeout(() => initWS(), 3000);
};
_ws.onerror = (e) => {
console.error('[argonode] WebSocket error', e);
console.error('[tono] WebSocket error', e);
};
_ws.onmessage = (e) => {

View File

@@ -1,7 +1,7 @@
import { sortNodesForParentOrder } from './nodeHierarchy.js';
export const NODE_CLIPBOARD_KIND = 'argonode/node-selection';
export const NODE_CLIPBOARD_MIME = 'application/x-argonode-node-selection';
export const NODE_CLIPBOARD_KIND = 'tono/node-selection';
export const NODE_CLIPBOARD_MIME = 'application/x-tono-node-selection';
function cloneValue(value) {
if (value == null) return value;

4
package-lock.json generated
View File

@@ -1,10 +1,10 @@
{
"name": "argonode",
"name": "tono",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "argonode",
"name": "tono",
"hasInstallScript": true,
"engines": {
"node": ">=18.0.0",

View File

@@ -1,5 +1,5 @@
{
"name": "argonode",
"name": "tono",
"private": true,
"engines": {
"node": ">=18.0.0",

View File

@@ -1,7 +1,7 @@
"""
Example argonode plugin: Normalize Z Range
Example tono plugin: Normalize Z Range
Drop any .py file into this plugins/ folder and restart argonode (or upload it
Drop any .py file into this plugins/ folder and restart tono (or upload it
via POST /upload-plugin) — the node will appear in the Add Node menu immediately.
─── What you need to import ─────────────────────────────────────────────────

View File

@@ -3,7 +3,7 @@ requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "argonode"
name = "tono"
version = "0.1.0"
description = "Node-based image analysis app with a Python backend and React frontend."
readme = "GWYDDION_FEATURE_GAP.md"

View File

@@ -8,8 +8,8 @@
version="1.1"
id="svg1"
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
sodipodi:docname="argonode.svg"
inkscape:export-filename="argonode.png"
sodipodi:docname="tono.svg"
inkscape:export-filename="tono.png"
inkscape:export-xdpi="130.05"
inkscape:export-ydpi="130.05"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -41,7 +41,7 @@ $PYTHON -m PyInstaller \
desktop.py \
--noconfirm \
--clean \
--name argonode \
--name tono \
--windowed \
$MODE \
--distpath desktop-dist \
@@ -55,9 +55,9 @@ $PYTHON -m PyInstaller \
--collect-all webview
if $CREATE_TAR; then
TAR_PATH="desktop-dist/argonode-linux.tar.gz"
TAR_PATH="desktop-dist/tono-linux.tar.gz"
echo "Creating tarball..."
tar -czf "$TAR_PATH" -C desktop-dist argonode
tar -czf "$TAR_PATH" -C desktop-dist tono
echo "Tarball created: $TAR_PATH"
fi

View File

@@ -41,7 +41,7 @@ $PYTHON -m PyInstaller \
desktop.py \
--noconfirm \
--clean \
--name argonode \
--name tono \
--windowed \
$MODE \
--distpath desktop-dist \
@@ -55,12 +55,12 @@ $PYTHON -m PyInstaller \
--collect-all webview \
--icon ../resources/icon.icns
APP_BUNDLE="desktop-dist/argonode.app"
APP_BUNDLE="desktop-dist/tono.app"
if [ ! -d "$APP_BUNDLE" ]; then
# --onedir puts it inside a folder
if [ -d "desktop-dist/argonode/argonode.app" ]; then
APP_BUNDLE="desktop-dist/argonode/argonode.app"
if [ -d "desktop-dist/tono/tono.app" ]; then
APP_BUNDLE="desktop-dist/tono/tono.app"
else
echo "Warning: .app bundle not found; skipping DMG creation."
CREATE_DMG=false
@@ -68,7 +68,7 @@ if [ ! -d "$APP_BUNDLE" ]; then
fi
if $CREATE_DMG; then
DMG_PATH="desktop-dist/argonode.dmg"
DMG_PATH="desktop-dist/tono.dmg"
echo "Creating DMG installer..."
rm -f "$DMG_PATH"
@@ -80,7 +80,7 @@ if $CREATE_DMG; then
ln -s /Applications "$DMG_STAGING/Applications"
hdiutil create \
-volname "argonode" \
-volname "tono" \
-srcfolder "$DMG_STAGING" \
-ov \
-format UDZO \

View File

@@ -57,7 +57,7 @@ $pyInstallerArgs = @(
"desktop.py",
"--noconfirm",
"--clean",
"--name", "argonode",
"--name", "tono",
"--windowed",
$mode,
"--distpath", "desktop-dist",
@@ -77,4 +77,4 @@ Write-Host "Packaging desktop app..."
Assert-LastExitCode "PyInstaller packaging"
Write-Host "Desktop build complete."
Write-Host "Output folder: $repoRoot\desktop-dist\argonode"
Write-Host "Output folder: $repoRoot\desktop-dist\tono"

View File

@@ -122,7 +122,7 @@ def test_save_generic():
node.save(filename="triangle", directory_path=tmpdir, format="STL", value=mesh)
stl_text = Path(tmpdir, "triangle.stl").read_text(encoding="utf-8")
assert stl_text.startswith("solid argonode")
assert stl_text.startswith("solid tono")
assert "facet normal" in stl_text
try:

View File

@@ -15,7 +15,7 @@ from backend.session_runtime import (
def test_session_paths_round_trip(monkeypatch, tmp_path):
monkeypatch.setenv("ARGONODE_APPDATA", str(tmp_path / "appdata"))
monkeypatch.setenv("TONO_APPDATA", str(tmp_path / "appdata"))
session_id = "session-test-1234"
input_dir, _ = ensure_session_runtime_dirs(session_id)
@@ -31,7 +31,7 @@ def test_session_paths_round_trip(monkeypatch, tmp_path):
def test_browser_sessions_cannot_escape_workspace(monkeypatch, tmp_path):
monkeypatch.setenv("ARGONODE_APPDATA", str(tmp_path / "appdata"))
monkeypatch.setenv("TONO_APPDATA", str(tmp_path / "appdata"))
session_id = "session-test-5678"
ensure_session_runtime_dirs(session_id)

View File

@@ -7,7 +7,7 @@ from backend.server import PNG_SIGNATURE, save_png_bytes
def test_save_png_bytes_writes_exact_png_payload(tmp_path: Path):
target = tmp_path / "workflow"
payload = PNG_SIGNATURE + b"argonode-test-payload"
payload = PNG_SIGNATURE + b"tono-test-payload"
saved_path = save_png_bytes(str(target), payload)