@energy8platform/golem 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49592,6 +49592,7 @@ ${parts.join("\n")}
49592
49592
  });
49593
49593
  var Interpolation = { interpolation: external_exports.enum(["segment", "pchip"]).optional() };
49594
49594
  var Track = external_exports.discriminatedUnion("target", [
49595
+ external_exports.object({ target: external_exports.literal("control"), id: Id, prop: external_exports.literal("value"), ...Interpolation, keys: external_exports.array(Key).min(1) }),
49595
49596
  external_exports.object({ target: external_exports.literal("bone"), id: Id, prop: BoneProp, ...Interpolation, keys: external_exports.array(Key).min(1) }),
49596
49597
  external_exports.object({ target: external_exports.literal("slot"), id: Id, prop: SlotProp, ...Interpolation, keys: external_exports.array(Key).min(1) }),
49597
49598
  external_exports.object({ target: external_exports.literal("deform"), id: Id, prop: DeformProp, ...Interpolation, keys: external_exports.array(Key).min(1) }),
@@ -49614,6 +49615,25 @@ ${parts.join("\n")}
49614
49615
  /** if generated from a preset — keep params so the agent can re-tune instead of re-keying */
49615
49616
  preset: external_exports.object({ name: external_exports.string(), params: external_exports.record(external_exports.unknown()) }).optional()
49616
49617
  });
49618
+ var RigControl = external_exports.object({
49619
+ id: Id,
49620
+ label: external_exports.string().optional(),
49621
+ min: external_exports.number().finite(),
49622
+ max: external_exports.number().finite(),
49623
+ default: external_exports.number().finite()
49624
+ }).strict();
49625
+ var ControlValues = external_exports.array(external_exports.union([external_exports.number().finite(), external_exports.array(external_exports.number().finite()).min(1)])).min(2);
49626
+ var ControlChannel = external_exports.discriminatedUnion("target", [
49627
+ external_exports.object({ target: external_exports.literal("bone"), id: Id, prop: BoneProp, values: ControlValues }).strict(),
49628
+ external_exports.object({ target: external_exports.literal("slot"), id: Id, prop: external_exports.literal("alpha"), values: ControlValues }).strict(),
49629
+ external_exports.object({ target: external_exports.literal("deform"), id: Id, prop: external_exports.literal("vertices"), values: ControlValues }).strict()
49630
+ ]);
49631
+ var ControlBinding = external_exports.object({
49632
+ id: Id,
49633
+ axes: external_exports.array(external_exports.object({ control: Id, keys: external_exports.array(external_exports.number().finite()).min(2).max(32) }).strict()).min(1).max(2),
49634
+ /** Flattened grid, first axis varies fastest. Linear / bilinear interpolation. */
49635
+ channels: external_exports.array(ControlChannel).min(1)
49636
+ }).strict();
49617
49637
  var Reference = external_exports.object({
49618
49638
  src: external_exports.string(),
49619
49639
  opacity: external_exports.number().min(0).max(1).default(0.5),
@@ -49639,7 +49659,9 @@ ${parts.join("\n")}
49639
49659
  ik: external_exports.array(IkConstraint).default([]),
49640
49660
  transform: external_exports.array(TransformConstraint).default([]),
49641
49661
  path: external_exports.array(PathConstraint).default([]),
49642
- animations: external_exports.array(Animation)
49662
+ animations: external_exports.array(Animation),
49663
+ controls: external_exports.array(RigControl).optional(),
49664
+ controlBindings: external_exports.array(ControlBinding).optional()
49643
49665
  });
