186 lines
5.5 KiB
Python
186 lines
5.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright (C) 2023 Ilia Kurochkin <brothermechanic@gmail.com>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
'''
|
|
DESCRIPTION.
|
|
- Reads a FreeCAD .FCStd file.
|
|
- Set tesselation parts to mesh.
|
|
- Return scene as JSON dictionary.
|
|
'''
|
|
__version__ = '0.1'
|
|
import json
|
|
import FreeCAD
|
|
import Part
|
|
import Mesh
|
|
import MeshPart
|
|
import logging
|
|
import math
|
|
from freecad.utils.is_object_solid import is_object_solid
|
|
from utils.custom_parser import CustomArgumentParser
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def freecad_to_json(**kwargs):
|
|
''' Reads a FreeCAD .FCStd file and return json assembly. '''
|
|
|
|
scene = {}
|
|
js_objs = {}
|
|
|
|
doc = FreeCAD.open(kwargs['fcstd_path'])
|
|
docname = doc.Name
|
|
|
|
# collect all materials
|
|
fem_mats = []
|
|
for fem_mat in doc.Objects:
|
|
if fem_mat.isDerivedFrom('App::MaterialObjectPython'):
|
|
fem_mats.append(fem_mat)
|
|
|
|
for obj in doc.Objects:
|
|
js_obj = {}
|
|
|
|
if kwargs['skiphidden']:
|
|
if not obj.Visibility:
|
|
continue
|
|
|
|
if obj.isDerivedFrom('PartDesign::CoordinateSystem'):
|
|
js_obj['type'] = 'LCS'
|
|
|
|
elif obj.isDerivedFrom('Part::Feature'):
|
|
js_obj['type'] = 'PART'
|
|
# filter for nonsolids
|
|
|
|
if is_object_solid(obj) or hasattr(obj, kwargs['property_forse_nonsolid']):
|
|
# create mesh from shape
|
|
shape = obj.Shape
|
|
shape = obj.Shape.copy()
|
|
shape.Placement = obj.Placement.inverse().multiply(shape.Placement)
|
|
meshfromshape = doc.addObject('Mesh::Feature', 'Mesh')
|
|
if kwargs['tesselation_method'] == 'Standard':
|
|
meshfromshape.Mesh = MeshPart.meshFromShape(
|
|
Shape=shape,
|
|
LinearDeflection=kwargs['linear_deflection'],
|
|
AngularDeflection=math.radians(kwargs['angular_deflection']),
|
|
Relative=False)
|
|
elif kwargs['tesselation_method'] == 'FEM':
|
|
meshfromshape.Mesh = MeshPart.meshFromShape(
|
|
Shape=shape,
|
|
MaxLength=kwargs['fem_size'])
|
|
else:
|
|
raise TypeError('Wrong tesselation method! '
|
|
'Standard and FEM methods are supported only!')
|
|
|
|
t = meshfromshape.Mesh.Topology
|
|
verts = [[v.x, v.y, v.z] for v in t[0]]
|
|
faces = t[1]
|
|
js_obj['mesh'] = (verts, faces)
|
|
|
|
# one material for the whole object
|
|
for fem_mat in fem_mats:
|
|
for ref in fem_mat.References:
|
|
if ref[0].Label == obj.Label:
|
|
js_obj['material'] = fem_mat.Material
|
|
|
|
# skip for other object's types
|
|
else:
|
|
continue
|
|
|
|
js_obj['fc_location'] = tuple(obj.Placement.Base)
|
|
js_obj['fc_rotation'] = obj.Placement.Rotation.Q
|
|
|
|
# construct assembly hierarchy
|
|
obj_parent = obj.getParentGeoFeatureGroup()
|
|
obj_child_name = None
|
|
parents = {}
|
|
deep_index = 0
|
|
while obj_parent:
|
|
parent = {}
|
|
parent['fc_location'] = tuple(obj_parent.Placement.Base)
|
|
parent['fc_rotation'] = obj_parent.Placement.Rotation.Q
|
|
obj_child_name = obj_parent.Label
|
|
obj_parent = obj_parent.getParentGeoFeatureGroup()
|
|
if obj_parent:
|
|
parent['parent'] = obj_parent.Label
|
|
else:
|
|
parent['parent'] = None
|
|
parents[obj_child_name] = parent
|
|
parent['deep_index'] = deep_index
|
|
deep_index += 1
|
|
js_obj['hierarchy'] = parents
|
|
|
|
js_objs[obj.Label] = js_obj
|
|
|
|
FreeCAD.closeDocument(docname)
|
|
|
|
scene[kwargs['fcstd_path']] = js_objs
|
|
|
|
logger.info('Passed %s objects without errors', len(js_objs))
|
|
|
|
print(json.dumps(scene))
|
|
|
|
|
|
# to run script via FreeCADCmd
|
|
parser = CustomArgumentParser()
|
|
parser.add_argument(
|
|
'--fcstd_path',
|
|
type=str,
|
|
help='Path to source FreeCAD scene',
|
|
required=True
|
|
)
|
|
parser.add_argument(
|
|
'--tesselation_method',
|
|
type=str,
|
|
help='Select tesselation method: Standard or FEM.',
|
|
default='Standard',
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
'--linear_deflection',
|
|
type=float,
|
|
help='Max linear distance error',
|
|
default=0.1,
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
'--angular_deflection',
|
|
type=float,
|
|
help='Max angular distance error',
|
|
default=20.0,
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
'--fem_size',
|
|
type=float,
|
|
help='For FEM method only! Finite element size in mm',
|
|
default=50.0,
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
'--skiphidden',
|
|
type=bool,
|
|
help='Skip processing for hidden FreeCAD objects',
|
|
default=True,
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
'--property_forse_nonsolid',
|
|
type=str,
|
|
help='FreeCAD property to enable processing for nonsolid objects',
|
|
default='Robossembler_NonSolid',
|
|
required=False
|
|
)
|
|
|
|
fc_kwargs = vars(parser.parse_known_args()[0])
|
|
|
|
freecad_to_json(**fc_kwargs)
|
|
|
|
logger.info('FreeCAD scene passed!')
|