framework/cg/freecad/utils/freecad_to_json.py
2023-11-13 13:07:33 +00:00

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!')