UNPKG

@bitbybit-dev/occt

Version:

Bit By Bit Developers CAD algorithms using OpenCascade Technology kernel. Run in Node and in Browser.

760 lines (759 loc) 32.2 kB
export class OCCTIOAssembly { constructor(occ, och) { this.occ = occ; this.och = och; // Add a property to cache color assignments this.colorAssignments = new Map(); } parseComponentWithXCAF(shape, label, shapeTool, colorTool, name, level) { const id = this.generateId(); const isAssembly = this.occ.XCAFDoc_ShapeTool.IsAssembly(label); const component = { id, name: this.extractNameFromLabel(label) || name || `${isAssembly ? "Assembly" : "Part"}_${id}`, type: isAssembly ? "assembly" : "part", shape: shape, children: [], transformation: this.extractTransformation(shape), properties: this.extractProperties(shape), attributes: this.extractAttributesFromXCAF(label), color: this.extractColorFromXCAF(shape, label, colorTool), pmi: this.extractPMI(shape) }; if (isAssembly) { component.children = this.extractChildrenWithXCAF(label, shapeTool, colorTool, level + 1); } return component; } extractChildrenWithXCAF(assemblyLabel, shapeTool, colorTool, level) { const children = []; // Get components of the assembly const components = new this.occ.TDF_LabelSequence_1(); this.occ.XCAFDoc_ShapeTool.GetComponents(assemblyLabel, components, false); for (let i = 1; i <= components.Length(); i++) { const componentLabel = components.Value(i); // Get the referenced shape const referredLabel = new this.occ.TDF_Label(); if (this.occ.XCAFDoc_ShapeTool.GetReferredShape(componentLabel, referredLabel)) { const componentShape = new this.occ.TopoDS_Shape(); if (this.occ.XCAFDoc_ShapeTool.GetShape_1(referredLabel, componentShape)) { // Get the location/transformation for this component instance const location = this.occ.XCAFDoc_ShapeTool.GetLocation(componentLabel); if (location && !location.IsIdentity()) { // Apply the location to the shape const locatedShape = componentShape.Located(location, false); const childComponent = this.parseComponentWithXCAF(locatedShape, referredLabel, shapeTool, colorTool, `Component_${level}_${i - 1}`, level); children.push(childComponent); // Clean up locatedShape.delete(); } else { // No specific location, use shape as-is const childComponent = this.parseComponentWithXCAF(componentShape, referredLabel, shapeTool, colorTool, `Component_${level}_${i - 1}`, level); children.push(childComponent); } componentShape.delete(); } referredLabel.delete(); } componentLabel.delete(); } components.delete(); return children; } extractColorFromXCAF(shape, label, colorTool) { try { const color = new this.occ.Quantity_Color_1(); if (colorTool.GetColor_1(label, color)) { const result = this.convertQuantityColorToRgba(color); color.delete(); return result; } if (colorTool.GetColor_1(label, color)) { const result = this.convertQuantityColorToRgba(color); color.delete(); return result; } if (colorTool.GetColor_7(shape, this.occ.XCAFDoc_ColorType.XCAFDoc_ColorSurf, color)) { const result = this.convertQuantityColorToRgba(color); color.delete(); return result; } if (colorTool.GetColor_7(shape, this.occ.XCAFDoc_ColorType.XCAFDoc_ColorCurv, color)) { const result = this.convertQuantityColorToRgba(color); color.delete(); return result; } if (colorTool.GetColor_7(shape, this.occ.XCAFDoc_ColorType.XCAFDoc_ColorGen, color)) { const result = this.convertQuantityColorToRgba(color); color.delete(); return result; } const subShapeColor = this.extractSubShapeColor(shape, colorTool); if (subShapeColor) { color.delete(); return subShapeColor; } color.delete(); } catch (error) { console.warn("Error extracting XCAF color:", error); } return undefined; } convertQuantityColorToRgba(color) { return { r: Math.round(color.Red() * 255) / 255, g: Math.round(color.Green() * 255) / 255, b: Math.round(color.Blue() * 255) / 255, a: 1.0 }; } extractSubShapeColor(shape, colorTool) { try { const faceExplorer = new this.occ.TopExp_Explorer_2(shape, this.occ.TopAbs_ShapeEnum.TopAbs_FACE, this.occ.TopAbs_ShapeEnum.TopAbs_SHAPE); while (faceExplorer.More()) { const face = this.occ.TopoDS.Face_1(faceExplorer.Current()); const color = new this.occ.Quantity_Color_1(); if (colorTool.GetColor_7(face, this.occ.XCAFDoc_ColorType.XCAFDoc_ColorSurf, color)) { const result = this.convertQuantityColorToRgba(color); color.delete(); face.delete(); faceExplorer.delete(); return result; } color.delete(); face.delete(); faceExplorer.Next(); } faceExplorer.delete(); } catch (error) { console.warn("Error extracting sub-shape color:", error); } return undefined; } extractNameFromLabel(label) { try { // Try to get name attribute from the label using correct API const nameAttrHandle = new this.occ.Handle_TDF_Attribute_1(); const nameID = this.occ.TDataStd_Name.GetID(); if (label.FindAttribute_1(nameID, nameAttrHandle)) { try { const TCollection_ExtendedString = nameAttrHandle.get().Get(); const nameString = new this.occ.TCollection_AsciiString_13(TCollection_ExtendedString, 0).ToCString(); nameAttrHandle.delete(); return nameString && nameString.trim() !== "" ? nameString.trim() : undefined; } catch (convertError) { console.warn("Error converting TCollection_ExtendedString:", convertError); } } nameAttrHandle.delete(); } catch (error) { console.warn("Error extracting name:", error); } return undefined; } extractAttributesFromXCAF(label) { const attributes = {}; try { // Try to extract various attributes from the label // This is where you would extract material properties, custom attributes, etc. // Example: Extract density if available using the correct FindAttribute method const realAttrHandle = new this.occ.Handle_TDF_Attribute_1(); const realID = new this.occ.TDataStd_Real().ID(); if (label.FindAttribute_1(realID, realAttrHandle)) { const realData = this.occ.TDataStd_Real.DownCast(realAttrHandle); // Cast the attribute to the correct type if (realData && !realData.IsNull()) { attributes.density = realData.Get(); realData.delete(); } } realAttrHandle.delete(); } catch (error) { console.warn("Error extracting attributes:", error); } return attributes; } extractProperties(shape) { const properties = {}; try { // Calculate volume const gprops = new this.occ.GProp_GProps_1(); this.occ.BRepGProp.VolumeProperties_1(shape, gprops, false, false, false); properties.volume = gprops.Mass(); // Calculate center of mass const centerOfMass = gprops.CentreOfMass(); properties.centerOfMass = [ centerOfMass.X(), centerOfMass.Y(), centerOfMass.Z() ]; gprops.delete(); centerOfMass.delete(); // Calculate surface area const surfaceGprops = new this.occ.GProp_GProps_1(); this.occ.BRepGProp.SurfaceProperties_1(shape, surfaceGprops, false, false); properties.surfaceArea = surfaceGprops.Mass(); surfaceGprops.delete(); // Calculate bounding box const bbox = new this.occ.Bnd_Box_1(); this.occ.BRepBndLib.Add(shape, bbox, false); const corner1 = bbox.CornerMin(); const corner2 = bbox.CornerMax(); properties.boundingBox = { min: [corner1.X(), corner1.Y(), corner1.Z()], max: [corner2.X(), corner2.Y(), corner2.Z()] }; bbox.delete(); corner1.delete(); corner2.delete(); } catch (error) { console.warn("Error extracting properties:", error); } return properties; } extractPMI(shape) { const pmiData = []; try { // Try to extract PMI data using XCAF dimension and tolerance tools // Note: This requires access to XCAFDoc_DimTolTool which may not be available in all OCCT builds // Extract annotations if available this.extractAnnotationsFromShape(shape, pmiData); } catch (error) { console.warn("Error extracting PMI data:", error); } return pmiData; } extractAnnotationsFromShape(shape, pmiData) { try { // This would require access to annotation tools in XCAF // For now, we'll add placeholder logic for common annotations // Check if shape has cylindrical features (holes, pins) this.extractCylindricalFeatures(shape, pmiData); } catch (error) { console.warn("Error extracting annotations:", error); } } extractCylindricalFeatures(shape, pmiData) { try { const faceExplorer = new this.occ.TopExp_Explorer_2(shape, this.occ.TopAbs_ShapeEnum.TopAbs_FACE, this.occ.TopAbs_ShapeEnum.TopAbs_SHAPE); while (faceExplorer.More()) { const face = this.occ.TopoDS.Face_1(faceExplorer.Current()); // Check if face is cylindrical const surface = this.occ.BRep_Tool.Surface_2(face); const surfaceType = surface.get().DynamicType().Name(); if (surfaceType.includes("Cylinder")) { // Extract cylinder properties const cylinderSurf = surface.get(); // Get cylinder axis and radius information const props = new this.occ.GProp_GProps_1(); this.occ.BRepGProp.SurfaceProperties_1(face, props, false, false); const centerOfMass = props.CentreOfMass(); pmiData.push({ type: "feature", text: "Cylindrical Feature", position: [centerOfMass.X(), centerOfMass.Y(), centerOfMass.Z()], direction: [0, 0, 1], properties: { type: "cylinder", surfaceArea: props.Mass() } }); props.delete(); centerOfMass.delete(); } surface.delete(); face.delete(); faceExplorer.Next(); } faceExplorer.delete(); } catch (error) { console.warn("Error extracting cylindrical features:", error); } } extractTransformation(shape) { // Extract transformation matrix from shape location const location = shape.Location_1(); const transformation = location.Transformation(); // Get translation const translation = transformation.TranslationPart(); const translationArray = [ translation.X(), translation.Y(), translation.Z() ]; // Get rotation matrix and convert to quaternion const rotationMatrix = transformation.HVectorialPart(); const quaternion = this.matrixToQuaternion(rotationMatrix); // Get scale (OCCT doesn't typically store non-uniform scale) const scale = [ transformation.ScaleFactor(), transformation.ScaleFactor(), transformation.ScaleFactor() ]; // Get full 4x4 matrix const matrix = this.extractTransformationMatrix(transformation); location.delete(); transformation.delete(); translation.delete(); rotationMatrix.delete(); return { translation: translationArray, rotation: quaternion, scale, matrix }; } extractTransformationMatrix(transformation) { // Extract 4x4 transformation matrix from gp_Trsf const matrix = new Array(16).fill(0); try { // Get the 3x4 transformation matrix values const values = [ [transformation.Value(1, 1), transformation.Value(1, 2), transformation.Value(1, 3), transformation.Value(1, 4)], [transformation.Value(2, 1), transformation.Value(2, 2), transformation.Value(2, 3), transformation.Value(2, 4)], [transformation.Value(3, 1), transformation.Value(3, 2), transformation.Value(3, 3), transformation.Value(3, 4)], [0, 0, 0, 1] ]; // Flatten to 16-element array (column-major order) for (let row = 0; row < 4; row++) { for (let col = 0; col < 4; col++) { matrix[col * 4 + row] = values[row][col]; } } } catch (error) { console.warn("Error extracting transformation matrix:", error); // Return identity matrix as fallback matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1; } return matrix; } matrixToQuaternion(rotMatrix) { try { // Extract quaternion from 3x3 rotation matrix const m11 = rotMatrix.Value(1, 1); const m12 = rotMatrix.Value(1, 2); const m13 = rotMatrix.Value(1, 3); const m21 = rotMatrix.Value(2, 1); const m22 = rotMatrix.Value(2, 2); const m23 = rotMatrix.Value(2, 3); const m31 = rotMatrix.Value(3, 1); const m32 = rotMatrix.Value(3, 2); const m33 = rotMatrix.Value(3, 3); // Convert rotation matrix to quaternion const trace = m11 + m22 + m33; let x, y, z, w; if (trace > 0) { const s = Math.sqrt(trace + 1.0) * 2; w = 0.25 * s; x = (m32 - m23) / s; y = (m13 - m31) / s; z = (m21 - m12) / s; } else if (m11 > m22 && m11 > m33) { const s = Math.sqrt(1.0 + m11 - m22 - m33) * 2; w = (m32 - m23) / s; x = 0.25 * s; y = (m12 + m21) / s; z = (m13 + m31) / s; } else if (m22 > m33) { const s = Math.sqrt(1.0 + m22 - m11 - m33) * 2; w = (m13 - m31) / s; x = (m12 + m21) / s; y = 0.25 * s; z = (m23 + m32) / s; } else { const s = Math.sqrt(1.0 + m33 - m11 - m22) * 2; w = (m21 - m12) / s; x = (m13 + m31) / s; y = (m23 + m32) / s; z = 0.25 * s; } return [x, y, z, w]; } catch (error) { console.warn("Error converting matrix to quaternion:", error); return [0, 0, 0, 1]; // Identity quaternion } } generateId() { return Math.random().toString(36).substr(2, 9); } countComponents(component, callback) { callback(component); component.children.forEach(child => this.countComponents(child, callback)); } // Utility method to serialize the assembly structure to JSON string serializeAssembly(assembly) { // Remove OCCT shape objects before serialization const cleanAssembly = this.cleanAssemblyForSerialization(assembly); return JSON.stringify(cleanAssembly, null, 2); } cleanAssemblyForSerialization(assembly) { const clean = JSON.parse(JSON.stringify(assembly)); const removeShapes = (component) => { delete component.shape; if (component.children) { component.children = component.children.map(removeShapes); } return component; }; clean.root = removeShapes(clean.root); return clean; } // New method for parsing XCAF documents with full metadata parseXCAFDocument(doc) { const mainLabel = doc.Main(); const shapeTool = this.occ.XCAFDoc_DocumentTool.ShapeTool(mainLabel).get(); const colorTool = this.occ.XCAFDoc_DocumentTool.ColorTool(mainLabel).get(); const materialTool = this.occ.XCAFDoc_DocumentTool.MaterialTool(mainLabel).get(); // Build color assignment map this.buildColorAssignments(colorTool); // Get all free shapes (top-level assembly components) const freeShapes = new this.occ.TDF_LabelSequence_1(); shapeTool.GetFreeShapes(freeShapes); let totalParts = 0; let totalAssemblies = 0; let rootComponent; if (freeShapes.Length() === 1) { // Single root assembly const rootLabel = freeShapes.Value(1); const rootShape = new this.occ.TopoDS_Shape(); if (this.occ.XCAFDoc_ShapeTool.GetShape_1(rootLabel, rootShape)) { rootComponent = this.parseXCAFComponent(rootShape, rootLabel, shapeTool, colorTool, materialTool, "root", 0); rootShape.delete(); } else { // Fallback rootComponent = this.createEmptyRootComponent(); } } else if (freeShapes.Length() > 1) { // Multiple top-level components - create virtual root rootComponent = this.createEmptyRootComponent(); for (let i = 1; i <= freeShapes.Length(); i++) { const label = freeShapes.Value(i); const shape = new this.occ.TopoDS_Shape(); if (this.occ.XCAFDoc_ShapeTool.GetShape_1(label, shape)) { const component = this.parseXCAFComponent(shape, label, shapeTool, colorTool, materialTool, `Component_0_${i - 1}`, 1); rootComponent.children.push(component); shape.delete(); } } } else { // No free shapes - create empty root rootComponent = this.createEmptyRootComponent(); } this.countComponents(rootComponent, (component) => { if (component.type === "part") totalParts++; else totalAssemblies++; }); // Clean up freeShapes.delete(); return { version: "1.0.0", units: "mm", root: rootComponent, metadata: { totalParts, totalAssemblies, createdAt: new Date().toISOString(), source: "STEP/XCAF" } }; } createEmptyRootComponent() { return { id: this.generateId(), name: "root", type: "assembly", children: [], transformation: { translation: [0, 0, 0], rotation: [0, 0, 0, 1], scale: [1, 1, 1], matrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }, properties: {}, attributes: {}, pmi: [] }; } parseXCAFComponent(shape, label, shapeTool, colorTool, materialTool, name, level) { const id = this.generateId(); const isAssembly = this.occ.XCAFDoc_ShapeTool.IsAssembly(label); const component = { id, name: this.extractNameFromXCAFLabel(label) || name || `${isAssembly ? "Assembly" : "Part"}_${id}`, type: isAssembly ? "assembly" : "part", shape: shape, children: [], transformation: this.extractTransformation(shape), properties: this.extractProperties(shape), attributes: this.extractXCAFAttributes(label, materialTool), color: this.extractXCAFColor(shape, label, colorTool), pmi: this.extractPMI(shape) }; if (isAssembly) { component.children = this.extractXCAFChildren(label, shapeTool, colorTool, materialTool, level + 1); } return component; } extractXCAFChildren(assemblyLabel, shapeTool, colorTool, materialTool, level) { const children = []; // Get components of the assembly const components = new this.occ.TDF_LabelSequence_1(); this.occ.XCAFDoc_ShapeTool.GetComponents(assemblyLabel, components, false); for (let i = 1; i <= components.Length(); i++) { const componentLabel = components.Value(i); // Get the referenced shape const referredLabel = new this.occ.TDF_Label(); if (this.occ.XCAFDoc_ShapeTool.GetReferredShape(componentLabel, referredLabel)) { const componentShape = new this.occ.TopoDS_Shape(); if (this.occ.XCAFDoc_ShapeTool.GetShape_1(referredLabel, componentShape)) { // Get the location/transformation for this component instance const location = this.occ.XCAFDoc_ShapeTool.GetLocation(componentLabel); let finalShape = componentShape; if (location && !location.IsIdentity()) { finalShape = componentShape.Located(location, false); } const childComponent = this.parseXCAFComponent(finalShape, referredLabel, shapeTool, colorTool, materialTool, `Component_${level}_${i - 1}`, level); children.push(childComponent); // Clean up if (finalShape !== componentShape) { finalShape.delete(); } componentShape.delete(); if (location) { location.delete(); } } referredLabel.delete(); } componentLabel.delete(); } components.delete(); return children; } extractNameFromXCAFLabel(label) { try { const nameID = this.occ.TDataStd_Name.GetID(); const nameAttrHandle = new this.occ.Handle_TDF_Attribute_1(); if (label.FindAttribute_1(nameID, nameAttrHandle)) { try { const TCollection_ExtendedString = nameAttrHandle.get().Get(); const nameString = new this.occ.TCollection_AsciiString_13(TCollection_ExtendedString, 0).ToCString(); nameAttrHandle.delete(); if (nameString && nameString.trim() !== "") { return nameString.trim(); } } catch (convertError) { console.warn("Error converting TCollection_ExtendedString:", convertError); } } nameAttrHandle.delete(); } catch (error) { console.warn("Error extracting XCAF name:", error); } return undefined; } extractXCAFColor(shape, label, colorTool) { try { let color = new this.occ.Quantity_Color_1(); const colorRGB = new this.occ.Quantity_ColorRGBA_1(); if (colorTool.GetColor_1(label, color)) { const result = this.convertQuantityColorToRgba(color); color.delete(); return result; } if (colorTool.GetColor_2(label, colorRGB)) { color = colorRGB.GetRGB(); const result = this.convertQuantityColorToRgba(color); color.delete(); colorRGB.delete(); return result; } if (colorTool.GetColor_7(shape, this.occ.XCAFDoc_ColorType.XCAFDoc_ColorSurf, color)) { const result = this.convertQuantityColorToRgba(color); color.delete(); return result; } if (colorTool.GetColor_8(shape, this.occ.XCAFDoc_ColorType.XCAFDoc_ColorSurf, colorRGB)) { color = colorRGB.GetRGB(); const result = this.convertQuantityColorToRgba(color); color.delete(); colorRGB.delete(); return result; } if (colorTool.GetColor_7(shape, this.occ.XCAFDoc_ColorType.XCAFDoc_ColorCurv, color)) { const result = this.convertQuantityColorToRgba(color); color.delete(); return result; } if (colorTool.GetColor_8(shape, this.occ.XCAFDoc_ColorType.XCAFDoc_ColorCurv, colorRGB)) { color = colorRGB.GetRGB(); const result = this.convertQuantityColorToRgba(color); color.delete(); colorRGB.delete(); return result; } if (colorTool.GetColor_7(shape, this.occ.XCAFDoc_ColorType.XCAFDoc_ColorGen, color)) { const result = this.convertQuantityColorToRgba(color); color.delete(); return result; } if (colorTool.GetColor_8(shape, this.occ.XCAFDoc_ColorType.XCAFDoc_ColorGen, colorRGB)) { color = colorRGB.GetRGB(); const result = this.convertQuantityColorToRgba(color); colorRGB.delete(); color.delete(); return result; } const labelTag = String(label.Tag()); if (this.colorAssignments.has(labelTag)) { return this.colorAssignments.get(labelTag); } const subShapeColor = this.extractSubShapeColor(shape, colorTool); if (subShapeColor) { color.delete(); return subShapeColor; } color.delete(); } catch (error) { console.warn("Error extracting XCAF color:", error); } return undefined; } extractXCAFAttributes(label, materialTool) { const attributes = {}; try { this.extractMaterialFromLabel(label, attributes); this.extractAdditionalMaterialProperties(label, attributes); } catch (error) { console.warn("Error extracting XCAF attributes:", error); } return attributes; } extractMaterialFromLabel(label, attributes) { try { const materialName = this.extractMaterialName(label); if (materialName) { attributes.material = materialName; } const density = this.extractMaterialDensity(label); if (density > 0) { attributes.density = density; } this.extractAdditionalMaterialProperties(label, attributes); } catch (error) { console.warn("Error extracting material from label:", error); } } extractMaterialName(materialLabel) { try { const nameAttrHandle = new this.occ.Handle_TDF_Attribute_1(); if (materialLabel.FindAttribute_1(this.occ.TDataStd_Name.GetID(), nameAttrHandle)) { try { const TCollection_ExtendedString = nameAttrHandle.get().Get(); const nameString = new this.occ.TCollection_AsciiString_13(TCollection_ExtendedString, 0).ToCString(); nameAttrHandle.delete(); if (nameString && nameString.trim() !== "") { return nameString.trim(); } } catch (convertError) { console.warn("Error converting material name:", convertError); } } nameAttrHandle.delete(); } catch (error) { console.warn("Error extracting material name:", error); } return undefined; } extractMaterialDensity(materialLabel) { try { // Try to get density value from real attributes const realAttrHandle = new this.occ.Handle_TDF_Attribute_1(); const realID = new this.occ.TDataStd_Real().ID(); if (materialLabel.FindAttribute_1(realID, realAttrHandle)) { const attr = realAttrHandle.get(); if (attr) { try { const density = attr.Get(); realAttrHandle.delete(); return density; } catch (accessError) { console.warn("Error accessing density value:", accessError); } } } realAttrHandle.delete(); } catch (error) { console.warn("Error extracting material density:", error); } return 0; } extractAdditionalMaterialProperties(label, attributes) { try { const intAttrHandle = new this.occ.Handle_TDF_Attribute_1(); const intID = new this.occ.TDataStd_Integer().ID(); if (label.FindAttribute_1(intID, intAttrHandle)) { const attr = intAttrHandle.get(); if (attr) { try { const materialId = attr.Get(); attributes.materialId = materialId; } catch (accessError) { console.warn("Error accessing material ID:", accessError); } } } intAttrHandle.delete(); } catch (error) { console.warn("Error extracting additional material properties:", error); } } buildColorAssignments(colorTool) { this.colorAssignments.clear(); // Get all color labels const colorLabels = new this.occ.TDF_LabelSequence_1(); colorTool.GetColors(colorLabels); for (let i = 1; i <= colorLabels.Length(); i++) { const colorLabel = colorLabels.Value(i); const color = new this.occ.Quantity_Color_1(); if (colorTool.GetColor_1(colorLabel, color)) { this.colorAssignments.set(String(colorLabel.Tag()), this.convertQuantityColorToRgba(color)); } color.delete(); colorLabel.delete(); } console.log(this.colorAssignments); colorLabels.delete(); } }