- creating the camlib functions for the ToolSolderPaste gcode generation functions

This commit is contained in:
Marius Stanciu 2019-02-21 17:07:38 +02:00 committed by Marius
parent 48e54a0655
commit d5768d3b34
4 changed files with 195 additions and 177 deletions

View File

@ -748,7 +748,8 @@ class App(QtCore.QObject):
"tools_solderpaste_speedfwd": 20, "tools_solderpaste_speedfwd": 20,
"tools_solderpaste_dwellfwd": 1, "tools_solderpaste_dwellfwd": 1,
"tools_solderpaste_speedrev": 10, "tools_solderpaste_speedrev": 10,
"tools_solderpaste_dwellrev": 1 "tools_solderpaste_dwellrev": 1,
"tools_solderpaste_pp": ''
}) })
############################### ###############################

View File

@ -17,7 +17,8 @@ CAD program, and create G-Code for Isolation routing.
- added the functions for GCode View and GCode Save in Tool SolderPaste - added the functions for GCode View and GCode Save in Tool SolderPaste
- some work in the Gcode generation function in Tool SolderPaste - some work in the Gcode generation function in Tool SolderPaste
- added protection against trying to create a CNCJob from a solder_paste dispenser geometry. This one is different than the default Geometry and can be handled only by SolderPaste Tool. - added protection against trying to create a CNCJob from a solder_paste dispenser geometry. This one is different than the default Geometry and can be handled only by SolderPaste Tool.
- ToolSoderPaste tools (nozzles) now have each it's own settings - ToolSolderPaste tools (nozzles) now have each it's own settings
- creating the camlib functions for the ToolSolderPaste gcode generation functions
20.02.2019 20.02.2019

135
camlib.py
View File

