- fixed issue with preamble not being inserted when used alone

- modified the way that the start GCode is stored such that now the bug in GCode Editor that did not allowed selection of the first tool is now solved
This commit is contained in:
Marius Stanciu 2020-10-30 02:01:33 +02:00 committed by Marius
parent 38abfa4f31
commit c7b96b7a68
5 changed files with 500 additions and 446 deletions

View File

@ -23,6 +23,8 @@ CHANGELOG for FlatCAM beta
- fixed the usage for Tools Database in Unix-like OS's
- done some modest refactoring
- fixed the Search and Add feature in Geometry Object UI
- fixed issue with preamble not being inserted when used alone
- modified the way that the start GCode is stored such that now the bug in GCode Editor that did not allowed selection of the first tool is now solved
28.10.2020

View File

@ -2164,7 +2164,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
if preamble == '':
preamble = self.app.defaults["cncjob_prepend"]
if postamble == '':
preamble = self.app.defaults["cncjob_append"]
postamble = self.app.defaults["cncjob_append"]
try:
if self.special_group:
@ -2190,7 +2190,6 @@ class CNCJobObject(FlatCAMObj, CNCjob):
gcode = ''
if include_header is False:
g = preamble
# detect if using multi-tool and make the Gcode summation correctly for each case
if self.multitool is True:
for tooluid_key in self.cnc_tools:
@ -2201,7 +2200,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
else:
gcode += self.gcode
g = g + gcode + postamble
g = preamble + '\n' + gcode + '\n' + postamble
else:
# search for the GCode beginning which is usually a G20 or G21
# fix so the preamble gets inserted in between the comments header and the actual start of GCODE
@ -2251,39 +2250,51 @@ class CNCJobObject(FlatCAMObj, CNCjob):
break
if hpgl:
processed_gcode = ''
processed_body_gcode = ''
pa_re = re.compile(r"^PA\s*(-?\d+\.\d*),?\s*(-?\d+\.\d*)*;?$")
# process body gcode
for gline in gcode.splitlines():
match = pa_re.search(gline)
if match:
x_int = int(float(match.group(1)))
y_int = int(float(match.group(2)))
new_line = 'PA%d,%d;\n' % (x_int, y_int)
processed_gcode += new_line
processed_body_gcode += new_line
else:
processed_gcode += gline + '\n'
processed_body_gcode += gline + '\n'
gcode = processed_gcode
g = self.gc_header + '\n' + preamble + '\n' + gcode + postamble + end_gcode
gcode = processed_body_gcode
g = self.gc_header + '\n' + self.gc_start + '\n' + preamble + '\n' + \
gcode + '\n' + postamble + end_gcode
else:
try:
g_idx = gcode.index('G94')
if preamble != '' and postamble != '':
g = self.gc_header + gcode[:g_idx + 3] + '\n' + preamble + '\n' + \
gcode[(g_idx + 3):] + postamble + end_gcode
elif preamble == '':
g = self.gc_header + gcode[:g_idx + 3] + '\n' + \
gcode[(g_idx + 3):] + postamble + end_gcode
elif postamble == '':
g = self.gc_header + gcode[:g_idx + 3] + '\n' + preamble + '\n' + \
gcode[(g_idx + 3):] + end_gcode
else:
g = self.gc_header + gcode[:g_idx + 3] + gcode[(g_idx + 3):] + end_gcode
except ValueError:
self.app.inform.emit('[ERROR_NOTCL] %s' %
_("G-code does not have a G94 code.\n"
"Append Code snippet will not be used.."))
g = self.gc_header + '\n' + gcode + postamble + end_gcode
# try:
# g_idx = gcode.index('G94')
# if preamble != '' and postamble != '':
# g = self.gc_header + gcode[:g_idx + 3] + '\n' + preamble + '\n' + \
# gcode[(g_idx + 3):] + postamble + end_gcode
# elif preamble == '':
# g = self.gc_header + gcode[:g_idx + 3] + '\n' + \
# gcode[(g_idx + 3):] + postamble + end_gcode
# elif postamble == '':
# g = self.gc_header + gcode[:g_idx + 3] + '\n' + preamble + '\n' + \
# gcode[(g_idx + 3):] + end_gcode
# else:
# g = self.gc_header + gcode[:g_idx + 3] + gcode[(g_idx + 3):] + end_gcode
# except ValueError:
# self.app.inform.emit('[ERROR_NOTCL] %s' %
# _("G-code does not have a G94 code.\n"
# "Append Code snippet will not be used.."))
# g = self.gc_header + '\n' + gcode + postamble + end_gcode
if preamble != '' and postamble != '':
g = self.gc_header + self.gc_start + '\n' + preamble + '\n' + gcode + '\n' + \
postamble + '\n' + end_gcode
if preamble == '':
g = self.gc_header + self.gc_start + '\n' + gcode + '\n' + postamble + '\n' + end_gcode
if postamble == '':
g = self.gc_header + self.gc_start + '\n' + preamble + '\n' + gcode + '\n' + end_gcode
if preamble == '' and postamble == '':
g = self.gc_header + self.gc_start + '\n' + gcode + '\n' + end_gcode
# if toolchange custom is used, replace M6 code with the code from the Toolchange Custom Text box
# if self.ui.toolchange_cb.get_value() is True:

