From 3f458dad33e285ec4ef3194df2b6ebfd8a0593e9 Mon Sep 17 00:00:00 2001 From: Milan Rother Date: Tue, 2 Jun 2026 09:03:34 +0200 Subject: [PATCH 1/3] Engine-parametric core: engine.ts (default pathsim) + enginePath codegen; engine-tolerant block keys/type_name, engine-reserved toolbox guard, screenshot env hooks --- scripts/capture-screenshots.js | 14 ++++++++++--- src/lib/constants/engine.ts | 34 ++++++++++++++++++++++++++++++++ src/lib/pyodide/pathsimRunner.ts | 28 +++++++++++++++----------- src/lib/pyodide/pythonHelpers.ts | 28 ++++++++++++++++++++------ src/lib/toolbox/dependencies.ts | 20 +++++++++++++++++-- 5 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 src/lib/constants/engine.ts diff --git a/scripts/capture-screenshots.js b/scripts/capture-screenshots.js index 871e5de5..e8f72e64 100644 --- a/scripts/capture-screenshots.js +++ b/scripts/capture-screenshots.js @@ -8,15 +8,23 @@ import puppeteer from 'puppeteer-core'; import { readFileSync, existsSync, mkdirSync } from 'fs'; -import { dirname, join } from 'path'; +import { dirname, join, resolve } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const STATIC_DIR = join(__dirname, '..', 'static', 'examples'); -const SCREENSHOTS_DIR = join(STATIC_DIR, 'screenshots'); const MANIFEST_PATH = join(STATIC_DIR, 'manifest.json'); -const BASE_URL = 'https://view.pathsim.org'; +// Where to write the PNGs. Override (SCREENSHOT_OUT_DIR) so a fastsim build can +// capture straight into its build output instead of the default static dir. +const SCREENSHOTS_DIR = process.env.SCREENSHOT_OUT_DIR + ? resolve(process.env.SCREENSHOT_OUT_DIR) + : join(STATIC_DIR, 'screenshots'); + +// Origin (+ base path) to screenshot. Defaults to the public blue pathview. +// The fastsim build points this at a local `vite preview` of the red /app +// build so the tiles match its styling (SCREENSHOT_BASE_URL=http://localhost:PORT/app). +const BASE_URL = process.env.SCREENSHOT_BASE_URL || 'https://view.pathsim.org'; const VIEWPORT = { width: 1000, height: 600 }; const DEVICE_SCALE_FACTOR = 1; const SETTLE_DELAY = 5000; diff --git a/src/lib/constants/engine.ts b/src/lib/constants/engine.ts new file mode 100644 index 00000000..8cbe9611 --- /dev/null +++ b/src/lib/constants/engine.ts @@ -0,0 +1,34 @@ +/** + * Simulation engine selection. + * + * pathview generates Python that imports from the `pathsim` package tree. The + * engine is parameterised so a drop-in replacement with the same module layout + * (``, `.blocks`, `.solvers`, `.events`) and + * class names can be selected at build time via the `VITE_ENGINE` env var. + * + * Defaults to `pathsim`, so an unconfigured build behaves exactly as before: + * `ENGINE_MODULE` is `pathsim` and `enginePath()` is the identity. + * (Uses the VITE_ prefix to match the repo's existing import.meta.env usage.) + */ + +/** Active engine module name, fixed at build time. Defaults to pathsim. */ +export const ENGINE: string = import.meta.env.VITE_ENGINE || 'pathsim'; + +/** Root import module for the active engine (alias of {@link ENGINE}). */ +export const ENGINE_MODULE: string = ENGINE; + +/** + * Map a `pathsim` package import path to the active engine's package tree. + * + * Core paths (`pathsim`, `pathsim.blocks`, `pathsim.solvers`, ...) are rewritten + * to the engine module; everything else (e.g. toolbox import paths like + * `pathsim_chem.blocks`) is left untouched. In the default pathsim build this is + * the identity function. + */ +export function enginePath(path: string): string { + if (ENGINE === 'pathsim') return path; + if (path === 'pathsim' || path.startsWith('pathsim.')) { + return ENGINE_MODULE + path.slice('pathsim'.length); + } + return path; +} diff --git a/src/lib/pyodide/pathsimRunner.ts b/src/lib/pyodide/pathsimRunner.ts index 5bd54b0a..c13ea372 100644 --- a/src/lib/pyodide/pathsimRunner.ts +++ b/src/lib/pyodide/pathsimRunner.ts @@ -12,6 +12,7 @@ import { NODE_TYPES } from '$lib/constants/nodeTypes'; import { BLOCK_CATEGORY_ORDER } from '$lib/constants/python'; import { isSubsystem, isInterface } from '$lib/nodes/shapes'; import { blockImportPaths } from '$lib/nodes/generated/blocks'; +import { ENGINE_MODULE, enginePath } from '$lib/constants/engine'; import { graphStore, findParentSubsystem } from '$lib/stores/graph'; import { runStreamingSimulation, @@ -165,8 +166,11 @@ function collectBlockImportGroups(nodes: NodeInstance[]): Map pkg +// `pathsim-chem`, importPath `pathsim_chem`) never match these exact names. +const ENGINE_MODULES = new Set(['pathsim', 'fastsim', ENGINE_MODULE]); + +function isEngineRequirement(r: ToolboxRequirement): boolean { + const s = (r.source ?? {}) as { pkg?: string; url?: string }; + const candidates = [r.importPath, r.id, s.pkg, s.url].filter(Boolean).map(String); + return candidates.some((c) => ENGINE_MODULES.has(c) || ENGINE_MODULES.has(c.replace(/-/g, '_'))); +} + function walkNodeTypes(nodes: NodeInstance[], out: Set): void { for (const node of nodes) { if (node.type !== NODE_TYPES.SUBSYSTEM && node.type !== NODE_TYPES.INTERFACE) { @@ -57,7 +72,8 @@ export function collectRequiredToolboxes(nodes: NodeInstance[]): ToolboxRequirem const t = installed.find((tb) => tb.id === id); if (t) result.push(toRequirement(t)); } - return result; + // Never persist the engine itself as a requirement (see isEngineRequirement). + return result.filter((r) => !isEngineRequirement(r)); } /** @@ -74,5 +90,5 @@ export function collectRequiredToolboxes(nodes: NodeInstance[]): ToolboxRequirem export function findMissingRequirements(reqs: ToolboxRequirement[]): ToolboxRequirement[] { if (!reqs || reqs.length === 0) return []; const installedKeys = new Set(get(toolboxes).map((t) => toolboxSourceKey(t.source))); - return reqs.filter((r) => !installedKeys.has(toolboxSourceKey(r.source))); + return reqs.filter((r) => !isEngineRequirement(r) && !installedKeys.has(toolboxSourceKey(r.source))); } From c9a604d10d745d97bf3e2153e9c3eb3e28cef09f Mon Sep 17 00:00:00 2001 From: Milan Rother Date: Tue, 2 Jun 2026 09:07:12 +0200 Subject: [PATCH 2/3] Brand config: env-driven BRAND (default PathView/blue) + data-brand hook; components/palette read it --- src/lib/components/WelcomeModal.svelte | 7 ++++--- src/lib/constants/brand.ts | 25 +++++++++++++++++++++++++ src/lib/utils/codemirror.ts | 11 +++++++---- src/lib/utils/colors.ts | 7 +++++-- src/routes/+layout.svelte | 7 +++++++ src/routes/+page.svelte | 7 ++++--- 6 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 src/lib/constants/brand.ts diff --git a/src/lib/components/WelcomeModal.svelte b/src/lib/components/WelcomeModal.svelte index 689a892c..4c0eaa53 100644 --- a/src/lib/components/WelcomeModal.svelte +++ b/src/lib/components/WelcomeModal.svelte @@ -5,6 +5,7 @@ import { cubicOut } from 'svelte/easing'; import Icon from '$lib/components/icons/Icon.svelte'; import { PATHVIEW_VERSION, EXTRACTED_VERSIONS } from '$lib/constants/dependencies'; + import { BRAND } from '$lib/constants/brand'; import { startGuidedTour, type TourId } from '$lib/tours'; interface Example { @@ -102,8 +103,8 @@
- -