@ -5026,7 +5026,7 @@ class CNCjob(Geometry):
:param depthpercut: Maximum depth in each pass. :param depthpercut: Maximum depth in each pass.
:param extracut: Adds (or not) an extra cut at the end of each path :param extracut: Adds (or not) an extra cut at the end of each path
overlapping the first point in path to ensure complete copper removal overlapping the first point in path to ensure complete copper removal
:return: None :return: GCode - string
""" """
log.debug("Generate_from_multitool_geometry()") log.debug("Generate_from_multitool_geometry()")
@ -5191,6 +5191,99 @@ class CNCjob(Geometry):
return self.gcode return self.gcode
def generate_gcode_from_solderpaste_geo(self, **kwargs):
"""
Algorithm to generate from multitool Geometry.
Algorithm description:
----------------------
Uses RTree to find the nearest path to follow.
:return: Gcode string
"""
log.debug("Generate_from_solderpaste_geometry()")
## Index first and last points in paths
# What points to index.
def get_pts(o):
return [o.coords[0], o.coords[-1]]
self.gcode = ""
if not kwargs:
log.debug("camlib.generate_from_solderpaste_geo() --> No tool in the solderpaste geometry.")
self.app.inform.emit("[ERROR_NOTCL] There is no tool data in the SolderPaste geometry.")
# this is the tool diameter, it is used as such to accommodate the postprocessor who need the tool diameter
# given under the name 'toolC'
self.postdata['toolC'] = kwargs['tooldia']
# Initial G-Code
pp_solderpaste_name = kwargs['data']['tools_solderpaste_pp'] if kwargs['data']['tools_solderpaste_pp'] else \
self.app.defaults['tools_solderpaste_pp']
p = self.app.postprocessors[pp_solderpaste_name]
self.gcode = self.doformat(p.start_code)
## Flatten the geometry. Only linear elements (no polygons) remain.
flat_geometry = self.flatten(kwargs['solid_geometry'], pathonly=True)
log.debug("%d paths" % len(flat_geometry))
# Create the indexed storage.
storage = FlatCAMRTreeStorage()
storage.get_points = get_pts
# Store the geometry
log.debug("Indexing geometry before generating G-Code...")
for shape in flat_geometry:
if shape is not None:
storage.insert(shape)
# kwargs length will tell actually the number of tools used so if we have more than one tools then
# we have toolchange event
if len(kwargs) > 1:
self.gcode += self.doformat(p.toolchange_code)
else:
self.gcode += self.doformat(p.lift_code, x=0, y=0) # Move (up) to travel height
## Iterate over geometry paths getting the nearest each time.
log.debug("Starting SolderPaste G-Code...")
path_count = 0
current_pt = (0, 0)
pt, geo = storage.nearest(current_pt)
try:
while True:
path_count += 1
# Remove before modifying, otherwise deletion will fail.
storage.remove(geo)
# If last point in geometry is the nearest but prefer the first one if last point == first point
# then reverse coordinates.
if pt != geo.coords[0] and pt == geo.coords[-1]:
geo.coords = list(geo.coords)[::-1]
self.gcode += self.create_soldepaste_gcode(geo, p=p)
current_pt = geo.coords[-1]
pt, geo = storage.nearest(current_pt) # Next
except StopIteration: # Nothing found in storage.
pass
log.debug("Finishing SolderPste G-Code... %s paths traced." % path_count)
# Finish
self.gcode += self.doformat(p.lift_code)
self.gcode += self.doformat(p.end_code)
return self.gcode
def generate_from_geometry_2(self, geometry, append=True, def generate_from_geometry_2(self, geometry, append=True,
tooldia=None, offset=0.0, tolerance=0, tooldia=None, offset=0.0, tolerance=0,
z_cut=1.0, z_move=2.0, z_cut=1.0, z_move=2.0,
@ -5443,13 +5536,51 @@ class CNCjob(Geometry):
return self.gcode return self.gcode
def create_soldepaste_gcode(self, geometry, p):
gcode = ''
path = self.segment(geometry.coords)
if type(geometry) == LineString or type(geometry) == LinearRing:
# Move fast to 1st point
gcode += self.doformat(p.rapid_code) # Move to first point
# Move down to cutting depth
gcode += self.doformat(p.feedrate_z_code)
gcode += self.doformat(p.down_z_start_code)
gcode += self.doformat(p.spindle_on_fwd_code) # Start dispensing
gcode += self.doformat(p.feedrate_xy_code)
# Cutting...
for pt in path[1:]:
gcode += self.doformat(p.linear_code) # Linear motion to point
# Up to travelling height.
gcode += self.doformat(p.spindle_off_code) # Stop dispensing
gcode += self.doformat(p.spindle_on_rev_code)
gcode += self.doformat(p.down_z_stop_code)
gcode += self.doformat(p.spindle_off_code)
gcode += self.doformat(p.lift_code)
elif type(geometry) == Point:
gcode += self.doformat(p.linear_code) # Move to first point
gcode += self.doformat(p.feedrate_z_code)
gcode += self.doformat(p.down_z_start_code)
gcode += self.doformat(p.spindle_on_fwd_code) # Start dispensing
# TODO A dwell time for dispensing?
gcode += self.doformat(p.spindle_off_code) # Stop dispensing
gcode += self.doformat(p.spindle_on_rev_code)
gcode += self.doformat(p.down_z_stop_code)
gcode += self.doformat(p.spindle_off_code)
gcode += self.doformat(p.lift_code)
return gcode
def create_gcode_single_pass(self, geometry, extracut, tolerance): def create_gcode_single_pass(self, geometry, extracut, tolerance):
# G-code. Note: self.linear2gcode() and self.point2gcode() will lower and raise the tool every time. # G-code. Note: self.linear2gcode() and self.point2gcode() will lower and raise the tool every time.
gcode_single_pass = '' gcode_single_pass = ''
if type(geometry) == LineString or type(geometry) == LinearRing: if type(geometry) == LineString or type(geometry) == LinearRing:
if extracut is False: if extracut is False:
gcode_single_pass = self.linear2gcode(geometry, tolerance=tolerance, ) gcode_single_pass = self.linear2gcode(geometry, tolerance=tolerance)
else: else:
if geometry.is_ring: if geometry.is_ring:
gcode_single_pass = self.linear2gcode_extra(geometry, tolerance=tolerance) gcode_single_pass = self.linear2gcode_extra(geometry, tolerance=tolerance)

View File

@ -109,20 +109,10 @@ class ToolSolderPaste(FlatCAMTool):
"Generate solder paste dispensing geometry." "Generate solder paste dispensing geometry."
) )
step1_lbl = QtWidgets.QLabel("<b>STEP 1:</b>")
step1_lbl.setToolTip(
"First step is to select a number of nozzle tools for usage\n"
"and then create a solder paste dispensing geometry out of an\n"
"Solder Paste Mask Gerber file."
)
grid0.addWidget(self.addtool_btn, 0, 0) grid0.addWidget(self.addtool_btn, 0, 0)
# grid2.addWidget(self.copytool_btn, 0, 1) # grid2.addWidget(self.copytool_btn, 0, 1)
grid0.addWidget(self.deltool_btn, 0, 2) grid0.addWidget(self.deltool_btn, 0, 2)
grid0.addWidget(step1_lbl, 2, 0)
grid0.addWidget(self.soldergeo_btn, 2, 2)
## Form Layout ## Form Layout
geo_form_layout = QtWidgets.QFormLayout() geo_form_layout = QtWidgets.QFormLayout()
self.layout.addLayout(geo_form_layout) self.layout.addLayout(geo_form_layout)
@ -259,16 +249,6 @@ class ToolSolderPaste(FlatCAMTool):
"on PCB pads." "on PCB pads."
) )
step2_lbl = QtWidgets.QLabel("<b>STEP 2:</b>")
step2_lbl.setToolTip(
"Second step is to select a solder paste dispensing geometry,\n"
"set the CAM parameters and then generate a CNCJob object which\n"
"will pe painted on canvas in blue color."
)
grid1.addWidget(step2_lbl, 0, 0)
grid1.addWidget(self.solder_gcode_btn, 0, 2)
## Form Layout ## Form Layout
cnc_form_layout = QtWidgets.QFormLayout() cnc_form_layout = QtWidgets.QFormLayout()
self.gcode_box.addLayout(cnc_form_layout) self.gcode_box.addLayout(cnc_form_layout)
@ -300,6 +280,25 @@ class ToolSolderPaste(FlatCAMTool):
grid2 = QtWidgets.QGridLayout() grid2 = QtWidgets.QGridLayout()
self.save_gcode_box.addLayout(grid2) self.save_gcode_box.addLayout(grid2)
step1_lbl = QtWidgets.QLabel("<b>STEP 1:</b>")
step1_lbl.setToolTip(
"First step is to select a number of nozzle tools for usage\n"
"and then create a solder paste dispensing geometry out of an\n"
"Solder Paste Mask Gerber file."
)
grid2.addWidget(step1_lbl, 0, 0)
grid2.addWidget(self.soldergeo_btn, 0, 2)
step2_lbl = QtWidgets.QLabel("<b>STEP 2:</b>")
step2_lbl.setToolTip(
"Second step is to select a solder paste dispensing geometry,\n"
"set the CAM parameters and then generate a CNCJob object which\n"
"will pe painted on canvas in blue color."
)
grid2.addWidget(step2_lbl, 1, 0)
grid2.addWidget(self.solder_gcode_btn, 1, 2)
self.solder_gcode_view_btn = QtWidgets.QPushButton("View GCode") self.solder_gcode_view_btn = QtWidgets.QPushButton("View GCode")
self.solder_gcode_view_btn.setToolTip( self.solder_gcode_view_btn.setToolTip(
"View the generated GCode for Solder Paste dispensing\n" "View the generated GCode for Solder Paste dispensing\n"
@ -318,9 +317,9 @@ class ToolSolderPaste(FlatCAMTool):
"a solder paste dispensing geometry, and then view/save it's GCode." "a solder paste dispensing geometry, and then view/save it's GCode."
) )
grid2.addWidget(step3_lbl, 0, 0) grid2.addWidget(step3_lbl, 2, 0)
grid2.addWidget(self.solder_gcode_view_btn, 0, 2) grid2.addWidget(self.solder_gcode_view_btn, 2, 2)
grid2.addWidget(self.solder_gcode_save_btn, 1, 2) grid2.addWidget(self.solder_gcode_save_btn, 3, 2)
self.layout.addStretch() self.layout.addStretch()
@ -853,6 +852,15 @@ class ToolSolderPaste(FlatCAMTool):
tooluid = int(uid) tooluid = int(uid)
break break
geo_obj.tools[tooluid] = {}
geo_obj.tools[tooluid]['tooldia'] = tool
geo_obj.tools[tooluid]['data'] = self.tools[tooluid]['data']
geo_obj.tools[tooluid]['solid_geometry'] = []
geo_obj.tools[tooluid]['offset'] = 'Path'
geo_obj.tools[tooluid]['offset_value'] = 0.0
geo_obj.tools[tooluid]['type'] = 'SolderPaste'
geo_obj.tools[tooluid]['tool_type'] = 'Dispenser Nozzle'
for g in work_geo: for g in work_geo:
if type(g) == MultiPolygon: if type(g) == MultiPolygon:
for poly in g: for poly in g:
@ -860,16 +868,8 @@ class ToolSolderPaste(FlatCAMTool):
if geom != 'fail': if geom != 'fail':
try: try:
geo_obj.tools[tooluid]['solid_geometry'].append(geom) geo_obj.tools[tooluid]['solid_geometry'].append(geom)
except KeyError: except Exception as e:
geo_obj.tools[tooluid] = {} log.debug('ToolSoderPaste.on_create_geo() --> %s' % str(e))
geo_obj.tools[tooluid]['solid_geometry'] = []
geo_obj.tools[tooluid]['solid_geometry'].append(geom)
geo_obj.tools[tooluid]['tooldia'] = tool
geo_obj.tools[tooluid]['offset'] = 'Path'
geo_obj.tools[tooluid]['offset_value'] = 0.0
geo_obj.tools[tooluid]['type'] = ' '
geo_obj.tools[tooluid]['tool_type'] = ' '
geo_obj.tools[tooluid]['data'] = {}
else: else:
rest_geo.append(poly) rest_geo.append(poly)
elif type(g) == Polygon: elif type(g) == Polygon:
@ -877,16 +877,8 @@ class ToolSolderPaste(FlatCAMTool):
if geom != 'fail': if geom != 'fail':
try: try:
geo_obj.tools[tooluid]['solid_geometry'].append(geom) geo_obj.tools[tooluid]['solid_geometry'].append(geom)
except KeyError: except Exception as e:
geo_obj.tools[tooluid] = {} log.debug('ToolSoderPaste.on_create_geo() --> %s' % str(e))
geo_obj.tools[tooluid]['solid_geometry'] = []
geo_obj.tools[tooluid]['solid_geometry'].append(geom)
geo_obj.tools[tooluid]['tooldia'] = tool
geo_obj.tools[tooluid]['offset'] = 'Path'
geo_obj.tools[tooluid]['offset_value'] = 0.0
geo_obj.tools[tooluid]['type'] = ' '
geo_obj.tools[tooluid]['tool_type'] = ' '
geo_obj.tools[tooluid]['data'] = {}
else: else:
rest_geo.append(g) rest_geo.append(g)
@ -1004,23 +996,11 @@ class ToolSolderPaste(FlatCAMTool):
def on_create_gcode(self, use_thread=True): def on_create_gcode(self, use_thread=True):
""" """
Creates a multi-tool CNCJob out of this Geometry object. Creates a multi-tool CNCJob out of this Geometry object.
The actual work is done by the target FlatCAMCNCjob object's :return: None
`generate_from_geometry_2()` method. """
:param z_cut: Cut depth (negative) name = self.geo_obj_combo.currentText()
:param z_move: Hight of the tool when travelling (not cutting)
:param feedrate: Feed rate while cutting on X - Y plane
:param feedrate_z: Feed rate while cutting on Z plane
:param feedrate_rapid: Feed rate while moving with rapids
:param tooldia: Tool diameter
:param outname: Name of the new object
:param spindlespeed: Spindle speed (RPM)
:param ppname_g Name of the postprocessor
:return: None
"""
name = self.obj_combo.currentText()
obj = self.app.collection.get_by_name(name) obj = self.app.collection.get_by_name(name)
if obj.special_group != 'solder_paste_tool': if obj.special_group != 'solder_paste_tool':
@ -1031,7 +1011,8 @@ class ToolSolderPaste(FlatCAMTool):
multitool_gcode = '' multitool_gcode = ''
# use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia # use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia
outname = "%s_%s" % (name, 'cnc_solderpaste') originar_name = obj.options['name'].rpartition('_')[0]
outname = "%s_%s" % (originar_name, '_cnc_solderpaste')
try: try:
xmin = obj.options['xmin'] xmin = obj.options['xmin']
@ -1053,9 +1034,7 @@ class ToolSolderPaste(FlatCAMTool):
assert isinstance(job_obj, FlatCAMCNCjob), \ assert isinstance(job_obj, FlatCAMCNCjob), \
"Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj) "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj)
# count the tools tool_cnc_dict = {}
tool_cnt = 0
dia_cnc_dict = {}
# this turn on the FlatCAMCNCJob plot for multiple tools # this turn on the FlatCAMCNCJob plot for multiple tools
job_obj.multitool = True job_obj.multitool = True
@ -1067,146 +1046,52 @@ class ToolSolderPaste(FlatCAMTool):
job_obj.options['xmax'] = xmax job_obj.options['xmax'] = xmax
job_obj.options['ymax'] = ymax job_obj.options['ymax'] = ymax
# try:
# job_obj.feedrate_probe = float(self.options["feedrate_probe"])
# except ValueError:
# # try to convert comma to decimal point. if it's still not working error message and return
# try:
# job_obj.feedrate_rapid = float(self.options["feedrate_probe"].replace(',', '.'))
# except ValueError:
# self.app.inform.emit(
# '[ERROR_NOTCL]Wrong value format for self.defaults["feedrate_probe"] '
# 'or self.options["feedrate_probe"]')
# make sure that trying to make a CNCJob from an empty file is not creating an app crash
a = 0 a = 0
for tooluid_key in self.tools: for tooluid_key in obj.tools:
if self.tools[tooluid_key]['solid_geometry'] is None: if obj.tools[tooluid_key]['solid_geometry'] is None:
a += 1 a += 1
if a == len(self.tools): if a == len(obj.tools):
self.app.inform.emit('[ERROR_NOTCL]Cancelled. Empty file, it has no geometry...') self.app.inform.emit('[ERROR_NOTCL]Cancelled. Empty file, it has no geometry...')
return 'fail' return 'fail'
for tooluid_key in self.tools: for tooluid_key, tooluid_value in obj.tools.items():
tool_cnt += 1
app_obj.progress.emit(20) app_obj.progress.emit(20)
# find the tool_dia associated with the tooluid_key # find the tool_dia associated with the tooluid_key
tool_dia = self.sel_tools[tooluid_key]['tooldia'] tool_dia = tooluid_value['tooldia']
tool_solid_geometry = self.tools[tooluid_key]['solid_geometry'] tool_cnc_dict = deepcopy(tooluid_value)
for diadict_key, diadict_value in self.sel_tools[tooluid_key].items():
if diadict_key == 'tooldia':
tooldia_val = float('%.4f' % float(diadict_value))
dia_cnc_dict.update({
diadict_key: tooldia_val
})
if diadict_key == 'offset':
dia_cnc_dict.update({
diadict_key: ''
})
if diadict_key == 'type':
dia_cnc_dict.update({
diadict_key: ''
})
if diadict_key == 'tool_type':
dia_cnc_dict.update({
diadict_key: ''
})
if diadict_key == 'data':
for data_key, data_value in diadict_value.items():
if data_key == "multidepth":
multidepth = data_value
if data_key == "depthperpass":
depthpercut = data_value
if data_key == "extracut":
extracut = data_value
if data_key == "startz":
startz = data_value
if data_key == "endz":
endz = data_value
if data_key == "toolchangez":
toolchangez = data_value
if data_key == "toolchangexy":
toolchangexy = data_value
if data_key == "toolchange":
toolchange = data_value
if data_key == "cutz":
z_cut = data_value
if data_key == "travelz":
z_move = data_value
if data_key == "feedrate":
feedrate = data_value
if data_key == "feedrate_z":
feedrate_z = data_value
if data_key == "feedrate_rapid":
feedrate_rapid = data_value
if data_key == "ppname_g":
pp_geometry_name = data_value
if data_key == "spindlespeed":
spindlespeed = data_value
if data_key == "dwell":
dwell = data_value
if data_key == "dwelltime":
dwelltime = data_value
datadict = copy.deepcopy(diadict_value)
dia_cnc_dict.update({
diadict_key: datadict
})
job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"] job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"] job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
# Propagate options # Propagate options
job_obj.options["tooldia"] = tooldia_val job_obj.options["tooldia"] = tool_dia
job_obj.options['type'] = 'Geometry' job_obj.options['tool_dia'] = tool_dia
job_obj.options['tool_dia'] = tooldia_val
app_obj.progress.emit(40) ### CREATE GCODE ###
res = job_obj.generate_gcode_from_solderpaste_geo(**tool_cnc_dict)
res = job_obj.generate_from_multitool_geometry(
tool_solid_geometry, tooldia=tooldia_val, offset=0.0,
tolerance=0.0005, z_cut=z_cut, z_move=z_move,
feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime,
multidepth=multidepth, depthpercut=depthpercut,
extracut=extracut, startz=startz, endz=endz,
toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
pp_geometry_name=pp_geometry_name,
tool_no=tool_cnt)
if res == 'fail': if res == 'fail':
log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed") log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed")
return 'fail' return 'fail'
else: else:
dia_cnc_dict['gcode'] = res tool_cnc_dict['gcode'] = res
dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse() ### PARSE GCODE ###
tool_cnc_dict['gcode_parsed'] = job_obj.gcode_parse()
# TODO this serve for bounding box creation only; should be optimized # TODO this serve for bounding box creation only; should be optimized
dia_cnc_dict['solid_geometry'] = cascaded_union([geo['geom'] for geo in dia_cnc_dict['gcode_parsed']]) tool_cnc_dict['solid_geometry'] = cascaded_union([geo['geom'] for geo in tool_cnc_dict['gcode_parsed']])
# tell gcode_parse from which point to start drawing the lines depending on what kind of # tell gcode_parse from which point to start drawing the lines depending on what kind of
# object is the source of gcode # object is the source of gcode
job_obj.toolchange_xy_type = "geometry" job_obj.toolchange_xy_type = "geometry"
app_obj.progress.emit(80) app_obj.progress.emit(80)
job_obj.cnc_tools.update({ job_obj.cnc_tools.update({
tooluid_key: copy.deepcopy(dia_cnc_dict) tooluid_key: copy.deepcopy(tool_cnc_dict)
}) })
dia_cnc_dict.clear() tool_cnc_dict.clear()
if use_thread: if use_thread:
# To be run in separate thread # To be run in separate thread