# -*- coding: utf-8 -*- # Copyright (C) 2023 Ilia Kurochkin # # 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!')