Files
flatcam-wsl/tclCommands/TclCommandCutout.py

191 lines
7.0 KiB
Python

# from matplotlib.colors import LinearSegmentedColormap
from tclCommands.TclCommand import TclCommand
import collections
import logging
from copy import deepcopy
import gettext
from shapely.geometry import LineString, box
from shapely.ops import linemerge
from camlib import flatten_shapely_geometry
log = logging.getLogger('base')
class TclCommandCutout(TclCommand):
"""
Tcl shell command to create a board cutout geometry.
example:
cutout cut_object -dia 1.2 -margin 0.1 -gapsize 1 -gaps "tb" -outname cutout_geo -type rect
"""
# List of all command aliases, to be able use old
# names for backward compatibility (add_poly, add_polygon)
aliases = ['cutout', 'geocutout']
description = '%s %s' % ("--", "Creates board cutout from an object (Gerber or Geometry).")
# Dictionary of types from Tcl command, needs to be ordered
arg_names = collections.OrderedDict([
('name', str),
])
# Dictionary of types from Tcl command, needs to be ordered,
# this is for options like -optionname value
option_types = collections.OrderedDict([
('type', str),
('dia', float),
('margin', float),
('gapsize', float),
('gaps', str),
('outname', str)
])
# array of mandatory options for current Tcl command: required = {'name','outname'}
required = ['name']
# structured help for current command, args needs to be ordered
help = {
'main': 'Creates board cutout from an object (Gerber or Geometry).',
'args': collections.OrderedDict([
('name', 'Name of the object.'),
('type', "Type of cutout. Can be: 'rect' or 'any'. default: any"),
('dia', 'Tool diameter.'),
('margin', 'Margin over bounds.'),
('gapsize', 'Size of gap.'),
('gaps', "Type of gaps. Can be (case-insensitive): 'None' = no-tabs, 'TB' = top-bottom, 'LR' = left-right, "
"'2TB' = 2-top-bottom, '2LR' = 2-left-right, '4' = one each side, and '8' = two each side."),
('outname', 'Name of the object to create.')
]),
'examples': ['cutout cut_object -dia 1.2 -margin 0.1 -gapsize 1 -gaps "tb" -outname cutout_geo -type rect']
}
def execute(self, args, unnamed_args):
"""
:param args:
:param unnamed_args:
:return:
"""
if 'name' in args:
name = args['name']
else:
self.app.log.warning(
"[WARNING] The name of the object for which cutout is done is missing. Add it and retry.")
return "fail"
if 'margin' in args:
margin_par = float(args['margin'])
else:
margin_par = float(self.app.options["tools_cutout_margin"])
if 'dia' in args:
dia_par = float(args['dia'])
else:
dia_par = float(self.app.options["tools_cutout_tooldia"])
if 'gaps' in args:
if str(args['gaps']).lower() not in ["none", "tb", "lr", "2tb", "2lr", "4", "8"]:
self.raise_tcl_error(
"Incorrect -gaps values. Can be only a string from: 'none', 'tb', 'lr', '2tb', '2lr', '4', and '8'.")
return "fail"
gaps_par = str(args['gaps'])
else:
gaps_par = str(self.app.options["tools_cutout_gaps_ff"])
if 'gapsize' in args:
gapsize_par = float(args['gapsize'])
else:
gapsize_par = float(self.app.options["tools_cutout_gapsize"])
if 'outname' in args:
outname = args['outname']
else:
outname = name + "_cutout"
if 'type' in args:
if args['type'] not in ['rect', 'any']:
self.raise_tcl_error(_("Incorrect -type value. Can only an be: 'rect', 'any'. default: any"))
return 'fail'
type_par = args['type']
else:
self.app.log.info(_("No type value specified. Using default: any."))
type_par = 'any'
try:
cutout_obj = self.app.collection.get_by_name(str(name))
except Exception as e:
self.app.log.error("TclCommandCutout.execute(). Missing object: --> %s" % str(e))
self.app.log.debug("Could not retrieve object: %s" % name)
return "fail"
def geo_init_me(geo_obj, app_obj):
geo_obj.multigeo = True
solid_geo = []
gapsize = gapsize_par + dia_par
if type_par == 'rect':
xmin, ymin, xmax, ymax = cutout_obj.bounds()
cutout_geom = flatten_shapely_geometry(box(xmin, ymin, xmax, ymax))
else:
cutout_geom = flatten_shapely_geometry(cutout_obj.solid_geometry)
for geom_struct in cutout_geom:
if cutout_obj.kind == 'gerber':
if margin_par >= 0:
geom_struct = (geom_struct.buffer(margin_par + abs(dia_par / 2))).exterior
else:
geom_struct_buff = geom_struct.buffer(-margin_par + abs(dia_par / 2))
geom_struct = geom_struct_buff.interiors
if type_par == 'rect':
solid_geo = self.app.cutout_tool.rect_cutout_handler(geom_struct, dia_par, gaps_par, gapsize, margin_par, xmin, ymin, xmax, ymax)
else:
solid_geo, r_geo = self.app.cutout_tool.any_cutout_handler(geom_struct, dia_par, gaps_par, gapsize, margin_par)
if not solid_geo:
self.app.log.debug("TclCommandCutout.geo_init_me() -> Empty solid geometry.")
self.app.log.error('[ERROR] %s' % _("Failed."))
return "fail"
try:
solid_geo = linemerge(solid_geo)
except Exception:
# there are not lines but polygon
pass
geo_obj.solid_geometry = solid_geo
if not geo_obj.solid_geometry:
app_obj.log("TclCommandCutout.execute(). No geometry after cutout.")
return "fail"
default_tool_data = self.app.options.copy()
geo_obj.tools = {
1: {
'tooldia': dia_par,
'data': default_tool_data,
'solid_geometry': deepcopy(geo_obj.solid_geometry)
}
}
geo_obj.tools[1]['data']['tools_cutout_tooldia'] = dia_par
geo_obj.tools[1]['data']['tools_cutout_gaps_ff'] = gaps_par
geo_obj.tools[1]['data']['tools_cutout_margin'] = margin_par
geo_obj.tools[1]['data']['tools_cutout_gapsize'] = gapsize_par
try:
ret = self.app.app_obj.new_object("geometry", outname, geo_init_me, plot=False)
if ret == 'fail':
self.app.log.error("Could not create a cutout Geometry object." )
return "fail"
self.app.log.info("[success] Cutout operation finished.")
except Exception as e:
self.app.log.error("Cutout operation failed: %s" % str(e))
return "fail"