import { Direction } from '../geometry/geometry.js'; import { ImporterBase } from './importerbase.js'; import { GetFileExtension } from '../io/fileutils.js'; import { GetExternalLibPath } from '../io/externallibs.js'; import { ConvertThreeGeometryToMesh } from '../threejs/threeutils.js'; import { ArrayBufferToUtf8String } from '../io/bufferutils.js'; import { Node } from '../model/node.js'; import { ColorToMaterialConverter } from './importerutils.js'; import { RGBAColor } from '../model/color.js'; import { Property, PropertyGroup, PropertyType } from '../model/property.js'; import { Loc } from '../core/localization.js'; import * as fflate from 'fflate'; const DocumentInitResult = { Success : 0, NoDocumentXml : 1 }; class FreeCadObject { constructor (name, type) { this.name = name; this.type = type; this.shapeName = null; this.isVisible = false; this.color = null; this.fileName = null; this.fileContent = null; this.inLinkCount = 0; this.properties = null; } IsConvertible () { if (this.fileName === null || this.fileContent === null) { return false; } if (!this.isVisible) { return false; } if (this.inLinkCount > 0) { return false; } return true; } } class FreeCadDocument { constructor () { this.files = null; this.properties = null; this.objectNames = []; this.objectData = new Map (); } Init (fileContent) { let fileContentBuffer = new Uint8Array (fileContent); this.files = fflate.unzipSync (fileContentBuffer); if (!this.LoadDocumentXml ()) { return DocumentInitResult.NoDocumentXml; } this.LoadGuiDocumentXml (); return DocumentInitResult.Success; } GetObjectListToConvert () { let objectList = []; for (let objectName of this.objectNames) { let object = this.objectData.get (objectName); if (!object.IsConvertible ()) { continue; } objectList.push (object); } return objectList; } IsSupportedType (type) { if (!type.startsWith ('Part::') && !type.startsWith ('PartDesign::')) { return false; } if (type.indexOf ('Part2D') !== -1) { return false; } return true; } HasFile (fileName) { return (fileName in this.files); } LoadDocumentXml () { let documentXml = this.GetXMLContent ('Document.xml'); if (documentXml === null) { return false; } this.properties = new PropertyGroup (Loc ('Properties')); let documentElements = documentXml.getElementsByTagName ('Document'); for (let documentElement of documentElements) { for (let childNode of documentElement.childNodes) { if (childNode.tagName === 'Properties') { this.GetPropertiesFromElement (childNode, this.properties); } } } let objectsElements = documentXml.getElementsByTagName ('Objects'); for (let objectsElement of objectsElements) { let objectElements = objectsElement.getElementsByTagName ('Object'); for (let objectElement of objectElements) { let name = objectElement.getAttribute ('name'); let type = objectElement.getAttribute ('type'); if (!this.IsSupportedType (type)) { continue; } let object = new FreeCadObject (name, type); this.objectNames.push (name); this.objectData.set (name, object); } } let objectDataElements = documentXml.getElementsByTagName ('ObjectData'); for (let objectDataElement of objectDataElements) { let objectElements = objectDataElement.getElementsByTagName ('Object'); for (let objectElement of objectElements) { let name = objectElement.getAttribute ('name'); if (!this.objectData.has (name)) { continue; } let object = this.objectData.get (name); object.properties = new PropertyGroup (Loc ('Properties')); for (let childNode of objectElement.childNodes) { if (childNode.tagName === 'Properties') { this.GetPropertiesFromElement (childNode, object.properties); } } let propertyElements = objectElement.getElementsByTagName ('Property'); for (let propertyElement of propertyElements) { let propertyName = propertyElement.getAttribute ('name'); if (propertyName === 'Label') { object.shapeName = this.GetFirstChildValue (propertyElement, 'String', 'value'); } else if (propertyName === 'Visibility') { let isVisibleString = this.GetFirstChildValue (propertyElement, 'Bool', 'value'); object.isVisible = (isVisibleString === 'true'); } else if (propertyName === 'Visible') { let isVisibleString = this.GetFirstChildValue (propertyElement, 'Bool', 'value'); object.isVisible = (isVisibleString === 'true'); } else if (propertyName === 'Shape') { let fileName = this.GetFirstChildValue (propertyElement, 'Part', 'file'); if (!this.HasFile (fileName)) { continue; } let extension = GetFileExtension (fileName); if (extension !== 'brp' && extension !== 'brep') { continue; } object.fileName = fileName; object.fileContent = this.files[fileName]; } } let linkElements = objectElement.getElementsByTagName ('Link'); for (let linkElement of linkElements) { let linkedName = linkElement.getAttribute ('value'); if (this.objectData.has (linkedName)) { let linkedObject = this.objectData.get (linkedName); linkedObject.inLinkCount += 1; } } } } return true; } LoadGuiDocumentXml () { let documentXml = this.GetXMLContent ('GuiDocument.xml'); if (documentXml === null) { return false; } let viewProviderElements = documentXml.getElementsByTagName ('ViewProvider'); for (let viewProviderElement of viewProviderElements) { let name = viewProviderElement.getAttribute ('name'); if (!this.objectData.has (name)) { continue; } let object = this.objectData.get (name); let propertyElements = viewProviderElement.getElementsByTagName ('Property'); for (let propertyElement of propertyElements) { let propertyName = propertyElement.getAttribute ('name'); if (propertyName === 'Visibility') { let isVisibleString = this.GetFirstChildValue (propertyElement, 'Bool', 'value'); object.isVisible = (isVisibleString === 'true'); } else if (propertyName === 'ShapeColor') { let colorString = this.GetFirstChildValue (propertyElement, 'PropertyColor', 'value'); let rgba = parseInt (colorString, 10); object.color = new RGBAColor ( rgba >> 24 & 0xff, rgba >> 16 & 0xff, rgba >> 8 & 0xff, 255 ); } } } return true; } GetPropertiesFromElement (propertiesElement, propertyGroup) { let propertyElements = propertiesElement.getElementsByTagName ('Property'); for (let propertyElement of propertyElements) { let propertyName = propertyElement.getAttribute ('name'); let propertyType = propertyElement.getAttribute ('type'); let property = null; if (propertyType === 'App::PropertyBool') { let propertyValue = this.GetFirstChildValue (propertyElement, 'String', 'bool'); if (propertyValue !== null && propertyValue.length > 0) { property = new Property (PropertyType.Boolean, propertyName, propertyValue === 'true'); } } else if (propertyType === 'App::PropertyInteger') { let propertyValue = this.GetFirstChildValue (propertyElement, 'Integer', 'value'); if (propertyValue !== null && propertyValue.length > 0) { property = new Property (PropertyType.Integer, propertyName, parseInt (propertyValue)); } } else if (propertyType === 'App::PropertyString') { let propertyValue = this.GetFirstChildValue (propertyElement, 'String', 'value'); if (propertyValue !== null && propertyValue.length > 0) { property = new Property (PropertyType.Text, propertyName, propertyValue); } } else if (propertyType === 'App::PropertyUUID') { let propertyValue = this.GetFirstChildValue (propertyElement, 'Uuid', 'value'); if (propertyValue !== null && propertyValue.length > 0) { property = new Property (PropertyType.Text, propertyName, propertyValue); } } else if (propertyType === 'App::PropertyFloat' || propertyType === 'App::PropertyLength' || propertyType === 'App::PropertyDistance' || propertyType === 'App::PropertyArea' || propertyType === 'App::PropertyVolume') { let propertyValue = this.GetFirstChildValue (propertyElement, 'Float', 'value'); if (propertyValue !== null && propertyValue.length > 0) { property = new Property (PropertyType.Number, propertyName, parseFloat (propertyValue)); } } if (property !== null) { propertyGroup.AddProperty (property); } } } GetXMLContent (xmlFileName) { if (!this.HasFile (xmlFileName)) { return null; } let xmlParser = new DOMParser (); let xmlString = ArrayBufferToUtf8String (this.files[xmlFileName]); return xmlParser.parseFromString (xmlString, 'text/xml'); } GetFirstChildValue (element, childTagName, childAttribute) { let childObjects = element.getElementsByTagName (childTagName); if (childObjects.length === 0) { return null; } return childObjects[0].getAttribute (childAttribute); } } export class ImporterFcstd extends ImporterBase { constructor () { super (); this.worker = null; this.document = null; } CanImportExtension (extension) { return extension === 'fcstd'; } GetUpDirection () { return Direction.Z; } ClearContent () { if (this.worker !== null) { this.worker.terminate (); this.worker = null; } this.document = null; } ResetContent () { this.worker = null; this.document = new FreeCadDocument (); } ImportContent (fileContent, onFinish) { let result = this.document.Init (fileContent); if (result === DocumentInitResult.NoDocumentXml) { this.SetError (Loc ('No Document.xml found.')); onFinish (); return; } if (this.document.properties !== null && this.document.properties.PropertyCount () > 0) { this.model.AddPropertyGroup (this.document.properties); } let objectsToConvert = this.document.GetObjectListToConvert (); if (objectsToConvert.length === 0) { this.SetError (Loc ('No importable object found.')); onFinish (); return; } this.ConvertObjects (objectsToConvert, onFinish); } ConvertObjects (objects, onFinish) { let workerPath = GetExternalLibPath ('loaders/occt-import-js-worker.js'); this.worker = new Worker (workerPath); let convertedObjectCount = 0; let colorToMaterial = new ColorToMaterialConverter (this.model); let onFileConverted = (resultContent) => { if (resultContent !== null) { let currentObject = objects[convertedObjectCount]; this.OnFileConverted (currentObject, resultContent, colorToMaterial); } convertedObjectCount += 1; if (convertedObjectCount === objects.length) { onFinish (); } else { let currentObject = objects[convertedObjectCount]; this.worker.postMessage ({ format : 'brep', buffer : currentObject.fileContent }); } }; this.worker.addEventListener ('message', (ev) => { onFileConverted (ev.data); }); this.worker.addEventListener ('error', (ev) => { onFileConverted (null); }); let currentObject = objects[convertedObjectCount]; this.worker.postMessage ({ format : 'brep', buffer : currentObject.fileContent }); } OnFileConverted (object, resultContent, colorToMaterial) { if (!resultContent.success || resultContent.meshes.length === 0) { return; } let objectNode = new Node (); if (object.shapeName !== null) { objectNode.SetName (object.shapeName); } let objectMeshIndex = 1; for (let resultMesh of resultContent.meshes) { let materialIndex = null; if (object.color !== null) { materialIndex = colorToMaterial.GetMaterialIndex ( object.color.r, object.color.g, object.color.b, object.color.a ); } let mesh = ConvertThreeGeometryToMesh (resultMesh, materialIndex, null); if (object.shapeName !== null) { let indexString = objectMeshIndex.toString ().padStart (3, '0'); mesh.SetName (object.shapeName + ' ' + indexString); } if (object.properties !== null && object.properties.PropertyCount () > 0) { mesh.AddPropertyGroup (object.properties); } let meshIndex = this.model.AddMesh (mesh); objectNode.AddMeshIndex (meshIndex); objectMeshIndex += 1; } let rootNode = this.model.GetRootNode (); rootNode.AddChildNode (objectNode); } }