ModelHandle/source/engine/import/importerfcstd.js

425 lines
15 KiB
JavaScript

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 ('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);
}
}