View File

@ -2172,10 +2172,13 @@ class GeometryObject(FlatCAMObj, Geometry):
job_obj.options['type'] = 'Geometry'
job_obj.options['tool_dia'] = tooldia_val
tool_lst = list(tools_dict.keys())
is_first = True if tooluid_key == tool_lst[0] else False
# it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially
# to a value of 0.0005 which is 20 times less than 0.01
tol = float(self.app.defaults['global_tolerance']) / 20
res = job_obj.generate_from_geometry_2(
res, start_gcode = job_obj.generate_from_geometry_2(
self, tooldia=tooldia_val, offset=tool_offset, tolerance=tol,
z_cut=z_cut, z_move=z_move,
feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
@ -2184,13 +2187,15 @@ class GeometryObject(FlatCAMObj, Geometry):
extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy,
toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
pp_geometry_name=pp_geometry_name,
tool_no=tool_cnt)
tool_no=tool_cnt, is_first=is_first)
if res == 'fail':
log.debug("GeometryObject.mtool_gen_cncjob() --> generate_from_geometry2() failed")
return 'fail'
else:
dia_cnc_dict['gcode'] = res
dia_cnc_dict['gcode'] = res
if start_gcode != '':
job_obj.gc_start = start_gcode
total_gcode += res
@ -2216,7 +2221,7 @@ class GeometryObject(FlatCAMObj, Geometry):
})
dia_cnc_dict.clear()
job_obj.source_file = total_gcode
job_obj.source_file = job_obj.gc_start + total_gcode
# Object initialization function for app.app_obj.new_object()
# RUNNING ON SEPARATE THREAD!
@ -2513,18 +2518,17 @@ class GeometryObject(FlatCAMObj, Geometry):
# it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially
# to a value of 0.0005 which is 20 times less than 0.01
tol = float(self.app.defaults['global_tolerance']) / 20
res = job_obj.generate_from_geometry_2(self, tooldia=tooldia, offset=offset, tolerance=tol,
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=depthperpass,
toolchange=toolchange, toolchangez=toolchangez,
toolchangexy=toolchangexy,
extracut=extracut, extracut_length=extracut_length,
startz=startz, endz=endz, endxy=endxy,
pp_geometry_name=ppname_g)
res, start_gcode = job_obj.generate_from_geometry_2(
self, tooldia=tooldia, offset=offset, tolerance=tol, 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=depthperpass, toolchange=toolchange,
toolchangez=toolchangez, toolchangexy=toolchangexy, extracut=extracut, extracut_length=extracut_length,
startz=startz, endz=endz, endxy=endxy, pp_geometry_name=ppname_g, is_first=True)
job_obj.source_file = res
if start_gcode != '':
job_obj.gc_start = start_gcode
job_obj.source_file = start_gcode + res
# tell gcode_parse from which point to start drawing the lines depending on what kind of object is the
# source of gcode
job_obj.toolchange_xy_type = "geometry"

844
camlib.py
View File

