Импорт фрейма (объекта локальных осей) в качестве локальной матрицы

This commit is contained in:
brothermechanic 2023-04-05 11:26:09 +00:00 committed by Igor Brylyov
parent 16f6f375b5
commit 5208577d86
9 changed files with 122 additions and 76 deletions

View file

@ -1,13 +1,14 @@
## import-fcstd
### Импорт .FCStd сцены FreeCAD в Blender.
Автор оригинальной версии сценария yorikvanhavre: [github](https://gist.github.com/yorikvanhavre/680156f59e2b42df8f5f5391cae2660b)
Представлен в качестве модуля `importer.py` c подмодулями.
Импортирует все видимые solid объекты в качестве mesh в Blender сцену.
Представлен в качестве модуля `import_cad_objects` c подмодулями.
По умолчанию, импортирует из FreeCAD cцены все видимые solid объекты в Blender сцену.
Задает имена mesh на основе solid объектов.
Импортирует локальные координаты и задает их mesh объектам.
Импортирует FEM материалы (если они есть) и задает их mesh объектам.
Может работать как в качестве модуля, так и внутри blender сцены.
Так же пресдставлен модуль импорта точек координат `import_coordinate_point`.
Модуль может использоваться для передачи осей координат и позиций из FreeCAD.
Например локальные координаты подобъекта плоскости, точки или задание локального нуля.

View file

@ -1,14 +1,2 @@
# -*- coding: utf-8 -*-
# Original code by (C) 2019 yorikvanhavre <yorik@uncreated.net>
# 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.
__version__ = "0.1"

View file

@ -11,7 +11,7 @@
# 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.
import time
import FreeCAD
import logging
import xml
@ -29,10 +29,10 @@ logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
def importer(filename,
def obj_importer(filename,
tessellation,
update=False,
placement=True,
tessellation=10.0,
skiphidden=True,
scale=0.001,
select=True,
@ -40,33 +40,8 @@ def importer(filename,
"""Reads a FreeCAD .FCStd file and creates Blender objects"""
#path = '/usr/lib64/freecad/lib64'
TRIANGULATE = False # set to True to triangulate all faces (will loose multimaterial info)
TRIANGULATE = True # set to True to triangulate all faces (will loose multimaterial info)
'''
try:
# append the FreeCAD path specified in addon preferences
user_preferences = bpy.context.preferences
addon_prefs = user_preferences.addons[__name__].preferences
path = addon_prefs.filepath
if path:
if os.path.isfile(path):
path = os.path.dirname(path)
logger.debug("Configured FreeCAD path:", path)
sys.path.append(path)
else:
logger.debug("FreeCAD path is not configured in preferences")
import FreeCAD
except:
logger.debug("Unable to import the FreeCAD Python module. Make sure it is installed on your system")
logger.debug("and compiled with Python3 (same version as Blender).")
logger.debug("It must also be found by Python, you might need to set its path in this Addon preferences")
logger.debug("(User preferences->Addons->expand this addon).")
if report:
report({'ERROR'},"Unable to import the FreeCAD Python module. Check Addon preferences.")
return {'CANCELLED'}
# check if we have a GUI document
'''
guidata = {}
zdoc = zipfile.ZipFile(filename)
if zdoc:
@ -91,7 +66,7 @@ def importer(filename,
cols.append((buf[i*4+3],buf[i*4+2],buf[i*4+1],buf[i*4]))
guidata[key]["DiffuseColor"] = cols
zdoc.close()
#logger.debug ("guidata:",guidata)
doc = FreeCAD.open(filename)
docname = doc.Name
if not doc:
@ -99,7 +74,6 @@ def importer(filename,
if report:
report({'ERROR'},"Unable to open the given FreeCAD file")
return {'CANCELLED'}
#logger.debug ("Transferring",len(doc.Objects),"objects to Blender")
# import some FreeCAD modules needed below. After "import FreeCAD" these modules become available
import Part
@ -131,17 +105,20 @@ def importer(filename,
verts = []
edges = []
faces = []
matindex = [] # face to material relationship
faceedges = [] # a placeholder to store edges that belong to a face
matindex = [] # face to material relationship
faceedges = [] # a placeholder to store edges that belong to a face
if obj.isDerivedFrom("Part::Feature"):
# !!!
# create mesh from shape
shape = obj.Shape
if placement:
# !!!
placement = obj.Placement
shape = obj.Shape.copy()
shape.Placement = placement.inverse().multiply(shape.Placement)
if shape.Faces:
# !!!
if TRIANGULATE:
# triangulate and make faces
rawdata = shape.tessellate(tessellation)
@ -153,9 +130,12 @@ def importer(filename,
for e in face.Edges:
faceedges.append(e.hashCode())
else:
# !!!
# write FreeCAD faces as polygons when possible
time_start = time.time()
for face in shape.Faces:
if (len(face.Wires) > 1) or (not isinstance(face.Surface,Part.Plane)) or hascurves(face):
# !!!
# face has holes or is curved, so we need to triangulate it
rawdata = face.tessellate(tessellation)
for v in rawdata[0]:
@ -170,6 +150,7 @@ def importer(filename,
faces.append(nf)
matindex.append(len(rawdata[1]))
else:
# !!!
f = []
ov = face.OuterWire.OrderedVertexes
for v in ov:
@ -188,11 +169,13 @@ def importer(filename,
matindex.append(1)
for e in face.Edges:
faceedges.append(e.hashCode())
logger.debug('faces time is %s', (time.time() - time_start))
for edge in shape.Edges:
# !!!
# Treat remaining edges (that are not in faces)
if not (edge.hashCode() in faceedges):
if hascurves(edge):
dv = edge.discretize(9) #TODO use tessellation value
dv = edge.discretize(9) # TODO use tessellation value
for i in range(len(dv)-1):
dv1 = [dv[i].x,dv[i].y,dv[i].z]
dv2 = [dv[i+1].x,dv[i+1].y,dv[i+1].z]
@ -216,12 +199,13 @@ def importer(filename,
mesh = obj.Mesh
if placement:
placement = obj.Placement
mesh = obj.Mesh.copy() # in meshes, this zeroes the placement
mesh = obj.Mesh.copy() # in meshes, this zeroes the placement
t = mesh.Topology
verts = [[v.x,v.y,v.z] for v in t[0]]
faces = t[1]
if verts and (faces or edges):
# !!!
# create or update object with mesh and material data
bobj = None
bmat = None
@ -230,14 +214,16 @@ def importer(filename,
for o in bpy.data.objects:
if o.data.name == obj.Name:
bobj = o
logger.debug("Replacing existing object:",obj.Label)
logger.debug('Replacing existing %s', obj.Label)
bmesh = bpy.data.meshes.new(name=obj.Name)
bmesh.from_pydata(verts, edges, faces)
bmesh.update()
if bobj:
logger.debug('Updating the mesh of existing %s', obj.Label)
# update only the mesh of existing object. Don't touch materials
bobj.data = bmesh
else:
# !!!
# create new object
bobj = bpy.data.objects.new(obj.Label, bmesh)
if placement:
@ -251,6 +237,7 @@ def importer(filename,
bobj.rotation_mode = m
bobj.scale = (scale, scale, scale)
if obj.Name in guidata:
# !!!
# one material for the whole object
for fem_mat in doc.Objects:
set_fem_mat(obj, bobj, fem_mat)
@ -267,7 +254,3 @@ def importer(filename,
logger.info("Import freecad scene finished without errors")
return {'FINISHED'}
if __name__ == '__main__':
importer()

View file

@ -0,0 +1,46 @@
# -*- 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.
# Import from json exported FreeCAD's asm4 coordinates as Blender's empty object.
__version__ = "0.1"
import logging
import bpy
import json
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
def empty_importer(path_json):
with open(path_json) as f:
data = json.load(f)
pivot_name = data['label']
pivot_pose = data['placement']
loc = tuple(pivot_pose['position'].values())
fori = tuple(pivot_pose['orientation'].values())
bori = (fori[3],)+fori[:3]
bpy.ops.object.empty_add(
type='ARROWS', radius=0.01, align='WORLD',
location=(0, 0, 0), rotation=(0, 0, 0))
pivot_obj = bpy.context.active_object # or bpy.context.object
pivot_obj.name = pivot_name
pivot_obj.rotation_mode = 'QUATERNION'
pivot_obj.location = loc
pivot_obj.rotation_quaternion = bori
f.close()
logger.info('Point %s imported without errors', pivot_name)

View file

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
# Original code by (C) 2019 yorikvanhavre <yorik@uncreated.net>
# Copyright (C) 2023 Ilia Kurochkin <brothermechanic@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
@ -11,6 +10,9 @@
# 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.
# Simple FreeCAD's object test for manifold mawater-tight surface.
import FreeCAD

View file

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
# Original code by (C) 2019 yorikvanhavre <yorik@uncreated.net>
# Copyright (C) 2023 Ilia Kurochkin <brothermechanic@gmail.com>
#
# This program is free software; you can redistribute it and/or modify

View file

@ -25,12 +25,10 @@ def asset_setup(transforms=True, sharpness=True, shading=True):
bpy.context.view_layer.objects.active = ob
if transforms:
# apply scale
apply_transforms(ob, location=False, rotation=False, scale=True)
# remove doubles
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles(threshold = 0.0001)
bpy.ops.mesh.remove_doubles(threshold = 0.00001)
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(type = 'FACE')
bpy.ops.mesh.select_interior_faces()

View file

@ -12,12 +12,7 @@ import sys
import xml.etree.ElementTree as ET
import os
sys.path.append('../')
from import_fcstd.importer import importer
from mathutils import Matrix
from utils.remove_collections import remove_collections
from utils.cleanup_orphan_data import cleanup_orphan_data
from remesh import asset_setup
from export.stl import export_stl
import bpy
logger = logging.getLogger(__name__)

View file

@ -4,12 +4,15 @@ DESCRIPTION.
Convert and setup FreeCAD solid objects to 3d assets mesh files.
Support Blender compiled as a Python Module only!
"""
__version__ = "0.1"
__version__ = "0.2"
import logging
import os
import sys
sys.path.append('../blender/')
from import_fcstd.importer import importer
from import_fcstd.import_cad_objects import obj_importer
from import_fcstd.import_coordinate_point import empty_importer
from utils.apply_transforms import apply_transforms
from utils.remove_collections import remove_collections
from utils.cleanup_orphan_data import cleanup_orphan_data
from utils.sdf_mesh_selector import sdf_mesh_selector
@ -17,27 +20,55 @@ from remesh import asset_setup
from export.dae import export_dae
from export.collision import export_col_stl
import bpy
import mathutils
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
def freecad_asset_pipeline(fcstd_path,
mesh_export_path,
tessellation=10,
tessellation,
mesh_export_path=None,
json_path=None,
blend_path=None,
sdf_path=None):
""" Setup FreeCAD scene to CG asset """
remove_collections()
cleanup_orphan_data()
importer(fcstd_path, tessellation)
obj_importer(fcstd_path, tessellation)
# apply scale to all objects
obs = bpy.context.selected_objects
for ob in obs:
bpy.ops.object.select_all(action='DESELECT')
ob.select_set(state=True)
bpy.context.view_layer.objects.active = ob
apply_transforms(ob, location=False, rotation=False, scale=True)
for ob in obs:
ob.select_set(state=True)
if json_path is not None:
for point in os.listdir(json_path):
if point.endswith('.json'):
empty_importer(point)
if sdf_path is not None:
sdf_mesh_selector(sdf_path)
asset_setup()
if blend_path is not None:
bpy.ops.wm.save_as_mainfile(filepath=blend_path)
export_dae(mesh_export_path)
export_col_stl(mesh_export_path)
# export all objects
if mesh_export_path is not None:
obs = bpy.context.selected_objects
for ob in obs:
ob.matrix_world = mathutils.Matrix()
for ob in obs:
ob.select_set(state=True)
export_dae(mesh_export_path)
export_col_stl(mesh_export_path)
if __name__ == '__main__':
@ -47,10 +78,12 @@ if __name__ == '__main__':
description='Convert and setup FreeCAD solid objects to 3d assets mesh files.')
parser.add_argument(
'--fcstd_path', type=str, help='Path to source FreeCAD scene', required=True)
parser.add_argument(
'--mesh_export_path', type=str, help='Path for export meshes', required=True)
parser.add_argument(
'--tessellation', type=int, help='Tessellation number', default=10, required=False)
parser.add_argument(
'--mesh_export_path', type=str, help='Path for export meshes', required=False)
parser.add_argument(
'--json_path', type=str, help='Path to DIR with coordinate points jsons', required=False)
parser.add_argument(
'--blend_path', type=str, help='Path for export blend assembly file', required=False)
parser.add_argument(
@ -58,8 +91,9 @@ if __name__ == '__main__':
args = parser.parse_args()
freecad_asset_pipeline(args.fcstd_path,
args.mesh_export_path,
args.tessellation,
args.mesh_export_path,
args.json_path,
args.blend_path,
args.sdf_path)