PartHistory lives in src/PartHistory.js and is the core modeling history manager. It owns the feature list, the shared Three.js scene, the expression scratchpad, configurator state, PMI views, metadata, and the assembly constraint history. It can rebuild geometry deterministically by replaying feature entries, serialize and restore part state, and maintain undo/redo via JSON snapshots.
- Manage the ordered list of modeling features (
features). - Own and mutate the shared Three.js
scene. - Evaluate parameter expressions via a user-defined expression script.
- Store configurator widget definitions and current configurator values.
- Run the history to (re)build geometry from feature inputs.
- Persist and restore feature history, PMI views, metadata, and assembly constraints.
- Track and apply undo/redo snapshots.
- Sync and update assembly component data.
Creates a new history manager and initializes:
features: empty arrayscene:new THREE.Scene()featureRegistry:new FeatureRegistry()assemblyConstraintRegistry:new AssemblyConstraintRegistry()assemblyConstraintHistory:new AssemblyConstraintHistory(this, registry)pmiViewsManager:new PMIViewsManager(this)metadataManager:new MetadataManager()expressions: default example script stringconfigurator:{ fields: [], values: {} }callbacks: empty bag for optional hooks- Undo/redo state in
_historyUndo
The constructor also soft-overrides scene.remove and scene.add to aid debugging and to block removal of objects with userData.preventRemove.
Each entry in features is a plain object with at least:
{
type: string, // Feature type string (registry key)
inputParams: Object, // User inputs; includes an id
persistentData: Object, // Saved outputs from previous runs
timestamp?: number, // Last run time (ms epoch)
dirty?: boolean, // Marks entry for re-run
lastRunInputParams?: string, // JSON string for dirty detection
lastRun?: { // Execution metadata
ok: boolean,
startedAt: number,
endedAt: number,
durationMs: number,
error: { name, message, stack } | null
},
effects?: { added: [], removed: [] },
previouseExpressions?: Object // Cached evaluated numeric inputs
}IDs are normalized and kept in sync:
feature.inputParams.idandfeature.idare strings.- A non-enumerable
feature.inputParams.featureIDproperty is defined as an alias ofidfor legacy compatibility.
A feature class (from FeatureRegistry) is expected to provide:
static inputParamsSchemadescribing inputs and defaults.run(partHistory)which returns{ added, removed }.- Optional
longName,shortName,namefor display. - (Sketch only)
hasSketchChanged(feature)for dirty detection.
run() should return an object with:
added: array of objects to add to the sceneremoved: array of objects to remove from the scene
Objects are expected to be compatible with Three.js scene nodes. If they implement free() and/or visualize(), those are called automatically.
For the user-facing guide focused specifically on the Expressions panel and configurator workflow, see Expressions and Configurator.
expressions is a user-editable script string stored on partHistory.expressions. Expression-aware inputs evaluate against a shared source built from:
- the default prelude (
resolution = 32;) - the current configurator values object (
configurator) - the user script in
expressions
This means feature dialogs can reference both scratch variables and configurator fields:
width = 20;
height = width * 2;configurator.panelWidth
configurator.materialName- Static helper:
PartHistory.evaluateExpression(expressionsSource, equation) - Instance helper:
partHistory.evaluateExpression(equation)
evaluateExpression uses Function() to execute the script and return an evaluated value. It returns null on error. Treat expression input as code (do not evaluate untrusted content).
configurator is stored on partHistory.configurator and has this normalized shape:
{
fields: [
{
name: 'panelWidth',
label: 'Panel Width',
type: 'slider', // slider | number | select | string
defaultValue: 42,
min: 0,
max: 100,
step: 1
}
],
values: {
panelWidth: 42
}
}Behavior in the Expressions sidebar:
- The live configurator form is shown above the expression editor only when at least one configurator field exists.
- The Edit Configurator session can add, remove, and edit widgets of type
slider,number,select, andstring. - While the editor is open, the live configurator form previews the draft widget set in real time.
- The model is not re-run for those draft edits until the edit session is closed or saved.
- After the edit session is committed,
runHistory()is triggered and the resulting configurator values become available throughconfigurator.fieldName.
Rebuilds the scene by replaying features in order.
High-level flow:
- Clear the scene and dispose resources, preserving lights, cameras, and transform gizmos.
- Iterate features in order:
- Normalize ID linkage for each feature.
- Stop after
currentHistoryStepId(if set) but keep later features in the list. - Resolve the feature class from the registry; if missing, mark
lastRunwith a MissingFeature error and continue. - Copy
persistentDatainto the feature instance. - Remove any existing scene children with
owningFeatureID === featureId(rerun case). - Compute
dirtystate based on:- changed input params
- feature timestamps vs. upstream features
- newer referenced objects
- expression evaluation changes
- Sketch change detection
- If dirty:
- call
run() - record
lastRun,timestamp,effects,lastRunInputParams
- call
- Apply
effectsviaapplyFeatureEffects().
- Run assembly constraints.
- Call optional callbacks.
Notes:
currentHistoryStepIdis not cleared insiderunHistory().- Any feature error stops the run and logs the error.
runHistory()returnsthis.
When adding objects:
applyFeatureEffects()callsfree()andvisualize()if present.- Each added object and its descendants get
timestampandowningFeatureID. - Each object gets an
onClickhandler that usesSelectionFilter.toggleSelection.
Objects with userData.preventRemove are not removed when the scene is cleared.
Returns a pretty-printed JSON string containing:
features(type, inputParams, persistentData, timestamp)idCounterexpressionsconfiguratorpmiViews(viaPMIViewsManager)metadata(fromMetadataManager)assemblyConstraintsandassemblyConstraintIdCounter
Before export, assembly component transforms are synced into feature input params.
Restores state from a JSON string. Behavior:
- Migrates legacy
featureIDfields toid. - Rehydrates PMI views and metadata.
- Loads assembly constraints into
AssemblyConstraintHistory. - Resets undo/redo history unless
options.skipUndoResetistrue.
PartHistory maintains a JSON snapshot stack:
_historyUndo.undoStackand_historyUndo.redoStack- Debounced snapshot capture (
debounceMs, default 350ms) - Max snapshot count (
max, default 50)
Key methods:
queueHistorySnapshot({ debounceMs, force })flushHistorySnapshot({ force })undoFeatureHistory()redoFeatureHistory()canUndoFeatureHistory()canRedoFeatureHistory()
Undo/redo restores by calling fromJSON() and re-running runHistory().
assemblyConstraintHistoryowns constraints and runs the solver.runAssemblyConstraints()delegates toAssemblyConstraintHistory.runAll().
Assembly component utilities:
hasAssemblyComponents(): returns true if any feature is an assembly component.syncAssemblyComponentTransforms(): copies scene transforms into feature input params.getOutdatedAssemblyComponentCount(): counts components whose source data changed.updateAssemblyComponents({ rerun = true }): refreshes component data and optionally re-runs history.
features: Array<Object>scene: THREE.SceneidCounter: numberfeatureRegistry: FeatureRegistryassemblyConstraintRegistry: AssemblyConstraintRegistryassemblyConstraintHistory: AssemblyConstraintHistorypmiViewsManager: PMIViewsManagermetadataManager: MetadataManagerexpressions: stringconfigurator: { fields: Array<Object>, values: Object }currentHistoryStepId: string | nullcallbacks: Object(optional hooks)_historyUndo: Object(internal state)
callbacks is a simple bag of optional functions:
callbacks.run(featureId)- called before each feature runs (awaited).callbacks.reset()- called afterreset()clears scene (awaited).callbacks.afterRunHistory()- called afterrunHistory()completes.callbacks.afterReset()- called afterreset()finishes.
Evaluates equation using expressionsSource as the preamble. Returns null on error.
Same as static helper but uses this.getExpressionsSource(), which includes the default prelude and the current configurator values.
Builds the executable expression preamble from the default resolution, the current configurator values, and the user script.
Returns the fully assembled expression source used by dialogs and evaluators.
Returns a normalized copy of the current configurator definition and values.
Returns the normalized configurator.values object that is injected into the expression runtime.
Delegates to scene.getObjectByName.
Clears features, resets managers, clears the scene, and resets undo/redo. Calls callbacks.reset and callbacks.afterReset.
Replays features, rebuilds the scene, runs constraints, and calls callbacks.afterRunHistory.
Applies added/removed artifacts to the scene, sets timestamps/ownership, and attaches selection handlers.
Serializes the full part history and related state.
Restores from a serialized history string.
Increments idCounter and returns ${prefix}${idCounter}.
Normalizes and evaluates input params based on schema types:
number: evaluates expressionsstringwithallowExpression: true: evaluates expressions and coerces the result back to a stringreference_selection: resolves object names to scene objectsboolean_operation: normalizes op and target list, optional bias/offsettransform: evaluates position/rotation/scale arraysvec3: evaluates 3-vector entriesboolean: normalizes to boolean- default: pass-through
Creates a new feature entry, seeds defaults from schema, assigns an ID, and appends it to features.
Removes any feature with matching ID.
Clears undo/redo stacks and internal flags.
Debounced snapshot capture.
Forces a snapshot immediately.
Restores the previous snapshot if available.
Reapplies the next snapshot if available.
Runs the assembly constraint solver.
Checks if any feature is an assembly component.
Writes current scene transforms into feature input params.
Returns number of outdated components.
Refreshes assembly component data; optionally re-runs history.
These methods are intended for internal use:
_coerceRunEffects,_attachSelectionHandlers,_safeRemove_commitHistorySnapshot,_applyHistorySnapshot_sanitizePersistentDataForExport,_collectAssemblyComponentUpdates#runFeatureEntryMigrations,#linkFeatureParams,#prepareFeatureEntry,#prepareFeatureList#disposeSceneObjects,#disposeObjectResources,#disposeMaterialResources
Exported alongside PartHistory. Returns a deep-cloned object of default_value for each schema key.
import { PartHistory } from './PartHistory.js';
const history = new PartHistory();
const feature = await history.newFeature('Sketch');
// set feature.inputParams as needed
await history.runHistory();history.expressions = "x = 10; y = x * 2;";
feature.inputParams.depth = "y + 5";
await history.runHistory();history.configurator = {
fields: [
{ name: 'panelWidth', label: 'Panel Width', type: 'number', defaultValue: 24, step: 1 }
],
values: {
panelWidth: 24
}
};
feature.inputParams.width = "configurator.panelWidth * 2";
await history.runHistory();const json = await history.toJSON();
const restored = new PartHistory();
await restored.fromJSON(json);
await restored.runHistory();history.queueHistorySnapshot();
await history.undoFeatureHistory();
await history.redoFeatureHistory();