Не подтверждена Коммит 87118e65 создал по автору Dealga McArdle's avatar Dealga McArdle Зафиксировано автором GitHub
Просмотр файлов

Fcstd fcad dicts (#4534)

* a few modifications and rst

* walrus assignment

* adds initial version of FreeCAD_utils module

* add corrected name

* append instead of add

* rename

* why wont the name change

* Rename FreeCad_utils.py to FreeCAD_utils.py

simple

* drop triangulation code for now

* change fcutil

* correct set add

* restore to working order

* explain why get_node isn't used

* drop some indent

* use python idioms

* add spaces

* more context managers

* rm whitespace

* modernize

* small change

* dict version?

* 6 fold speed increase using lame dict (without rounding)

* list comprehension version

* some comments

* forgot to pass faceedges, but wasn't using them

* apply some aliasing

* remove commented out code

* shuffle whitespace

* rewrite.. but keep old in the comment

* revert code of untested part

* add lenient mode to solid_to_mesh does ngons nicely

* rm whitespace

* typo

* typo

* register differently

* update docs

* update docs

* update solid to mesh doc

* correct rst syntax

* testing this lame registration cycle

* title change

* back to old registration tactic

* wtf

* place in correct location

* fstring in doctests

* revert to F.closeDocument(doc.Name)
владелец a1b60d49
FCStd Read Mod (Exchange)
=========================
Functionality
-------------
This node is an experimental modification of the original FCStd Read node.
This node reads one or more FCStd files, and
- intputs: Labels can be used to narrow down which objects you wish retrieve from the files.
- outputs: the `obj.Shape` as a Solid socket, it also
- outputs: `obj.FullName, obj.Name, obj.Label` in the Names socket.
Parameters
----------
- **global update**:, when this is enabled, any updates upstream into this node will trigger an update and the file is read again. Useful if your file is changing externally.
- **read body**:, add ``PartDesign Body`` items to the seek list
- **read part**:, add ``Part`` items to the seek list.
- **tool parts**:, (todo)
- **inverse filter**:, this will produce only the items listed in the Filter labels.
- **merge linked**:, this will pull in objects that are linked from other FreeCAD files. ( ``App Link`` )
- **read all**:, this "greedy mode" tells the node to ignore any filter labels and instead all possible shapes.
- **unit factor**: selected object shapes will be scaled locally by this factor, unchanged if 1.0
Inputs
------
filepath, label1 filter, label2 filter
Outputs
-------
solids, names
Examples
--------
......@@ -14,4 +14,5 @@ Exchange
FCStd_spreadsheet
FCStd_write
FCStd_read
FCStd_read_mod
gcode_exporter
......@@ -29,6 +29,12 @@ Methods
- Maximum edge length: If this number is smaller the mesh becomes finer. The smallest value is 0.
**Trivial**: ...
**Lenient**: This mode is based on Yorik van Havre's Blender FCStd importer code, but modified for speed. This mode produces tris, quads and ngons. It may not have coherent polygon normals. This handles curves and polygons with holes. This mode attemps to remove any duplicate/reverse faces.
If you really want to understand these algorithms, you should read the source code - it is the best reference.
Examples
--------
......
......@@ -678,6 +678,7 @@
SvReceiveFromSorcarNode
SvExportGcodeNode
SvReadFCStdNode
SvReadFCStdModNode
SvWriteFCStdNode
SvReadFCStdSketchNode
SvFCStdSpreadsheetNode
......
......@@ -166,7 +166,8 @@ else:
info(f'FCStd label read error: {Fname=}')
info(err)
finally:
del doc
# del doc
F.closeDocument(doc.Name)
return labels
......@@ -228,7 +229,8 @@ def LoadSolid(fc_file, part_filter, obj_mask, tool_parts, inv_filter):
except:
info('FCStd read error')
finally:
del doc
# del doc
F.closeDocument(doc.Name)
return solids
......
from sverchok.dependencies import FreeCAD
from sverchok.utils.dummy_nodes import add_dummy
from sverchok.utils.sv_operator_mixins import SvGenericNodeLocator
if FreeCAD is None:
add_dummy('SvReadFCStdModNode', 'SvReadFCStdModNode', 'FreeCAD')
else:
# june '22
# modified version of FCStd_read includes changes by rastart, zeffii, et al
# please feed changes back to the Sverchok issue tracker.
F = FreeCAD
import bpy
from mathutils import Matrix
from bpy.props import StringProperty, BoolProperty, EnumProperty, FloatProperty
from sverchok.node_tree import SverchCustomTreeNode
from sverchok.data_structure import updateNode
from sverchok.utils.logging import info
from FreeCAD import Base
class SvReadFCStdModOperator(bpy.types.Operator, SvGenericNodeLocator):
bl_idname = "node.sv_read_fcstd_operator_mod"
bl_label = "read freecad file"
bl_options = {'INTERNAL', 'REGISTER'}
def sv_execute(self, context, node):
if not any(socket.is_linked for socket in node.outputs):
return {'CANCELLED'}
if not node.inputs['File Path'].is_linked:
return {'CANCELLED'}
node.read_FCStd(node)
updateNode(node, context)
return {'FINISHED'}
def LabelReader(operator):
tree = bpy.data.node_groups[operator.tree_name]
node = tree.nodes[operator.node_name]
module_filter = []
# \/ does not appear to be available from the items= func
# node = self.get_node(context)
#
if node.read_features: module_filter.append('PartDesign')
if node.read_part: module_filter.append('Part')
if node.read_body: module_filter.append('PartDesign::Body')
if node.merge_linked: module_filter.append('App::Link')
labels = [('', '', '')]
fc_file_list = node.inputs['File Path'].sv_get()[0]
for fc_file in fc_file_list:
try:
doc = F.open(fc_file)
for obj in doc.Objects:
if obj.Module in module_filter or obj.TypeId in module_filter:
labels.append( (obj.Label, obj.Label, obj.Label) )
except:
info('FCStd label read error')
finally:
#del doc
F.closeDocument(doc.Name)
return labels
class SvShowFcstdNamesModOp(bpy.types.Operator, SvGenericNodeLocator):
bl_idname = "node.sv_show_fcstd_names_mod"
bl_label = "Show parts list"
bl_options = {'INTERNAL', 'REGISTER'}
bl_property = "option"
option: EnumProperty(items=lambda s, c: LabelReader(s))
def sv_execute(self, context, node):
node.name_filter = self.option
node.selected_label = self.option
node.selected_part = self.option
bpy.context.area.tag_redraw()
return {'FINISHED'}
def invoke(self, context, event):
context.space_data.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y)
wm = context.window_manager
wm.invoke_search_popup(self)
return {'FINISHED'}
class SvReadFCStdModNode(bpy.types.Node, SverchCustomTreeNode):
"""
Triggers: Read FreeCAD file mod
Tooltip: import parts from a .FCStd file
"""
bl_idname = 'SvReadFCStdModNode'
bl_label = 'Read FCStd MOD'
bl_icon = 'IMPORT'
solid_catergory = "Outputs"
read_update : BoolProperty(name="read_update", default=True)
read_body : BoolProperty(name="read_body", default=True, update=updateNode)
read_part : BoolProperty(name="read_part", default=True, update=updateNode)
tool_parts : BoolProperty(name="tool_parts", default=False, update=updateNode)
read_features : BoolProperty(name="read_features", default=False, update=updateNode)
inv_filter : BoolProperty(name="inv_filter", default=False, update=updateNode)
scale_factor : FloatProperty(name="unit factor", default=1, update=updateNode)
selected_label : StringProperty( default= 'Select FC Part')
selected_part : StringProperty( default='', update=updateNode)
merge_linked : BoolProperty(name="merge_linked", default=False, update=updateNode)
READ_ALL : BoolProperty(name="read all", default=False, update=updateNode)
def draw_buttons(self, context, layout):
col = layout.column(align=True)
if self.inputs['File Path'].is_linked:
self.wrapper_tracked_ui_draw_op(
col, "node.sv_show_fcstd_names_mod",
icon= 'TRIA_DOWN',
text= self.selected_label )
col.prop(self, 'read_update', text='global update')
col.prop(self, 'read_body')
col.prop(self, 'read_part')
col.prop(self, 'tool_parts')
if self.tool_parts:
col.prop(self, 'read_features')
col.prop(self, 'inv_filter')
col.prop(self, 'merge_linked')
col.prop(self,'READ_ALL')
col.prop(self, 'scale_factor')
self.wrapper_tracked_ui_draw_op(layout, "node.sv_read_fcstd_operator_mod", icon='FILE_REFRESH', text="UPDATE")
def sv_init(self, context):
self.inputs.new('SvFilePathSocket', "File Path")
self.inputs.new('SvStringsSocket', "Label1 Filter")
self.inputs.new('SvStringsSocket', "Label2 Filter")
self.outputs.new('SvSolidSocket', "Solid")
self.outputs.new('SvTextSocket', "Names")
def read_FCStd(self, node):
files = node.inputs['File Path'].sv_get()[0]
label_1_filter = []
label_2_filter = []
label_1_tags = []
label_2_tags = []
if (L1_Filter := node.inputs['Label1 Filter']).is_linked:
raw_filter = L1_Filter.sv_get()[0]
if len(raw_filter) == 1 and ',' in raw_filter[0]:
raw_filter = raw_filter[0].split(',')
for i in raw_filter:
label_1_tags.append(i) if '#' in i else label_1_filter.append(i)
if (L2_Filter := node.inputs['Label2 Filter']).is_linked:
raw_filter = L2_Filter.sv_get()[0]
if len(raw_filter) == 1 and ',' in raw_filter[0]:
raw_filter = raw_filter[0].split(',')
for i in raw_filter:
label_2_tags.append(i) if '#' in i else label_2_filter.append(i)
#ADD TO LABEL 1 FILTER THE DROPDOWN ENTRY IF SELECTED
if node.selected_part != '' and not node.selected_part in label_1_filter:
label_1_filter.append(node.selected_part)
solids = []
identifiers = []
module_filter = []
if node.read_features: module_filter.append('PartDesign')
if node.read_part: module_filter.append('Part')
if node.read_body: module_filter.append('PartDesign::Body')
for fname in files:
S = LoadSolid(
self.scale_factor, fname,
label_1_filter, label_1_tags, label_2_filter, label_2_tags,
module_filter, node.tool_parts,
node.inv_filter, self.READ_ALL, self.merge_linked )
for s, ident in S:
solids.append(s)
identifiers.append(ident)
node.outputs['Solid'].sv_set(solids)
node.outputs['Names'].sv_set(identifiers)
def process(self):
if not any(socket.is_linked for socket in self.outputs):
return
if not self.inputs['File Path'].is_linked:
return
if self.read_update:
self.read_FCStd(self)
else:
return
def LoadSolid(
scale_factor, fc_file,
label_1_filter, label_1_tags, label_2_filter, label_2_tags,
module_filter, tool_parts,
inv_filter, READ_ALL, merge_linked):
objs = set()
sel_objs = set()
outList = set()
solids = set()
try:
doc = F.open(fc_file)
#PRE-FILTER FREECAD ENTITY BY TYPE_ID
#AVOID REIMPORT COMPOUND CHILDS
#for obj in F.ActiveDocument.Objects:
#if obj.TypeId == 'Part::Compound':
#for child in obj.Links:
#outList.add(child)
if merge_linked: module_filter.append('App::Link')
for obj in doc.Objects:
#print ('START',obj)
'''
if merge_linked and obj.TypeId == 'App::Link':
if obj.LinkedObject.Module in ('Part', 'PartDesign'):
if len(obj.LinkedObject.Shape.Solids)>0:
try:
shapeobj = F.ActiveDocument.addObject(obj.LinkedObject.TypeId, obj.LinkedObject.Name + '_' + obj.Name )
M=F.Matrix();M.scale(1, 1, 1)#M.scale(scale_factor, scale_factor, scale_factor)
new_shape = obj.LinkedObject.Shape.transformGeometry(M)
new_shape.Placement = obj.Placement
shapeobj.Shape = new_shape
obj = shapeobj
except:
print ('ERROR',obj)
'''
if obj.Module in module_filter or obj.TypeId in module_filter:
objs.add (obj)
elif not tool_parts and obj.TypeId in ( 'Part::Cut','Part::Fuse','Part::MultiCommon','Part::Section','Part::FeaturePython' ):
if len(obj.OutList) > 0:
for out_obj in obj.OutList:
outList.add (out_obj)
objs = objs - outList
#SEARCH FOR TAGGED ITEM
if len(label_1_tags)>0:
for tag in label_1_tags:
for obj in objs:
if tag[1:] in obj.Label:
label_1_filter.append(obj.Label)
if len(label_2_tags)>0:
for tag in label_2_tags:
for obj in objs:
if tag[1:] in obj.Label2:
label_2_filter.append(obj.Label2)
for obj in objs:
if READ_ALL:
sel_objs.add( obj )
elif len(label_1_filter)>0:
if obj.Label in label_1_filter:
if len(label_2_filter)>0:
if obj.Label2 in label_2_filter:
sel_objs.add(obj)
else:
sel_objs.add(obj)
elif len(label_1_filter)==0 and len(label_2_filter)>0:
if obj.Label2 in label_2_filter: sel_objs.add(obj)
if inv_filter:
sel_objs = objs - sel_objs
for obj in sel_objs:
'''
if obj.TypeId == 'App::Link':
M=F.Matrix(); M.scale(scale_factor, scale_factor, scale_factor)
new_shape = obj.LinkedObject.Shape.transformGeometry(M)
new_shape.Placement = obj.Placement
solids.add( new_shape )
'''
obj_info = obj.FullName, obj.Name, obj.Label
if scale_factor != 1:
if len(obj.Shape.Solids) > 0:
M = F.Matrix()
M.scale(scale_factor, scale_factor, scale_factor)
solids.add(( obj.Shape.transformGeometry(M), obj_info ))
else:
solids.add(( obj.Shape, obj_info ))
except Exception as err:
info(f"FCStd read error {err}")
finally:
#del doc
F.closeDocument(doc.Name)
return solids
def unitCheck(solid, scale_factor):
if len(solid.Solids) > 0:
M = F.Matrix()
M.scale(scale_factor, scale_factor, scale_factor)
return solid.transformGeometry(M)
else:
return solid
def register():
if FreeCAD:
bpy.utils.register_class(SvReadFCStdModNode)
bpy.utils.register_class(SvShowFcstdNamesModOp)
bpy.utils.register_class(SvReadFCStdModOperator)
def unregister():
if FreeCAD:
bpy.utils.unregister_class(SvReadFCStdModOperator)
bpy.utils.unregister_class(SvShowFcstdNamesModOp)
bpy.utils.unregister_class(SvReadFCStdModNode)
......@@ -14,7 +14,7 @@ else:
from sverchok.node_tree import SverchCustomTreeNode
from sverchok.data_structure import updateNode, has_element, match_long_repeat as mlr
from sverchok.utils.solid import mesh_from_solid_faces
from sverchok.utils.solid import mesh_from_solid_faces, mesh_from_solid_faces_MOD, drop_existing_faces
from sverchok.utils.sv_bmesh_utils import recalc_normals
from sverchok.utils.sv_mesh_utils import non_redundant_faces_indices_np as clean
......@@ -34,11 +34,12 @@ else:
sv_icon = 'SV_SOLID_TO_MESH'
solid_catergory = "Outputs"
modes = [
('Basic', 'Basic', '', 0),
('Basic', 'Basic', '', 0),
('Standard', 'Standard', '', 1),
('Mefisto', 'Mefisto', '', 2),
# ('NetGen', 'NetGen', '', 3),
('Trivial', 'Trivial', '', 10)
('Mefisto', 'Mefisto', '', 2),
# ('NetGen', 'NetGen', '', 3),
('Trivial', 'Trivial', '', 10),
('Lenient', 'Lenient', '', 14)
]
shape_types = [
('Solid', 'Solid', '', 0),
......@@ -153,15 +154,12 @@ else:
rawdata = solid.tessellate(precision)
else:
rawdata = solid.face.tessellate(precision)
b_verts = []
b_faces = []
for v in rawdata[0]:
b_verts.append((v.x, v.y, v.z))
for f in rawdata[1]:
b_faces.append(f)
verts.append(b_verts)
b_verts = [(v.x, v.y, v.z) for v in rawdata[0]]
b_faces = [f for f in rawdata[1]]
b_faces = clean(b_faces).tolist() if is_triangles_only(b_faces) else b_faces
verts.append(b_verts)
faces.append(b_faces)
return verts, faces
......@@ -226,6 +224,7 @@ else:
shape = solid
else:
shape = solid.face
new_verts, new_edges, new_faces = mesh_from_solid_faces(shape)
new_verts, new_edges, new_faces = recalc_normals(new_verts, new_edges, new_faces)
......@@ -234,6 +233,26 @@ else:
return verts, faces
def lenient_mesher(self):
"""
this mode will produce a variety of polygon types (tris, quads, ngons...)
"""
solids = self.inputs[self["shape_type"]].sv_get()
verts = []
faces = []
for idx, solid in enumerate(solids):
if self.shape_type == 'Solid':
shape = solid
else:
shape = solid.face
new_verts, new_faces = mesh_from_solid_faces_MOD(shape, quality=1.0)
verts.append(new_verts)
faces.append(new_faces)
return verts, faces
def process(self):
if not any(socket.is_linked for socket in self.outputs):
return
......@@ -244,6 +263,8 @@ else:
verts, faces = self.standard_mesher()
elif self.mode == 'Mefisto':
verts, faces = self.mefisto_mesher()
elif self.mode == 'Lenient':
verts, faces = self.lenient_mesher()
else: # Trivial
verts, faces = self.trivial_mesher()
......
......@@ -168,11 +168,15 @@ sun_position.py""".split("\n")
else:
bad_files.append(module_file)
category = dir_name
if known:
info("Category %s: Tolerating unexistance of the documentation for the following nodes for now:\n%s", dir_name, "\n".join(known))
explicitly_missing = "\n".join(known)
info(f"{category=}: Tolerating missing documentation for the following nodes for now:\n{explicitly_missing=}")
if bad_files:
self.fail("Not all nodes of category `{}' have corresponding documentation; missing are:\n{}".format(dir_name, "\n".join(bad_files)))
missing = "\n".join(bad_files)
self.fail(f"Not all nodes of {category=} have corresponding documentation; \n{missing=}")
for directory, subdirs, fnames in walk(nodes_dir):
with self.subTest(directory=basename(directory)):
......
......@@ -131,7 +131,7 @@ utils_modules = [
"wfc_algorithm", "handle_blender_data", "nodes_mixins.generating_objects", "decorators_compilation",
"nodes_mixins.show_3d_properties", "modules_inspection", "sv_json_export", "sv_json_import",
"meshes", "tree_walk", "mesh_functions", 'mesh.inset_faces', 'mesh.extrude_edges', "sv_json_struct",
"nodeview_time_graph_drawing", "modules.shader_utils", "dummy_nodes",
"nodeview_time_graph_drawing", "modules.shader_utils", "modules.FreeCAD_utils", "dummy_nodes",
# UI text editor ui
"text_editor_submenu", "text_editor_plugins",
# UI operators and tools
......
......@@ -327,7 +327,7 @@ def np_vectors_angle(v1, v2):
return np.arccos(dot)
def np_dot(u, v, axis=1):
'conveniece function to calculate dot vector between vector arrays'
'''convenience function to calculate dot vector between vector arrays'''
return np.sum(u * v, axis=axis)
def np_normalized_vectors(vecs):
......
"""
find the add-on version of this module at:
https://gist.github.com/yorikvanhavre/680156f59e2b42df8f5f5391cae2660b
reproduced large parts with generous permission, includes modifications for convenience
"""
from types import SimpleNamespace
import sys, bpy, xml.sax, zipfile, os
from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
from mathutils import Quaternion, Matrix
from sverchok.dependencies import FreeCAD
if FreeCAD:
from sverchok.utils.decorators import duration
import Part
class FreeCAD_xml_handler(xml.sax.ContentHandler):
"""A XML handler to process the FreeCAD GUI xml data"""
# this creates a dictionary where each key is a FC object name,
# and each value is a dictionary of property:value pairs
def __init__(self):
self.guidata = {}
self.current = None
self.properties = {}
self.currentprop = None
self.currentval = None
# Call when an element starts
def startElement(self, tag, attributes):
if tag == "ViewProvider":
self.current = attributes["name"]
elif tag == "Property":
name = attributes["name"]
if name in ["Visibility", "ShapeColor", "Transparency", "DiffuseColor"]:
self.currentprop = name
elif tag == "Bool":
if attributes["value"] == "true":
self.currentval = True
else:
self.currentval = False
elif tag == "PropertyColor":
c = int(attributes["value"])
r = float((c>>24)&0xFF)/255.0
g = float((c>>16)&0xFF)/255.0
b = float((c>>8)&0xFF)/255.0
self.currentval = (r,g,b)
elif tag == "Integer":
self.currentval = int(attributes["value"])
elif tag == "Float":
self.currentval = float(attributes["value"])
elif tag == "ColorList":
self.currentval = attributes["file"]
# Call when an elements ends
def endElement(self, tag):
if tag == "ViewProvider":
if self.current and self.properties:
self.guidata[self.current] = self.properties
self.current = None
self.properties = {}
elif tag == "Property":
if self.currentprop and (self.currentval != None):
self.properties[self.currentprop] = self.currentval
self.currentprop = None
self.currentval = None
def get_guidata(filename):
# check if we have a GUI document
guidata = {}
with zipfile.ZipFile(filename) as zdoc:
if "GuiDocument.xml" in zdoc.namelist():
with zdoc.open("GuiDocument.xml") as gf:
guidata = gf.read()
Handler = FreeCAD_xml_handler()
xml.sax.parseString(guidata, Handler)
guidata = Handler.guidata
for key, properties in guidata.items():
if (diffuse_file := properties.get("DiffuseColor")):
with zdoc.open(diffuse_file) as df:
buf = df.read()
# first 4 bytes are the array length, then each group of 4 bytes is abgr
# overwrite file reference with color data.
cols = [(buf[i*4+3], buf[i*4+2], buf[i*4+1], buf[i*4]) for i in range(1,int(len(buf)/4))]
guidata[key]["DiffuseColor"] = cols
return guidata
def hascurves(shape):
for e in shape.Edges:
if not isinstance(e.Curve, (Part.Line, Part.LineSegment)): return True
return False
@duration
def import_fcstd(
filename,
update=False,
placement=True,
tessellation=1.0,
skiphidden=True,
scale=1.0,
sharemats=True,
report=None):
guidata = get_guidata(filename)
doc = FreeCAD.open(filename)
docname = doc.Name
if not doc:
print("Unable to open the given FreeCAD file")
return
matdatabase = {} # to store reusable materials
obj_data = []
for obj in doc.Objects:
if skiphidden:
if obj.Name in guidata:
if "Visibility" in guidata[obj.Name]:
if guidata[obj.Name]["Visibility"] == False:
continue
verts = []
vdict = dict() # maybe we gain some speed in lookups.
edges = []
faces = []
add_face = faces.append # alias increase speed
matindex = [] # face to material relationship
faceedges = [] # a placeholder to store edges that belong to a face
name = "Unnamed"
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:
# write FreeCAD faces as polygons when possible
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]:
if not (v1 := (v.x, v.y, v.z)) in vdict:
vdict[v1] = len(vdict)
for f in rawdata[1]:
raw = rawdata[0]
nf = [vdict[(nv.x, nv.y, nv.z)] for nv in [raw[vi] for vi in f]]
add_face(nf)
matindex.append(len(rawdata[1]))
else:
f = []
ov = face.OuterWire.OrderedVertexes
for v in ov:
if not (vec := (v.X, v.Y, v.Z)) in vdict:
vdict[vec] = len(vdict)
f.append(len(vdict) - 1)
else:
f.append(vdict[(v.X, v.Y, v.Z)])
# FreeCAD doesn't care about verts order. Make sure our loop goes clockwise
c = face.CenterOfMass
v1 = ov[0].Point.sub(c)
v2 = ov[1].Point.sub(c)
n = face.normalAt(0,0)
if (v1.cross(v2)).getAngle(n) > 1.57:
f.reverse() # inverting verts order if the direction is couterclockwise
add_face(f)
matindex.append(1)
for e in face.Edges:
faceedges.append(e.hashCode())
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
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)
if not dv1 in vdict:
vdict[dv1] = len(vdict)
if not dv2 in vdict:
vdict[dv2] = len(vdict)
edges.append([vdict[dv1], vdict[dv2]])
else:
e = []
for vert in edge.Vertexes:
# TODO discretize non-linear edges
v = (vert.X,vert.Y,vert.Z)
if not v in vdict: vdict[v] = len(vdict)
e.append(vdict[v])
# using walrus :=
# if not (v := (vert.X,vert.Y,vert.Z)) in vdict:
# vdict[v] = len(vdict)
# e.append(vdict[v])
edges.append(e)
verts = list(vdict.keys())
elif obj.isDerivedFrom("Mesh::Feature"):
# convert freecad mesh to blender mesh
mesh = obj.Mesh
if placement:
placement = obj.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]
current_obj = SimpleNamespace(verts=verts, edges=edges, faces=faces, matindex=matindex, plac=None, faceedges=faceedges, name=obj.Name)
current_obj.matrix = Matrix()
current_obj.loc = (0.0, 0.0, 0.0)
if placement:
current_obj.loc = placement.Base.multiply(scale)[:]
if placement.Rotation.Angle:
# FreeCAD Quaternion is XYZW while Blender is WXYZ
x, y, z, w = placement.Rotation.Q
new_quaternion = Quaternion((w, x, y, z))
current_obj.matrix = new_quaternion.to_matrix().to_4x4()
current_obj.loc = placement.Base.multiply(scale)[:]
obj_data.append(current_obj)
if verts and (faces or edges):
continue # this is here so the code below can be syntax highlighted while not being run :)
if not obj.Name in guidata: continue
if matindex and ("DiffuseColor" in guidata[obj.Name]) and (len(matindex) == len(guidata[obj.Name]["DiffuseColor"])):
# we have per-face materials. Create new mats and attribute faces to them
fi = 0
objmats = []
for i in range(len(matindex)):
# DiffuseColor stores int values, Blender use floats
rgba = tuple([float(x)/255.0 for x in guidata[obj.Name]["DiffuseColor"][i]])
# FreeCAD stores transparency, not alpha
alpha = 1.0
if rgba[3] > 0:
alpha = 1.0 - rgba[3]
rgba = rgba[:3]+(alpha,)
bmat = None
if sharemats:
if rgba in matdatabase:
bmat = matdatabase[rgba]
if not rgba in objmats:
objmats.append(rgba)
bobj.data.materials.append(bmat)
if not bmat:
if rgba in objmats:
bmat = bobj.data.materials[objmats.index(rgba)]
if not bmat:
bmat = bpy.data.materials.new(name=obj.Name+str(len(objmats)))
bmat.use_nodes = True
principled = PrincipledBSDFWrapper(bmat, is_readonly=False)
principled.base_color = rgba[:3]
if alpha < 1.0:
bmat.diffuse_color = rgba
principled.alpha = alpha
bmat.blend_method = "BLEND"
objmats.append(rgba)
bobj.data.materials.append(bmat)
if sharemats:
matdatabase[rgba] = bmat
for fj in range(matindex[i]):
bobj.data.polygons[fi+fj].material_index = objmats.index(rgba)
fi += matindex[i]
else:
# one material for the whole object
# alpha = 1.0
# rgb = (0.5,0.5,0.5)
# if "Transparency" in guidata[obj.Name]:
# if guidata[obj.Name]["Transparency"] > 0:
# alpha = (100 - guidata[obj.Name]["Transparency"]) / 100.0
if (transparency := guidata[obj.Name].get("Transparency", 1.0)) > 0:
alpha = (100 - transparency) / 100.0
else:
alpha = 1.0
# if "ShapeColor" in guidata[obj.Name]:
# rgb = guidata[obj.Name]["ShapeColor"]
rgb = guidata[obj.Name].get("ShapeColor", (0.5, 0.5, 0.5))
rgba = rgb + (alpha,)
bmat = None
if sharemats:
if rgba in matdatabase:
bmat = matdatabase[rgba]
if not bmat:
bmat = bpy.data.materials.new(name=obj.Name)
bmat.use_nodes = True
principled = PrincipledBSDFWrapper(bmat, is_readonly=False)
principled.base_color = rgb
if alpha < 1.0:
bmat.diffuse_color = rgba
if sharemats:
matdatabase[rgba] = bmat
bobj.data.materials.append(bmat)
FreeCAD.closeDocument(docname)
print("Import finished without errors")
return obj_data
class SVFreeCADImporterProps(bpy.types.PropertyGroup):
# usage :
# props: bpy.props.CollectionProperty(type=SVFreeCADImporterProps)
option_skiphidden : bpy.props.BoolProperty(name="Skip hidden objects", default=True,
description="Only import objects that where visible in FreeCAD"
)
# option_update : bpy.props.BoolProperty(name="Update existing objects", default=True,
# description="Keep objects with same names in current scene and their materials, only replace the geometry"
# )
option_placement : bpy.props.BoolProperty(name="Use Placements", default=True,
description="Set Blender pivot points to the FreeCAD placements"
)
option_tessellation : bpy.props.FloatProperty(name="Tessellation value", default=1.0,
description="The tessellation value to apply when triangulating shapes"
)
option_scale : bpy.props.FloatProperty(name="Scaling value", default=0.001,
description="A scaling value to apply to imported objects. Default value of 0.001 means one Blender unit = 1 meter"
)
option_sharemats : bpy.props.BoolProperty(name="Share similar materials", default=True,
description="Objects with same color/transparency will use the same material"
)
classes = [SVFreeCADImporterProps]
register, unregister = bpy.utils.register_classes_factory(classes)
......@@ -548,3 +548,79 @@ def mesh_from_solid_faces(solid):
return verts, edges, faces
def hascurves(shape):
for e in shape.Edges:
if not isinstance(e.Curve, (Part.Line, Part.LineSegment)): return True
return False
def drop_existing_faces(faces):
"""
this avoids the following bmesh exception:
faces.new(verts): face already exists
"""
faces_set = set()
new_faces = []
good_face = new_faces.append
for face in faces:
proposed_face = tuple(sorted(face))
if proposed_face in faces_set:
continue
else:
faces_set.add(proposed_face)
good_face(face)
return new_faces
def mesh_from_solid_faces_MOD(shape, quality=1.0, tessellate=False):
"""
modified from yorik van havre's FreeCAD importer for Blender.
"""
vdict = {}
faces = []
add_face = faces.append # alias increase speed
# write FreeCAD faces as polygons when possible
for face in shape.Faces:
if (len(face.Wires) > 1) or (not isinstance(face.Surface, Part.Plane)) or hascurves(face) or tessellate:
# face has holes or is curved, so we need to triangulate it
rawdata = face.tessellate(quality)
for v in rawdata[0]:
if not (v1 := (v.x, v.y, v.z)) in vdict:
vdict[v1] = len(vdict)
for f in rawdata[1]:
raw = rawdata[0]
nf = [vdict[(nv.x, nv.y, nv.z)] for nv in [raw[vi] for vi in f]]
add_face(nf)
else:
f = []
ov = face.OuterWire.OrderedVertexes
for v in ov:
if not (vec := (v.X, v.Y, v.Z)) in vdict:
vdict[vec] = len(vdict)
f.append(len(vdict) - 1)
else:
f.append(vdict[(v.X, v.Y, v.Z)])
# FreeCAD doesn't care about verts order. Make sure our loop goes clockwise
c = face.CenterOfMass
v1 = ov[0].Point.sub(c)
v2 = ov[1].Point.sub(c)
n = face.normalAt(0,0)
if (v1.cross(v2)).getAngle(n) > 1.57:
f.reverse() # inverting verts order if the direction is couterclockwise
add_face(f)
faces = drop_existing_faces(faces)
verts = list(vdict.keys())
return verts, faces
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать