From 8a5d791de50718889afb78674ac3c0e5f3aa0b82 Mon Sep 17 00:00:00 2001 From: alessandro-zomparelli Date: Thu, 11 Oct 2018 23:35:43 +0200 Subject: [PATCH 01/13] added gcode node --- index.md | 1 + nodes/text/export_gcode.py | 227 +++++++++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+) create mode 100644 nodes/text/export_gcode.py diff --git a/index.md b/index.md index 43bb96e110..5b19b7cf16 100644 --- a/index.md +++ b/index.md @@ -317,6 +317,7 @@ SvTypeViewerNode SvSkinViewerNodeMK1b SvMatrixViewer + SvExportGcodeNnode --- SvBManalyzinNode SvBMObjinputNode diff --git a/nodes/text/export_gcode.py b/nodes/text/export_gcode.py new file mode 100644 index 0000000000..3e4394d7cf --- /dev/null +++ b/nodes/text/export_gcode.py @@ -0,0 +1,227 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# 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 2 +# 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. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# made by: Linus Yng, haxed by zeffii to mk2 +# pylint: disable=c0326 + +import io +import itertools +import pprint +import sverchok + +import bpy, os, mathutils +from numpy import mean +import operator + +from bpy.props import BoolProperty, EnumProperty, StringProperty, FloatProperty, IntProperty + +from sverchok.node_tree import SverchCustomTreeNode, StringsSocket +from sverchok.data_structure import node_id, multi_socket, updateNode + +from sverchok.utils.sv_text_io_common import ( + FAIL_COLOR, READY_COLOR, TEXT_IO_CALLBACK, + get_socket_type, + new_output_socket, + name_dict, + text_modes +) + +def convert_to_text(list): + while True: + if type(list) is str: break + elif type(list) in (tuple, list): + try: + list = '\n'.join(list) + break + except: list = list[0] + else: break + return list + +class SvExportGcodeNnode(bpy.types.Node, SverchCustomTreeNode): + """ + Triggers: Text Out to datablock + Tooltip: Quickly write data from NodeView to text datablock + """ + bl_idname = 'SvExportGcodeNnode' + bl_label = 'Export Gcode' + bl_icon = 'COPYDOWN' + + + _start_code = ''' +G21 ; set untis to millimeters +G90 ; use absolute coordinates +M82; set extruder to absolute positions.... +G28 ; home all axe +G28 E ; home Extruder +G93 ; ingresso tubo +G92 E0 ; set position +''' + + _end_code = ''' + +M83 + +G0 E-0.5 +G92 ; set position +G28 ; home all axes +G28 E; homing extruder +M84 ; disable motors +''' + + folder = StringProperty(name="File", default="", subtype='FILE_PATH') + pull = FloatProperty(name="Pull", default=5.0, min=0, soft_max=10) + push = FloatProperty(name="Push", default=4.0, min=0, soft_max=10) + dz = FloatProperty(name="dz", default=2.0, min=0, soft_max=20) + #flow_mult = FloatProperty(name="Flow Mult", default=1.0, min=0, soft_max=3) + feed = IntProperty(name="Feed Rate (F)", default=1000, min=0, soft_max=5000) + flow = FloatProperty(name="Flow (E/mm)", default=0.044, min=0, soft_max=1) + start_code = StringProperty(name="Start", default=_start_code) + end_code = StringProperty(name="End", default=_end_code) + auto_sort = BoolProperty(name="Auto Sort", default=True) + + gcode_mode = EnumProperty(items=[ + ("CONT", "Continuous", ""), + ("RETR", "Retraction", "") + ], default='CONT', name="Mode") + + def sv_init(self, context): + #self.inputs.new('StringsSocket', 'Flow', 'Flow').prop_name = 'flow' + #self.inputs.new('StringsSocket', 'Start Code', 'Start Code').prop_name = 'start_code' + #self.inputs.new('StringsSocket', 'End Code', 'End Code').prop_name = 'end_code' + self.inputs.new('VerticesSocket', 'Vertices', 'Vertices') + + def draw_buttons(self, context, layout): + + addon = context.user_preferences.addons.get(sverchok.__name__) + over_sized_buttons = addon.preferences.over_sized_buttons + + col = layout.column(align=True) + row = col.row() + row.prop(self, 'folder', toggle=True, text='') + col = layout.column(align=True) + row = col.row() + row.prop(self, 'gcode_mode', expand=True, toggle=True) + #col = layout.column(align=True) + if self.gcode_mode == 'RETR': + #col.label(text="Retraction:") + col.prop(self, 'auto_sort') + col.prop(self, 'pull') + col.prop(self, 'dz') + col.prop(self, 'push') + col = layout.column(align=True) + col.prop(self, 'feed') + col.prop(self, 'flow') + #col.prop(self, 'flow_mult') + col = layout.column(align=True) + col.prop_search(self, 'start_code', bpy.data, 'texts') + col.prop_search(self, 'end_code', bpy.data, 'texts') + col = layout.column(align=True) + row = col.row(align=True) + row.scale_y = 4.0 + row.operator(TEXT_IO_CALLBACK, text='Export Gcode').fn_name = 'process' + + + def update_socket(self, context): + self.update() + + def process(self): + # manage data + feed = self.feed + constant_flow = False + flow = self.flow + vertices = self.inputs['Vertices'].sv_get() + #start_code = '\n'.join(self.inputs['Start Code'].sv_get()[0]) + #end_code = '\n'.join(self.inputs['End Code'].sv_get()[0]) + + # open file + if self.folder == '': + folder = '//' + os.path.splitext(bpy.path.basename(bpy.context.blend_data.filepath))[0] + else: + folder = self.folder + if '.gcode' not in folder: folder += '.gcode' + path = bpy.path.abspath(folder) + file = open(path, 'w') + for line in bpy.data.texts[self.start_code].lines: + file.write(line.body + '\n') + + # sort vertices + if self.auto_sort: + sorted_verts = [] + for curve in vertices: + # mean z + listz = [v[2] for v in curve] + meanz = mean(listz) + # store curve and meanz + sorted_verts.append((curve, meanz)) + vertices = [data[0] for data in sorted(sorted_verts, key=lambda height: height[1])] + + # initialize variables + e = 0.5 + first_point = True + count = 0 + last_vert = mathutils.Vector((0,0,0)) + maxz = 0 + + # write movements + for curve in vertices: + #path = path[0] + #print(curve) + for v in curve: + #print(v) + new_vert = mathutils.Vector(v) + dist = (new_vert-last_vert).length + + # record max z + maxz = max(maxz,v[2]) + + # first point of the gcode + if first_point: + file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' F' + format(feed, '.0f') + '\n') + file.write('G0 E0.5 \n') + file.write('M82 \n') + first_point = False + else: + # start after retraction + if v == curve[0]: + file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + '\n') + e += self.push + file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' E' + format(e, '.4f') + '\n') + # regular extrusion + else: + e += flow*dist + file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' E' + format(e, '.4f') + '\n') + count+=1 + last_vert = new_vert + if curve != vertices[-1]: #file.write(stop_extrusion) + e -= self.pull + file.write('G0 E' + format(e, '.4f') + '\n') + file.write('G1 X' + format(last_vert[0], '.4f') + ' Y' + format(last_vert[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + '\n') + + # end code + for line in bpy.data.texts[self.end_code].lines: + file.write(line.body + '\n') + #file.write(end_code) + file.close() + print("Saved gcode to " + path) + +def register(): + bpy.utils.register_class(SvExportGcodeNnode) + + +def unregister(): + bpy.utils.unregister_class(SvExportGcodeNnode) From f9f9e4de472292db44416272e9ac3c1bee02bd45 Mon Sep 17 00:00:00 2001 From: alessandro-zomparelli Date: Thu, 11 Oct 2018 23:49:23 +0200 Subject: [PATCH 02/13] description corrected --- nodes/text/export_gcode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodes/text/export_gcode.py b/nodes/text/export_gcode.py index 3e4394d7cf..22b1affaab 100644 --- a/nodes/text/export_gcode.py +++ b/nodes/text/export_gcode.py @@ -54,8 +54,8 @@ def convert_to_text(list): class SvExportGcodeNnode(bpy.types.Node, SverchCustomTreeNode): """ - Triggers: Text Out to datablock - Tooltip: Quickly write data from NodeView to text datablock + Triggers: Export gcode from vertices position + Tooltip: Generate a gcode file from a list of vertices """ bl_idname = 'SvExportGcodeNnode' bl_label = 'Export Gcode' From 4e64d85edb6b23cabfe33652ec07e8d3224a3989 Mon Sep 17 00:00:00 2001 From: alessandro-zomparelli Date: Fri, 12 Oct 2018 01:06:59 +0200 Subject: [PATCH 03/13] fixed continuous extrusion --- nodes/text/export_gcode.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/nodes/text/export_gcode.py b/nodes/text/export_gcode.py index 22b1affaab..8ce0ffd503 100644 --- a/nodes/text/export_gcode.py +++ b/nodes/text/export_gcode.py @@ -142,7 +142,6 @@ def update_socket(self, context): def process(self): # manage data feed = self.feed - constant_flow = False flow = self.flow vertices = self.inputs['Vertices'].sv_get() #start_code = '\n'.join(self.inputs['Start Code'].sv_get()[0]) @@ -156,11 +155,14 @@ def process(self): if '.gcode' not in folder: folder += '.gcode' path = bpy.path.abspath(folder) file = open(path, 'w') - for line in bpy.data.texts[self.start_code].lines: - file.write(line.body + '\n') + try: + for line in bpy.data.texts[self.start_code].lines: + file.write(line.body + '\n') + except: + pass # sort vertices - if self.auto_sort: + if self.auto_sort and self.gcode_mode == 'RETR': sorted_verts = [] for curve in vertices: # mean z @@ -197,7 +199,7 @@ def process(self): first_point = False else: # start after retraction - if v == curve[0]: + if v == curve[0] and self.gcode_mode == 'RETR': file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + '\n') e += self.push file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' E' + format(e, '.4f') + '\n') @@ -207,14 +209,17 @@ def process(self): file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' E' + format(e, '.4f') + '\n') count+=1 last_vert = new_vert - if curve != vertices[-1]: #file.write(stop_extrusion) + if curve != vertices[-1] and self.gcode_mode == 'RETR': #file.write(stop_extrusion) e -= self.pull file.write('G0 E' + format(e, '.4f') + '\n') file.write('G1 X' + format(last_vert[0], '.4f') + ' Y' + format(last_vert[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + '\n') # end code - for line in bpy.data.texts[self.end_code].lines: - file.write(line.body + '\n') + try: + for line in bpy.data.texts[self.end_code].lines: + file.write(line.body + '\n') + except: + pass #file.write(end_code) file.close() print("Saved gcode to " + path) From c610cdd0deda7314692e63f410a25e87a540f778 Mon Sep 17 00:00:00 2001 From: alessandro-zomparelli Date: Fri, 12 Oct 2018 01:16:03 +0200 Subject: [PATCH 04/13] added credits --- nodes/text/export_gcode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodes/text/export_gcode.py b/nodes/text/export_gcode.py index 8ce0ffd503..73b62152de 100644 --- a/nodes/text/export_gcode.py +++ b/nodes/text/export_gcode.py @@ -16,8 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### -# made by: Linus Yng, haxed by zeffii to mk2 -# pylint: disable=c0326 +# made by: Alessandro Zomparelli +# url: www.alessandrozomparelli.com import io import itertools From 95191201b834b78cf01afa87eb83b9d0ca80b975 Mon Sep 17 00:00:00 2001 From: alessandro-zomparelli Date: Fri, 12 Oct 2018 13:20:06 +0200 Subject: [PATCH 05/13] added feed movements --- nodes/text/export_gcode.py | 56 ++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/nodes/text/export_gcode.py b/nodes/text/export_gcode.py index 73b62152de..d34e70884e 100644 --- a/nodes/text/export_gcode.py +++ b/nodes/text/export_gcode.py @@ -61,37 +61,18 @@ class SvExportGcodeNnode(bpy.types.Node, SverchCustomTreeNode): bl_label = 'Export Gcode' bl_icon = 'COPYDOWN' - - _start_code = ''' -G21 ; set untis to millimeters -G90 ; use absolute coordinates -M82; set extruder to absolute positions.... -G28 ; home all axe -G28 E ; home Extruder -G93 ; ingresso tubo -G92 E0 ; set position -''' - - _end_code = ''' - -M83 - -G0 E-0.5 -G92 ; set position -G28 ; home all axes -G28 E; homing extruder -M84 ; disable motors -''' - folder = StringProperty(name="File", default="", subtype='FILE_PATH') pull = FloatProperty(name="Pull", default=5.0, min=0, soft_max=10) push = FloatProperty(name="Push", default=4.0, min=0, soft_max=10) dz = FloatProperty(name="dz", default=2.0, min=0, soft_max=20) #flow_mult = FloatProperty(name="Flow Mult", default=1.0, min=0, soft_max=3) - feed = IntProperty(name="Feed Rate (F)", default=1000, min=0, soft_max=5000) + feed = IntProperty(name="Feed Rate (F)", default=1000, min=0, soft_max=20000) + feed_horizontal = IntProperty(name="Feed Horizontal", default=1000, min=0, soft_max=20000) + feed_vertical = IntProperty(name="Feed Vertical", default=1000, min=0, soft_max=20000) + feed = IntProperty(name="Feed Rate (F)", default=1000, min=0, soft_max=20000) flow = FloatProperty(name="Flow (E/mm)", default=0.044, min=0, soft_max=1) - start_code = StringProperty(name="Start", default=_start_code) - end_code = StringProperty(name="End", default=_end_code) + start_code = StringProperty(name="Start", default='') + end_code = StringProperty(name="End", default='') auto_sort = BoolProperty(name="Auto Sort", default=True) gcode_mode = EnumProperty(items=[ @@ -119,13 +100,18 @@ def draw_buttons(self, context, layout): #col = layout.column(align=True) if self.gcode_mode == 'RETR': #col.label(text="Retraction:") - col.prop(self, 'auto_sort') + col.prop(self, 'auto_sort', text="Sort Layers") col.prop(self, 'pull') col.prop(self, 'dz') col.prop(self, 'push') col = layout.column(align=True) - col.prop(self, 'feed') - col.prop(self, 'flow') + col.label(text="Speed (Feed Rate F):") + col.prop(self, 'feed', text='Print') + if self.gcode_mode == 'RETR': + col.prop(self, 'feed_vertical', text='Vertical') + col.prop(self, 'feed_horizontal', text='Horizontal') + col.label(text="Extrusion (E/Length):") + col.prop(self, 'flow', text="Flow") #col.prop(self, 'flow_mult') col = layout.column(align=True) col.prop_search(self, 'start_code', bpy.data, 'texts') @@ -142,6 +128,8 @@ def update_socket(self, context): def process(self): # manage data feed = self.feed + feed_v = self.feed_vertical + feed_h = self.feed_horizontal flow = self.flow vertices = self.inputs['Vertices'].sv_get() #start_code = '\n'.join(self.inputs['Start Code'].sv_get()[0]) @@ -193,16 +181,18 @@ def process(self): # first point of the gcode if first_point: - file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' F' + format(feed, '.0f') + '\n') - file.write('G0 E0.5 \n') + file.write('G92 E0 \n') file.write('M82 \n') + file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' F' + format(feed, '.0f') + '\n') + #file.write('G0 E0.5 \n') first_point = False else: # start after retraction if v == curve[0] and self.gcode_mode == 'RETR': - file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + '\n') + file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + ' F' + format(feed_h, '.0f') + '\n') e += self.push - file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' E' + format(e, '.4f') + '\n') + file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' E' + format(e, '.4f') + ' F' + format(feed_v, '.0f') + '\n') + file.write( 'G1 F' + format(feed, '.0f') + '\n') # regular extrusion else: e += flow*dist @@ -212,7 +202,7 @@ def process(self): if curve != vertices[-1] and self.gcode_mode == 'RETR': #file.write(stop_extrusion) e -= self.pull file.write('G0 E' + format(e, '.4f') + '\n') - file.write('G1 X' + format(last_vert[0], '.4f') + ' Y' + format(last_vert[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + '\n') + file.write('G1 X' + format(last_vert[0], '.4f') + ' Y' + format(last_vert[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + ' F' + format(feed_v, '.0f') + '\n') # end code try: From c268007ab7e1f05c7da6d4337c4f18588fd2061a Mon Sep 17 00:00:00 2001 From: alessandro-zomparelli Date: Fri, 12 Oct 2018 18:19:50 +0200 Subject: [PATCH 06/13] new formula --- nodes/text/export_gcode.py | 46 +++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/nodes/text/export_gcode.py b/nodes/text/export_gcode.py index d34e70884e..cf5f2db43f 100644 --- a/nodes/text/export_gcode.py +++ b/nodes/text/export_gcode.py @@ -27,6 +27,7 @@ import bpy, os, mathutils from numpy import mean import operator +from math import pi from bpy.props import BoolProperty, EnumProperty, StringProperty, FloatProperty, IntProperty @@ -70,10 +71,13 @@ class SvExportGcodeNnode(bpy.types.Node, SverchCustomTreeNode): feed_horizontal = IntProperty(name="Feed Horizontal", default=1000, min=0, soft_max=20000) feed_vertical = IntProperty(name="Feed Vertical", default=1000, min=0, soft_max=20000) feed = IntProperty(name="Feed Rate (F)", default=1000, min=0, soft_max=20000) - flow = FloatProperty(name="Flow (E/mm)", default=0.044, min=0, soft_max=1) + esteps = FloatProperty(name="E Steps/Unit", default=5, min=0, soft_max=100) start_code = StringProperty(name="Start", default='') end_code = StringProperty(name="End", default='') auto_sort = BoolProperty(name="Auto Sort", default=True) + nozzle = FloatProperty(name="Nozzle", default=0.4, min=0, soft_max=10) + layer_height = FloatProperty(name="Layer Height", default=0.1, min=0, soft_max=10) + filament = FloatProperty(name="Filament (\u03A6)", default=1.75, min=0, soft_max=120) gcode_mode = EnumProperty(items=[ ("CONT", "Continuous", ""), @@ -83,7 +87,7 @@ class SvExportGcodeNnode(bpy.types.Node, SverchCustomTreeNode): def sv_init(self, context): #self.inputs.new('StringsSocket', 'Flow', 'Flow').prop_name = 'flow' #self.inputs.new('StringsSocket', 'Start Code', 'Start Code').prop_name = 'start_code' - #self.inputs.new('StringsSocket', 'End Code', 'End Code').prop_name = 'end_code' + self.inputs.new('StringsSocket', 'Layer Height', 'Layer Height').prop_name = 'layer_height' self.inputs.new('VerticesSocket', 'Vertices', 'Vertices') def draw_buttons(self, context, layout): @@ -98,25 +102,30 @@ def draw_buttons(self, context, layout): row = col.row() row.prop(self, 'gcode_mode', expand=True, toggle=True) #col = layout.column(align=True) - if self.gcode_mode == 'RETR': - #col.label(text="Retraction:") - col.prop(self, 'auto_sort', text="Sort Layers") - col.prop(self, 'pull') - col.prop(self, 'dz') - col.prop(self, 'push') col = layout.column(align=True) - col.label(text="Speed (Feed Rate F):") + col.label(text="Extrusion:", icon='MOD_FLUIDSIM') + #col.prop(self, 'esteps') + col.prop(self, 'filament') + col.prop(self, 'nozzle') + col.separator() + col.label(text="Speed (Feed Rate F):", icon='DRIVER') col.prop(self, 'feed', text='Print') if self.gcode_mode == 'RETR': col.prop(self, 'feed_vertical', text='Vertical') - col.prop(self, 'feed_horizontal', text='Horizontal') - col.label(text="Extrusion (E/Length):") - col.prop(self, 'flow', text="Flow") + col.prop(self, 'feed_horizontal', text='Travel') + col.separator() + if self.gcode_mode == 'RETR': + col.label(text="Retraction:", icon='NOCURVE') + col.prop(self, 'pull', text='Retraction') + col.prop(self, 'dz', text='Z Hop') + col.prop(self, 'push', text='Preload') + col.prop(self, 'auto_sort', text="Sort Layers (z)") + col.separator() #col.prop(self, 'flow_mult') - col = layout.column(align=True) + col.label(text='Custom Code:', icon='SCRIPT') col.prop_search(self, 'start_code', bpy.data, 'texts') col.prop_search(self, 'end_code', bpy.data, 'texts') - col = layout.column(align=True) + col.separator() row = col.row(align=True) row.scale_y = 4.0 row.operator(TEXT_IO_CALLBACK, text='Export Gcode').fn_name = 'process' @@ -130,7 +139,8 @@ def process(self): feed = self.feed feed_v = self.feed_vertical feed_h = self.feed_horizontal - flow = self.flow + flow = self.layer_height * self.nozzle / ((self.filament/2)**2 * pi) + print("flow: " + str(flow)) vertices = self.inputs['Vertices'].sv_get() #start_code = '\n'.join(self.inputs['Start Code'].sv_get()[0]) #end_code = '\n'.join(self.inputs['End Code'].sv_get()[0]) @@ -143,6 +153,7 @@ def process(self): if '.gcode' not in folder: folder += '.gcode' path = bpy.path.abspath(folder) file = open(path, 'w') + #file.write('M92 E' + format(self.esteps, '.4f') + '\n') try: for line in bpy.data.texts[self.start_code].lines: file.write(line.body + '\n') @@ -161,7 +172,7 @@ def process(self): vertices = [data[0] for data in sorted(sorted_verts, key=lambda height: height[1])] # initialize variables - e = 0.5 + e = 0 first_point = True count = 0 last_vert = mathutils.Vector((0,0,0)) @@ -182,7 +193,6 @@ def process(self): # first point of the gcode if first_point: file.write('G92 E0 \n') - file.write('M82 \n') file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' F' + format(feed, '.0f') + '\n') #file.write('G0 E0.5 \n') first_point = False @@ -195,7 +205,7 @@ def process(self): file.write( 'G1 F' + format(feed, '.0f') + '\n') # regular extrusion else: - e += flow*dist + e += dist*flow file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' E' + format(e, '.4f') + '\n') count+=1 last_vert = new_vert From fad2ffd61133118598cf5331af5a6adef765ba53 Mon Sep 17 00:00:00 2001 From: alessandro-zomparelli Date: Fri, 12 Oct 2018 23:40:57 +0200 Subject: [PATCH 07/13] close all shapes --- nodes/text/export_gcode.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/nodes/text/export_gcode.py b/nodes/text/export_gcode.py index cf5f2db43f..9051cfae3f 100644 --- a/nodes/text/export_gcode.py +++ b/nodes/text/export_gcode.py @@ -75,6 +75,7 @@ class SvExportGcodeNnode(bpy.types.Node, SverchCustomTreeNode): start_code = StringProperty(name="Start", default='') end_code = StringProperty(name="End", default='') auto_sort = BoolProperty(name="Auto Sort", default=True) + close_all = BoolProperty(name="Close Shapes", default=False) nozzle = FloatProperty(name="Nozzle", default=0.4, min=0, soft_max=10) layer_height = FloatProperty(name="Layer Height", default=0.1, min=0, soft_max=10) filament = FloatProperty(name="Filament (\u03A6)", default=1.75, min=0, soft_max=120) @@ -111,7 +112,7 @@ def draw_buttons(self, context, layout): col.label(text="Speed (Feed Rate F):", icon='DRIVER') col.prop(self, 'feed', text='Print') if self.gcode_mode == 'RETR': - col.prop(self, 'feed_vertical', text='Vertical') + col.prop(self, 'feed_vertical', text='Z Lift') col.prop(self, 'feed_horizontal', text='Travel') col.separator() if self.gcode_mode == 'RETR': @@ -120,6 +121,7 @@ def draw_buttons(self, context, layout): col.prop(self, 'dz', text='Z Hop') col.prop(self, 'push', text='Preload') col.prop(self, 'auto_sort', text="Sort Layers (z)") + col.prop(self, 'close_all') col.separator() #col.prop(self, 'flow_mult') col.label(text='Custom Code:', icon='SCRIPT') @@ -209,7 +211,12 @@ def process(self): file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' E' + format(e, '.4f') + '\n') count+=1 last_vert = new_vert - if curve != vertices[-1] and self.gcode_mode == 'RETR': #file.write(stop_extrusion) + if curve != vertices[-1] and self.gcode_mode == 'RETR': + if self.close_all: + new_vert = mathutils.Vector(curve[0]) + dist = (new_vert-last_vert).length + e += dist*flow + file.write('G1 X' + format(new_vert[0], '.4f') + ' Y' + format(new_vert[1], '.4f') + ' Z' + format(new_vert[2], '.4f') + ' E' + format(e, '.4f') + '\n') e -= self.pull file.write('G0 E' + format(e, '.4f') + '\n') file.write('G1 X' + format(last_vert[0], '.4f') + ' Y' + format(last_vert[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + ' F' + format(feed_v, '.0f') + '\n') From 6e13d2dc2728ca8ca68a5626527d97dc2d710d25 Mon Sep 17 00:00:00 2001 From: alessandro-zomparelli Date: Tue, 16 Oct 2018 01:41:50 +0200 Subject: [PATCH 08/13] list matches and path preview --- nodes/text/export_gcode.py | 121 ++++++++++++++++++++++++++----------- utils/sv_itertools.py | 93 +++++++++++++++++++++++++--- 2 files changed, 172 insertions(+), 42 deletions(-) diff --git a/nodes/text/export_gcode.py b/nodes/text/export_gcode.py index 9051cfae3f..d54cfcc062 100644 --- a/nodes/text/export_gcode.py +++ b/nodes/text/export_gcode.py @@ -33,6 +33,7 @@ from sverchok.node_tree import SverchCustomTreeNode, StringsSocket from sverchok.data_structure import node_id, multi_socket, updateNode +from sverchok.utils.sv_itertools import sv_zip_longest2, flatten, list_of_lists, recurse_verts_fxy, match_longest_lists from sverchok.utils.sv_text_io_common import ( FAIL_COLOR, READY_COLOR, TEXT_IO_CALLBACK, @@ -62,14 +63,17 @@ class SvExportGcodeNnode(bpy.types.Node, SverchCustomTreeNode): bl_label = 'Export Gcode' bl_icon = 'COPYDOWN' + last_e = FloatProperty(name="Pull", default=5.0, min=0, soft_max=10) + path_length = FloatProperty(name="Pull", default=5.0, min=0, soft_max=10) + folder = StringProperty(name="File", default="", subtype='FILE_PATH') pull = FloatProperty(name="Pull", default=5.0, min=0, soft_max=10) push = FloatProperty(name="Push", default=4.0, min=0, soft_max=10) dz = FloatProperty(name="dz", default=2.0, min=0, soft_max=20) - #flow_mult = FloatProperty(name="Flow Mult", default=1.0, min=0, soft_max=3) + flow_mult = FloatProperty(name="Flow Mult", default=1.0, min=0, soft_max=3) feed = IntProperty(name="Feed Rate (F)", default=1000, min=0, soft_max=20000) - feed_horizontal = IntProperty(name="Feed Horizontal", default=1000, min=0, soft_max=20000) - feed_vertical = IntProperty(name="Feed Vertical", default=1000, min=0, soft_max=20000) + feed_horizontal = IntProperty(name="Feed Horizontal", default=2000, min=0, soft_max=20000) + feed_vertical = IntProperty(name="Feed Vertical", default=500, min=0, soft_max=20000) feed = IntProperty(name="Feed Rate (F)", default=1000, min=0, soft_max=20000) esteps = FloatProperty(name="E Steps/Unit", default=5, min=0, soft_max=100) start_code = StringProperty(name="Start", default='') @@ -89,8 +93,15 @@ def sv_init(self, context): #self.inputs.new('StringsSocket', 'Flow', 'Flow').prop_name = 'flow' #self.inputs.new('StringsSocket', 'Start Code', 'Start Code').prop_name = 'start_code' self.inputs.new('StringsSocket', 'Layer Height', 'Layer Height').prop_name = 'layer_height' + self.inputs.new('StringsSocket', 'Flow Mult', 'Flow Mult').prop_name = 'flow_mult' self.inputs.new('VerticesSocket', 'Vertices', 'Vertices') + self.outputs.new('StringsSocket', 'Info', 'Info') + self.outputs.new('VerticesSocket', 'Print Verts', 'Print Verts') + self.outputs.new('StringsSocket', 'Print Edges', 'Print Edges') + self.outputs.new('VerticesSocket', 'Travel Verts', 'Travel Verts') + self.outputs.new('StringsSocket', 'Travel Edges', 'Travel Edges') + def draw_buttons(self, context, layout): addon = context.user_preferences.addons.get(sverchok.__name__) @@ -132,7 +143,6 @@ def draw_buttons(self, context, layout): row.scale_y = 4.0 row.operator(TEXT_IO_CALLBACK, text='Export Gcode').fn_name = 'process' - def update_socket(self, context): self.update() @@ -141,11 +151,18 @@ def process(self): feed = self.feed feed_v = self.feed_vertical feed_h = self.feed_horizontal - flow = self.layer_height * self.nozzle / ((self.filament/2)**2 * pi) - print("flow: " + str(flow)) + layer = self.layer_height + layer = self.inputs['Layer Height'].sv_get() vertices = self.inputs['Vertices'].sv_get() - #start_code = '\n'.join(self.inputs['Start Code'].sv_get()[0]) - #end_code = '\n'.join(self.inputs['End Code'].sv_get()[0]) + flow_mult = self.inputs['Flow Mult'].sv_get() + + # data matching + vertices = list_of_lists(vertices) + flow_mult = list_of_lists(flow_mult) + layer = list_of_lists(layer) + vertices, flow_mult, layer = match_longest_lists([vertices, flow_mult, layer]) + print(vertices) + print(layer) # open file if self.folder == '': @@ -175,51 +192,79 @@ def process(self): # initialize variables e = 0 - first_point = True - count = 0 last_vert = mathutils.Vector((0,0,0)) maxz = 0 + path_length = 0 + + printed_verts = [] + printed_edges = [] + travel_verts = [] + travel_edges = [] # write movements - for curve in vertices: - #path = path[0] - #print(curve) - for v in curve: - #print(v) - new_vert = mathutils.Vector(v) - dist = (new_vert-last_vert).length + for i in range(len(vertices)): + curve = vertices[i] + first_id = len(printed_verts) + for j in range(len(curve)): + v = curve[j] + v_flow_mult = flow_mult[i][j] + v_layer = layer[i][j] # record max z maxz = max(maxz,v[2]) + printed_verts.append(v) + # first point of the gcode - if first_point: + if i == j == 0: file.write('G92 E0 \n') file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' F' + format(feed, '.0f') + '\n') - #file.write('G0 E0.5 \n') - first_point = False else: # start after retraction - if v == curve[0] and self.gcode_mode == 'RETR': + if j == 0 and self.gcode_mode == 'RETR': file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + ' F' + format(feed_h, '.0f') + '\n') e += self.push - file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' E' + format(e, '.4f') + ' F' + format(feed_v, '.0f') + '\n') - file.write( 'G1 F' + format(feed, '.0f') + '\n') + file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' F' + format(feed_v, '.0f') + '\n') + file.write( 'G1 E' + format(e, '.4f') + '\n') + travel_verts.append((v[0], v[1], maxz+self.dz)) + travel_edges.append((len(travel_verts)-1, len(travel_verts)-2)) + travel_verts.append(v) + travel_edges.append((len(travel_verts)-1, len(travel_verts)-2)) # regular extrusion else: - e += dist*flow + v1 = mathutils.Vector(v) + v0 = mathutils.Vector(curve[j-1]) + dist = (v1-v0).length + print(dist) + area = v_layer * self.nozzle + pi*(v_layer/2)**2 # rectangle + circle + cylinder = pi*(self.filament/2)**2 + flow = area / cylinder + e += dist * v_flow_mult * flow file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' E' + format(e, '.4f') + '\n') - count+=1 - last_vert = new_vert - if curve != vertices[-1] and self.gcode_mode == 'RETR': + path_length += dist + printed_edges.append([len(printed_verts)-1, len(printed_verts)-2]) + if self.gcode_mode == 'RETR': + v0 = mathutils.Vector(curve[-1]) if self.close_all: - new_vert = mathutils.Vector(curve[0]) - dist = (new_vert-last_vert).length - e += dist*flow - file.write('G1 X' + format(new_vert[0], '.4f') + ' Y' + format(new_vert[1], '.4f') + ' Z' + format(new_vert[2], '.4f') + ' E' + format(e, '.4f') + '\n') - e -= self.pull - file.write('G0 E' + format(e, '.4f') + '\n') - file.write('G1 X' + format(last_vert[0], '.4f') + ' Y' + format(last_vert[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + ' F' + format(feed_v, '.0f') + '\n') + #printed_verts.append(v0) + printed_edges.append([len(printed_verts)-1, first_id]) + + v1 = mathutils.Vector(curve[0]) + dist = (v0-v1).length + area = v_layer * self.nozzle + pi*(v_layer/2)**2 # rectangle + circle + cylinder = pi*(self.filament/2)**2 + flow = area / cylinder + e += dist * v_flow_mult * flow + file.write('G1 X' + format(v1[0], '.4f') + ' Y' + format(v1[1], '.4f') + ' Z' + format(v1[2], '.4f') + ' E' + format(e, '.4f') + '\n') + path_length += dist + v0 = v1 + if i < len(vertices)-1: + e -= self.pull + file.write('G0 E' + format(e, '.4f') + '\n') + file.write('G1 X' + format(v0[0], '.4f') + ' Y' + format(v0[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + ' F' + format(feed_v, '.0f') + '\n') + travel_verts.append(v0.to_tuple()) + travel_verts.append((v0.x, v0.y, maxz+self.dz)) + travel_edges.append((len(travel_verts)-1, len(travel_verts)-2)) # end code try: @@ -230,6 +275,14 @@ def process(self): #file.write(end_code) file.close() print("Saved gcode to " + path) + info = "Extruded Filament: " + format(e, '.2f') + '\n' + info += "Extruded Volume: " + format(e*pi*(self.filament/2)**2, '.2f') + '\n' + info += "Printed Path: " + format(path_length, '.2f') + self.outputs[0].sv_set(info) + self.outputs[1].sv_set(printed_verts) + self.outputs[2].sv_set(printed_edges) + self.outputs[3].sv_set(travel_verts) + self.outputs[4].sv_set(travel_edges) def register(): bpy.utils.register_class(SvExportGcodeNnode) diff --git a/utils/sv_itertools.py b/utils/sv_itertools.py index 315e5ed72e..c6b4baa6c7 100644 --- a/utils/sv_itertools.py +++ b/utils/sv_itertools.py @@ -25,7 +25,7 @@ def __iter__(self): class sv_zip_longest: def __init__(self, *args): - self.counter = len(args) + self.counter = len(args) self.iterators = [] for lst in args: fl = lst[-1] @@ -33,7 +33,7 @@ def __init__(self, *args): self.iterators.append(chain(lst, SvSentinel(fl,self), filler)) def __next__(self): - try: + try: if self.counter: return tuple(map(next, self.iterators)) else: @@ -42,8 +42,7 @@ def __next__(self): raise StopIteration def __iter__(self): - return self - + return self def sv_zip_longest2(*args): # by zeffi @@ -51,8 +50,8 @@ def sv_zip_longest2(*args): itrs = [iter(sl) for sl in args] for i in range(longest): yield tuple((next(iterator, args[idx][-1]) for idx, iterator in enumerate(itrs))) - - + + def recurse_fx(l, f): if isinstance(l, (list, tuple)): return [recurse_fx(i, f) for i in l] @@ -61,9 +60,9 @@ def recurse_fx(l, f): def recurse_fxy(l1, l2, f): l1_type = isinstance(l1, (list, tuple)) - l2_type = isinstance(l2, (list, tuple)) + l2_type = isinstance(l2, (list, tuple)) if not (l1_type or l2_type): - return f(l1, l2) + return f(l1, l2) elif l1_type and l2_type: fl = l2[-1] if len(l1) > len(l2) else l1[-1] res = [] @@ -75,3 +74,81 @@ def recurse_fxy(l1, l2, f): return [recurse_fxy(x, l2, f) for x in l1] else: #not l1_type and l2_type return [recurse_fxy(l1, y, f) for y in l2] + +def recurse_verts_fxy(l1, l2, f): + l1_type = isinstance(l1, (list)) + l2_type = isinstance(l2, (list)) + if not (l1_type or l2_type): + return f(l1, l2) + elif l1_type and l2_type: + fl = l2[-1] if len(l1) > len(l2) else l1[-1] + res = [] + res_append = res.append + for x, y in zip_longest(l1, l2, fillvalue=fl): + res_append(recurse_verts_fxy(x, y, f)) + return res + elif l1_type and not l2_type: + return [recurse_verts_fxy(x, l2, f) for x in l1] + else: #not l1_type and l2_type + return [recurse_verts_fxy(l1, y, f) for y in l2] + +# append all the elements to one single list +def append_all(l, flat): + # by Alessandro Zomparelli + if isinstance(l,(list)): + return [append_all(i, flat) for i in l] + else: + flat.append(l) + return l + +# flatten sublists +def flatten(l): + # by Alessandro Zomparelli + flat = [] + append_all(l, flat) + return flat + +# append all the lists to one single list +def append_lists(l, lists): + # by Alessandro Zomparelli + if isinstance(l,(list)): + flat_list = True + for i in l: + if isinstance(i,(list)): + flat_list = False + break + if flat_list: + lists.append(l) + return None + else: + return [append_lists(i, lists) for i in l] + else: + lists.append([l]) + return None + +# generate a single list with 1 level lists inside +def list_of_lists(l): + # by Alessandro Zomparelli + out_list = [] + append_lists(l, out_list) + return out_list + +# works with irregular sublists +def match_longest_lists(lists): + # by Alessandro Zomparelli + add_level = max([isinstance(l, list) for l in lists]) + if add_level: + for i in range(len(lists)): + l = lists[i] + if not isinstance(l, list): + lists[i] = [l] + length = [len(l) for l in lists] + max_length = max(length) + # extend shorter lists + for l in lists: + for i in range(len(l), max_length): + l.append(l[-1]) + try: + return zip(*[match_longest_lists([l[i] for l in lists]) for i in range(max_length)]) + except: + return lists From 6224a32d839e6c4dc41ba33ebbe5568c8de954e0 Mon Sep 17 00:00:00 2001 From: alessandro-zomparelli Date: Tue, 16 Oct 2018 02:14:01 +0200 Subject: [PATCH 09/13] text format --- nodes/text/export_gcode.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/nodes/text/export_gcode.py b/nodes/text/export_gcode.py index d54cfcc062..c8a20bf754 100644 --- a/nodes/text/export_gcode.py +++ b/nodes/text/export_gcode.py @@ -218,13 +218,22 @@ def process(self): # first point of the gcode if i == j == 0: file.write('G92 E0 \n') - file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' F' + format(feed, '.0f') + '\n') + params = v[:3] + (feed,) + to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} F{3:.0f}\n'.format(*params) + file.write(to_write) + #file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' F' + format(feed, '.0f') + '\n') else: # start after retraction if j == 0 and self.gcode_mode == 'RETR': - file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + ' F' + format(feed_h, '.0f') + '\n') + params = v[:2] + (maxz+self.dz,) + (feed_h,) + to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} F{3:.0f}\n'.format(*params) + file.write(to_write) + #file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + ' F' + format(feed_h, '.0f') + '\n') e += self.push - file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' F' + format(feed_v, '.0f') + '\n') + params = v[:3] + (feed_v,) + to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} F{3:.0f}\n'.format(*params) + file.write(to_write) + #file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' F' + format(feed_v, '.0f') + '\n') file.write( 'G1 E' + format(e, '.4f') + '\n') travel_verts.append((v[0], v[1], maxz+self.dz)) travel_edges.append((len(travel_verts)-1, len(travel_verts)-2)) @@ -240,7 +249,10 @@ def process(self): cylinder = pi*(self.filament/2)**2 flow = area / cylinder e += dist * v_flow_mult * flow - file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' E' + format(e, '.4f') + '\n') + params = v[:3] + (e,) + to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} E{3:.4f}\n'.format(*params) + file.write(to_write) + #file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' E' + format(e, '.4f') + '\n') path_length += dist printed_edges.append([len(printed_verts)-1, len(printed_verts)-2]) if self.gcode_mode == 'RETR': @@ -255,13 +267,19 @@ def process(self): cylinder = pi*(self.filament/2)**2 flow = area / cylinder e += dist * v_flow_mult * flow - file.write('G1 X' + format(v1[0], '.4f') + ' Y' + format(v1[1], '.4f') + ' Z' + format(v1[2], '.4f') + ' E' + format(e, '.4f') + '\n') + params = v1[:3] + (e,) + to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} E{3:.4f}\n'.format(*params) + file.write(to_write) + #file.write('G1 X' + format(v1[0], '.4f') + ' Y' + format(v1[1], '.4f') + ' Z' + format(v1[2], '.4f') + ' E' + format(e, '.4f') + '\n') path_length += dist v0 = v1 if i < len(vertices)-1: e -= self.pull file.write('G0 E' + format(e, '.4f') + '\n') - file.write('G1 X' + format(v0[0], '.4f') + ' Y' + format(v0[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + ' F' + format(feed_v, '.0f') + '\n') + params = v0[:2] + (maxz+self.dz,) + (feed_v,) + to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} F{3:.0f}\n'.format(*params) + file.write(to_write) + #file.write('G1 X' + format(v0[0], '.4f') + ' Y' + format(v0[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + ' F' + format(feed_v, '.0f') + '\n') travel_verts.append(v0.to_tuple()) travel_verts.append((v0.x, v0.y, maxz+self.dz)) travel_edges.append((len(travel_verts)-1, len(travel_verts)-2)) From 20b06e1ba2332c55a4eb672e1eda8fad14e54088 Mon Sep 17 00:00:00 2001 From: alessandro-zomparelli Date: Tue, 16 Oct 2018 17:08:45 +0200 Subject: [PATCH 10/13] removed one output socket --- nodes/text/export_gcode.py | 29 +++++++++++++++-------------- utils/sv_itertools.py | 5 ----- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/nodes/text/export_gcode.py b/nodes/text/export_gcode.py index c8a20bf754..25e012d966 100644 --- a/nodes/text/export_gcode.py +++ b/nodes/text/export_gcode.py @@ -68,7 +68,7 @@ class SvExportGcodeNnode(bpy.types.Node, SverchCustomTreeNode): folder = StringProperty(name="File", default="", subtype='FILE_PATH') pull = FloatProperty(name="Pull", default=5.0, min=0, soft_max=10) - push = FloatProperty(name="Push", default=4.0, min=0, soft_max=10) + push = FloatProperty(name="Push", default=5.0, min=0, soft_max=10) dz = FloatProperty(name="dz", default=2.0, min=0, soft_max=20) flow_mult = FloatProperty(name="Flow Mult", default=1.0, min=0, soft_max=3) feed = IntProperty(name="Feed Rate (F)", default=1000, min=0, soft_max=20000) @@ -97,9 +97,9 @@ def sv_init(self, context): self.inputs.new('VerticesSocket', 'Vertices', 'Vertices') self.outputs.new('StringsSocket', 'Info', 'Info') - self.outputs.new('VerticesSocket', 'Print Verts', 'Print Verts') - self.outputs.new('StringsSocket', 'Print Edges', 'Print Edges') - self.outputs.new('VerticesSocket', 'Travel Verts', 'Travel Verts') + self.outputs.new('VerticesSocket', 'Vertices', 'Vertices') + self.outputs.new('StringsSocket', 'Printed Edges', 'Printed Edges') + #self.outputs.new('VerticesSocket', 'Travel Verts', 'Travel Verts') self.outputs.new('StringsSocket', 'Travel Edges', 'Travel Edges') def draw_buttons(self, context, layout): @@ -213,10 +213,10 @@ def process(self): # record max z maxz = max(maxz,v[2]) - printed_verts.append(v) # first point of the gcode if i == j == 0: + printed_verts.append(v) file.write('G92 E0 \n') params = v[:3] + (feed,) to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} F{3:.0f}\n'.format(*params) @@ -235,12 +235,13 @@ def process(self): file.write(to_write) #file.write('G1 X' + format(v[0], '.4f') + ' Y' + format(v[1], '.4f') + ' Z' + format(v[2], '.4f') + ' F' + format(feed_v, '.0f') + '\n') file.write( 'G1 E' + format(e, '.4f') + '\n') - travel_verts.append((v[0], v[1], maxz+self.dz)) - travel_edges.append((len(travel_verts)-1, len(travel_verts)-2)) - travel_verts.append(v) - travel_edges.append((len(travel_verts)-1, len(travel_verts)-2)) + printed_verts.append((v[0], v[1], maxz+self.dz)) + travel_edges.append((len(printed_verts)-1, len(printed_verts)-2)) + printed_verts.append(v) + travel_edges.append((len(printed_verts)-1, len(printed_verts)-2)) # regular extrusion else: + printed_verts.append(v) v1 = mathutils.Vector(v) v0 = mathutils.Vector(curve[j-1]) dist = (v1-v0).length @@ -280,9 +281,9 @@ def process(self): to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} F{3:.0f}\n'.format(*params) file.write(to_write) #file.write('G1 X' + format(v0[0], '.4f') + ' Y' + format(v0[1], '.4f') + ' Z' + format(maxz+self.dz, '.4f') + ' F' + format(feed_v, '.0f') + '\n') - travel_verts.append(v0.to_tuple()) - travel_verts.append((v0.x, v0.y, maxz+self.dz)) - travel_edges.append((len(travel_verts)-1, len(travel_verts)-2)) + printed_verts.append(v0.to_tuple()) + printed_verts.append((v0.x, v0.y, maxz+self.dz)) + travel_edges.append((len(printed_verts)-1, len(printed_verts)-2)) # end code try: @@ -299,8 +300,8 @@ def process(self): self.outputs[0].sv_set(info) self.outputs[1].sv_set(printed_verts) self.outputs[2].sv_set(printed_edges) - self.outputs[3].sv_set(travel_verts) - self.outputs[4].sv_set(travel_edges) + #self.outputs[3].sv_set(travel_verts) + self.outputs[3].sv_set(travel_edges) def register(): bpy.utils.register_class(SvExportGcodeNnode) diff --git a/utils/sv_itertools.py b/utils/sv_itertools.py index c6b4baa6c7..cd165f1ca7 100644 --- a/utils/sv_itertools.py +++ b/utils/sv_itertools.py @@ -94,7 +94,6 @@ def recurse_verts_fxy(l1, l2, f): # append all the elements to one single list def append_all(l, flat): - # by Alessandro Zomparelli if isinstance(l,(list)): return [append_all(i, flat) for i in l] else: @@ -103,14 +102,12 @@ def append_all(l, flat): # flatten sublists def flatten(l): - # by Alessandro Zomparelli flat = [] append_all(l, flat) return flat # append all the lists to one single list def append_lists(l, lists): - # by Alessandro Zomparelli if isinstance(l,(list)): flat_list = True for i in l: @@ -128,14 +125,12 @@ def append_lists(l, lists): # generate a single list with 1 level lists inside def list_of_lists(l): - # by Alessandro Zomparelli out_list = [] append_lists(l, out_list) return out_list # works with irregular sublists def match_longest_lists(lists): - # by Alessandro Zomparelli add_level = max([isinstance(l, list) for l in lists]) if add_level: for i in range(len(lists)): From 5453bd47ea570d96d6e96c1004c500d9414c5126 Mon Sep 17 00:00:00 2001 From: alessandro-zomparelli Date: Thu, 1 Nov 2018 02:46:41 +0100 Subject: [PATCH 11/13] Added Evaluate Image Node and fixed limit in Image Node --- index.md | 4 +- nodes/analyzer/evaluate_image.py | 195 +++++++++++++++++++++++++++++++ nodes/generator/image.py | 18 +-- nodes/text/export_gcode.py | 8 +- 4 files changed, 210 insertions(+), 15 deletions(-) create mode 100644 nodes/analyzer/evaluate_image.py diff --git a/index.md b/index.md index 5b19b7cf16..69726fe2aa 100644 --- a/index.md +++ b/index.md @@ -63,6 +63,7 @@ SvProportionalEditNode SvRaycasterLiteNode SvOBJInsolationNode + EvaluateImageNode ## Transforms SvRotationNode @@ -309,6 +310,7 @@ SvSetCustomMeshNormals --- SvSpiralNode + SvExportGcodeNode ## Alpha Nodes SvCurveViewerNode @@ -317,7 +319,6 @@ SvTypeViewerNode SvSkinViewerNodeMK1b SvMatrixViewer - SvExportGcodeNnode --- SvBManalyzinNode SvBMObjinputNode @@ -340,4 +341,3 @@ SvOffsetLineNode SvContourNode SvPlanarEdgenetToPolygons - diff --git a/nodes/analyzer/evaluate_image.py b/nodes/analyzer/evaluate_image.py new file mode 100644 index 0000000000..63297f0db2 --- /dev/null +++ b/nodes/analyzer/evaluate_image.py @@ -0,0 +1,195 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# 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 2 +# 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. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy +from bpy.props import IntProperty, FloatProperty, StringProperty, EnumProperty + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, fullList +from math import floor + + +class EvaluateImageNode(bpy.types.Node, SverchCustomTreeNode): + ''' Evaluate Image ''' + bl_idname = 'EvaluateImageNode' + bl_label = 'Evaluate Image' + bl_icon = 'FILE_IMAGE' + + image_name = StringProperty(name='image_name', description='image name', default='', update=updateNode) + + boundary_modes = [ + ("CLIP", "Clip", "", 0), + ("CYCLIC", "Repeat", "", 1), + ("MIRROR", "Mirror", "", 2)] + + shift_modes = [ + ("NONE", "None", "", 0), + ("ALTERNATE", "Alternate", "", 1), + ("CONSTANT", "Constant", "", 2)] + + shift_mode_U = EnumProperty( + name="U Shift", + description="U Shift", + default="NONE", items=shift_modes, + update=updateNode) + + shift_mode_V = EnumProperty( + name="V Shift", + description="V Shift", + default="NONE", items=shift_modes, + update=updateNode) + + boundU = EnumProperty( + name="U Bounds", + description="U Boundaries", + default="CYCLIC", items=boundary_modes, + update=updateNode) + + boundV = EnumProperty( + name="V Bounds", + description="V Boundaries", + default="CYCLIC", items=boundary_modes, + update=updateNode) + + domU = FloatProperty( + name='U domain', description='U domain', default=1, min=0.00001, + options={'ANIMATABLE'}, update=updateNode) + + domV = FloatProperty( + name='V domain', description='V domain', default=1, min=0.00001, + options={'ANIMATABLE'}, update=updateNode) + + shiftU = FloatProperty( + name='U shift', description='U shift', default=0.5, soft_min=-1, soft_max=1, + options={'ANIMATABLE'}, update=updateNode) + + shiftV = FloatProperty( + name='V shift', description='V shift', default=0, soft_min=-1, soft_max=1, + options={'ANIMATABLE'}, update=updateNode) + + def sv_init(self, context): + self.inputs.new('VerticesSocket', "Verts UV") + self.inputs.new('StringsSocket', "U domain").prop_name = 'domU' + self.inputs.new('StringsSocket', "V domain").prop_name = 'domV' + #if self.shift_mode_U not 'NONE': + #self.inputs.new('StringsSocket', "U shift").prop_name = 'shiftU' + #if self.shift_mode_V not 'NONE': + #self.inputs.new('StringsSocket', "V shift").prop_name = 'shiftV' + self.outputs.new('StringsSocket', "R") + self.outputs.new('StringsSocket', "G") + self.outputs.new('StringsSocket', "B") + + def draw_buttons(self, context, layout): + layout.label(text="Image:") + layout.prop_search(self, "image_name", bpy.data, 'images', text="") + + row = layout.row(align=True) + col = row.column(align=True) + col.label(text="Tile U:") + col.prop(self, "boundU", text="") + col.label(text="Shift U:") + col.prop(self, "shift_mode_U", text="") + if self.shift_mode_U != 'NONE': + col.prop(self, "shiftU", text="") + + col = row.column(align=True) + col.label(text="Tile V:") + col.prop(self, "boundV", text="") + col.label(text="Shift V:") + col.prop(self, "shift_mode_V", text="") + if self.shift_mode_V != 'NONE': + col.prop(self, "shiftV", text="") + + + def process(self): + verts = self.inputs['Verts UV'].sv_get() + + inputs, outputs = self.inputs, self.outputs + + # inputs + if inputs['Verts UV'].is_linked: + verts = inputs['Verts UV'].sv_get()[0] + else: + verts = [(0,0,0),(0,1,0),(1,0,0),(1,1,0)] + + if inputs['U domain'].is_linked: + domU = inputs['U domain'].sv_get()[0][0] + else: domU = self.domU + + if inputs['V domain'].is_linked: + domV = inputs['V domain'].sv_get()[0][0] + else: domV = self.domV + + # outputs + red = [[]] + green = [[]] + blue = [[]] + + if outputs['R'].is_linked or outputs['G'].is_linked or outputs['B'].is_linked: + # copy images data, pixels is created on every access with [i], extreme speedup. + # http://blender.stackexchange.com/questions/3673/why-is-accessing-image-data-so-slow + imag = bpy.data.images[self.image_name].pixels[:] + sizeU = bpy.data.images[self.image_name].size[0] + sizeV = bpy.data.images[self.image_name].size[1] + for vert in verts: + vx = vert[0]*(sizeU-1)/domU + vy = vert[1]*sizeV/domV + u = floor(vx) + v = floor(vy) + u0 = u + + if self.shift_mode_U == 'ALTERNATE': + if (v//sizeV)%2: u += floor(sizeU*self.shiftU) + if self.shift_mode_U == 'CONSTANT': + u += floor(sizeU*self.shiftU*(v//sizeV)) + if self.boundU == 'CLIP': + u = max(0,min(u,sizeU-1)) + elif self.boundU == 'CYCLIC': + u = u%sizeU + elif self.boundU == 'MIRROR': + if (u//sizeU)%2: u = sizeU - 1 - u%(sizeU) + else: u = u%(sizeU) + + + if self.shift_mode_V == 'ALTERNATE': + if (u0//sizeU)%2: v += floor(sizeV*self.shiftV) + if self.shift_mode_V == 'CONSTANT': + v += floor(sizeV*self.shiftV*(u0//sizeU)) + if self.boundV == 'CLIP': + v = max(0,min(v,sizeV-1)) + elif self.boundV == 'CYCLIC': + v = v%sizeV + elif self.boundV == 'MIRROR': + if (v//sizeV)%2: v = sizeV - 1 - v%(sizeV) + else: v = v%(sizeV) + + index = int(u*4 + v*4*sizeU) + red[0].append(imag[index]) + green[0].append(imag[index+1]) + blue[0].append(imag[index+2]) + outputs['R'].sv_set(red) + outputs['G'].sv_set(green) + outputs['B'].sv_set(blue) + + +def register(): + bpy.utils.register_class(EvaluateImageNode) + + +def unregister(): + bpy.utils.unregister_class(EvaluateImageNode) diff --git a/nodes/generator/image.py b/nodes/generator/image.py index fd1399f910..ffb789caed 100644 --- a/nodes/generator/image.py +++ b/nodes/generator/image.py @@ -45,19 +45,19 @@ class ImageNode(bpy.types.Node, SverchCustomTreeNode): options={'ANIMATABLE'}, update=updateNode) Xvecs = IntProperty( - name='Xvecs', description='Xvecs', default=10, min=2, max=100, + name='Xvecs', description='Xvecs', default=10, min=2, soft_max=100, options={'ANIMATABLE'}, update=updateNode) Yvecs = IntProperty( - name='Yvecs', description='Yvecs', default=10, min=2, max=100, + name='Yvecs', description='Yvecs', default=10, min=2, soft_max=100, options={'ANIMATABLE'}, update=updateNode) Xstep = FloatProperty( - name='Xstep', description='Xstep', default=1.0, min=0.01, max=100, + name='Xstep', description='Xstep', default=1.0, min=0.01, soft_max=100, options={'ANIMATABLE'}, update=updateNode) Ystep = FloatProperty( - name='Ystep', description='Ystep', default=1.0, min=0.01, max=100, + name='Ystep', description='Ystep', default=1.0, min=0.01, soft_max=100, options={'ANIMATABLE'}, update=updateNode) def sv_init(self, context): @@ -82,12 +82,12 @@ def process(self): # inputs if inputs['vecs X'].is_linked: - IntegerX = min(int(inputs['vecs X'].sv_get()[0][0]), 100) + IntegerX = min(int(inputs['vecs X'].sv_get()[0][0]), 1000000) else: IntegerX = int(self.Xvecs) if inputs['vecs Y'].is_linked: - IntegerY = min(int(inputs['vecs Y'].sv_get()[0][0]), 100) + IntegerY = min(int(inputs['vecs Y'].sv_get()[0][0]), 1000000) else: IntegerY = int(self.Yvecs) @@ -109,7 +109,7 @@ def process(self): if outputs['edgs'].is_linked: listEdg = [] - + for i in range(IntegerY): for j in range(IntegerX-1): listEdg.append((IntegerX*i+j, IntegerX*i+j+1)) @@ -127,7 +127,7 @@ def process(self): listPlg.append((IntegerX*j+i, IntegerX*j+i+1, IntegerX*j+i+IntegerX+1, IntegerX*j+i+IntegerX)) plg = [list(listPlg)] outputs['pols'].sv_set(plg) - + def make_vertices(self, delitelx, delitely, stepx, stepy, image_name): lenx = bpy.data.images[image_name].size[0] @@ -150,7 +150,7 @@ def make_vertices(self, delitelx, delitely, stepx, stepy, image_name): # каждый пиксель кодируется RGBA, и записан строкой, без разделения на строки и столбцы. middle = (imag[addition]*R+imag[addition+1]*G+imag[addition+2]*B)*imag[addition+3] vertex = [x*stepx[x], y*stepy[y], middle] - vertices.append(vertex) + vertices.append(vertex) addition += int(xcoef*4) return vertices diff --git a/nodes/text/export_gcode.py b/nodes/text/export_gcode.py index 25e012d966..fe5d9a1163 100644 --- a/nodes/text/export_gcode.py +++ b/nodes/text/export_gcode.py @@ -54,12 +54,12 @@ def convert_to_text(list): else: break return list -class SvExportGcodeNnode(bpy.types.Node, SverchCustomTreeNode): +class SvExportGcodeNode(bpy.types.Node, SverchCustomTreeNode): """ Triggers: Export gcode from vertices position Tooltip: Generate a gcode file from a list of vertices """ - bl_idname = 'SvExportGcodeNnode' + bl_idname = 'SvExportGcodeNode' bl_label = 'Export Gcode' bl_icon = 'COPYDOWN' @@ -304,8 +304,8 @@ def process(self): self.outputs[3].sv_set(travel_edges) def register(): - bpy.utils.register_class(SvExportGcodeNnode) + bpy.utils.register_class(SvExportGcodeNode) def unregister(): - bpy.utils.unregister_class(SvExportGcodeNnode) + bpy.utils.unregister_class(SvExportGcodeNode) From e292ed1c7bc2747639f20a96772388be09be9006 Mon Sep 17 00:00:00 2001 From: alessandro-zomparelli Date: Sat, 17 Nov 2018 11:30:14 +0100 Subject: [PATCH 12/13] Added Alpha and BW grayscale --- nodes/analyzer/evaluate_image.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/nodes/analyzer/evaluate_image.py b/nodes/analyzer/evaluate_image.py index 66d455d50c..244569cdde 100644 --- a/nodes/analyzer/evaluate_image.py +++ b/nodes/analyzer/evaluate_image.py @@ -87,9 +87,11 @@ def sv_init(self, context): self.inputs.new('VerticesSocket', "Verts UV") self.inputs.new('StringsSocket', "U domain").prop_name = 'domU' self.inputs.new('StringsSocket', "V domain").prop_name = 'domV' + self.outputs.new('StringsSocket', "BW") self.outputs.new('StringsSocket', "R") self.outputs.new('StringsSocket', "G") self.outputs.new('StringsSocket', "B") + self.outputs.new('StringsSocket', "A") def draw_buttons(self, context, layout): layout.label(text="Image:") @@ -133,11 +135,13 @@ def process(self): else: domV = self.domV # outputs + bw = [[]] red = [[]] + alpha = [[]] green = [[]] blue = [[]] - if outputs['R'].is_linked or outputs['G'].is_linked or outputs['B'].is_linked: + if outputs['R'].is_linked or outputs['G'].is_linked or outputs['B'].is_linked or outputs['A'].is_linked or outputs['BW'].is_linked: imag = bpy.data.images[self.image_name].pixels[:] sizeU = bpy.data.images[self.image_name].size[0] sizeV = bpy.data.images[self.image_name].size[1] @@ -163,7 +167,6 @@ def process(self): elif self.boundU == 'EXTEND': u = max(0,min(u,sizeU-1)) - if self.shift_mode_V == 'ALTERNATE': if (u0//sizeU)%2: v += floor(sizeV*self.shiftV) if self.shift_mode_V == 'CONSTANT': @@ -183,10 +186,16 @@ def process(self): red[0].append(imag[index]) green[0].append(imag[index+1]) blue[0].append(imag[index+2]) + alpha[0].append(imag[index+3]) else: red[0].append(0) green[0].append(0) blue[0].append(0) + alpha[0].append(0) + if outputs['BW'].is_linked: + bw[0] = [(r+g+b)/3 for r,g,b in zip(red[0], green[0], blue[0])] + outputs['BW'].sv_set(bw) + outputs['A'].sv_set(alpha) outputs['R'].sv_set(red) outputs['G'].sv_set(green) outputs['B'].sv_set(blue) From 7332ab753c10349219eef03e013f426b5748185c Mon Sep 17 00:00:00 2001 From: alessandro-zomparelli Date: Sat, 17 Nov 2018 13:26:02 +0100 Subject: [PATCH 13/13] fixed grayscale --- nodes/analyzer/evaluate_image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodes/analyzer/evaluate_image.py b/nodes/analyzer/evaluate_image.py index 244569cdde..2cc58c6864 100644 --- a/nodes/analyzer/evaluate_image.py +++ b/nodes/analyzer/evaluate_image.py @@ -87,11 +87,11 @@ def sv_init(self, context): self.inputs.new('VerticesSocket', "Verts UV") self.inputs.new('StringsSocket', "U domain").prop_name = 'domU' self.inputs.new('StringsSocket', "V domain").prop_name = 'domV' - self.outputs.new('StringsSocket', "BW") self.outputs.new('StringsSocket', "R") self.outputs.new('StringsSocket', "G") self.outputs.new('StringsSocket', "B") self.outputs.new('StringsSocket', "A") + self.outputs.new('StringsSocket', "BW") def draw_buttons(self, context, layout): layout.label(text="Image:") @@ -193,7 +193,7 @@ def process(self): blue[0].append(0) alpha[0].append(0) if outputs['BW'].is_linked: - bw[0] = [(r+g+b)/3 for r,g,b in zip(red[0], green[0], blue[0])] + bw[0] = [0.2126*r + 0.7152*g + 0.0722*b for r,g,b in zip(red[0], green[0], blue[0])] outputs['BW'].sv_set(bw) outputs['A'].sv_set(alpha) outputs['R'].sv_set(red)