425 lines
15 KiB
JavaScript
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 ('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);
|
|
}
|
|
}
|