@bitbybit-dev/occt
Version:
Bit By Bit Developers CAD algorithms using OpenCascade Technology kernel. Run in Node and in Browser.
317 lines (316 loc) • 14.2 kB
JavaScript
/**
* OCCT Assembly Manager for creating assembly documents with parts, instances, and colors.
*
* This class provides methods for:
* - Building assembly documents from structure definitions
* - Creating parts and nodes for visual programming
* - Querying document parts, hierarchy, colors, and transforms
* - Modifying document labels (color, name)
* - Exporting to STEP and glTF formats
* - Loading STEP files into documents
*
* All methods use document handles directly (no global document storage).
* The caller is responsible for managing document lifetime by calling document.delete() when done.
*/
export class OCCTAssemblyManager {
constructor(occ, och) {
this.occ = occ;
this.och = och;
}
/**
* Create a part definition for use in assembly structures.
* This is a helper for visual programming - it simply wraps the inputs into a part object.
*
* @param inputs - Part details including id, shape, name, and optional colorRgba
* @returns Part definition that can be added to an assembly structure
*
* @example
* \`\`\`typescript
* const box = occt.shapes.solid.createBox({ width: 10, length: 10, height: 10 });
* const part = occt.assembly.manager.createPart({ id: "box", shape: box, name: "Box", colorRgba: { r: 1, g: 0, b: 0, a: 1 } });
* \`\`\`
*/
createPart(inputs) {
return {
id: inputs.id,
shape: inputs.shape,
name: inputs.name,
colorRgba: inputs.colorRgba
};
}
/**
* Create an assembly node definition (a container for other nodes).
* Assembly nodes group instances and other assemblies together in the hierarchy.
*
* @param inputs - Assembly node details including id, name, and optional parent
* @returns Node definition that can be added to an assembly structure
*/
createAssemblyNode(inputs) {
return {
id: inputs.id,
type: "assembly",
name: inputs.name,
parentId: inputs.parentId,
colorRgba: inputs.colorRgba
};
}
/**
* Create an instance node definition (a reference to a part with transform).
* Instance nodes place a part at a specific location with optional translation, rotation, and scale.
*
* @param inputs - Instance node details including id, partId, name, and transform
* @returns Node definition that can be added to an assembly structure
*/
createInstanceNode(inputs) {
return {
id: inputs.id,
type: "instance",
name: inputs.name,
parentId: inputs.parentId,
partId: inputs.partId,
translation: inputs.translation,
rotation: inputs.rotation,
scale: inputs.scale,
colorRgba: inputs.colorRgba
};
}
/**
* Create a part update definition for modifying an existing part in a document.
* Part updates can change the shape, name, and/or color of an existing part.
*
* @param inputs - Update details including label and optional new shape/name/color
* @returns Part update definition that can be added to an assembly structure's partUpdates array
*
* @example
* ```typescript
* // Get existing parts from document
* const parts = occt.assembly.query.getDocumentParts({ document });
*
* // Create a new shape to replace the old one
* const newBox = occt.shapes.solid.createBox({ width: 20, length: 20, height: 20 });
*
* // Create an update definition
* const update = occt.assembly.manager.createPartUpdate({
* label: parts[0].label,
* shape: newBox,
* name: "Bigger Box",
* colorRgba: { r: 0, g: 1, b: 0, a: 1 }
* });
*
* // Combine with structure and rebuild
* const structure = occt.assembly.manager.combineStructure({ parts: [], nodes: [], partUpdates: [update] });
* occt.assembly.manager.buildAssemblyDocument({ structure, existingDocument: document });
* ```
*/
createPartUpdate(inputs) {
return {
label: inputs.label,
shape: inputs.shape,
name: inputs.name,
colorRgba: inputs.colorRgba
};
}
/**
* Combine parts and nodes into a complete assembly structure definition.
* This is the final step before calling buildAssemblyDocument.
*
* @param inputs - Lists of parts and nodes to combine
* @returns Complete assembly structure ready for building
*/
combineStructure(inputs) {
var _a, _b;
return {
parts: (_a = inputs.parts) !== null && _a !== void 0 ? _a : [],
nodes: (_b = inputs.nodes) !== null && _b !== void 0 ? _b : [],
removals: inputs.removals,
partUpdates: inputs.partUpdates,
clearDocument: inputs.clearDocument,
loadedParts: inputs.loadedParts
};
}
/**
* Create an imported part definition that copies a label tree from another document
* (typically a STEP-loaded document) into the new assembly. Preserves sub-assembly
* hierarchy, names and colors. The result can be referenced by `partId` from any
* instance node to place the imported assembly multiple times.
*
* @param inputs - Imported part details: id, sourceDocumentIndex, optional sourceLabel/name/colorRgba
* @returns Imported part definition to add to an assembly structure
*
* @example
* ```typescript
* const chairDoc = occt.assembly.manager.loadStepToDoc({ stepData });
* const chair = occt.assembly.manager.createImportedPart({
* id: "chair", sourceDocumentIndex: 0, name: "Chair"
* });
* const i1 = occt.assembly.manager.createInstanceNode({ id: "c1", partId: "chair", name: "Chair 1", translation: [0,0,0] });
* const i2 = occt.assembly.manager.createInstanceNode({ id: "c2", partId: "chair", name: "Chair 2", translation: [500,0,0] });
* const structure = occt.assembly.manager.combineStructure({ parts: [], nodes: [i1, i2], loadedParts: [chair] });
* const doc = occt.assembly.manager.buildAssemblyDocument({ structure, sourceDocuments: [chairDoc] });
* ```
*/
createImportedPart(inputs) {
return {
id: inputs.id,
sourceDocumentIndex: inputs.sourceDocumentIndex,
sourceLabel: inputs.sourceLabel,
name: inputs.name,
colorRgba: inputs.colorRgba
};
}
/**
* Build an assembly document from a structure definition.
* Returns the document handle directly - caller manages lifetime.
* Call document.delete() when done to free memory.
*
* If existingDocument is provided and valid, the document will be cleared and
* updated instead of creating a new one. This is useful for updating an assembly
* without allocating a new document each time.
*
* When updating an existing document (existingDocument provided):
* - If `structure.removals` is provided, those labels are removed first
* - If `structure.partUpdates` is provided, those parts are updated (shape, name, color)
* - New `parts` and `nodes` are added to the document
* - If neither `removals` nor `partUpdates` is provided, the document is cleared first (backward compatible)
* - Use `clearDocument: false` in structure to preserve existing content while adding new parts/nodes
*
* @param inputs - Assembly structure definition and optional existing document
* @returns The document handle (new or updated)
* @throws Error if assembly building fails
*/
buildAssemblyDocument(inputs) {
const { structure, existingDocument, sourceDocuments } = inputs;
const shapes = [];
const partsJson = [];
// Add new parts to shapes array
for (const part of structure.parts) {
partsJson.push({
id: part.id,
shapeIndex: shapes.length,
name: part.name,
colorRgba: part.colorRgba
});
shapes.push(part.shape);
}
// Process partUpdates - add their shapes to the shapes array and reference by index
const partUpdatesJson = [];
if (structure.partUpdates) {
for (const update of structure.partUpdates) {
const updateJson = {
label: update.label
};
if (update.shape) {
updateJson.shapeIndex = shapes.length;
shapes.push(update.shape);
}
if (update.name !== undefined) {
updateJson.name = update.name;
}
if (update.colorRgba !== undefined) {
updateJson.colorRgba = update.colorRgba;
}
partUpdatesJson.push(updateJson);
}
}
// Imported parts reference source documents by index; they carry no shape payload.
const loadedPartsJson = structure.loadedParts && structure.loadedParts.length > 0
? structure.loadedParts.map(p => ({
id: p.id,
sourceDocumentIndex: p.sourceDocumentIndex,
sourceLabel: p.sourceLabel,
name: p.name,
colorRgba: p.colorRgba
}))
: undefined;
const structureJson = JSON.stringify({
parts: partsJson,
nodes: structure.nodes,
removals: structure.removals,
partUpdates: partUpdatesJson.length > 0 ? partUpdatesJson : undefined,
clearDocument: structure.clearDocument,
loadedParts: loadedPartsJson
});
const document = this.occ.BuildAssemblyDocument(structureJson, shapes, existingDocument, sourceDocuments !== null && sourceDocuments !== void 0 ? sourceDocuments : []);
if (document.IsNull()) {
throw new Error("Failed to create assembly document");
}
return document;
}
/**
* Load STEP data and return document handle directly.
* Supports both regular STEP and gzip-compressed STEP-Z.
* Call document.delete() when done to free memory.
*
* @param inputs - STEP file data
* @returns The document handle
* @throws Error if STEP loading fails
*/
loadStepToDoc(inputs) {
const document = this.occ.LoadStepToDoc(inputs.stepData);
if (document.IsNull()) {
throw new Error("Failed to load STEP file");
}
return document;
}
/**
* Set the color of a label in a document.
*
* @param inputs - Document handle, label, and color
* @returns true on success, false on failure
*/
setLabelColor(inputs) {
return this.occ.SetDocLabelColor(inputs.document, inputs.label, inputs.r, inputs.g, inputs.b, inputs.a);
}
/**
* Set or change the name of a label.
*
* @param inputs - Document handle, label, and new name
* @returns true on success, false on failure
*/
setLabelName(inputs) {
return this.occ.SetDocLabelName(inputs.document, inputs.label, inputs.name);
}
/**
* Export an assembly document to STEP format.
*
* @param inputs - Export options including document, fileName, author, organization
* @returns STEP file content as Uint8Array
*/
exportDocumentToStep(inputs) {
const result = inputs.compress
? this.occ.ExportDocumentToStepZ(inputs.document, inputs.fileName, inputs.author, inputs.organization)
: this.occ.ExportDocumentToStep(inputs.document, inputs.fileName, inputs.author, inputs.organization);
if (!result) {
throw new Error("Failed to export document to STEP");
}
return result;
}
/**
* Export an assembly document to glTF binary (GLB) format.
*
* @param inputs - Export options including document and mesh settings
* @returns GLB content as Uint8Array
*/
exportDocumentToGltf(inputs) {
var _a, _b;
const result = this.occ.ExportDocumentToGltf(inputs.document, inputs.meshDeflection, inputs.meshAngle, (_a = inputs.internalVerticesMode) !== null && _a !== void 0 ? _a : false, (_b = inputs.controlSurfaceDeflection) !== null && _b !== void 0 ? _b : false, inputs.mergeFaces, inputs.forceUVExport);
if (!result) {
throw new Error("Failed to export document to glTF");
}
return result;
}
/**
* Export an assembly document to glTF binary (GLB) format with explicit Draco
* geometry compression settings.
* @param inputs - Export options including document, mesh settings and Draco knobs
* @returns GLB content as Uint8Array
*/
exportDocumentToGltfWithDraco(inputs) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
const result = this.occ.ExportDocumentToGltfWithDraco(inputs.document, (_a = inputs.meshDeflection) !== null && _a !== void 0 ? _a : 0.1, (_b = inputs.meshAngle) !== null && _b !== void 0 ? _b : 0.5, (_c = inputs.internalVerticesMode) !== null && _c !== void 0 ? _c : false, (_d = inputs.controlSurfaceDeflection) !== null && _d !== void 0 ? _d : false, (_e = inputs.mergeFaces) !== null && _e !== void 0 ? _e : false, (_f = inputs.forceUVExport) !== null && _f !== void 0 ? _f : false, (_g = inputs.useDraco) !== null && _g !== void 0 ? _g : true, (_h = inputs.dracoCompressionLevel) !== null && _h !== void 0 ? _h : 7, (_j = inputs.dracoQuantizePositionBits) !== null && _j !== void 0 ? _j : 14, (_k = inputs.dracoQuantizeNormalBits) !== null && _k !== void 0 ? _k : 10, (_l = inputs.dracoQuantizeTexcoordBits) !== null && _l !== void 0 ? _l : 12, (_m = inputs.dracoQuantizeColorBits) !== null && _m !== void 0 ? _m : 8, (_o = inputs.dracoQuantizeGenericBits) !== null && _o !== void 0 ? _o : 12, (_p = inputs.dracoUnifiedQuantization) !== null && _p !== void 0 ? _p : false);
if (!result) {
throw new Error("Failed to export document to glTF");
}
return result;
}
}