Add draggable GLB hierarchy tree and node origins
This commit is contained in:
@@ -13,7 +13,9 @@ export type MachinePartDraft = MachinePartSource & {
|
||||
originMm: Vec3;
|
||||
translationMm: Vec3;
|
||||
rotationDeg: Vec3;
|
||||
nodeParents: Record<string, string | null>;
|
||||
};
|
||||
export type MachineTreeNode = { path: string; name: string; type: "group" | "mesh"; originMm: Vec3; children: MachineTreeNode[] };
|
||||
export type PhysicalZero = { x: number; y: number; z: number; a: number; b: number; c: number };
|
||||
export type ManagedMachineGeometry = {
|
||||
schema: "linuxcnc.machine-manager/1";
|
||||
@@ -21,7 +23,7 @@ export type ManagedMachineGeometry = {
|
||||
cOriginMm: Vec3;
|
||||
tiltOffsetMm: Vec3;
|
||||
physicalZero: PhysicalZero;
|
||||
parts: Array<{ id: string; filename: string; hash: string; role: MachinePartRole; parentId: string | null; originMm: Vec3; translationMm: Vec3; rotationDeg: Vec3; visible: boolean }>;
|
||||
parts: Array<{ id: string; filename: string; hash: string; role: MachinePartRole; parentId: string | null; originMm: Vec3; translationMm: Vec3; rotationDeg: Vec3; visible: boolean; nodeParents: Record<string, string | null> }>;
|
||||
};
|
||||
|
||||
export const MACHINE_PART_ROLES: MachinePartRole[] = ["base", "x", "y", "z", "tool", "tilt", "c", "work"];
|
||||
@@ -47,6 +49,40 @@ export function cloneMachinePartScene(source: THREE.Object3D) {
|
||||
return clone;
|
||||
}
|
||||
|
||||
function scenePathMap(scene: THREE.Object3D) {
|
||||
const nodes = new Map<string, THREE.Object3D>(), paths = new WeakMap<THREE.Object3D, string>();
|
||||
const visit = (node: THREE.Object3D, path: string) => { nodes.set(path, node); paths.set(node, path); node.children.forEach((child, index) => visit(child, `${path}/${index}`)); };
|
||||
visit(scene, "scene"); return { nodes, paths };
|
||||
}
|
||||
|
||||
export function applyMachineNodeParents(scene: THREE.Object3D, overrides: Record<string, string | null>) {
|
||||
const { nodes } = scenePathMap(scene);
|
||||
for (const [sourcePath, targetPath] of Object.entries(overrides ?? {})) {
|
||||
const source = nodes.get(sourcePath), target = targetPath === null ? scene : nodes.get(targetPath);
|
||||
if (!source || source === scene || !target || source === target) throw new Error("GLB 层级拖放目标无效");
|
||||
let cursor: THREE.Object3D | null = target;
|
||||
while (cursor) { if (cursor === source) throw new Error("GLB 层级不能形成循环"); cursor = cursor.parent; }
|
||||
scene.updateMatrixWorld(true); target.attach(source); scene.updateMatrixWorld(true);
|
||||
}
|
||||
return scene;
|
||||
}
|
||||
|
||||
export function machineSceneNode(scene: THREE.Object3D, path: string) {
|
||||
return scenePathMap(scene).nodes.get(path) ?? null;
|
||||
}
|
||||
|
||||
export function machinePartTree(part: Pick<MachinePartDraft, "filename" | "scene" | "nodeParents">): MachineTreeNode {
|
||||
const scene = part.scene.clone(true), { paths } = scenePathMap(scene); applyMachineNodeParents(scene, part.nodeParents ?? {});
|
||||
const build = (node: THREE.Object3D): MachineTreeNode => ({
|
||||
path: paths.get(node)!,
|
||||
name: node.name || (paths.get(node) === "scene" ? part.filename : `${(node as THREE.Mesh).isMesh ? "Mesh" : "Node"} ${paths.get(node)!.split("/").at(-1)}`),
|
||||
type: (node as THREE.Mesh).isMesh ? "mesh" : "group",
|
||||
originMm: node.position.toArray().map((value) => value * 1000) as Vec3,
|
||||
children: node.children.map(build),
|
||||
});
|
||||
return build(scene);
|
||||
}
|
||||
|
||||
export async function loadMachinePart(bytes: ArrayBuffer, filename: string): Promise<MachinePartSource> {
|
||||
if (!bytes.byteLength) throw new Error("GLB 部件为空");
|
||||
const manager = new THREE.LoadingManager();
|
||||
@@ -109,7 +145,7 @@ export async function buildManagedMachine(input: { coordinates: "XYZAC" | "XYZBC
|
||||
const structure: MachineStructure = parseMachineUrdf(urdf);
|
||||
const metadata: ManagedMachineGeometry = {
|
||||
schema: "linuxcnc.machine-manager/1", coordinates: input.coordinates, cOriginMm: [...input.cOriginMm], tiltOffsetMm: [...input.tiltOffsetMm], physicalZero: { ...input.physicalZero },
|
||||
parts: MACHINE_PART_ROLES.map((role) => { const part = byRole.get(role)!; return { id: part.id, filename: part.filename, hash: part.hash, role, parentId: part.parentId, originMm: [...part.originMm], translationMm: [...part.translationMm], rotationDeg: [...part.rotationDeg], visible: part.visible }; }),
|
||||
parts: MACHINE_PART_ROLES.map((role) => { const part = byRole.get(role)!; return { id: part.id, filename: part.filename, hash: part.hash, role, parentId: part.parentId, originMm: [...part.originMm], translationMm: [...part.translationMm], rotationDeg: [...part.rotationDeg], visible: part.visible, nodeParents: { ...(part.nodeParents ?? {}) } }; }),
|
||||
};
|
||||
const scene = new THREE.Group(); scene.name = `Managed_${input.coordinates}`;
|
||||
scene.userData.linuxcncMachine = { schema: "linuxcnc.machine/1", urdf };
|
||||
@@ -124,7 +160,7 @@ export async function buildManagedMachine(input: { coordinates: "XYZAC" | "XYZBC
|
||||
placement.position.fromArray(part.translationMm).multiplyScalar(.001);
|
||||
placement.rotation.set(...part.rotationDeg.map(THREE.MathUtils.degToRad) as Vec3);
|
||||
placement.userData.machinePart = { role, filename: part.filename, hash: part.hash, originMm: [...part.originMm] };
|
||||
const visual = cloneMachinePartScene(part.scene); visual.position.fromArray(part.originMm).multiplyScalar(-.001); placement.add(visual); links[role].add(placement);
|
||||
const visual = applyMachineNodeParents(cloneMachinePartScene(part.scene), part.nodeParents ?? {}); visual.position.fromArray(part.originMm).multiplyScalar(-.001); placement.add(visual); links[role].add(placement);
|
||||
}
|
||||
links.base.userData.linuxcncMachineGeometry = metadata;
|
||||
const signature = JSON.stringify({ metadata, hashes: MACHINE_PART_ROLES.map((role) => byRole.get(role)!.hash) });
|
||||
|
||||
Reference in New Issue
Block a user