diff --git a/docs/nodes/exchange/FCStd_read.rst b/docs/nodes/exchange/FCStd_read.rst new file mode 100644 index 0000000000..d40eed9681 --- /dev/null +++ b/docs/nodes/exchange/FCStd_read.rst @@ -0,0 +1,19 @@ +FCStd Read (Exchange) +=========== + +Functionality +------------- + +Parameters +---------- + +Inputs +------ + + +Outputs +------- + + +Examples +-------- diff --git a/docs/nodes/exchange/FCStd_sketch.rst b/docs/nodes/exchange/FCStd_sketch.rst new file mode 100644 index 0000000000..24a378ad55 --- /dev/null +++ b/docs/nodes/exchange/FCStd_sketch.rst @@ -0,0 +1,19 @@ +FCStd Sketch (Exchange) +=========== + +Functionality +------------- + +Parameters +---------- + +Inputs +------ + + +Outputs +------- + + +Examples +-------- \ No newline at end of file diff --git a/docs/nodes/exchange/FCStd_spreadsheet.rst b/docs/nodes/exchange/FCStd_spreadsheet.rst new file mode 100644 index 0000000000..5ff82b6054 --- /dev/null +++ b/docs/nodes/exchange/FCStd_spreadsheet.rst @@ -0,0 +1,19 @@ +FCStd Spreadsheet (Exchange) +=========== + +Functionality +------------- + +Parameters +---------- + +Inputs +------ + + +Outputs +------- + + +Examples +-------- \ No newline at end of file diff --git a/docs/nodes/exchange/FCStd_write.rst b/docs/nodes/exchange/FCStd_write.rst new file mode 100644 index 0000000000..cd167bb1c9 --- /dev/null +++ b/docs/nodes/exchange/FCStd_write.rst @@ -0,0 +1,19 @@ +FCStd Write (Exchange) +=========== + +Functionality +------------- + +Parameters +---------- + +Inputs +------ + + +Outputs +------- + + +Examples +-------- \ No newline at end of file diff --git a/docs/nodes/exchange/exchange_index.rst b/docs/nodes/exchange/exchange_index.rst index d3b2b7a6a7..e46a9918e0 100644 --- a/docs/nodes/exchange/exchange_index.rst +++ b/docs/nodes/exchange/exchange_index.rst @@ -10,4 +10,8 @@ Exchange bezier_in nurbs_in receive_from_sorcar - gcode_exporter \ No newline at end of file + FCStd_sketch + FCStd_spreadsheet + FCStd_write + FCStd_read + gcode_exporter diff --git a/index.md b/index.md index ea09bf1fbe..af8c123e0e 100644 --- a/index.md +++ b/index.md @@ -675,7 +675,12 @@ SvExportSolidNode SvReceiveFromSorcarNode SvExportGcodeNode - + SvReadFCStdNode + SvWriteFCStdNode + SvReadFCStdSketchNode + SvFCStdSpreadsheetNode + SvApproxSubdtoNurbsNode + ## Script SvFormulaNodeMk5 SvFormulaInterpolateNode diff --git a/nodes/exchange/FCStd_read.py b/nodes/exchange/FCStd_read.py new file mode 100644 index 0000000000..79178ee767 --- /dev/null +++ b/nodes/exchange/FCStd_read.py @@ -0,0 +1,242 @@ + +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('SvReadFCStdNode', 'SvReadFCStdNode', 'FreeCAD') + +else: + F = FreeCAD + import bpy + from bpy.props import StringProperty, BoolProperty, EnumProperty + from sverchok.node_tree import SverchCustomTreeNode + from sverchok.data_structure import updateNode + from sverchok.utils.logging import info + + class SvReadFCStdOperator(bpy.types.Operator, SvGenericNodeLocator): + + bl_idname = "node.sv_read_fcstd_operator" + bl_label = "read freecad file" + bl_options = {'INTERNAL', 'REGISTER'} + + def execute(self, context): + node = self.get_node(context) + + if not node: return {'CANCELLED'} + + 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'} + + class SvReadFCStdNode(bpy.types.Node, SverchCustomTreeNode): + """ + Triggers: Read FreeCAD file + Tooltip: import parts from a .FCStd file + """ + bl_idname = 'SvReadFCStdNode' + bl_label = 'Read FCStd' + 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) + + selected_label : StringProperty( default= 'Select FC Part') + selected_part : StringProperty( default='', 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, SvShowFcstdNamesOp.bl_idname, + 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') + self.wrapper_tracked_ui_draw_op(layout, SvReadFCStdOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") + + def sv_init(self, context): + + self.inputs.new('SvFilePathSocket', "File Path") + self.inputs.new('SvStringsSocket', "Part Filter") + self.outputs.new('SvSolidSocket', "Solid") + + def read_FCStd(self,node): + + files = node.inputs['File Path'].sv_get()[0] + + part_filter = [] + if node.inputs['Part Filter'].is_linked: + part_filter = node.inputs['Part Filter'].sv_get()[0] + + if node.selected_part != '' and not node.selected_part in part_filter: + part_filter.append(node.selected_part) + + solids = [] + obj_mask = [] + + if node.read_features: + obj_mask.append('PartDesign') + if node.read_part: + obj_mask.append('Part') + if node.read_body: + obj_mask.append('PartDesign::Body') + + for f in files: + S = LoadSolid(f, part_filter, obj_mask, node.tool_parts, node.inv_filter) + + for s in S: + solids.append(s) + + node.outputs['Solid'].sv_set(solids) + + + 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 + + + class SvShowFcstdNamesOp(bpy.types.Operator, SvGenericNodeLocator): + + bl_idname = "node.sv_show_fcstd_names" + bl_label = "Show parts list" + bl_options = {'INTERNAL', 'REGISTER'} + bl_property = "option" + + def LabelReader(self,context): + labels=[('','','')] + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + fc_file_list = node.inputs['File Path'].sv_get()[0] + obj_mask = [] + if node.read_features: + obj_mask.append('PartDesign') + if node.read_part: + obj_mask.append('Part') + if node.read_body: + obj_mask.append('PartDesign::Body') + + for f in fc_file_list: + try: + F.open(f) + Fname = bpy.path.display_name_from_filepath(f) + F.setActiveDocument(Fname) + + for obj in F.ActiveDocument.Objects: + if obj.Module in obj_mask or obj.TypeId in obj_mask: + labels.append( (obj.Label, obj.Label, obj.Label) ) + + except: + info('FCStd label read error') + finally: + F.closeDocument(Fname) + + return labels + + + option : EnumProperty(items=LabelReader) + tree_name : StringProperty() + node_name : StringProperty() + + + def execute(self, context): + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + 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'} + + +def LoadSolid(fc_file,part_filter,obj_mask,tool_parts, inv_filter): + objs= set() + outList = set() + solids = set() + + try: + + F.open(fc_file) + Fname = bpy.path.display_name_from_filepath(fc_file) + F.setActiveDocument(Fname) + + for obj in F.ActiveDocument.Objects: + + if obj.Module in obj_mask or obj.TypeId in obj_mask: + objs.add (obj) + + if 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 + + for obj in objs: + + if not inv_filter: + if obj.Label in part_filter or len(part_filter)==0: + solids.add(obj.Shape) + + else: + if not obj.Label in part_filter: + solids.add(obj.Shape) + + except: + info('FCStd read error') + + finally: + F.closeDocument(Fname) + + return solids + +def open_fc_file(fc_file): + F.open(fc_file) + Fname = bpy.path.display_name_from_filepath(fc_file) + F.setActiveDocument(Fname) + +def register(): + if FreeCAD is not None: + bpy.utils.register_class(SvReadFCStdNode) + bpy.utils.register_class(SvShowFcstdNamesOp) + bpy.utils.register_class(SvReadFCStdOperator) + +def unregister(): + if FreeCAD is not None: + bpy.utils.unregister_class(SvReadFCStdNode) + bpy.utils.unregister_class(SvShowFcstdNamesOp) + bpy.utils.register_class(SvReadFCStdOperator) \ No newline at end of file diff --git a/nodes/exchange/FCStd_sketch.py b/nodes/exchange/FCStd_sketch.py new file mode 100644 index 0000000000..73ba06b3d2 --- /dev/null +++ b/nodes/exchange/FCStd_sketch.py @@ -0,0 +1,357 @@ +from sverchok.dependencies import FreeCAD +from sverchok.utils.dummy_nodes import add_dummy +import mathutils +from sverchok.utils.sv_operator_mixins import SvGenericNodeLocator + +if FreeCAD is None: + add_dummy('SvReadFCStdSketchNode', 'SvReadFCStdSketchNode', 'FreeCAD') + +else: + F = FreeCAD + import bpy + import numpy as np + from bpy.props import StringProperty, IntProperty, BoolProperty, EnumProperty + from sverchok.node_tree import SverchCustomTreeNode + from sverchok.data_structure import updateNode + from sverchok.utils.logging import info + + class SvReadFCStdSketchOperator(bpy.types.Operator, SvGenericNodeLocator): + + bl_idname = "node.sv_read_fcstd_sketch_operator" + bl_label = "read freecad sketch" + bl_options = {'INTERNAL', 'REGISTER'} + + def execute(self, context): + node = self.get_node(context) + + if not node: return {'CANCELLED'} + + node.read_sketch(node) + updateNode(node,context) + + return {'FINISHED'} + + class SvReadFCStdSketchNode(bpy.types.Node, SverchCustomTreeNode): + """ + Triggers: Read FreeCAD file + Tooltip: import parts from a .FCStd file + """ + bl_idname = 'SvReadFCStdSketchNode' + bl_label = 'Read FCStd Sketches' + bl_icon = 'IMPORT' + solid_catergory = "Outputs" + + max_points : IntProperty(name="max_points", default=50, update = updateNode) + read_update : BoolProperty(name="read_update", default=True) + inv_filter : BoolProperty(name="inv_filter", default=False, update = updateNode) + selected_label : StringProperty(default='Select FC Part') + selected_part : StringProperty(default='',update = updateNode) + + read_mode : EnumProperty( + name='mode', + description='read geometry / construction', + items=[ + ('geometry', 'geometry', 'geometry'), + ('construction', 'construction', 'construction'), + ('BOTH', 'BOTH', 'BOTH')], + default='geometry', + update = updateNode ) + + def draw_buttons(self, context, layout): + + col = layout.column(align=True) + col.prop(self, 'read_mode') + + if self.inputs['File Path'].is_linked: + self.wrapper_tracked_ui_draw_op( + col, SvShowFcstdSketchNamesOp.bl_idname, + icon= 'TRIA_DOWN', + text= self.selected_label ) + + col.prop(self, 'max_points') + col.prop(self, 'read_update') + col.prop(self, 'inv_filter') + self.wrapper_tracked_ui_draw_op(layout, SvReadFCStdSketchOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") + + def sv_init(self, context): + self.inputs.new('SvFilePathSocket', "File Path") + self.inputs.new('SvStringsSocket', "Sketch Filter") + + self.outputs.new('SvVerticesSocket', "Verts") + self.outputs.new('SvStringsSocket', "Edges") + self.outputs.new('SvCurveSocket', "Curve") + + def read_sketch(self,node): + + if not any(socket.is_linked for socket in node.outputs): + return + + if not node.inputs['File Path'].is_linked: + return + + if node.read_update: + + files = node.inputs['File Path'].sv_get()[0] + + sketch_filter = [] + if node.inputs['Sketch Filter'].is_linked: + sketch_filter = node.inputs['Sketch Filter'].sv_get()[0] + + if node.selected_part != '' and not node.selected_part in sketch_filter: + sketch_filter.append(node.selected_part) + + Verts = [] + Edges = [] + curves_out = [] + for f in files: + S = LoadSketch(f, sketch_filter, node.max_points, node.inv_filter, node.read_mode) + for i in S[0]: + Verts.append(i) + for i in S[1]: + Edges.append(i) + for i in S[2]: + curves_out.append(i) + node.outputs['Verts'].sv_set([Verts]) + node.outputs['Edges'].sv_set([Edges]) + node.outputs['Curve'].sv_set(curves_out) + + else: + return + + def process(self): + self.read_sketch(self) + + class SvShowFcstdSketchNamesOp(bpy.types.Operator): + bl_idname = "node.sv_show_fcstd_sketch_names" + bl_label = "Show parts list" + bl_options = {'INTERNAL', 'REGISTER'} + bl_property = "option" + + def LabelReader(self,context): + labels=[('','','')] + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + fc_file_list = node.inputs['File Path'].sv_get()[0] + + try: + + for f in fc_file_list: + F.open(f) + Fname = bpy.path.display_name_from_filepath(f) + F.setActiveDocument(Fname) + + for obj in F.ActiveDocument.Objects: + + if obj.Module == 'Sketcher': + labels.append( (obj.Label, obj.Label, obj.Label) ) + F.closeDocument(Fname) + + except: + info('FCStd read error') + + return labels + + option : EnumProperty(items=LabelReader) + tree_name : StringProperty() + node_name : StringProperty() + + def execute(self, context): + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + 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'} + + +def LoadSketch(fc_file, sketch_filter, max_points, inv_filter, read_mode): + import Part + sketches = [] + Verts = [] + Edges = [] + Curves = [] + + #___________________GET FC FILE + try: + F.open(fc_file) + Fname = bpy.path.display_name_from_filepath(fc_file) + F.setActiveDocument(Fname) + + except: + info('FCStd read error') + return (Verts,Edges,Curves) + + #___________________SEARCH FOR SKETCHES + + for obj in F.ActiveDocument.Objects: + + if obj.Module == 'Sketcher': + + if not inv_filter: + + if obj.Label in sketch_filter or len(sketch_filter)==0: + sketches.append(obj) + + else: + if not obj.Label in sketch_filter: + sketches.append(obj) + + if len(sketches)==0: + return (Verts,Edges) + + #__ search for max single perimeter in sketches geometry + #__ (to use as resampling reference) + + max_len=set() + for s in sketches: + for g in s.Geometry: + if not isinstance(g, Part.Point ) : + max_len.add( g.length() ) + + max_len=max(max_len) + + #___________________CONVERT SKETCHES GEOMETRY + + for s in sketches: + + #get sketch plane placement to local - global conversion + s_placement = s.Placement + if len(s.InList) > 0: + s_placement = s.InList[0].Placement.multiply( s_placement ) + + + #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> EVALUATE CURVE START + for i,geo in enumerate(s.Geometry): + + if read_mode == 'geometry': + if s.getConstruction(i) : continue + #FREECAD 0.18 #if geo.Construction : continue + + elif read_mode == 'construction': + if not s.getConstruction(i) : continue + #FREECAD 0.18 #if not geo.Construction : continue + + v_set=[] + e_set=[] + + #LINE CASE + if isinstance(geo, Part.LineSegment ): + geo_points = 2 + + #POINT CASE + elif isinstance(geo, Part.Point ): + geo_points = 1 + + else: + geo_points = int (max_points * geo.length() / max_len) + 1 + + if geo_points < 2: + geo_points = 2 + + if geo_points!=1: + verts = geo.discretize(Number = geo_points ) + else: + verts = [ (geo.X,geo.Y,geo.Z) ] + + for v in verts: + v_co = F.Vector( ( v[0], v[1], v[2] ) ) + + abs_co = FreeCAD_abs_placement(s_placement,v_co).Base + v_set.append ( (abs_co.x, abs_co.y, abs_co.z) ) + + for i in range ( len(v_set)-1 ): + v_count = len(Verts) + e_set.append ( (v_count+i,v_count+i+1) ) + + Verts.extend (v_set) + Edges.extend (e_set) + #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< EVALUATE CURVE END + #------------------------------------------------------------- + #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> FC CURVE TO SV CURVE + #LINE + if isinstance(geo, Part.LineSegment ): + from sverchok.utils.curve import SvLine + point1 = np.array(v_set[0]) + direction = np.array(v_set[1]) - point1 + line = SvLine(point1, direction) + line.u_bounds = (0, 1) + Curves.append(line) + + #CIRCLE + elif isinstance(geo, Part.Circle ) or isinstance(geo, Part.ArcOfCircle ): + from sverchok.utils.curve import SvCircle + from mathutils import Matrix + + center = F.Vector( (geo.Location.x,geo.Location.y,geo.Location.z) ) + c_placement = FreeCAD_abs_placement(s_placement,center) + placement_mat = c_placement.toMatrix() + b_mat = Matrix() + + r=0; c=0 + for i in placement_mat.A: + if c == 4: + r+=1; c=0 + b_mat[r][c]=i + c+=1 + + curve = SvCircle(matrix=b_mat, radius=geo.Radius) + if isinstance(geo, Part.ArcOfCircle ): + curve.u_bounds = (geo.FirstParameter, geo.LastParameter) + + Curves.append(curve) + + elif geo_points!=1: + geo = geo.toNurbs() + from sverchok.utils.nurbs_common import SvNurbsMaths + from sverchok.utils.curve import SvNurbsCurve + + abs_poles = [] + + for vec in geo.getPoles(): + abs_co = FreeCAD_abs_placement(s_placement,vec).Base + abs_poles.append ( (abs_co.x, abs_co.y, abs_co.z) ) + + new_curve = SvNurbsCurve.build( SvNurbsMaths.FREECAD, geo.Degree, geo.KnotSequence, abs_poles, geo.getWeights() ) + + Curves.append(new_curve) + + F.closeDocument(Fname) + return (Verts,Edges,Curves) + +def FC_matrix_to_mathutils_format(fc_matrix): + row=0 + col=0 + b_matrix = mathutils.Matrix() + for i in fc_matrix.A: + if col == 4: + row += 1; col = 0 + b_matrix[row][col] = i + col += 1 + return b_matrix + +def FreeCAD_abs_placement(sketch_placement,p_co): + p_co = F.Vector( ( p_co[0], p_co[1], p_co[2] ) ) + local_placement = F.Placement() + local_placement.Base = p_co + return sketch_placement.multiply(local_placement) + +def register(): + if FreeCAD is not None: + bpy.utils.register_class(SvReadFCStdSketchNode) + bpy.utils.register_class(SvShowFcstdSketchNamesOp) + bpy.utils.register_class(SvReadFCStdSketchOperator) + +def unregister(): + if FreeCAD is not None: + bpy.utils.unregister_class(SvReadFCStdSketchNode) + bpy.utils.unregister_class(SvShowFcstdSketchNamesOp) + bpy.utils.unregister_class(SvReadFCStdSketchOperator) diff --git a/nodes/exchange/FCStd_spreadsheet.py b/nodes/exchange/FCStd_spreadsheet.py new file mode 100644 index 0000000000..137e149ef9 --- /dev/null +++ b/nodes/exchange/FCStd_spreadsheet.py @@ -0,0 +1,269 @@ +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('SvFCStdSpreadsheetNode', 'SvFCStdSpreadsheetNode', 'FreeCAD') + +else: + F = FreeCAD + import bpy + from bpy.props import StringProperty, IntProperty, BoolProperty, EnumProperty, FloatProperty + from sverchok.node_tree import SverchCustomTreeNode + from sverchok.data_structure import updateNode + from sverchok.utils.logging import info + + class SvFCStdSpreadsheetOperator(bpy.types.Operator, SvGenericNodeLocator): + + bl_idname = "node.sv_fcstd_spreadsheet_operator" + bl_label = "read/write freecad spreadsheet" + bl_options = {'INTERNAL', 'REGISTER'} + + def execute(self, context): + node = self.get_node(context) + + if not node: return {'CANCELLED'} + + node.edit_spreadsheet(node) + updateNode(node,context) + + return {'FINISHED'} + + + class SvFCStdSpreadsheetNode(bpy.types.Node, SverchCustomTreeNode): + """ + Triggers: Read FreeCAD file + Tooltip: Read/write FCStd Spreadsheets from a .FCStd file + """ + bl_idname = 'SvFCStdSpreadsheetNode' + bl_label = 'Read/write Spreadsheets' + bl_icon = 'IMPORT' + solid_catergory = "Outputs" + + auto_update : BoolProperty(name="auto_update", default=True) + write_update : BoolProperty(name="read_update", default=True) + write_parameter : BoolProperty(name="write_parameter", default=False) + + selected_label : StringProperty(default='Spreadsheet') + selected_sheet : StringProperty(default='',update = updateNode) + + selected_par_label : StringProperty(default='Parameter') + selected_par : StringProperty(default='',update = updateNode) + + cell_in : FloatProperty( name="cell_in", description='cell_in', default=0.0 ) + + 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, SvShowFcstdSpreadsheetsOp.bl_idname, + icon= 'TRIA_DOWN', + text= self.selected_label ) + + + if self.inputs['File Path'].is_linked: + self.wrapper_tracked_ui_draw_op( + col, SvShowFcstdParNamesOp.bl_idname, + icon= 'TRIA_DOWN', + text= self.selected_par_label ) + + col.prop(self, 'auto_update') + col.prop(self, 'write_parameter') + self.wrapper_tracked_ui_draw_op(layout, SvFCStdSpreadsheetOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") + + def sv_init(self, context): + self.inputs.new('SvFilePathSocket', "File Path") + self.inputs.new('SvStringsSocket', "cell_in").prop_name = 'cell_in' + + self.outputs.new('SvStringsSocket', "cell_out") + + + def edit_spreadsheet(self,node): + + if not node.inputs['File Path'].is_linked: + return + + if node.selected_par != '' : + + files = node.inputs['File Path'].sv_get()[0] + + cell_out=None + + for f in files: + cell_out = WriteParameter( + f, + node.selected_sheet, + node.selected_par, + node.inputs['cell_in'].sv_get()[0][0], + node.write_parameter) + + if cell_out != None: + + node.outputs['cell_out'].sv_set( [[cell_out]] ) + + else: + node.outputs['cell_out'].sv_set( [ ] ) + return + + def process(self): + if self.auto_update: + self.edit_spreadsheet(self) + + + class SvShowFcstdSpreadsheetsOp(bpy.types.Operator, SvGenericNodeLocator): + bl_idname = "node.sv_show_fcstd_spreadsheets" + bl_label = "Show spreadsheet list" + bl_options = {'INTERNAL', 'REGISTER'} + bl_property = "option" + + def LabelReader(self,context): + labels=[('','','')] + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + fc_file_list = node.inputs['File Path'].sv_get()[0] + + try: + for f in fc_file_list: + F.open(f) + Fname = bpy.path.display_name_from_filepath(f) + F.setActiveDocument(Fname) + + for obj in F.ActiveDocument.Objects: + if obj.Module == 'Spreadsheet': + labels.append( (obj.Label, obj.Label, obj.Label) ) + + except: + info('LabelReader Spreadsheet error') + finally: + F.closeDocument(Fname) + + return labels + + option : EnumProperty(items=LabelReader) + tree_name : StringProperty() + node_name : StringProperty() + + def execute(self, context): + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + node.name_filter = self.option + node.selected_label = self.option + node.selected_sheet = 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 SvShowFcstdParNamesOp(bpy.types.Operator, SvGenericNodeLocator): + bl_idname = "node.sv_show_fcstd_par_names" + bl_label = "Show parameter list" + bl_options = {'INTERNAL', 'REGISTER'} + bl_property = "option" + + def LabelReader(self,context): + labels=[('','','')] + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + fc_file_list = node.inputs['File Path'].sv_get()[0] + + try: + + for f in fc_file_list: + F.open(f) + Fname = bpy.path.display_name_from_filepath(f) + F.setActiveDocument(Fname) + + for obj in F.ActiveDocument.Objects: + + if obj.Label == node.selected_sheet: + props = obj.PropertiesList + for label in props: + alias = obj.getCellFromAlias(label) + if alias: + labels.append( (label, label, label) ) + + except: + info('Label reader read cell error') + + finally: + F.closeDocument(Fname) + + return labels + + option : EnumProperty(items=LabelReader) + tree_name : StringProperty() + node_name : StringProperty() + + def execute(self, context): + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + node.name_filter = self.option + node.selected_par_label = self.option + node.selected_par = 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'} + +def WriteParameter(fc_file,spreadsheet,alias,par_write,write): + + #___________________GET FC FILE + + try: + F.open(fc_file) + Fname = bpy.path.display_name_from_filepath(fc_file) + F.setActiveDocument(Fname) + + #___________________SEARCH FOR SKETCHES + cell_out = None + + for obj in F.ActiveDocument.Objects: + + if obj.Label == spreadsheet: + + if alias in obj.PropertiesList: + cell = obj.getCellFromAlias(alias) + if write: + obj.set(cell,str(par_write)) + F.ActiveDocument.recompute() + F.getDocument(Fname).save() + + cell_out = obj.get(cell) + break + + except: + info('WriteParameter error') + + finally: + F.closeDocument(Fname) + + return cell_out + + + +def register(): + if FreeCAD is not None: + bpy.utils.register_class(SvFCStdSpreadsheetNode) + bpy.utils.register_class(SvShowFcstdSpreadsheetsOp) + bpy.utils.register_class(SvShowFcstdParNamesOp) + bpy.utils.register_class(SvFCStdSpreadsheetOperator) + +def unregister(): + if FreeCAD is not None: + bpy.utils.unregister_class(SvFCStdSpreadsheetNode) + bpy.utils.unregister_class(SvShowFcstdSpreadsheetsOp) + bpy.utils.unregister_class(SvShowFcstdParNamesOp) + bpy.utils.unregister_class(SvFCStdSpreadsheetOperator) diff --git a/nodes/exchange/FCStd_write.py b/nodes/exchange/FCStd_write.py new file mode 100644 index 0000000000..102e09c484 --- /dev/null +++ b/nodes/exchange/FCStd_write.py @@ -0,0 +1,212 @@ +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('SvWriteFCStdNode', 'SvWriteFCStdNode', 'FreeCAD') + +else: + F = FreeCAD + import bpy + from bpy.props import StringProperty, BoolProperty,EnumProperty + from sverchok.node_tree import SverchCustomTreeNode # OLD throttled + from sverchok.data_structure import updateNode, match_long_repeat # NEW throttle_and_update_node + from sverchok.utils.logging import info + + + class SvWriteFCStdOperator(bpy.types.Operator, SvGenericNodeLocator): + + bl_idname = "node.sv_write_fcstd_operator" + bl_label = "write freecad file" + bl_options = {'INTERNAL', 'REGISTER'} + + def execute(self, context): + node = self.get_node(context) + + if not node: return {'CANCELLED'} + + node.write_FCStd(node) + updateNode(node,context) + + return {'FINISHED'} + + + class SvWriteFCStdNode(bpy.types.Node, SverchCustomTreeNode): + """ + Triggers: write FreeCAD file + Tooltip: write parts in a .FCStd file + """ + + bl_idname = 'SvWriteFCStdNode' + bl_label = 'Write FCStd' + bl_icon = 'IMPORT' + solid_catergory = "Inputs" + + write_update : BoolProperty( + name="write_update", + default=False) + + part_name : StringProperty( + name="part_name", + default="part_name") + + #@throttled + def changeMode(self, context): + + if self.obj_format == 'mesh': + if 'Verts' not in self.inputs: + self.inputs.remove(self.inputs['Solid']) + self.inputs.new('SvVerticesSocket', 'Verts') + self.inputs.new('SvVerticesSocket', 'Faces') + return + else: + if 'Solid' not in self.inputs: + self.inputs.remove(self.inputs['Verts']) + self.inputs.remove(self.inputs['Faces']) + self.inputs.new('SvSolidSocket', 'Solid') + return + + + obj_format : EnumProperty( + name='format', + description='choose format', + items={ + ('solid', 'solid', 'solid'), + ('mesh', 'mesh', 'mesh')}, + default='solid', + update=changeMode) + + def draw_buttons(self, context, layout): + + layout.label(text="write name:") + col = layout.column(align=True) + col.prop(self, 'part_name',text="") + col.prop(self, 'obj_format',text="") + col.prop(self, 'write_update') + if self.obj_format == 'mesh': + col.label(text="need triangle meshes") + self.wrapper_tracked_ui_draw_op(layout, SvWriteFCStdOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") + + + def sv_init(self, context): + self.inputs.new('SvFilePathSocket', "File Path") + + if self.obj_format == 'mesh': + self.inputs.new('SvVerticesSocket', "Verts") + self.inputs.new('SvStringsSocket', "Faces") + + else: + self.inputs.new('SvSolidSocket', 'Solid') + + def write_FCStd(self,node): + + if not node.inputs['File Path'].is_linked: + return + + files = node.inputs['File Path'].sv_get() + + if not len(files[0]) == 1: + print ('FCStd write node support just 1 file at once') + return + + fc_file=files[0][0] + + if node.obj_format == 'mesh': + + if any((node.inputs['Verts'].is_linked,node.inputs['Faces'].is_linked)): + + verts_in = node.inputs['Verts'].sv_get(deepcopy=False) + pols_in = node.inputs['Faces'].sv_get(deepcopy=False) + verts, pols = match_long_repeat([verts_in, pols_in]) + fc_write_parts(fc_file, verts, pols, node.part_name, None, node.obj_format) + + elif node.obj_format == 'solid': + + if node.inputs['Solid'].is_linked: + solid=node.inputs['Solid'].sv_get() + fc_write_parts(fc_file, None, None, node.part_name, solid, node.obj_format) + + else: + return + + def process(self): + + if self.write_update: + self.write_FCStd(self) + else: + return + + + + +def fc_write_parts(fc_file, verts, faces, part_name, solid, mod): + + try: + F.open(fc_file) + Fname = bpy.path.display_name_from_filepath(fc_file) + except: + info ('FCStd open error') + return + + F.setActiveDocument(Fname) + fc_root = F.getDocument(Fname) + + obj_names = set( [ i.Name for i in fc_root.Objects] ) + + part_name += '_sv_' #->suffix added to avoid deleting erroneusly freecad objects + + # SEARCH the freecad project for previous writed parts from this node + + if part_name in obj_names: #if the part name is numberless is detected as single + fc_root.removeObject(part_name) + + else: + for name in obj_names: #if not, check the fc project if there are parts with same root name + if part_name in name: + fc_root.removeObject(name) + + ############### if there, previous writed parts are removed #################### + ############### so then write them again... + + if mod == 'solid': #EXPORT SOLID + + for i,s in enumerate(solid): + new_part = F.ActiveDocument.addObject( "Part::Feature",part_name+str(i) ) #multiple: give numbered name + new_part.Shape = s + + else: #EXPORT MESH + + import Mesh + + for i in range(len(verts)): + + temp_faces=faces[i] + temp_verts=verts[i] + + meshdata=[] + + for f in temp_faces: + v1,v2,v3 = f[0],f[1],f[2] + meshdata.append( temp_verts[v1] ) + meshdata.append( temp_verts[v2] ) + meshdata.append( temp_verts[v3] ) + + mesh = Mesh.Mesh( meshdata ) + obj = F.ActiveDocument.addObject( "Mesh::Feature", part_name+str(i) ) + obj.Mesh = mesh + + + F.ActiveDocument.recompute() + F.getDocument(Fname).save() + F.closeDocument(Fname) + + +def register(): + if FreeCAD is not None: + bpy.utils.register_class(SvWriteFCStdNode) + bpy.utils.register_class(SvWriteFCStdOperator) + +def unregister(): + if FreeCAD is not None: + bpy.utils.unregister_class(SvWriteFCStdNode) + bpy.utils.unregister_class(SvWriteFCStdOperator) diff --git a/nodes/exchange/approx_subd_to_nurbs.py b/nodes/exchange/approx_subd_to_nurbs.py new file mode 100644 index 0000000000..8a28d20b01 --- /dev/null +++ b/nodes/exchange/approx_subd_to_nurbs.py @@ -0,0 +1,269 @@ + +import bpy,bmesh +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('SvReadFCStdNode', 'SvReadFCStdNode', 'FreeCAD') + +else: + F = FreeCAD + import bpy + from bpy.props import StringProperty, BoolProperty, EnumProperty + from sverchok.node_tree import SverchCustomTreeNode + from sverchok.data_structure import updateNode + from sverchok.utils.logging import info + + class SvApproxSubdtoNurbsOperator(bpy.types.Operator, SvGenericNodeLocator): + + bl_idname = "node.approx_subd_nurbs_operator" + bl_label = "Approx Subd-Nurbs" + bl_options = {'INTERNAL', 'REGISTER'} + + def execute(self, context): + node = self.get_node(context) + + if not node: + return {'CANCELLED'} + + if not any(socket.is_linked for socket in node.outputs): + return {'CANCELLED'} + + try: + node.inputs['Subd Obj'].sv_get()[0] + except: + return {'CANCELLED'} + + node.Approximate(node) + updateNode(node,context) + + return {'FINISHED'} + + + class SvApproxSubdtoNurbsNode(bpy.types.Node, SverchCustomTreeNode): + """ + Triggers: Approximate Subd to Nurbs + Tooltip: Approximate Subd to Nurbs + """ + bl_idname = 'SvApproxSubdtoNurbsNode' + bl_label = 'Approximate Subd to Nurb' + bl_icon = 'IMPORT' + solid_catergory = "Outputs" + + auto_update : BoolProperty(name="auto_update", default=True) + + + + def draw_buttons(self, context, layout): + + col = layout.column(align=True) + col.prop(self, 'auto_update', text = 'global update') + + self.wrapper_tracked_ui_draw_op(layout, SvApproxSubdtoNurbsOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") + + def sv_init(self, context): + + self.inputs.new('SvObjectSocket', "Subd Obj") + self.outputs.new('SvSolidSocket', "Solid") + + + def Approximate(self,node): + + S = ApproxSubdToNurbs( node.inputs['Subd Obj'].sv_get()[0] ) + + node.outputs['Solid'].sv_set(S) + + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + try: + self.inputs['Subd Obj'].sv_get()[0] + except: + return + + if self.auto_update: + self.Approximate(self) + else: + return + + +def ApproxSubdToNurbs(Object): + + from FreeCAD import Part + from FreeCAD.Part import BSplineCurve + from FreeCAD.Part import makeCompound + + F.newDocument("freecad_temp") + F.setActiveDocument('freecad_temp') + + patches=[] + + obj = Object + + if obj.modifiers[0].levels <= 1: + return [] + else: + obj.modifiers[0].levels -= 1 + + obj.modifiers[0].subdivision_type = "SIMPLE" + depsgraph = bpy.context.evaluated_depsgraph_get() + obj = obj.evaluated_get(depsgraph) + + bm = bmesh.new() + bm.from_mesh(obj.data) + + face_corners = set() + + for f in bm.faces: + corners = [] + for l in f.loops: + pos = (l.vert.co.x,l.vert.co.y,l.vert.co.z) + corners.append( pos ) + face_corners.add(tuple(corners)) + + bm.free() + + obj = Object + obj.modifiers[0].levels += 1 + depsgraph = bpy.context.evaluated_depsgraph_get() + obj = obj.evaluated_get(depsgraph) + + bm = bmesh.new() + bm.from_mesh(obj.data) + + borders=[] + centers=[] + + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + + + for quad_co in face_corners: + quad = [] + for co in quad_co: + for v in bm.verts: + if (v.co.x,v.co.y,v.co.z) == co: quad.append(v.index) + edges = [] + for i in range(4): + j = 0 if i == 3 else i+1 + for vert in bm.verts: + pool = set() + for e in vert.link_edges: + for v in e.verts: + pool.add(v.index) + edge = set((quad[i],quad[j])) + + + if edge.issubset(pool): + edge = ( quad[i], vert.index, quad[j] ) + edges.append( edge ) + + set1 = set() + for e in bm.verts[edges[0][1]].link_edges: + for v in e.verts: + set1.add(v.index) + + set2 = set() + for e in bm.verts[edges[1][1]].link_edges: + for v in e.verts: + set2.add(v.index) + + borders.append(edges) + centers.append( ((set1&set2)-set(quad)).pop() ) + + + obj = Object + obj.modifiers[0].subdivision_type = "CATMULL_CLARK" + depsgraph = bpy.context.evaluated_depsgraph_get() + obj = obj.evaluated_get(depsgraph) + bm = bmesh.new() + bm.from_mesh(obj.data) + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + + v_borders=[] + v_centers=[] + + for p in centers: + p = bm.verts[p] + v_centers.append( (p.co.x,p.co.y,p.co.z) ) + + for b in borders: + border=[] + for e in b: + edges=[] + for p in e: + p = bm.verts[p] + edges.append( (p.co.x,p.co.y,p.co.z) ) + border.append( edges ) + v_borders.append(border) + + bm.free() + + for i in range(len(centers)): + p = v_borders[i] + cen = v_centers[i] + curves=[] + item=0 + + CEN = F.ActiveDocument.addObject('Part::Feature', 'boundary_center%s'%item) + CEN.Shape = Part.Point( F.Vector( cen ) ).toShape() + + for b in p: + + Points=[] + + Points.append( F.Vector( b[0]) ) + Points.append( F.Vector( b[1]) ) + Points.append( F.Vector( b[2]) ) + + curve = BSplineCurve() + curve.increaseDegree(1) + curve.interpolate(Points) + curves.append(curve) + + com = makeCompound([x.toShape() for x in curves]) + com_obj = F.ActiveDocument.addObject('Part::Feature', 'boundary_edges%s'%item) + com_obj.Shape = com + F.ActiveDocument.recompute() + edge_names = ["Edge%d"%(n+1) for n in range(len(com.Edges))] + patch = F.ActiveDocument.addObject("Surface::Filling","Surface%s"%item) + patch.BoundaryEdges = (com_obj, edge_names) + patch.Points = (CEN, "Vertex1") + F.ActiveDocument.recompute() + item+=1 + + + F.ActiveDocument.recompute() + + SURFS= [] + + for obj in F.ActiveDocument.Objects: + if 'Surface' in obj.Name: + SURFS.append(obj) + + + F.activeDocument().addObject("Part::Compound","Compound") + COMPOUND = F.ActiveDocument.getObject("Compound") + COMPOUND.Links = SURFS + + F.ActiveDocument.recompute() + + COMPOUND.recompute() + SHELL = Part.Solid( Part.Shell(COMPOUND.Shape.Faces) ) + + F.closeDocument("freecad_temp") + + return [SHELL] + +def register(): + if FreeCAD is not None: + bpy.utils.register_class(SvApproxSubdtoNurbsOperator) + bpy.utils.register_class(SvApproxSubdtoNurbsNode) + +def unregister(): + if FreeCAD is not None: + bpy.utils.unregister_class(SvApproxSubdtoNurbsOperator) + bpy.utils.unregister_class(SvApproxSubdtoNurbsNode)