@ -3109,6 +3109,7 @@ class CNCjob(Geometry):
self.app.inform.emit('[WARNING] %s.' % _("The Cut Z parameter is zero. There will be no cut, aborting"))
return 'fail'
# used in Tool Drilling
def excellon_tool_gcode_gen(self, tool, points, tools, first_pt, is_first=False, is_last=False, opt_type='T',
toolchange=False):
"""
@ -3295,7 +3296,7 @@ class CNCjob(Geometry):
start_gcode = ''
if is_first:
start_gcode = self.doformat(p.start_code)
t_gcode += start_gcode
# t_gcode += start_gcode
# do the ToolChange event
t_gcode += self.doformat(p.z_feedrate_code)
@ -3308,11 +3309,9 @@ class CNCjob(Geometry):
if self.dwell is True:
t_gcode += self.doformat(p.dwell_code)
current_tooldia = float('%.*f' % (self.decimals, float(tools[tool]["tooldia"])))
current_tooldia = self.app.dec_format(float(tools[tool]["tooldia"]), self.decimals)
self.app.inform.emit(
'%s: %s%s.' % (_("Starting G-Code for tool with diameter"),
str(current_tooldia),
str(self.units))
'%s: %s%s.' % (_("Starting G-Code for tool with diameter"), str(current_tooldia), str(self.units))
)
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@ -3340,6 +3339,7 @@ class CNCjob(Geometry):
# graceful abort requested by the user
raise grace
# if we use Traveling Salesman Algorithm as an optimization
if opt_type == 'T':
locx = point[0]
locy = point[1]
@ -3447,21 +3447,395 @@ class CNCjob(Geometry):
self.app.inform.emit('%s %s' % (_("Finished G-Code generation for tool:"), str(tool)))
return t_gcode, (locx, locy), start_gcode
def generate_from_excellon_by_tool(self, exobj, tools="all", order='fwd', use_ui=False):
# used in Geometry (and soon in Tool Milling)
def geometry_tool_gcode_gen(self, tool, tools, first_pt, tolerance, is_first=False, is_last=False,
toolchange=False):
"""
Algorithm to generate GCode from multitool Geometry.
:param tool: tool number for which to generate GCode
:type tool: int
:param tools: a dictionary holding all the tools and data
:type tools: dict
:param first_pt: a tuple of coordinates for the first point of the current tool
:type first_pt: tuple
:param tolerance: geometry tolerance
:type tolerance:
:param is_first: if the current tool is the first tool (for this we need to add start GCode)
:type is_first: bool
:param is_last: if the current tool is the last tool (for this we need to add the end GCode)
:type is_last: bool
:param toolchange: add toolchange event
:type toolchange: bool
:return: GCode
:rtype: str
"""
log.debug("geometry_tool_gcode_gen()")
t_gcode = ''
temp_solid_geometry = []
# The Geometry from which we create GCode
geometry = tools[tool]['solid_geometry']
# ## Flatten the geometry. Only linear elements (no polygons) remain.
flat_geometry = self.flatten(geometry, reset=True, pathonly=True)
log.debug("%d paths" % len(flat_geometry))
# #########################################################################################################
# #########################################################################################################
# ############# PARAMETERS used in PREPROCESSORS so they need to be updated ###############################
# #########################################################################################################
# #########################################################################################################
self.tool = str(tool)
tool_dict = tools[tool]['data']
# this is the tool diameter, it is used as such to accommodate the preprocessor who need the tool diameter
# given under the name 'toolC'
self.postdata['toolC'] = float(tools[tool]['tooldia'])
self.tooldia = float(tools[tool]['tooldia'])
self.use_ui = True
self.tolerance = tolerance
# Optimization type. Can be: 'M', 'B', 'T', 'R', 'No'
opt_type = tool_dict['optimization_type']
opt_time = tool_dict['search_time'] if 'search_time' in tool_dict else 'R'
if opt_type == 'M':
log.debug("Using OR-Tools Metaheuristic Guided Local Search path optimization.")
elif opt_type == 'B':
log.debug("Using OR-Tools Basic path optimization.")
elif opt_type == 'T':
log.debug("Using Travelling Salesman path optimization.")
elif opt_type == 'R':
log.debug("Using RTree path optimization.")
else:
log.debug("Using no path optimization.")
# Preprocessor
self.pp_geometry_name = tool_dict['ppname_g']
self.pp_geometry = self.app.preprocessors[self.pp_geometry_name]
p = self.pp_geometry
# Offset the Geometry if it is the case
# FIXME need to test if in ["Path", "In", "Out", "Custom"]. For now only 'Custom' is somehow done
offset = tools[tool]['offset_value']
if offset != 0.0:
for it in flat_geometry:
# if the geometry is a closed shape then create a Polygon out of it
if isinstance(it, LineString):
if it.is_ring:
it = Polygon(it)
temp_solid_geometry.append(it.buffer(offset, join_style=2))
temp_solid_geometry = self.flatten(temp_solid_geometry, reset=True, pathonly=True)
else:
temp_solid_geometry = flat_geometry
if self.z_cut is None:
if 'laser' not in self.pp_geometry_name:
self.app.inform.emit(
'[ERROR_NOTCL] %s' % _("Cut_Z parameter is None or zero. Most likely a bad combinations of "
"other parameters."))
return 'fail'
else:
self.z_cut = 0
if self.machinist_setting == 0:
if self.z_cut > 0:
self.app.inform.emit('[WARNING] %s' %
_("The Cut Z parameter has positive value. "
"It is the depth value to cut into material.\n"
"The Cut Z parameter needs to have a negative value, assuming it is a typo "
"therefore the app will convert the value to negative."
"Check the resulting CNC code (Gcode etc)."))
self.z_cut = -self.z_cut
elif self.z_cut == 0 and 'laser' not in self.pp_geometry_name:
self.app.inform.emit('[WARNING] %s: %s' %
(_("The Cut Z parameter is zero. There will be no cut, skipping file"),
self.options['name']))
return 'fail'
if self.z_move is None:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Travel Z parameter is None or zero."))
return 'fail'
if self.z_move < 0:
self.app.inform.emit('[WARNING] %s' %
_("The Travel Z parameter has negative value. "
"It is the height value to travel between cuts.\n"
"The Z Travel parameter needs to have a positive value, assuming it is a typo "
"therefore the app will convert the value to positive."
"Check the resulting CNC code (Gcode etc)."))
self.z_move = -self.z_move
elif self.z_move == 0:
self.app.inform.emit('[WARNING] %s: %s' %
(_("The Z Travel parameter is zero. This is dangerous, skipping file"),
self.options['name']))
return 'fail'
# made sure that depth_per_cut is no more then the z_cut
if abs(self.z_cut) < self.z_depthpercut:
self.z_depthpercut = abs(self.z_cut)
# Depth parameters
self.z_cut = float(tool_dict['cutz'])
self.multidepth = tool_dict['multidepth']
self.z_depthpercut = float(tool_dict['depthperpass'])
self.z_move = float(tool_dict['travelz'])
self.f_plunge = self.app.defaults["geometry_f_plunge"]
self.feedrate = float(tool_dict['feedrate'])
self.z_feedrate = float(tool_dict['feedrate_z'])
self.feedrate_rapid = float(tool_dict['feedrate_rapid'])
self.spindlespeed = float(tool_dict['spindlespeed'])
try:
self.spindledir = tool_dict['spindledir']
except KeyError:
self.spindledir = self.app.defaults["geometry_spindledir"]
self.dwell = tool_dict['dwell']
self.dwelltime = float(tool_dict['dwelltime'])
self.startz = float(tool_dict['startz']) if tool_dict['startz'] else None
if self.startz == '':
self.startz = None
self.z_end = float(tool_dict['endz'])
self.xy_end = tool_dict['endxy']
try:
if self.xy_end == '' or self.xy_end is None:
self.xy_end = None
else:
# either originally it was a string or not, xy_end will be made string
self.xy_end = re.sub('[()\[\]]', '', str(self.xy_end)) if self.xy_end else None
# and now, xy_end is made into a list of floats in format [x, y]
if self.xy_end:
self.xy_end = [float(eval(a)) for a in self.xy_end.split(",")]
if self.xy_end and len(self.xy_end) != 2:
self.app.inform.emit('[ERROR]%s' % _("The End X,Y format has to be (x, y)."))
return 'fail'
except Exception as e:
log.debug("camlib.CNCJob.geometry_from_excellon_by_tool() xy_end --> %s" % str(e))
self.xy_end = [0, 0]
self.z_toolchange = tool_dict['toolchangez']
self.xy_toolchange = tool_dict["toolchangexy"]
try:
if self.xy_toolchange == '':
self.xy_toolchange = None
else:
# either originally it was a string or not, xy_toolchange will be made string
self.xy_toolchange = re.sub('[()\[\]]', '', str(self.xy_toolchange)) if self.xy_toolchange else None
# and now, xy_toolchange is made into a list of floats in format [x, y]
if self.xy_toolchange:
self.xy_toolchange = [float(eval(a)) for a in self.xy_toolchange.split(",")]
if self.xy_toolchange and len(self.xy_toolchange) != 2:
self.app.inform.emit('[ERROR] %s' % _("The Toolchange X,Y format has to be (x, y)."))
return 'fail'
except Exception as e:
log.debug("camlib.CNCJob.geometry_from_excellon_by_tool() --> %s" % str(e))
pass
self.extracut = tool_dict['extracut']
self.extracut_length = tool_dict['extracut_length']
# Probe parameters
# self.z_pdepth = tool_dict["tools_drill_z_pdepth"]
# self.feedrate_probe = tool_dict["tools_drill_feedrate_probe"]
# #########################################################################################################
# ############ Create the data. ###########################################################################
# #########################################################################################################
optimized_path = []
geo_storage = {}
for geo in temp_solid_geometry:
if not geo is None:
geo_storage[geo.coords[0]] = geo
locations = list(geo_storage.keys())
if opt_type == 'M':
# if there are no locations then go to the next tool
if not locations:
return 'fail'
optimized_locations = self.optimized_ortools_meta(locations=locations, opt_time=opt_time)
optimized_path = [(locations[loc], geo_storage[locations[loc]]) for loc in optimized_locations]
elif opt_type == 'B':
# if there are no locations then go to the next tool
if not locations:
return 'fail'
optimized_locations = self.optimized_ortools_basic(locations=locations)
optimized_path = [(locations[loc], geo_storage[locations[loc]]) for loc in optimized_locations]
elif opt_type == 'T':
# if there are no locations then go to the next tool
if not locations:
return 'fail'
optimized_locations = self.optimized_travelling_salesman(locations)
optimized_path = [(loc, geo_storage[loc]) for loc in optimized_locations]
elif opt_type == 'R':
optimized_path = self.geo_optimized_rtree(temp_solid_geometry)
if optimized_path == 'fail':
return 'fail'
else:
# it's actually not optimized path but here we build a list of (x,y) coordinates
# out of the tool's drills
for geo in temp_solid_geometry:
optimized_path.append(geo.coords[0])
# #########################################################################################################
# #########################################################################################################
# Only if there are locations to mill
if not optimized_path:
log.debug("camlib.CNCJob.geometry_tool_gcode_gen() -> Optimized path is empty.")
return 'fail'
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
# #############################################################################################################
# #############################################################################################################
# ################# MILLING !!! ##############################################################################
# #############################################################################################################
# #############################################################################################################
log.debug("Starting G-Code...")
current_tooldia = float('%.*f' % (self.decimals, float(self.tooldia)))
self.app.inform.emit('%s: %s%s.' % (_("Starting G-Code for tool with diameter"),
str(current_tooldia),
str(self.units)))
# Measurements
total_travel = 0.0
total_cut = 0.0
# Start GCode
start_gcode = ''
if is_first:
start_gcode = self.doformat(p.start_code)
# t_gcode += start_gcode
# Toolchange code
t_gcode += self.doformat(p.feedrate_code) # sets the feed rate
if toolchange:
t_gcode += self.doformat(p.toolchange_code)
if 'laser' not in self.pp_geometry_name.lower():
t_gcode += self.doformat(p.spindle_code) # Spindle start
else:
# for laser this will disable the laser
t_gcode += self.doformat(p.lift_code, x=self.oldx, y=self.oldy) # Move (up) to travel height
if self.dwell:
t_gcode += self.doformat(p.dwell_code) # Dwell time
else:
t_gcode += self.doformat(p.lift_code, x=0, y=0) # Move (up) to travel height
t_gcode += self.doformat(p.startz_code, x=0, y=0)
if 'laser' not in self.pp_geometry_name.lower():
t_gcode += self.doformat(p.spindle_code) # Spindle start
if self.dwell is True:
t_gcode += self.doformat(p.dwell_code) # Dwell time
t_gcode += self.doformat(p.feedrate_code) # sets the feed rate
# ## Iterate over geometry paths getting the nearest each time.
path_count = 0
# variables to display the percentage of work done
geo_len = len(flat_geometry)
log.warning("Number of paths for which to generate GCode: %s" % str(geo_len))
old_disp_number = 0
current_pt = (0, 0)
for pt, geo in optimized_path:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
path_count += 1
# 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 = LineString(list(geo.coords)[::-1])
# ---------- Single depth/pass --------
if not self.multidepth:
# calculate the cut distance
total_cut = total_cut + geo.length
t_gcode += self.create_gcode_single_pass(geo, current_tooldia, self.extracut,
self.extracut_length, self.tolerance,
z_move=self.z_move, old_point=current_pt)
# --------- Multi-pass ---------
else:
# calculate the cut distance
# due of the number of cuts (multi depth) it has to multiplied by the number of cuts
nr_cuts = 0
depth = abs(self.z_cut)
while depth > 0:
nr_cuts += 1
depth -= float(self.z_depthpercut)
total_cut += (geo.length * nr_cuts)
gc, geo = self.create_gcode_multi_pass(geo, current_tooldia, self.extracut,
self.extracut_length, self.tolerance,
z_move=self.z_move, postproc=p, old_point=current_pt)
t_gcode += gc
# calculate the total distance
total_travel = total_travel + abs(distance(pt1=current_pt, pt2=pt))
current_pt = geo.coords[-1]
disp_number = int(np.interp(path_count, [0, geo_len], [0, 100]))
if old_disp_number < disp_number <= 100:
self.app.proc_container.update_view_text(' %d%%' % disp_number)
old_disp_number = disp_number
log.debug("Finished G-Code... %s paths traced." % path_count)
# add move to end position
total_travel += abs(distance_euclidian(current_pt[0], current_pt[1], 0, 0))
self.travel_distance += total_travel + total_cut
self.routing_time += total_cut / self.feedrate
# Finish
if is_last:
t_gcode += self.doformat(p.spindle_stop_code)
t_gcode += self.doformat(p.lift_code, x=current_pt[0], y=current_pt[1])
t_gcode += self.doformat(p.end_code, x=0, y=0)
self.app.inform.emit(
'%s... %s %s.' % (_("Finished G-Code generation"), str(path_count), _("paths traced"))
)
self.gcode = t_gcode
return self.gcode, start_gcode
# used by the Tcl command Drillcncjob
def generate_from_excellon_by_tool(self, exobj, tools="all", order='fwd', is_first=False, use_ui=False):
"""
Creates Gcode for this object from an Excellon object
for the specified tools.
:param exobj: Excellon object to process
:type exobj: Excellon
:param tools: Comma separated tool names
:type tools: str
:param order: order of tools processing: "fwd", "rev" or "no"
:type order: str
:param use_ui: if True the method will use parameters set in UI
:type use_ui: bool
:return: None
:rtype: None
:param exobj: Excellon object to process
:type exobj: Excellon
:param tools: Comma separated tool names
:type tools: str
:param order: order of tools processing: "fwd", "rev" or "no"
:type order: str
:param is_first: if the tool is the first one should generate the start gcode (not that it matter much
which is the one doing it)
:type is_first: bool
:param use_ui: if True the method will use parameters set in UI
:type use_ui: bool
:return: None
:rtype: None
"""
# #############################################################################################################
@ -3651,7 +4025,11 @@ class CNCjob(Geometry):
# Initialization
# #############################################################################################################
# #############################################################################################################
gcode = self.doformat(p.start_code)
gcode = ''
start_gcode = ''
if is_first:
start_gcode = self.doformat(p.start_code)
if use_ui is False:
gcode += self.doformat(p.z_feedrate_code)
@ -4829,8 +5207,9 @@ class CNCjob(Geometry):
self.gcode = gcode
self.app.inform.emit(_("Finished G-Code generation..."))
return 'OK'
return gcode, start_gcode
# no longer used
def generate_from_multitool_geometry(self, geometry, append=True, tooldia=None, offset=0.0, tolerance=0, z_cut=1.0,
z_move=2.0, feedrate=2.0, feedrate_z=2.0, feedrate_rapid=30,
spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0,
@ -5163,381 +5542,12 @@ class CNCjob(Geometry):
)
return self.gcode
def geometry_tool_gcode_gen(self, tool, tools, first_pt, tolerance, is_first=False, is_last=False,
toolchange=False):
"""
Algorithm to generate GCode from multitool Geometry.
:param tool: tool number for which to generate GCode
:type tool: int
:param tools: a dictionary holding all the tools and data
:type tools: dict
:param first_pt: a tuple of coordinates for the first point of the current tool
:type first_pt: tuple
:param tolerance: geometry tolerance
:type tolerance:
:param is_first: if the current tool is the first tool (for this we need to add start GCode)
:type is_first: bool
:param is_last: if the current tool is the last tool (for this we need to add the end GCode)
:type is_last: bool
:param toolchange: add toolchange event
:type toolchange: bool
:return: GCode
:rtype: str
"""
log.debug("geometry_tool_gcode_gen()")
t_gcode = ''
temp_solid_geometry = []
# The Geometry from which we create GCode
geometry = tools[tool]['solid_geometry']
# ## Flatten the geometry. Only linear elements (no polygons) remain.
flat_geometry = self.flatten(geometry, reset=True, pathonly=True)
log.debug("%d paths" % len(flat_geometry))
# #########################################################################################################
# #########################################################################################################
# ############# PARAMETERS used in PREPROCESSORS so they need to be updated ###############################
# #########################################################################################################
# #########################################################################################################
self.tool = str(tool)
tool_dict = tools[tool]['data']
# this is the tool diameter, it is used as such to accommodate the preprocessor who need the tool diameter
# given under the name 'toolC'
self.postdata['toolC'] = float(tools[tool]['tooldia'])
self.tooldia = float(tools[tool]['tooldia'])
self.use_ui = True
self.tolerance = tolerance
# Optimization type. Can be: 'M', 'B', 'T', 'R', 'No'
opt_type = tool_dict['optimization_type']
opt_time = tool_dict['search_time'] if 'search_time' in tool_dict else 'R'
if opt_type == 'M':
log.debug("Using OR-Tools Metaheuristic Guided Local Search path optimization.")
elif opt_type == 'B':
log.debug("Using OR-Tools Basic path optimization.")
elif opt_type == 'T':
log.debug("Using Travelling Salesman path optimization.")
elif opt_type == 'R':
log.debug("Using RTree path optimization.")
else:
log.debug("Using no path optimization.")
# Preprocessor
self.pp_geometry_name = tool_dict['ppname_g']
self.pp_geometry = self.app.preprocessors[self.pp_geometry_name]
p = self.pp_geometry
# Offset the Geometry if it is the case
# FIXME need to test if in ["Path", "In", "Out", "Custom"]. For now only 'Custom' is somehow done
offset = tools[tool]['offset_value']
if offset != 0.0:
for it in flat_geometry:
# if the geometry is a closed shape then create a Polygon out of it
if isinstance(it, LineString):
if it.is_ring:
it = Polygon(it)
temp_solid_geometry.append(it.buffer(offset, join_style=2))
temp_solid_geometry = self.flatten(temp_solid_geometry, reset=True, pathonly=True)
else:
temp_solid_geometry = flat_geometry
if self.z_cut is None:
if 'laser' not in self.pp_geometry_name:
self.app.inform.emit(
'[ERROR_NOTCL] %s' % _("Cut_Z parameter is None or zero. Most likely a bad combinations of "
"other parameters."))
return 'fail'
else:
self.z_cut = 0
if self.machinist_setting == 0:
if self.z_cut > 0:
self.app.inform.emit('[WARNING] %s' %
_("The Cut Z parameter has positive value. "
"It is the depth value to cut into material.\n"
"The Cut Z parameter needs to have a negative value, assuming it is a typo "
"therefore the app will convert the value to negative."
"Check the resulting CNC code (Gcode etc)."))
self.z_cut = -self.z_cut
elif self.z_cut == 0 and 'laser' not in self.pp_geometry_name:
self.app.inform.emit('[WARNING] %s: %s' %
(_("The Cut Z parameter is zero. There will be no cut, skipping file"),
self.options['name']))
return 'fail'
if self.z_move is None:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Travel Z parameter is None or zero."))
return 'fail'
if self.z_move < 0:
self.app.inform.emit('[WARNING] %s' %
_("The Travel Z parameter has negative value. "
"It is the height value to travel between cuts.\n"
"The Z Travel parameter needs to have a positive value, assuming it is a typo "
"therefore the app will convert the value to positive."
"Check the resulting CNC code (Gcode etc)."))
self.z_move = -self.z_move
elif self.z_move == 0:
self.app.inform.emit('[WARNING] %s: %s' %
(_("The Z Travel parameter is zero. This is dangerous, skipping file"),
self.options['name']))
return 'fail'
# made sure that depth_per_cut is no more then the z_cut
if abs(self.z_cut) < self.z_depthpercut:
self.z_depthpercut = abs(self.z_cut)
# Depth parameters
self.z_cut = float(tool_dict['cutz'])
self.multidepth = tool_dict['multidepth']
self.z_depthpercut = float(tool_dict['depthperpass'])
self.z_move = float(tool_dict['travelz'])
self.f_plunge = self.app.defaults["geometry_f_plunge"]
self.feedrate = float(tool_dict['feedrate'])
self.z_feedrate = float(tool_dict['feedrate_z'])
self.feedrate_rapid = float(tool_dict['feedrate_rapid'])
self.spindlespeed = float(tool_dict['spindlespeed'])
try:
self.spindledir = tool_dict['spindledir']
except KeyError:
self.spindledir = self.app.defaults["geometry_spindledir"]
self.dwell = tool_dict['dwell']
self.dwelltime = float(tool_dict['dwelltime'])
self.startz = float(tool_dict['startz']) if tool_dict['startz'] else None
if self.startz == '':
self.startz = None
self.z_end = float(tool_dict['endz'])
self.xy_end = tool_dict['endxy']
try:
if self.xy_end == '' or self.xy_end is None:
self.xy_end = None
else:
# either originally it was a string or not, xy_end will be made string
self.xy_end = re.sub('[()\[\]]', '', str(self.xy_end)) if self.xy_end else None
# and now, xy_end is made into a list of floats in format [x, y]
if self.xy_end:
self.xy_end = [float(eval(a)) for a in self.xy_end.split(",")]
if self.xy_end and len(self.xy_end) != 2:
self.app.inform.emit('[ERROR]%s' % _("The End X,Y format has to be (x, y)."))
return 'fail'
except Exception as e:
log.debug("camlib.CNCJob.geometry_from_excellon_by_tool() xy_end --> %s" % str(e))
self.xy_end = [0, 0]
self.z_toolchange = tool_dict['toolchangez']
self.xy_toolchange = tool_dict["toolchangexy"]
try:
if self.xy_toolchange == '':
self.xy_toolchange = None
else:
# either originally it was a string or not, xy_toolchange will be made string
self.xy_toolchange = re.sub('[()\[\]]', '', str(self.xy_toolchange)) if self.xy_toolchange else None
# and now, xy_toolchange is made into a list of floats in format [x, y]
if self.xy_toolchange:
self.xy_toolchange = [float(eval(a)) for a in self.xy_toolchange.split(",")]
if self.xy_toolchange and len(self.xy_toolchange) != 2:
self.app.inform.emit('[ERROR] %s' % _("The Toolchange X,Y format has to be (x, y)."))
return 'fail'
except Exception as e:
log.debug("camlib.CNCJob.geometry_from_excellon_by_tool() --> %s" % str(e))
pass
self.extracut = tool_dict['extracut']
self.extracut_length = tool_dict['extracut_length']
# Probe parameters
# self.z_pdepth = tool_dict["tools_drill_z_pdepth"]
# self.feedrate_probe = tool_dict["tools_drill_feedrate_probe"]
# #########################################################################################################
# ############ Create the data. ###########################################################################
# #########################################################################################################
optimized_path = []
geo_storage = {}
for geo in temp_solid_geometry:
if not geo is None:
geo_storage[geo.coords[0]] = geo
locations = list(geo_storage.keys())
if opt_type == 'M':
# if there are no locations then go to the next tool
if not locations:
return 'fail'
optimized_locations = self.optimized_ortools_meta(locations=locations, opt_time=opt_time)
optimized_path = [(locations[loc], geo_storage[locations[loc]]) for loc in optimized_locations]
elif opt_type == 'B':
# if there are no locations then go to the next tool
if not locations:
return 'fail'
optimized_locations = self.optimized_ortools_basic(locations=locations)
optimized_path = [(locations[loc], geo_storage[locations[loc]]) for loc in optimized_locations]
elif opt_type == 'T':
# if there are no locations then go to the next tool
if not locations:
return 'fail'
optimized_locations = self.optimized_travelling_salesman(locations)
optimized_path = [(loc, geo_storage[loc]) for loc in optimized_locations]
elif opt_type == 'R':
optimized_path = self.geo_optimized_rtree(temp_solid_geometry)
if optimized_path == 'fail':
return 'fail'
else:
# it's actually not optimized path but here we build a list of (x,y) coordinates
# out of the tool's drills
for geo in temp_solid_geometry:
optimized_path.append(geo.coords[0])
# #########################################################################################################
# #########################################################################################################
# Only if there are locations to mill
if not optimized_path:
log.debug("camlib.CNCJob.geometry_tool_gcode_gen() -> Optimized path is empty.")
return 'fail'
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
# #############################################################################################################
# #############################################################################################################
# ################# MILLING !!! ##############################################################################
# #############################################################################################################
# #############################################################################################################
log.debug("Starting G-Code...")
current_tooldia = float('%.*f' % (self.decimals, float(self.tooldia)))
self.app.inform.emit('%s: %s%s.' % (_("Starting G-Code for tool with diameter"),
str(current_tooldia),
str(self.units)))
# Measurements
total_travel = 0.0
total_cut = 0.0
# Start GCode
start_gcode = ''
if is_first:
start_gcode = self.doformat(p.start_code)
t_gcode += start_gcode
# Toolchange code
t_gcode += self.doformat(p.feedrate_code) # sets the feed rate
if toolchange:
t_gcode += self.doformat(p.toolchange_code)
if 'laser' not in self.pp_geometry_name.lower():
t_gcode += self.doformat(p.spindle_code) # Spindle start
else:
# for laser this will disable the laser
t_gcode += self.doformat(p.lift_code, x=self.oldx, y=self.oldy) # Move (up) to travel height
if self.dwell:
t_gcode += self.doformat(p.dwell_code) # Dwell time
else:
t_gcode += self.doformat(p.lift_code, x=0, y=0) # Move (up) to travel height
t_gcode += self.doformat(p.startz_code, x=0, y=0)
if 'laser' not in self.pp_geometry_name.lower():
t_gcode += self.doformat(p.spindle_code) # Spindle start
if self.dwell is True:
t_gcode += self.doformat(p.dwell_code) # Dwell time
t_gcode += self.doformat(p.feedrate_code) # sets the feed rate
# ## Iterate over geometry paths getting the nearest each time.
path_count = 0
# variables to display the percentage of work done
geo_len = len(flat_geometry)
log.warning("Number of paths for which to generate GCode: %s" % str(geo_len))
old_disp_number = 0
current_pt = (0, 0)
for pt, geo in optimized_path:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
path_count += 1
# 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 = LineString(list(geo.coords)[::-1])
# ---------- Single depth/pass --------
if not self.multidepth:
# calculate the cut distance
total_cut = total_cut + geo.length
t_gcode += self.create_gcode_single_pass(geo, current_tooldia, self.extracut,
self.extracut_length, self.tolerance,
z_move=self.z_move, old_point=current_pt)
# --------- Multi-pass ---------
else:
# calculate the cut distance
# due of the number of cuts (multi depth) it has to multiplied by the number of cuts
nr_cuts = 0
depth = abs(self.z_cut)
while depth > 0:
nr_cuts += 1
depth -= float(self.z_depthpercut)
total_cut += (geo.length * nr_cuts)
gc, geo = self.create_gcode_multi_pass(geo, current_tooldia, self.extracut,
self.extracut_length, self.tolerance,
z_move=self.z_move, postproc=p, old_point=current_pt)
t_gcode += gc
# calculate the total distance
total_travel = total_travel + abs(distance(pt1=current_pt, pt2=pt))
current_pt = geo.coords[-1]
disp_number = int(np.interp(path_count, [0, geo_len], [0, 100]))
if old_disp_number < disp_number <= 100:
self.app.proc_container.update_view_text(' %d%%' % disp_number)
old_disp_number = disp_number
log.debug("Finished G-Code... %s paths traced." % path_count)
# add move to end position
total_travel += abs(distance_euclidian(current_pt[0], current_pt[1], 0, 0))
self.travel_distance += total_travel + total_cut
self.routing_time += total_cut / self.feedrate
# Finish
if is_last:
t_gcode += self.doformat(p.spindle_stop_code)
t_gcode += self.doformat(p.lift_code, x=current_pt[0], y=current_pt[1])
t_gcode += self.doformat(p.end_code, x=0, y=0)
self.app.inform.emit(
'%s... %s %s.' % (_("Finished G-Code generation"), str(path_count), _("paths traced"))
)
self.gcode = t_gcode
return self.gcode, start_gcode
def generate_from_geometry_2(self, geometry, append=True, tooldia=None, offset=0.0, tolerance=0, z_cut=None,
z_move=None, feedrate=None, feedrate_z=None, feedrate_rapid=None, spindlespeed=None,
spindledir='CW', dwell=False, dwelltime=None, multidepth=False, depthpercut=None,
toolchange=False, toolchangez=None, toolchangexy="0.0, 0.0", extracut=False,
extracut_length=None, startz=None, endz=None, endxy='', pp_geometry_name=None,
tool_no=1):
tool_no=1, is_first=False):
"""
Second algorithm to generate from Geometry.
@ -5572,6 +5582,7 @@ class CNCjob(Geometry):
:param endxy:
:param pp_geometry_name:
:param tool_no:
:param is_first: if the processed tool is the first one and if we should process the start gcode
:return: None
"""
log.debug("Executing camlib.CNCJob.generate_from_geometry_2()")
@ -5811,7 +5822,11 @@ class CNCjob(Geometry):
self.oldx = 0.0
self.oldy = 0.0
self.gcode = self.doformat(p.start_code)
start_gcode = ''
if is_first:
start_gcode = self.doformat(p.start_code)
# self.gcode = self.doformat(p.start_code)
self.gcode += self.doformat(p.feedrate_code) # sets the feed rate
if toolchange is False:
@ -5936,7 +5951,7 @@ class CNCjob(Geometry):
'%s... %s %s' % (_("Finished G-Code generation"), str(path_count), _(" paths traced."))
)
return self.gcode
return self.gcode, start_gcode
def generate_gcode_from_solderpaste_geo(self, **kwargs):
"""
@ -7551,24 +7566,45 @@ class CNCjob(Geometry):
miny = np.Inf
maxx = -np.Inf
maxy = -np.Inf
for k, v in self.cnc_tools.items():
minx = np.Inf
miny = np.Inf
maxx = -np.Inf
maxy = -np.Inf
try:
for k in v['solid_geometry']:
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
if self.cnc_tools:
for k, v in self.cnc_tools.items():
minx = np.Inf
miny = np.Inf
maxx = -np.Inf
maxy = -np.Inf
try:
for k in v['solid_geometry']:
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
except TypeError:
minx_, miny_, maxx_, maxy_ = bounds_rec(v['solid_geometry'])
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
if self.exc_cnc_tools:
for k, v in self.exc_cnc_tools.items():
minx = np.Inf
miny = np.Inf
maxx = -np.Inf
maxy = -np.Inf
try:
for k in v['solid_geometry']:
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
except TypeError:
minx_, miny_, maxx_, maxy_ = bounds_rec(v['solid_geometry'])
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
except TypeError:
minx_, miny_, maxx_, maxy_ = bounds_rec(v['solid_geometry'])
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
bounds_coords = minx, miny, maxx, maxy
return bounds_coords

View File

@ -337,6 +337,7 @@ class TclCommandDrillcncjob(TclCommandSignaled):
if ret_val == 'fail':
return 'fail'
job_obj.gc_start = ret_val[1]
for t_item in job_obj.exc_cnc_tools:
job_obj.exc_cnc_tools[t_item]['data']['tools_drill_offset'] = \