Visual block-diagram editor for the PathSim simulation framework

+ +

Visual block-diagram editor for the {BRAND.framework} simulation framework

@@ -112,7 +113,7 @@ New - + Home diff --git a/src/lib/constants/brand.ts b/src/lib/constants/brand.ts new file mode 100644 index 00000000..1f1c4027 --- /dev/null +++ b/src/lib/constants/brand.ts @@ -0,0 +1,25 @@ +/** + * Visible product branding, configurable at build time. + * + * Defaults to PathView (PathSim blue). A re-branded distribution overrides any + * of these via `VITE_BRAND_*` env vars at build time, without touching the + * components that read them. `key` is set as `data-brand` on so CSS can + * key an accent override off it; the JS accent (`accent` / `keywordColor`) feeds + * the canvas default color and the CodeMirror palette. + */ +export const BRAND = { + /** Short key, set as `data-brand` on for CSS overrides. */ + key: import.meta.env.VITE_BRAND_KEY || 'pathsim', + /** Display name (window title, logo alt, autosave prompt, welcome header). */ + name: import.meta.env.VITE_BRAND_NAME || 'PathView', + /** Logo asset filename under static/. */ + logo: import.meta.env.VITE_BRAND_LOGO || 'pathview_logo.png', + /** Primary accent (matches the CSS `--accent` default). */ + accent: import.meta.env.VITE_BRAND_ACCENT || '#0070C0', + /** CodeMirror keyword color (control flow / imports). */ + keywordColor: import.meta.env.VITE_BRAND_KEYWORD || '#E57373', + /** Home link target (welcome modal). */ + home: import.meta.env.VITE_BRAND_HOME || 'https://pathsim.org', + /** Simulation framework name (welcome tagline). */ + framework: import.meta.env.VITE_BRAND_FRAMEWORK || 'PathSim' +}; diff --git a/src/lib/utils/codemirror.ts b/src/lib/utils/codemirror.ts index 47f40608..b6d1fa81 100644 --- a/src/lib/utils/codemirror.ts +++ b/src/lib/utils/codemirror.ts @@ -2,14 +2,17 @@ * Shared CodeMirror utilities for consistent editor setup across the app. */ -// Syntax highlighting colors - aligned with node color palette +import { BRAND } from '$lib/constants/brand'; + +// Syntax highlighting colors - aligned with node color palette. keyword and the +// operator/function accent follow the active brand (defaults: red + PathSim blue). export const SYNTAX_COLORS = { - keyword: '#E57373', // Red - control flow, imports - operator: '#0070C0', // PathSim blue - symbols, operators + keyword: BRAND.keywordColor, // control flow, imports + operator: BRAND.accent, // brand accent - symbols, operators special: '#FFB74D', // Orange - classes, types, decorators number: '#4DB6AC', // Teal - numeric literals string: '#81C784', // Green - string literals - function: '#0070C0', // PathSim blue - function names + function: BRAND.accent, // brand accent - function names comment: { dark: '#505060', light: '#909098' }, // Same as line numbers invalid: '#BA68C8', // Purple - errors // Theme-specific values for variables and punctuation diff --git a/src/lib/utils/colors.ts b/src/lib/utils/colors.ts index 25ed5fe2..dd266f40 100644 --- a/src/lib/utils/colors.ts +++ b/src/lib/utils/colors.ts @@ -2,8 +2,11 @@ * Color definitions for PathView */ -// Default node/event color (matches --pathsim-blue CSS variable) -export const DEFAULT_NODE_COLOR = '#0070C0'; +import { BRAND } from '$lib/constants/brand'; + +// Default node/event/annotation color: the brand accent (matches the CSS +// `--accent` default). Items without an explicit color follow the active brand. +export const DEFAULT_NODE_COLOR = BRAND.accent; // Port colors export const PORT_COLORS = { diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 56a29925..cfb38377 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,6 +1,7 @@