@aura3d/editor-runtime 1.3.3 → 1.4.1
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.
- package/package.json +36 -5
- package/src/AnimationSceneEditor.ts +0 -368
- package/src/AssetDropZone.ts +0 -144
- package/src/CameraPathEditor.ts +0 -223
- package/src/Command.ts +0 -11
- package/src/CommandHistory.ts +0 -136
- package/src/CurveEditor.ts +0 -152
- package/src/DiagnosticsOverlayModel.ts +0 -85
- package/src/EditorPluginHost.ts +0 -106
- package/src/EditorRuntime.ts +0 -349
- package/src/EditorStateModel.ts +0 -336
- package/src/EpisodeReviewPanel.ts +0 -236
- package/src/EventTrackEditor.ts +0 -169
- package/src/Gizmo.ts +0 -101
- package/src/HierarchyModel.ts +0 -57
- package/src/InspectorModel.ts +0 -71
- package/src/KeyframeEditor.ts +0 -214
- package/src/LocalizationAccessibilityFixtures.ts +0 -368
- package/src/MaterialVariantWorkflow.ts +0 -101
- package/src/MultiUserReviewWorkflow.ts +0 -229
- package/src/NonlinearAnimationEditor.ts +0 -262
- package/src/PickingService.ts +0 -174
- package/src/PlayModeBridge.ts +0 -27
- package/src/PrefabRegistry.ts +0 -164
- package/src/ProjectSerializer.ts +0 -200
- package/src/PropertyPanel.ts +0 -82
- package/src/RenderQueuePanel.ts +0 -106
- package/src/RotateGizmo.ts +0 -35
- package/src/ScaleGizmo.ts +0 -40
- package/src/SceneOutliner.ts +0 -78
- package/src/Selection.ts +0 -63
- package/src/ShaderGraphModel.ts +0 -254
- package/src/StaticExportRuntime.ts +0 -322
- package/src/TimelineEditorController.ts +0 -525
- package/src/TimelineModel.ts +0 -466
- package/src/TimelineRuntimeBridge.ts +0 -280
- package/src/TimelineTrackTypes.ts +0 -89
- package/src/TimelineUI.ts +0 -153
- package/src/TranslateGizmo.ts +0 -39
- package/src/VisualReviewDashboard.ts +0 -87
- package/src/commands/CreateNodeCommand.ts +0 -23
- package/src/commands/DeleteNodeCommand.ts +0 -74
- package/src/commands/ReparentNodeCommand.ts +0 -55
- package/src/commands/SetPropertyCommand.ts +0 -47
- package/src/commands/TransformCommand.ts +0 -107
- package/src/index.ts +0 -140
- package/tests/editor-runtime.test.ts +0 -1087
- package/tsconfig.json +0 -7
package/src/ProjectSerializer.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { TimelineModel, type TimelineModelConfig } from "./TimelineModel";
|
|
2
|
-
import type { TimelineRuntimeBindingConfig } from "./TimelineRuntimeBridge";
|
|
3
|
-
|
|
4
|
-
export interface EditorProjectAssetDocument {
|
|
5
|
-
readonly id: string;
|
|
6
|
-
readonly name: string;
|
|
7
|
-
readonly uri?: string;
|
|
8
|
-
readonly type?: "gltf" | "glb" | "image" | "audio" | "material" | "unknown";
|
|
9
|
-
readonly source?: string;
|
|
10
|
-
readonly license?: string;
|
|
11
|
-
readonly clips?: readonly string[];
|
|
12
|
-
readonly morphTargets?: readonly string[];
|
|
13
|
-
readonly diagnostics?: readonly string[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface EditorProjectTimelineDocument extends TimelineModelConfig {
|
|
17
|
-
readonly bindings?: readonly TimelineRuntimeBindingConfig[];
|
|
18
|
-
readonly evidence?: {
|
|
19
|
-
readonly authoredInEditor?: boolean;
|
|
20
|
-
readonly runtimeReplay?: boolean;
|
|
21
|
-
readonly animationEvents?: boolean;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface EditorProjectVisualGraphDocument {
|
|
26
|
-
readonly id: string;
|
|
27
|
-
readonly name: string;
|
|
28
|
-
readonly nodes: readonly unknown[];
|
|
29
|
-
readonly edges: readonly unknown[];
|
|
30
|
-
readonly runtimeBindings?: readonly {
|
|
31
|
-
readonly nodeId: string;
|
|
32
|
-
readonly targetId: string;
|
|
33
|
-
readonly event?: string;
|
|
34
|
-
}[];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface EditorProjectStateDocument {
|
|
38
|
-
readonly selectedNodeId?: string | number | null;
|
|
39
|
-
readonly activeTool?: string;
|
|
40
|
-
readonly activeTimelineId?: string | null;
|
|
41
|
-
readonly playMode?: "edit" | "play" | "paused";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface EditorProjectDocument {
|
|
45
|
-
readonly schema: "a3d-editor-project";
|
|
46
|
-
readonly version?: number;
|
|
47
|
-
readonly name: string;
|
|
48
|
-
readonly nodes: readonly unknown[];
|
|
49
|
-
readonly assets?: readonly (string | EditorProjectAssetDocument)[];
|
|
50
|
-
readonly timelines?: readonly EditorProjectTimelineDocument[];
|
|
51
|
-
readonly visualGraphs?: readonly EditorProjectVisualGraphDocument[];
|
|
52
|
-
readonly editor?: EditorProjectStateDocument;
|
|
53
|
-
readonly evidence?: {
|
|
54
|
-
readonly serializedBy?: "editor-runtime" | "editor-app" | string;
|
|
55
|
-
readonly roundTripReady?: boolean;
|
|
56
|
-
readonly browserWorkflowReady?: boolean;
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface EditorProjectEvidence {
|
|
61
|
-
readonly kind: "aura-editor-project-evidence";
|
|
62
|
-
readonly schema: EditorProjectDocument["schema"];
|
|
63
|
-
readonly version: number | null;
|
|
64
|
-
readonly name: string;
|
|
65
|
-
readonly nodeCount: number;
|
|
66
|
-
readonly assetCount: number;
|
|
67
|
-
readonly timelineCount: number;
|
|
68
|
-
readonly visualGraphCount: number;
|
|
69
|
-
readonly timelineBindingCount: number;
|
|
70
|
-
readonly signalMarkerCount: number;
|
|
71
|
-
readonly typedAssetEvidenceCount: number;
|
|
72
|
-
readonly hasAnimationTimeline: boolean;
|
|
73
|
-
readonly hasRuntimeReplayBindings: boolean;
|
|
74
|
-
readonly hasVisualGraphBindings: boolean;
|
|
75
|
-
readonly roundTripReady: boolean;
|
|
76
|
-
readonly evidence: {
|
|
77
|
-
readonly projectSerialization: true;
|
|
78
|
-
readonly timelineSerialization: boolean;
|
|
79
|
-
readonly visualGraphSerialization: boolean;
|
|
80
|
-
readonly runtimeReplayBindings: boolean;
|
|
81
|
-
readonly sourceLicenseAssetEvidence: boolean;
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function serializeEditorProject(project: EditorProjectDocument): string {
|
|
86
|
-
const normalized = validateEditorProject(project);
|
|
87
|
-
return `${JSON.stringify(normalized, null, 2)}\n`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function parseEditorProject(text: string): EditorProjectDocument {
|
|
91
|
-
const parsed = JSON.parse(text) as EditorProjectDocument;
|
|
92
|
-
return validateEditorProject(parsed);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function validateEditorProject(project: EditorProjectDocument): EditorProjectDocument {
|
|
96
|
-
if (!isRecord(project)) throw new Error("Editor project document must be an object.");
|
|
97
|
-
if (project.schema !== "a3d-editor-project") throw new Error("Unsupported editor project schema.");
|
|
98
|
-
if (typeof project.name !== "string" || !project.name.trim()) throw new Error("Editor project name is required.");
|
|
99
|
-
if (!Array.isArray(project.nodes)) throw new Error("Editor project nodes must be an array.");
|
|
100
|
-
validateAssets(project.assets ?? []);
|
|
101
|
-
validateTimelines(project.timelines ?? []);
|
|
102
|
-
validateVisualGraphs(project.visualGraphs ?? []);
|
|
103
|
-
return project;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function collectEditorProjectEvidence(project: EditorProjectDocument): EditorProjectEvidence {
|
|
107
|
-
const parsed = validateEditorProject(project);
|
|
108
|
-
const timelines = parsed.timelines ?? [];
|
|
109
|
-
const visualGraphs = parsed.visualGraphs ?? [];
|
|
110
|
-
const timelineBindingCount = timelines.reduce((total, timeline) => total + (timeline.bindings?.length ?? 0), 0);
|
|
111
|
-
const signalMarkerCount = timelines.reduce((total, timeline) => {
|
|
112
|
-
return total + (timeline.tracks ?? []).reduce((trackTotal, track) => {
|
|
113
|
-
if (track.type !== "signal") return trackTotal;
|
|
114
|
-
return trackTotal + (track.clips?.length ?? 0);
|
|
115
|
-
}, 0);
|
|
116
|
-
}, 0);
|
|
117
|
-
const typedAssetEvidenceCount = (parsed.assets ?? []).filter((asset) => {
|
|
118
|
-
return typeof asset !== "string" && Boolean(asset.id && asset.name && asset.source && asset.license);
|
|
119
|
-
}).length;
|
|
120
|
-
const assetCount = parsed.assets?.length ?? 0;
|
|
121
|
-
const hasAnimationTimeline = timelines.some((timeline) => (timeline.tracks ?? []).some((track) => track.type === "animation"));
|
|
122
|
-
const hasVisualGraphBindings = visualGraphs.some((graph) => (graph.runtimeBindings?.length ?? 0) > 0);
|
|
123
|
-
const hasRuntimeReplayBindings = timelineBindingCount > 0;
|
|
124
|
-
return {
|
|
125
|
-
kind: "aura-editor-project-evidence",
|
|
126
|
-
schema: parsed.schema,
|
|
127
|
-
version: parsed.version ?? null,
|
|
128
|
-
name: parsed.name,
|
|
129
|
-
nodeCount: parsed.nodes.length,
|
|
130
|
-
assetCount,
|
|
131
|
-
timelineCount: timelines.length,
|
|
132
|
-
visualGraphCount: visualGraphs.length,
|
|
133
|
-
timelineBindingCount,
|
|
134
|
-
signalMarkerCount,
|
|
135
|
-
typedAssetEvidenceCount,
|
|
136
|
-
hasAnimationTimeline,
|
|
137
|
-
hasRuntimeReplayBindings,
|
|
138
|
-
hasVisualGraphBindings,
|
|
139
|
-
roundTripReady: parsed.nodes.length > 0 && timelines.length > 0 && hasRuntimeReplayBindings,
|
|
140
|
-
evidence: {
|
|
141
|
-
projectSerialization: true,
|
|
142
|
-
timelineSerialization: timelines.length > 0,
|
|
143
|
-
visualGraphSerialization: visualGraphs.length > 0,
|
|
144
|
-
runtimeReplayBindings: hasRuntimeReplayBindings || hasVisualGraphBindings,
|
|
145
|
-
sourceLicenseAssetEvidence: assetCount > 0 && typedAssetEvidenceCount === assetCount
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function validateAssets(assets: readonly (string | EditorProjectAssetDocument)[]): void {
|
|
151
|
-
if (!Array.isArray(assets)) throw new Error("Editor project assets must be an array.");
|
|
152
|
-
for (const asset of assets) {
|
|
153
|
-
if (typeof asset === "string") {
|
|
154
|
-
if (!asset.trim()) throw new Error("Editor project asset ids must be non-empty.");
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
if (!asset.id.trim()) throw new Error("Editor project asset id is required.");
|
|
158
|
-
if (!asset.name.trim()) throw new Error(`Editor project asset name is required: ${asset.id}`);
|
|
159
|
-
assertStringArray(asset.clips, `asset clips for ${asset.id}`);
|
|
160
|
-
assertStringArray(asset.morphTargets, `asset morphTargets for ${asset.id}`);
|
|
161
|
-
assertStringArray(asset.diagnostics, `asset diagnostics for ${asset.id}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function validateTimelines(timelines: readonly EditorProjectTimelineDocument[]): void {
|
|
166
|
-
if (!Array.isArray(timelines)) throw new Error("Editor project timelines must be an array.");
|
|
167
|
-
for (const timeline of timelines) {
|
|
168
|
-
new TimelineModel(timeline);
|
|
169
|
-
for (const binding of timeline.bindings ?? []) {
|
|
170
|
-
if (!binding.targetId.trim()) throw new Error(`Timeline binding targetId is required for timeline ${timeline.id ?? timeline.name ?? "unnamed"}.`);
|
|
171
|
-
if (binding.trackId !== undefined && !binding.trackId.trim()) throw new Error("Timeline binding trackId cannot be empty.");
|
|
172
|
-
if (binding.trackName !== undefined && !binding.trackName.trim()) throw new Error("Timeline binding trackName cannot be empty.");
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function validateVisualGraphs(graphs: readonly EditorProjectVisualGraphDocument[]): void {
|
|
178
|
-
if (!Array.isArray(graphs)) throw new Error("Editor project visualGraphs must be an array.");
|
|
179
|
-
for (const graph of graphs) {
|
|
180
|
-
if (!graph.id.trim()) throw new Error("Editor project visual graph id is required.");
|
|
181
|
-
if (!graph.name.trim()) throw new Error(`Editor project visual graph name is required: ${graph.id}`);
|
|
182
|
-
if (!Array.isArray(graph.nodes)) throw new Error(`Editor project visual graph nodes must be an array: ${graph.id}`);
|
|
183
|
-
if (!Array.isArray(graph.edges)) throw new Error(`Editor project visual graph edges must be an array: ${graph.id}`);
|
|
184
|
-
for (const binding of graph.runtimeBindings ?? []) {
|
|
185
|
-
if (!binding.nodeId.trim()) throw new Error(`Visual graph runtime binding nodeId is required: ${graph.id}`);
|
|
186
|
-
if (!binding.targetId.trim()) throw new Error(`Visual graph runtime binding targetId is required: ${graph.id}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function assertStringArray(values: readonly string[] | undefined, label: string): void {
|
|
192
|
-
if (values === undefined) return;
|
|
193
|
-
if (!Array.isArray(values) || values.some((value) => !value.trim())) {
|
|
194
|
-
throw new Error(`Editor project ${label} must be non-empty strings.`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
199
|
-
return typeof value === "object" && value !== null;
|
|
200
|
-
}
|
package/src/PropertyPanel.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { CommandHistory } from "./CommandHistory";
|
|
2
|
-
import { InspectorModel, type InspectorEditableValue, type InspectorProperty } from "./InspectorModel";
|
|
3
|
-
|
|
4
|
-
export interface PropertyPanelField extends InspectorProperty {
|
|
5
|
-
readonly label: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface PropertyPanelOptions {
|
|
9
|
-
readonly history?: CommandHistory;
|
|
10
|
-
readonly inspector?: InspectorModel;
|
|
11
|
-
readonly onEdit?: (path: readonly string[], value: InspectorEditableValue) => void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class PropertyPanel {
|
|
15
|
-
readonly history: CommandHistory;
|
|
16
|
-
readonly inspector: InspectorModel;
|
|
17
|
-
private disposers: (() => void)[] = [];
|
|
18
|
-
|
|
19
|
-
constructor(options: PropertyPanelOptions = {}) {
|
|
20
|
-
this.history = options.history ?? new CommandHistory();
|
|
21
|
-
this.inspector = options.inspector ?? new InspectorModel();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
describe(target: object): readonly PropertyPanelField[] {
|
|
25
|
-
return this.inspector.describe(target).map((property) => ({
|
|
26
|
-
...property,
|
|
27
|
-
label: property.path.join(".")
|
|
28
|
-
}));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async edit(target: object, path: readonly string[], value: InspectorEditableValue): Promise<void> {
|
|
32
|
-
await this.history.execute(this.inspector.createSetPropertyCommand(target, path, value));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
render(target: object | undefined, container: HTMLElement, options: Pick<PropertyPanelOptions, "onEdit"> = {}): readonly PropertyPanelField[] {
|
|
36
|
-
this.dispose();
|
|
37
|
-
container.replaceChildren();
|
|
38
|
-
if (!target) return [];
|
|
39
|
-
const fields = this.describe(target);
|
|
40
|
-
const documentRef = container.ownerDocument;
|
|
41
|
-
for (const field of fields) {
|
|
42
|
-
const row = documentRef.createElement("label");
|
|
43
|
-
row.className = "aura-property-panel__row";
|
|
44
|
-
const name = documentRef.createElement("span");
|
|
45
|
-
name.className = "aura-property-panel__label";
|
|
46
|
-
name.textContent = field.label;
|
|
47
|
-
row.append(name);
|
|
48
|
-
if (field.editable) {
|
|
49
|
-
const input = documentRef.createElement("input");
|
|
50
|
-
input.className = "aura-property-panel__input";
|
|
51
|
-
input.name = field.label;
|
|
52
|
-
input.value = String(field.value);
|
|
53
|
-
input.type = field.type === "number" ? "number" : field.type === "boolean" ? "checkbox" : "text";
|
|
54
|
-
if (field.type === "boolean") input.checked = Boolean(field.value);
|
|
55
|
-
const change = (): void => {
|
|
56
|
-
const value = parseInput(input, field.type);
|
|
57
|
-
void this.edit(target, field.path, value).then(() => options.onEdit?.(field.path, value));
|
|
58
|
-
};
|
|
59
|
-
input.addEventListener("change", change);
|
|
60
|
-
this.disposers.push(() => input.removeEventListener("change", change));
|
|
61
|
-
row.append(input);
|
|
62
|
-
} else {
|
|
63
|
-
const value = documentRef.createElement("span");
|
|
64
|
-
value.className = "aura-property-panel__value";
|
|
65
|
-
value.textContent = field.type;
|
|
66
|
-
row.append(value);
|
|
67
|
-
}
|
|
68
|
-
container.append(row);
|
|
69
|
-
}
|
|
70
|
-
return fields;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
dispose(): void {
|
|
74
|
-
for (const dispose of this.disposers.splice(0)) dispose();
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function parseInput(input: HTMLInputElement, type: InspectorProperty["type"]): InspectorEditableValue {
|
|
79
|
-
if (type === "number") return Number(input.value);
|
|
80
|
-
if (type === "boolean") return input.checked;
|
|
81
|
-
return input.value;
|
|
82
|
-
}
|
package/src/RenderQueuePanel.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
export type RenderQueuePanelItemStatus = "queued" | "running" | "done" | "failed";
|
|
2
|
-
|
|
3
|
-
export interface RenderQueuePanelItem {
|
|
4
|
-
readonly id: string;
|
|
5
|
-
readonly label: string;
|
|
6
|
-
readonly status: RenderQueuePanelItemStatus;
|
|
7
|
-
readonly progress: number;
|
|
8
|
-
readonly currentFrame?: number;
|
|
9
|
-
readonly totalFrames?: number;
|
|
10
|
-
readonly outputPath?: string;
|
|
11
|
-
readonly error?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface RenderQueuePanelSnapshot {
|
|
15
|
-
readonly kind: "render-queue-panel";
|
|
16
|
-
readonly itemCount: number;
|
|
17
|
-
readonly queuedCount: number;
|
|
18
|
-
readonly runningCount: number;
|
|
19
|
-
readonly doneCount: number;
|
|
20
|
-
readonly failedCount: number;
|
|
21
|
-
readonly progress: number;
|
|
22
|
-
readonly items: readonly RenderQueuePanelItem[];
|
|
23
|
-
readonly outputPaths: readonly string[];
|
|
24
|
-
readonly errors: readonly string[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class RenderQueuePanel {
|
|
28
|
-
private readonly items = new Map<string, RenderQueuePanelItem>();
|
|
29
|
-
|
|
30
|
-
constructor(items: readonly RenderQueuePanelItem[] = []) {
|
|
31
|
-
for (const item of items) this.upsert(item);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
upsert(item: RenderQueuePanelItem): RenderQueuePanelSnapshot {
|
|
35
|
-
this.items.set(item.id, sanitizeRenderQueueItem(item));
|
|
36
|
-
return this.snapshot();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
updateProgress(id: string, progress: number, options: {
|
|
40
|
-
readonly currentFrame?: number;
|
|
41
|
-
readonly totalFrames?: number;
|
|
42
|
-
readonly status?: RenderQueuePanelItemStatus;
|
|
43
|
-
readonly outputPath?: string;
|
|
44
|
-
readonly error?: string;
|
|
45
|
-
} = {}): RenderQueuePanelSnapshot {
|
|
46
|
-
const existing = this.items.get(id);
|
|
47
|
-
if (!existing) throw new Error(`Render queue item does not exist: ${id}`);
|
|
48
|
-
return this.upsert({
|
|
49
|
-
...existing,
|
|
50
|
-
progress,
|
|
51
|
-
status: options.status ?? existing.status,
|
|
52
|
-
currentFrame: options.currentFrame ?? existing.currentFrame,
|
|
53
|
-
totalFrames: options.totalFrames ?? existing.totalFrames,
|
|
54
|
-
outputPath: options.outputPath ?? existing.outputPath,
|
|
55
|
-
error: options.error ?? existing.error
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
snapshot(): RenderQueuePanelSnapshot {
|
|
60
|
-
const items = [...this.items.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
61
|
-
const sum = items.reduce((total, item) => total + item.progress, 0);
|
|
62
|
-
return {
|
|
63
|
-
kind: "render-queue-panel",
|
|
64
|
-
itemCount: items.length,
|
|
65
|
-
queuedCount: items.filter((item) => item.status === "queued").length,
|
|
66
|
-
runningCount: items.filter((item) => item.status === "running").length,
|
|
67
|
-
doneCount: items.filter((item) => item.status === "done").length,
|
|
68
|
-
failedCount: items.filter((item) => item.status === "failed").length,
|
|
69
|
-
progress: items.length === 0 ? 0 : sum / items.length,
|
|
70
|
-
items,
|
|
71
|
-
outputPaths: items.flatMap((item) => item.outputPath ? [item.outputPath] : []),
|
|
72
|
-
errors: items.flatMap((item) => item.error ? [item.error] : [])
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function createRenderQueuePanel(items: readonly RenderQueuePanelItem[] = []): RenderQueuePanel {
|
|
78
|
-
return new RenderQueuePanel(items);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function sanitizeRenderQueueItem(item: RenderQueuePanelItem): RenderQueuePanelItem {
|
|
82
|
-
const progress = Number.isFinite(item.progress) ? Math.min(1, Math.max(0, item.progress)) : Number.NaN;
|
|
83
|
-
if (!Number.isFinite(progress)) throw new Error("Render queue item progress must be finite.");
|
|
84
|
-
if (item.currentFrame !== undefined && (!Number.isFinite(item.currentFrame) || item.currentFrame < 0)) {
|
|
85
|
-
throw new Error("Render queue currentFrame must be a non-negative finite number.");
|
|
86
|
-
}
|
|
87
|
-
if (item.totalFrames !== undefined && (!Number.isFinite(item.totalFrames) || item.totalFrames <= 0)) {
|
|
88
|
-
throw new Error("Render queue totalFrames must be a positive finite number.");
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
id: nonEmpty(item.id, "Render queue item id"),
|
|
92
|
-
label: nonEmpty(item.label, "Render queue item label"),
|
|
93
|
-
status: item.status,
|
|
94
|
-
progress,
|
|
95
|
-
currentFrame: item.currentFrame,
|
|
96
|
-
totalFrames: item.totalFrames,
|
|
97
|
-
outputPath: item.outputPath,
|
|
98
|
-
error: item.error
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function nonEmpty(value: string, label: string): string {
|
|
103
|
-
const trimmed = value.trim();
|
|
104
|
-
if (!trimmed) throw new Error(`${label} must be a non-empty string.`);
|
|
105
|
-
return trimmed;
|
|
106
|
-
}
|
package/src/RotateGizmo.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { Gizmo, type GizmoDrag } from "./Gizmo";
|
|
2
|
-
import { TransformCommand, type TransformLike } from "./commands/TransformCommand";
|
|
3
|
-
|
|
4
|
-
export class RotateGizmo extends Gizmo {
|
|
5
|
-
async drag(input: GizmoDrag): Promise<void> {
|
|
6
|
-
if (!this.target) return;
|
|
7
|
-
const current = readTransform(this.target);
|
|
8
|
-
const next = clone(current);
|
|
9
|
-
next.rotation ??= { x: 0, y: 0, z: 0, w: 1 };
|
|
10
|
-
const delta = this.snapRotationDelta(input.delta);
|
|
11
|
-
if (input.axis === "x") next.rotation.x += delta;
|
|
12
|
-
if (input.axis === "y") next.rotation.y += delta;
|
|
13
|
-
if (input.axis === "z" || input.axis === "uniform") next.rotation.z += delta;
|
|
14
|
-
await this.history.execute(new TransformCommand(this.target, next));
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function readTransform(target: NonNullable<RotateGizmo["target"]>): TransformLike {
|
|
19
|
-
if ("transform" in target) {
|
|
20
|
-
return {
|
|
21
|
-
position: { x: target.transform.position[0], y: target.transform.position[1], z: target.transform.position[2] },
|
|
22
|
-
rotation: { x: target.transform.rotation[0], y: target.transform.rotation[1], z: target.transform.rotation[2], w: target.transform.rotation[3] },
|
|
23
|
-
scale: { x: target.transform.scale[0], y: target.transform.scale[1], z: target.transform.scale[2] }
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
return clone(target);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function clone(transform: TransformLike): TransformLike {
|
|
30
|
-
return {
|
|
31
|
-
position: { ...transform.position },
|
|
32
|
-
rotation: transform.rotation ? { ...transform.rotation } : undefined,
|
|
33
|
-
scale: transform.scale ? { ...transform.scale } : undefined
|
|
34
|
-
};
|
|
35
|
-
}
|
package/src/ScaleGizmo.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { Gizmo, type GizmoDrag } from "./Gizmo";
|
|
2
|
-
import { TransformCommand, type TransformLike } from "./commands/TransformCommand";
|
|
3
|
-
|
|
4
|
-
export class ScaleGizmo extends Gizmo {
|
|
5
|
-
async drag(input: GizmoDrag): Promise<void> {
|
|
6
|
-
if (!this.target) return;
|
|
7
|
-
const current = readTransform(this.target);
|
|
8
|
-
const next = clone(current);
|
|
9
|
-
next.scale ??= { x: 1, y: 1, z: 1 };
|
|
10
|
-
const amount = Math.max(0.001, this.snapScaleFactor(1 + input.delta));
|
|
11
|
-
if (input.axis === "x") next.scale.x *= amount;
|
|
12
|
-
if (input.axis === "y") next.scale.y *= amount;
|
|
13
|
-
if (input.axis === "z") next.scale.z *= amount;
|
|
14
|
-
if (input.axis === "uniform") {
|
|
15
|
-
next.scale.x *= amount;
|
|
16
|
-
next.scale.y *= amount;
|
|
17
|
-
next.scale.z *= amount;
|
|
18
|
-
}
|
|
19
|
-
await this.history.execute(new TransformCommand(this.target, next));
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function readTransform(target: NonNullable<ScaleGizmo["target"]>): TransformLike {
|
|
24
|
-
if ("transform" in target) {
|
|
25
|
-
return {
|
|
26
|
-
position: { x: target.transform.position[0], y: target.transform.position[1], z: target.transform.position[2] },
|
|
27
|
-
rotation: { x: target.transform.rotation[0], y: target.transform.rotation[1], z: target.transform.rotation[2], w: target.transform.rotation[3] },
|
|
28
|
-
scale: { x: target.transform.scale[0], y: target.transform.scale[1], z: target.transform.scale[2] }
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
return clone(target);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function clone(transform: TransformLike): TransformLike {
|
|
35
|
-
return {
|
|
36
|
-
position: { ...transform.position },
|
|
37
|
-
rotation: transform.rotation ? { ...transform.rotation } : undefined,
|
|
38
|
-
scale: transform.scale ? { ...transform.scale } : undefined
|
|
39
|
-
};
|
|
40
|
-
}
|
package/src/SceneOutliner.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { HierarchyModel, type HierarchyNodeDescriptor } from "./HierarchyModel";
|
|
2
|
-
import type { SelectionId } from "./Selection";
|
|
3
|
-
import type { AnimationSceneNode } from "./AnimationSceneEditor";
|
|
4
|
-
|
|
5
|
-
export interface SceneOutlinerItem extends HierarchyNodeDescriptor {
|
|
6
|
-
readonly icon: string;
|
|
7
|
-
readonly kind: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface SceneOutlinerOptions {
|
|
11
|
-
readonly selectedIds?: ReadonlySet<SelectionId>;
|
|
12
|
-
readonly onSelect?: (id: SelectionId) => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class SceneOutliner {
|
|
16
|
-
private readonly hierarchy = new HierarchyModel();
|
|
17
|
-
private disposers: (() => void)[] = [];
|
|
18
|
-
|
|
19
|
-
describe(root: AnimationSceneNode, selectedIds: ReadonlySet<SelectionId> = new Set()): readonly SceneOutlinerItem[] {
|
|
20
|
-
return this.hierarchy.flatten(root, selectedIds).map((item) => {
|
|
21
|
-
const node = findNode(root, String(item.id));
|
|
22
|
-
const kind = node?.kind ?? "unknown";
|
|
23
|
-
return {
|
|
24
|
-
...item,
|
|
25
|
-
kind,
|
|
26
|
-
icon: iconForKind(kind)
|
|
27
|
-
};
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
render(root: AnimationSceneNode, container: HTMLElement, options: SceneOutlinerOptions = {}): readonly SceneOutlinerItem[] {
|
|
32
|
-
this.dispose();
|
|
33
|
-
const items = this.describe(root, options.selectedIds);
|
|
34
|
-
container.replaceChildren();
|
|
35
|
-
const documentRef = container.ownerDocument;
|
|
36
|
-
for (const item of items) {
|
|
37
|
-
const row = documentRef.createElement("button");
|
|
38
|
-
row.type = "button";
|
|
39
|
-
row.className = "aura-scene-outliner__item";
|
|
40
|
-
row.dataset.nodeId = String(item.id);
|
|
41
|
-
row.dataset.kind = item.kind;
|
|
42
|
-
row.dataset.selected = String(item.selected);
|
|
43
|
-
row.style.paddingLeft = `${item.depth * 14}px`;
|
|
44
|
-
row.textContent = `${item.icon} ${item.name}`;
|
|
45
|
-
const click = (): void => options.onSelect?.(item.id);
|
|
46
|
-
row.addEventListener("click", click);
|
|
47
|
-
this.disposers.push(() => row.removeEventListener("click", click));
|
|
48
|
-
container.append(row);
|
|
49
|
-
}
|
|
50
|
-
return items;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
dispose(): void {
|
|
54
|
-
for (const dispose of this.disposers.splice(0)) dispose();
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function iconForKind(kind: string): string {
|
|
59
|
-
switch (kind) {
|
|
60
|
-
case "character": return "[CHAR]";
|
|
61
|
-
case "set": return "[SET]";
|
|
62
|
-
case "prop": return "[PROP]";
|
|
63
|
-
case "camera": return "[CAM]";
|
|
64
|
-
case "light": return "[LIGHT]";
|
|
65
|
-
case "audio": return "[AUD]";
|
|
66
|
-
case "material": return "[MAT]";
|
|
67
|
-
default: return "[NODE]";
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function findNode(root: AnimationSceneNode, id: string): AnimationSceneNode | undefined {
|
|
72
|
-
if (root.id === id) return root;
|
|
73
|
-
for (const child of root.children) {
|
|
74
|
-
const found = findNode(child, id);
|
|
75
|
-
if (found) return found;
|
|
76
|
-
}
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
package/src/Selection.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
export type SelectionId = string | number;
|
|
2
|
-
|
|
3
|
-
export interface SelectionChange {
|
|
4
|
-
readonly previous: readonly SelectionId[];
|
|
5
|
-
readonly current: readonly SelectionId[];
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export type SelectionListener = (change: SelectionChange) => void;
|
|
9
|
-
|
|
10
|
-
export class Selection {
|
|
11
|
-
private readonly ids = new Set<SelectionId>();
|
|
12
|
-
private readonly listeners = new Set<SelectionListener>();
|
|
13
|
-
|
|
14
|
-
current(): readonly SelectionId[] {
|
|
15
|
-
return [...this.ids];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
has(id: SelectionId): boolean {
|
|
19
|
-
return this.ids.has(id);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
set(ids: readonly SelectionId[]): void {
|
|
23
|
-
this.replace(new Set(ids));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
add(id: SelectionId): void {
|
|
27
|
-
this.replace(new Set([...this.ids, id]));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
remove(id: SelectionId): void {
|
|
31
|
-
const next = new Set(this.ids);
|
|
32
|
-
next.delete(id);
|
|
33
|
-
this.replace(next);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
clear(): void {
|
|
37
|
-
this.replace(new Set());
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
subscribe(listener: SelectionListener): () => void {
|
|
41
|
-
this.listeners.add(listener);
|
|
42
|
-
return () => this.listeners.delete(listener);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
prune(alive: (id: SelectionId) => boolean): void {
|
|
46
|
-
this.replace(new Set([...this.ids].filter(alive)));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
private replace(next: Set<SelectionId>): void {
|
|
50
|
-
const previous = this.current();
|
|
51
|
-
this.ids.clear();
|
|
52
|
-
for (const id of next) {
|
|
53
|
-
this.ids.add(id);
|
|
54
|
-
}
|
|
55
|
-
const current = this.current();
|
|
56
|
-
if (previous.length !== current.length || previous.some((id, index) => id !== current[index])) {
|
|
57
|
-
const change = { previous, current };
|
|
58
|
-
for (const listener of [...this.listeners]) {
|
|
59
|
-
listener(change);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|