49644
49666
  var RigDocument = external_exports.preprocess((input) => {
49645
49667
  const v2 = input?.version;
@@ -49714,6 +49736,8 @@ ${parts.join("\n")}
49714
49736
  dup(doc.ik, "ik");
49715
49737
  dup(doc.transform, "transform");
49716
49738
  dup(doc.path, "path");
49739
+ dup(doc.controls ?? [], "control");
49740
+ dup(doc.controlBindings ?? [], "control binding");
49717
49741
  const bones = new Set(doc.bones.map((b2) => b2.id));
49718
49742
  const assets = new Map(doc.assets.map((a2) => [a2.id, a2]));
49719
49743
  const slots = new Set(doc.slots.map((s2) => s2.id));
@@ -49788,13 +49812,47 @@ ${parts.join("\n")}
49788
49812
  for (const b2 of c2.bones) if (!bones.has(b2)) errs.push(`path "${c2.id}": unknown bone "${b2}"`);
49789
49813
  if (!slots.has(c2.target)) errs.push(`path "${c2.id}": unknown target slot "${c2.target}"`);
49790
49814
  }
49815
+ const controls = new Map((doc.controls ?? []).map((c2) => [c2.id, c2]));
49816
+ const driven = /* @__PURE__ */ new Set();
49817
+ for (const c2 of controls.values()) if (!(c2.min < c2.max && c2.default >= c2.min && c2.default <= c2.max))
49818
+ errs.push(`control "${c2.id}": require min < max and default within range`);
49819
+ for (const b2 of doc.controlBindings ?? []) {
49820
+ const prefix = `control binding "${b2.id}"`;
49821
+ if (new Set(b2.axes.map((a2) => a2.control)).size !== b2.axes.length) errs.push(`${prefix}: axes must use different controls`);
49822
+ for (const axis of b2.axes) {
49823
+ const c2 = controls.get(axis.control);
49824
+ if (!c2) errs.push(`${prefix}: unknown control "${axis.control}"`);
49825
+ else if (axis.keys[0] !== c2.min || axis.keys.at(-1) !== c2.max) errs.push(`${prefix}: keys must span the control's full range`);
49826
+ if (axis.keys.some((v2, i2) => i2 > 0 && v2 <= axis.keys[i2 - 1])) errs.push(`${prefix}: keys must be strictly increasing`);
49827
+ }
49828
+ const count2 = b2.axes.reduce((n2, a2) => n2 * a2.keys.length, 1);
49829
+ for (const ch of b2.channels) {
49830
+ const key = `${ch.target}.${ch.id}.${ch.prop}`;
49831
+ if (driven.has(key)) errs.push(`${prefix}: channel ${key} already owned by a binding`);
49832
+ driven.add(key);
49833
+ if (ch.values.length !== count2) errs.push(`${prefix}: ${key} needs ${count2} grid values`);
49834
+ if (ch.target === "deform") {
49835
+ const att = attachments.get(ch.id);
49836
+ if (!att || att.type === "region") errs.push(`${prefix}: vertices need a mesh or path attachment`);
49837
+ else {
49838
+ const n2 = att.weights ? att.weights.reduce((s2, a2) => s2 + a2.length * 2, 0) : att.vertices.length;
49839
+ if (ch.values.some((v2) => !Array.isArray(v2) || v2.length !== n2)) errs.push(`${prefix}: ${key} needs arrays of ${n2} offsets`);
49840
+ }
49841
+ } else {
49842
+ if (!(ch.target === "bone" ? bones : slots).has(ch.id)) errs.push(`${prefix}: unknown ${ch.target} "${ch.id}"`);
49843
+ if (ch.values.some((v2) => typeof v2 !== "number")) errs.push(`${prefix}: ${key} needs numeric values`);
49844
+ if (ch.target === "slot" && ch.values.some((v2) => typeof v2 === "number" && (v2 < 0 || v2 > 1))) errs.push(`${prefix}: alpha must be in 0..1`);
49845
+ }
49846
+ }
49847
+ }
49791
49848
  for (const a2 of doc.animations) {
49792
49849
  const tracks = /* @__PURE__ */ new Set();
49793
49850
  for (const t2 of a2.tracks) {
49794
49851
  const tid = trackId(t2);
49795
49852
  if (tracks.has(tid)) errs.push(`animation "${a2.id}": duplicate track ${tid}`);
49796
49853
  tracks.add(tid);
49797
- const known = t2.target === "bone" ? bones.has(t2.id) : t2.target === "slot" ? slots.has(t2.id) : t2.target === "deform" ? attachments.has(t2.id) : t2.target === "ik" ? iks.has(t2.id) : t2.target === "transform" ? transforms.has(t2.id) : t2.target === "path" ? paths.has(t2.id) : true;
49854
+ if (driven.has(tid)) errs.push(`animation "${a2.id}": ${tid} is controlled by a binding; animate its control instead`);
49855
+ const known = t2.target === "bone" ? bones.has(t2.id) : t2.target === "slot" ? slots.has(t2.id) : t2.target === "deform" ? attachments.has(t2.id) : t2.target === "ik" ? iks.has(t2.id) : t2.target === "transform" ? transforms.has(t2.id) : t2.target === "path" ? paths.has(t2.id) : t2.target === "control" ? controls.has(t2.id) : true;
49798
49856
  if (!known) errs.push(`animation "${a2.id}": unknown ${t2.target} "${t2.id}"`);
49799
49857
  const bound = t2.target === "deform" ? DEFORM_BOUNDS[t2.prop] : void 0;
49800
49858
  const att = t2.target === "deform" ? attachments.get(t2.id) : void 0;
@@ -49802,6 +49860,10 @@ ${parts.join("\n")}
49802
49860
  for (const k2 of t2.keys) {
49803
49861
  if (k2.t <= prev && prev >= 0) errs.push(`animation "${a2.id}" ${tid}: keys not strictly increasing`);
49804
49862
  if (k2.t > a2.duration) errs.push(`animation "${a2.id}" ${tid}: key at ${k2.t}s exceeds duration`);
49863
+ if (t2.target === "control") {
49864
+ const c2 = controls.get(t2.id);
49865
+ if (typeof k2.v !== "number" || !Number.isFinite(k2.v) || c2 && (k2.v < c2.min || k2.v > c2.max)) errs.push(`animation "${a2.id}" ${tid}: control key outside its finite range`);
49866
+ }
49805
49867
  if (t2.target === "slot" && t2.prop === "attachment") {
49806
49868
  if (k2.v !== null && typeof k2.v !== "string") errs.push(`animation "${a2.id}" ${tid}: attachment key must be an attachment id or null`);
49807
49869
  else if (typeof k2.v === "string") {
@@ -49840,6 +49902,52 @@ ${parts.join("\n")}
49840
49902
  return doc;
49841
49903
  }
49842
49904
 
49905
+ // src/rig-controls.ts
49906
+ function controlPose(doc, values) {
49907
+ const out2 = /* @__PURE__ */ new Map();
49908
+ for (const [id, value] of Object.entries(values)) {
49909
+ const c2 = doc.controls?.find((c3) => c3.id === id);
49910
+ if (!c2) throw new Error(`unknown control "${id}"`);
49911
+ if (!Number.isFinite(value)) throw new Error(`control "${id}" must be finite`);
49912
+ out2.set(`control:${id}:value`, Math.max(c2.min, Math.min(c2.max, value)));
49913
+ }
49914
+ return out2;
49915
+ }
49916
+ function bracket(keys, value) {
49917
+ let i2 = 0;
49918
+ while (i2 < keys.length - 2 && value > keys[i2 + 1]) i2++;
49919
+ return [i2, Math.max(0, Math.min(1, (value - keys[i2]) / (keys[i2 + 1] - keys[i2])))];
49920
+ }
49921
+ function sampleBinding(binding, values) {
49922
+ const [x2, u2] = bracket(binding.axes[0].keys, values[0]);
49923
+ const [y2, v2] = binding.axes[1] ? bracket(binding.axes[1].keys, values[1]) : [0, 0];
49924
+ const width = binding.axes[0].keys.length, a2 = y2 * width + x2;
49925
+ const mix = (a3, b2, u3) => a3 + (b2 - a3) * u3;
49926
+ return binding.channels.map((ch) => {
49927
+ const read = (i2, j2) => j2 === void 0 ? ch.values[i2] : ch.values[i2][j2];
49928
+ const at = (j2) => {
49929
+ const top = mix(read(a2, j2), read(a2 + 1, j2), u2);
49930
+ return binding.axes[1] ? mix(top, mix(read(a2 + width, j2), read(a2 + width + 1, j2), u2), v2) : top;
49931
+ };
49932
+ return Array.isArray(ch.values[0]) ? ch.values[0].map((_, j2) => at(j2)) : at();
49933
+ });
49934
+ }
49935
+ function resolveControls(doc, input) {
49936
+ if (!doc.controlBindings?.length) return input;
49937
+ const out2 = new Map(input), controls = new Map(doc.controls?.map((c2) => [c2.id, c2]));
49938
+ for (const b2 of doc.controlBindings) {
49939
+ const values = b2.axes.map((axis) => {
49940
+ const c2 = controls.get(axis.control);
49941
+ const value = input.get(`control:${c2.id}:value`) ?? c2.default;
49942
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`control "${c2.id}" must be finite`);
49943
+ return Math.max(c2.min, Math.min(c2.max, value));
49944
+ });
49945
+ const sampled = sampleBinding(b2, values);
49946
+ b2.channels.forEach((ch, i2) => out2.set(`${ch.target}:${ch.id}:${ch.prop}`, sampled[i2]));
49947
+ }
49948
+ return out2;
49949
+ }
49950
+
49843
49951
  // src/rig-anim.ts
49844
49952
  var DEG2 = Math.PI / 180;
49845
49953
  var named = {
@@ -49943,6 +50051,7 @@ ${parts.join("\n")}
49943
50051
  }
49944
50052
  function restValue(doc, key) {
49945
50053
  const [target, id, prop] = key.split(":");
50054
+ if (target === "control") return doc.controls?.find((c2) => c2.id === id)?.default ?? 0;
49946
50055
  if (target === "bone") return doc.bones.find((b2) => b2.id === id).transform[prop];
49947
50056
  if (target === "slot") {
49948
50057
  const s2 = doc.slots.find((s3) => s3.id === id);
@@ -50045,7 +50154,9 @@ ${parts.join("\n")}
50045
50154
  for (const k2 of kids.get(id) ?? []) stack.push(k2.id);
50046
50155
  }
50047
50156
  }
50048
- function evaluate(doc, pose) {
50157
+ function evaluate(doc, pose, modifiers) {
50158
+ pose = resolveControls(doc, pose);
50159
+ if (modifiers) for (const modify of modifiers) pose = modify(new Map(pose));
50049
50160
  const skel = { doc, pose, bones: /* @__PURE__ */ new Map(), attachment: /* @__PURE__ */ new Map() };
50050
50161
  for (const b2 of boneOrder(doc)) {
50051
50162
  const s2 = localOf(doc, pose, b2);
@@ -50850,9 +50961,23 @@ ${parts.join("\n")}
50850
50961
  this.state = new AnimationState(this, { ...opts, mix: opts.mix ?? 0.18 });
50851
50962
  this.setPose(/* @__PURE__ */ new Map());
50852
50963
  }
50964
+ inputPose = /* @__PURE__ */ new Map();
50965
+ controlOverrides = /* @__PURE__ */ new Map();
50966
+ /** Manual inputs override animated controls until resetControls; values are clamped to declared ranges. */
50967
+ setControls(values) {
50968
+ this.controlOverrides = controlPose(this.doc, values);
50969
+ this.setPose(this.inputPose);
50970
+ }
50971
+ resetControls() {
50972
+ this.controlOverrides.clear();
50973
+ this.setPose(this.inputPose);
50974
+ }
50853
50975
  view = new Container();
50854
50976
  state;
50855
50977
  items = [];
50978
+ beforePose = /* @__PURE__ */ new Set();
50979
+ afterPose = /* @__PURE__ */ new Set();
50980
+ clipPoints = /* @__PURE__ */ new Map();
50856
50981
  skel;
50857
50982
  textures;
50858
50983
  pages = /* @__PURE__ */ new Map();
@@ -50861,6 +50986,35 @@ ${parts.join("\n")}
50861
50986
  /** slots the editor has selected; setPose draws them at GHOST_ALPHA at least */
50862
50987
  ghosts = /* @__PURE__ */ new Set();
50863
50988
  clipGraphics = /* @__PURE__ */ new Map();
50989
+ /** Read-only render handles for in-page tools. Nodes remain owned by the player. */
50990
+ get renderItems() {
50991
+ return this.items;
50992
+ }
50993
+ /** Current document-space clipping polygons, keyed by path attachment id. */
50994
+ get clipPolygons() {
50995
+ return this.clipPoints;
50996
+ }
50997
+ /**
50998
+ * Modify resolved control outputs before skeleton evaluation, constraints and skinning.
50999
+ * Callbacks run in registration order, each with its own map. Change control inputs via setControls.
51000
+ */
51001
+ onBeforePose(callback) {
51002
+ this.beforePose.add(callback);
51003
+ return () => {
51004
+ this.beforePose.delete(callback);
51005
+ };
51006
+ }
51007
+ /** Subscribe after the base pose has been rebuilt. Return value removes the callback. */
51008
+ onAfterPose(callback) {
51009
+ this.afterPose.add(callback);
51010
+ return () => {
51011
+ this.afterPose.delete(callback);
51012
+ };
51013
+ }
51014
+ /** Rebuild the current pose without advancing playback; useful for reversible overlays. */
51015
+ refreshPose() {
51016
+ this.setPose(this.inputPose);
51017
+ }
50864
51018
  /** whole atlas page as a plain texture (identity uv mapping) for skinned meshes */
50865
51019
  pageTexture(tex) {
50866
51020
  let page = this.pages.get(tex.source);
@@ -50915,7 +51069,11 @@ ${parts.join("\n")}
50915
51069
  }
50916
51070
  /** Apply a pose (rest where silent) to the display objects. */
50917
51071
  setPose(pose) {
50918
- const skel = this.skel = evaluate(this.doc, pose);
51072
+ this.inputPose = new Map(pose);
51073
+ if (this.controlOverrides.size) pose = new Map([...pose, ...this.controlOverrides]);
51074
+ this.clipPoints.clear();
51075
+ const skel = this.skel = evaluate(this.doc, pose, this.beforePose);
51076
+ pose = skel.pose;
50919
51077
  const clips = new Set(this.doc.slots.flatMap((s2) => s2.clip ? [s2.clip] : []));
50920
51078
  for (const [id, g2] of this.clipGraphics) if (!clips.has(id)) {
50921
51079
  g2.destroy();
@@ -50933,7 +51091,9 @@ ${parts.join("\n")}
50933
51091
  const vertices = attachmentWorldVertices(skel, att.slot, att);
50934
51092
  const n2 = Math.max(32, vertices.length * 4);
50935
51093
  const points = samplePath(vertices, true, att.lengths, true, Array.from({ length: n2 }, (_, i2) => i2 / n2), true);
50936
- g2.clear().poly(points.flatMap((p2) => [p2.x, p2.y]), true).fill(16777215);
51094
+ const polygon = points.flatMap((p2) => [p2.x, p2.y]);
51095
+ this.clipPoints.set(id, polygon);
51096
+ g2.clear().poly(polygon, true).fill(16777215);
50937
51097
  }
50938
51098
  const m2 = new Matrix();
50939
51099
  const orderV = pose.get(pk("drawOrder", "order", "order"));
@@ -50954,6 +51114,7 @@ ${parts.join("\n")}
50954
51114
  it.node.tint = rgbOf(color);
50955
51115
  it.node.zIndex = order.size ? order.get(s2.id) ?? 0 : numOf(pose, pk("slot", s2.id, "z"), s2.z);
50956
51116
  if (it.skinned) {
51117
+ it.node.setFromMatrix(m2.identity());
50957
51118
  const v2 = attachmentWorldVertices(skel, s2.id, it.att);
50958
51119
  const buf = it.skinned.mesh.geometry.getBuffer("aPosition");
50959
51120
  buf.data.set(v2);
@@ -50986,6 +51147,7 @@ ${parts.join("\n")}
50986
51147
  it.node.scale.y *= h2 / (this.textures.get(att.asset)?.height ?? h2);
50987
51148
  }
50988
51149
  }
51150
+ for (const callback of this.afterPose) callback(this);
50989
51151
  }
50990
51152
  /** World-space bone positions — used by editor gizmos and renderPreview overlays */
50991
51153
  bonePositions() {
@@ -0,0 +1,37 @@
1
+ import type { RigPlayer } from "./rig-runtime";
2
+ import type { PartTransform } from "./interactive-editor";
3
+ export interface BoneEditPatch {
4
+ format: "golem.bone-edits";
5
+ version: 1;
6
+ documentId: string;
7
+ transforms: Record<string, PartTransform>;
8
+ }
9
+ export interface InteractiveBoneEditorOptions {
10
+ player: Pick<RigPlayer, "doc" | "onBeforePose" | "refreshPose">;
11
+ documentId: string;
12
+ onChange?: (patch: BoneEditPatch) => void;
13
+ onRender?: () => void;
14
+ historyLimit?: number;
15
+ }
16
+ /** Add bone offsets after expression controls, before constraints and native skinning. */
17
+ export declare function createInteractiveBoneEditor(options: InteractiveBoneEditorOptions): {
18
+ getTransform: (id: string) => {
19
+ x: number;
20
+ y: number;
21
+ rotation: number;
22
+ scaleX: number;
23
+ scaleY: number;
24
+ };
25
+ exportPatch: () => BoneEditPatch;
26
+ validatePatch: (value: unknown) => BoneEditPatch;
27
+ readonly canUndo: boolean;
28
+ readonly canRedo: boolean;
29
+ setTransform(id: string, value: Partial<PartTransform>): void;
30
+ importPatch(value: unknown): void;
31
+ reset(id?: string): void;
32
+ setVisible(value: boolean): void;
33
+ undo(): void;
34
+ redo(): void;
35
+ dispose(): void;
36
+ };
37
+ export type InteractiveBoneEditor = ReturnType<typeof createInteractiveBoneEditor>;
@@ -0,0 +1,80 @@
1
+ import type { RigPlayer } from "./rig-runtime";
2
+ export interface EditorPoint {
3
+ x: number;
4
+ y: number;
5
+ }
6
+ export interface PartTransform {
7
+ x: number;
8
+ y: number;
9
+ rotation: number;
10
+ scaleX: number;
11
+ scaleY: number;
12
+ }
13
+ export interface InteractiveEditPatch {
14
+ format: "golem.interactive-edits";
15
+ version: 1;
16
+ documentId: string;
17
+ transforms: Record<string, PartTransform>;
18
+ }
19
+ export interface EditorGroup {
20
+ id: string;
21
+ members: readonly string[];
22
+ pivotBone?: string;
23
+ }
24
+ export type InteractivePlayer = Pick<RigPlayer, "doc" | "view" | "world" | "renderItems" | "clipPolygons" | "onAfterPose" | "refreshPose">;
25
+ export interface InteractiveEditorOptions {
26
+ player: InteractivePlayer;
27
+ /** Transparent overlay aligned with the player's rendered viewport. It is cleared by the editor. */
28
+ overlay: HTMLCanvasElement;
29
+ /** Host-defined stable rig fingerprint; mismatched patches are rejected. */
30
+ documentId: string;
31
+ groups?: readonly EditorGroup[];
32
+ /** Override when overlay backing pixels differ from the renderer's logical coordinates. */
33
+ screenToDocument?: (client: EditorPoint) => EditorPoint;
34
+ documentToOverlay?: (point: EditorPoint) => EditorPoint;
35
+ alphaFallback?: "skip" | "geometry";
36
+ onChange?: (patch: InteractiveEditPatch) => void;
37
+ onSelection?: (id: string | null) => void;
38
+ onHover?: (id: string | null) => void;
39
+ onError?: (error: Error) => void;
40
+ onGestureStart?: () => void;
41
+ /** Render the host scene after a manual refresh; never called from the pose hook. */
42
+ onRender?: () => void;
43
+ historyLimit?: number;
44
+ }
45
+ export interface EditorFrame {
46
+ id: string;
47
+ center: EditorPoint;
48
+ corners: EditorPoint[];
49
+ handles: {
50
+ name: string;
51
+ p: EditorPoint;
52
+ }[];
53
+ }
54
+ /** Embedded part editor. Source documents are never mutated; edits replay after every evaluated pose. */
55
+ export declare function createInteractiveEditor(options: InteractiveEditorOptions): {
56
+ select: (id: string | null) => void;
57
+ hitTest: (p: EditorPoint) => string | null;
58
+ getFrame: (id: string) => EditorFrame | null;
59
+ getTransform: (id: string) => {
60
+ x: number;
61
+ y: number;
62
+ rotation: number;
63
+ scaleX: number;
64
+ scaleY: number;
65
+ };
66
+ readonly selection: string | null;
67
+ readonly canUndo: boolean;
68
+ readonly canRedo: boolean;
69
+ exportPatch: () => InteractiveEditPatch;
70
+ setEnabled(value: boolean): void;
71
+ setVisible(value: boolean): void;
72
+ setTransform(id: string, value: Partial<PartTransform>): void;
73
+ reset(id?: string): void;
74
+ undo(): void;
75
+ redo(): void;
76
+ importPatch(value: unknown): void;
77
+ dispose(): void;
78
+ };
79
+ export type InteractiveEditor = ReturnType<typeof createInteractiveEditor>;
80
+ export { createInteractiveBoneEditor, type BoneEditPatch, type InteractiveBoneEditorOptions } from "./interactive-bones";