Support for FCStd format #307
This commit is contained in:
parent
b06b0c97a4
commit
f9b3d9bc82
@ -22,7 +22,7 @@ The repository is separated into two parts. See more information in the [Develop
|
|||||||
|
|
||||||
## Supported file formats
|
## Supported file formats
|
||||||
|
|
||||||
* **Import**: obj, 3ds, stl, ply, gltf, glb, off, 3dm, fbx, dae, wrl, 3mf, ifc, brep, step, iges, bim.
|
* **Import**: obj, 3ds, stl, ply, gltf, glb, off, 3dm, fbx, dae, wrl, 3mf, ifc, brep, step, iges, fcstd, bim.
|
||||||
* **Export**: obj, stl, ply, gltf, glb, off, 3dm, bim.
|
* **Export**: obj, stl, ply, gltf, glb, off, 3dm, bim.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
"keywords": [
|
"keywords": [
|
||||||
"3d", "viewer", "cad",
|
"3d", "viewer", "cad",
|
||||||
"obj", "3ds", "stl", "ply", "gltf", "glb", "off", "3dm", "fbx",
|
"obj", "3ds", "stl", "ply", "gltf", "glb", "off", "3dm", "fbx",
|
||||||
"dae", "wrl", "3mf", "ifc", "brep", "step", "iges", "bim"
|
"dae", "wrl", "3mf", "ifc", "brep", "step", "iges", "fcstd", "bim"
|
||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
"build/o3dv.min.js",
|
"build/o3dv.min.js",
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { ImporterBim } from './importerbim.js';
|
|||||||
import { ImporterThree3mf, ImporterThreeDae, ImporterThreeFbx, ImporterThreeWrl } from './importerthree.js';
|
import { ImporterThree3mf, ImporterThreeDae, ImporterThreeFbx, ImporterThreeWrl } from './importerthree.js';
|
||||||
|
|
||||||
import * as fflate from 'fflate';
|
import * as fflate from 'fflate';
|
||||||
|
import { ImporterFcstd } from './importerfcstd.js';
|
||||||
|
|
||||||
export class ImportSettings
|
export class ImportSettings
|
||||||
{
|
{
|
||||||
@ -91,6 +92,7 @@ export class Importer
|
|||||||
new Importer3dm (),
|
new Importer3dm (),
|
||||||
new ImporterIfc (),
|
new ImporterIfc (),
|
||||||
new ImporterOcct (),
|
new ImporterOcct (),
|
||||||
|
new ImporterFcstd (),
|
||||||
new ImporterThreeFbx (),
|
new ImporterThreeFbx (),
|
||||||
new ImporterThreeDae (),
|
new ImporterThreeDae (),
|
||||||
new ImporterThreeWrl (),
|
new ImporterThreeWrl (),
|
||||||
|
|||||||
418
source/engine/import/importerfcstd.js
Normal file
418
source/engine/import/importerfcstd.js
Normal file
@ -0,0 +1,418 @@
|
|||||||
|
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, NodeType } from '../model/node.js';
|
||||||
|
import { ColorToMaterialConverter } from './importerutils.js';
|
||||||
|
import { RGBAColor } from '../model/color.js';
|
||||||
|
import { Property, PropertyGroup, PropertyType } from '../model/property.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 ('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 ('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 ('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 ();
|
||||||
|
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 ();
|
||||||
|
objectNode.SetType (NodeType.GroupNode);
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -26,6 +26,7 @@ import { Importer3dm } from './import/importer3dm.js';
|
|||||||
import { Importer3ds } from './import/importer3ds.js';
|
import { Importer3ds } from './import/importer3ds.js';
|
||||||
import { ImporterBase } from './import/importerbase.js';
|
import { ImporterBase } from './import/importerbase.js';
|
||||||
import { ImporterBim } from './import/importerbim.js';
|
import { ImporterBim } from './import/importerbim.js';
|
||||||
|
import { ImporterFcstd } from './import/importerfcstd.js';
|
||||||
import { InputFile, ImporterFile, ImporterFileList, InputFilesFromUrls, InputFilesFromFileObjects } from './import/importerfiles.js';
|
import { InputFile, ImporterFile, ImporterFileList, InputFilesFromUrls, InputFilesFromFileObjects } from './import/importerfiles.js';
|
||||||
import { ImporterGltf } from './import/importergltf.js';
|
import { ImporterGltf } from './import/importergltf.js';
|
||||||
import { ImporterIfc } from './import/importerifc.js';
|
import { ImporterIfc } from './import/importerifc.js';
|
||||||
@ -152,6 +153,7 @@ export {
|
|||||||
Importer3ds,
|
Importer3ds,
|
||||||
ImporterBase,
|
ImporterBase,
|
||||||
ImporterBim,
|
ImporterBim,
|
||||||
|
ImporterFcstd,
|
||||||
InputFile,
|
InputFile,
|
||||||
ImporterFile,
|
ImporterFile,
|
||||||
ImporterFileList,
|
ImporterFileList,
|
||||||
|
|||||||
@ -75,7 +75,7 @@ export class Mesh extends ModelObject3D
|
|||||||
return this.normals.length - 1;
|
return this.normals.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetNormal (index, normal)
|
SetNormal (index, normal)
|
||||||
{
|
{
|
||||||
this.normals[index] = normal;
|
this.normals[index] = normal;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -206,6 +206,14 @@
|
|||||||
<td class="center red">✗</td>
|
<td class="center red">✗</td>
|
||||||
<td><a href="https://github.com/kovacsv/occt-import-js">occt-import-js</a></td>
|
<td><a href="https://github.com/kovacsv/occt-import-js">occt-import-js</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>FreeCAD Standard file format</td>
|
||||||
|
<td>FCStd</td>
|
||||||
|
<td>text</td>
|
||||||
|
<td class="center green">✓</td>
|
||||||
|
<td class="center red">✗</td>
|
||||||
|
<td><a href="https://github.com/kovacsv/occt-import-js">occt-import-js</a></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Industry Foundation Classes</td>
|
<td>Industry Foundation Classes</td>
|
||||||
<td>ifc</td>
|
<td>ifc</td>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user