- remade the Tool Cutout to work on panels

- remade the Tool Cutour such that on multiple applications on the same object it will yield the same result
This commit is contained in:
Marius Stanciu
2019-05-17 17:17:58 +03:00
committed by Marius
parent e0001dc9b7
commit 3713a5d78f
2 changed files with 210 additions and 171 deletions

View File

@@ -9,6 +9,11 @@ CAD program, and create G-Code for Isolation routing.
================================================= =================================================
17.05.2019
- remade the Tool Cutout to work on panels
- remade the Tool Cutour such that on multiple applications on the same object it will yield the same result
16.05.2019 16.05.2019
- Gerber Export: made sure that if some of the coordinates in a Gerber object geometry are repeating then the resulting Gerber code include only one copy - Gerber Export: made sure that if some of the coordinates in a Gerber object geometry are repeating then the resulting Gerber code include only one copy

View File

@@ -114,7 +114,8 @@ class CutOut(FlatCAMTool):
self.convex_box = FCCheckBox() self.convex_box = FCCheckBox()
self.convex_box_label = QtWidgets.QLabel(_("Convex Sh.:")) self.convex_box_label = QtWidgets.QLabel(_("Convex Sh.:"))
self.convex_box_label.setToolTip( self.convex_box_label.setToolTip(
_("Create a convex shape surrounding the entire PCB.") _("Create a convex shape surrounding the entire PCB.\n"
"Used only if the source object type is Gerber.")
) )
form_layout.addRow(self.convex_box_label, self.convex_box) form_layout.addRow(self.convex_box_label, self.convex_box)
@@ -327,9 +328,9 @@ class CutOut(FlatCAMTool):
def on_freeform_cutout(self): def on_freeform_cutout(self):
def subtract_rectangle(obj_, x0, y0, x1, y1): # def subtract_rectangle(obj_, x0, y0, x1, y1):
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)] # pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
obj_.subtract_polygon(pts) # obj_.subtract_polygon(pts)
name = self.obj_combo.currentText() name = self.obj_combo.currentText()
@@ -400,77 +401,83 @@ class CutOut(FlatCAMTool):
convex_box = self.convex_box.get_value() convex_box = self.convex_box.get_value()
# Get min and max data for each object as we just cut rectangles across X or Y
xmin, ymin, xmax, ymax = cutout_obj.bounds()
px = 0.5 * (xmin + xmax) + margin
py = 0.5 * (ymin + ymax) + margin
lenghtx = (xmax - xmin) + (margin * 2)
lenghty = (ymax - ymin) + (margin * 2)
gapsize = gapsize / 2 + (dia / 2) gapsize = gapsize / 2 + (dia / 2)
if isinstance(cutout_obj, FlatCAMGeometry): def geo_init(geo_obj, app_obj):
# rename the obj name so it can be identified as cutout solid_geo = []
cutout_obj.options["name"] += "_cutout"
else: if isinstance(cutout_obj, FlatCAMGerber):
def geo_init(geo_obj, app_obj):
if convex_box: if convex_box:
geo = cutout_obj.solid_geometry.convex_hull object_geo = cutout_obj.solid_geometry.convex_hull
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
else: else:
geo = cutout_obj.solid_geometry object_geo = cutout_obj.solid_geometry
geo = geo.buffer(margin + abs(dia / 2)) else:
object_geo = cutout_obj.solid_geometry
if isinstance(geo, Polygon): try:
geo_obj.solid_geometry = deepcopy(geo.exterior) _ = iter(object_geo)
elif isinstance(geo, MultiPolygon): except TypeError:
solid_geo = [] object_geo = [object_geo]
for poly in geo:
solid_geo.append(poly.exterior)
geo_obj.solid_geometry = deepcopy(solid_geo)
outname = cutout_obj.options["name"] + "_cutout" for geo in object_geo:
self.app.new_object('geometry', outname, geo_init) if isinstance(cutout_obj, FlatCAMGerber):
geo = (geo.buffer(margin + abs(dia / 2))).exterior
cutout_obj = self.app.collection.get_by_name(outname) # Get min and max data for each object as we just cut rectangles across X or Y
xmin, ymin, xmax, ymax = geo.bounds
px = 0.5 * (xmin + xmax) + margin
py = 0.5 * (ymin + ymax) + margin
lenx = (xmax - xmin) + (margin * 2)
leny = (ymax - ymin) + (margin * 2)
if gaps == '8' or gaps == '2LR': if gaps == '8' or gaps == '2LR':
subtract_rectangle(cutout_obj, geo = self.subtract_poly_from_geo(geo,
xmin - gapsize, # botleft_x xmin - gapsize, # botleft_x
py - gapsize + lenghty / 4, # botleft_y py - gapsize + leny / 4, # botleft_y
xmax + gapsize, # topright_x xmax + gapsize, # topright_x
py + gapsize + lenghty / 4) # topright_y py + gapsize + leny / 4) # topright_y
subtract_rectangle(cutout_obj, geo = self.subtract_poly_from_geo(geo,
xmin - gapsize, xmin - gapsize,
py - gapsize - lenghty / 4, py - gapsize - leny / 4,
xmax + gapsize, xmax + gapsize,
py + gapsize - lenghty / 4) py + gapsize - leny / 4)
if gaps == '8' or gaps == '2TB': if gaps == '8' or gaps == '2TB':
subtract_rectangle(cutout_obj, geo = self.subtract_poly_from_geo(geo,
px - gapsize + lenghtx / 4, px - gapsize + lenx / 4,
ymin - gapsize, ymin - gapsize,
px + gapsize + lenghtx / 4, px + gapsize + lenx / 4,
ymax + gapsize) ymax + gapsize)
subtract_rectangle(cutout_obj, geo = self.subtract_poly_from_geo(geo,
px - gapsize - lenghtx / 4, px - gapsize - lenx / 4,
ymin - gapsize, ymin - gapsize,
px + gapsize - lenghtx / 4, px + gapsize - lenx / 4,
ymax + gapsize) ymax + gapsize)
if gaps == '4' or gaps == 'LR': if gaps == '4' or gaps == 'LR':
subtract_rectangle(cutout_obj, geo = self.subtract_poly_from_geo(geo,
xmin - gapsize, xmin - gapsize,
py - gapsize, py - gapsize,
xmax + gapsize, xmax + gapsize,
py + gapsize) py + gapsize)
if gaps == '4' or gaps == 'TB': if gaps == '4' or gaps == 'TB':
subtract_rectangle(cutout_obj, geo = self.subtract_poly_from_geo(geo,
px - gapsize, px - gapsize,
ymin - gapsize, ymin - gapsize,
px + gapsize, px + gapsize,
ymax + gapsize) ymax + gapsize)
try:
for g in geo:
solid_geo.append(g)
except TypeError:
solid_geo.append(geo)
geo_obj.solid_geometry = deepcopy(solid_geo)
outname = cutout_obj.options["name"] + "_cutout"
self.app.new_object('geometry', outname, geo_init)
cutout_obj.plot() cutout_obj.plot()
self.app.inform.emit(_("[success] Any form CutOut operation finished.")) self.app.inform.emit(_("[success] Any form CutOut operation finished."))
@@ -479,9 +486,9 @@ class CutOut(FlatCAMTool):
def on_rectangular_cutout(self): def on_rectangular_cutout(self):
def subtract_rectangle(obj_, x0, y0, x1, y1): # def subtract_rectangle(obj_, x0, y0, x1, y1):
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)] # pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
obj_.subtract_polygon(pts) # obj_.subtract_polygon(pts)
name = self.obj_combo.currentText() name = self.obj_combo.currentText()
@@ -550,75 +557,75 @@ class CutOut(FlatCAMTool):
return return
# Get min and max data for each object as we just cut rectangles across X or Y # Get min and max data for each object as we just cut rectangles across X or Y
if isinstance(cutout_obj.solid_geometry, Polygon):
xmin, ymin, xmax, ymax = cutout_obj.bounds()
geo = box(xmin, ymin, xmax, ymax)
elif isinstance(cutout_obj.solid_geometry, MultiPolygon):
geo = []
for poly in cutout_obj.solid_geometry:
xmin, ymin, xmax, ymax = poly.bounds
poly_geo = box(xmin, ymin, xmax, ymax)
geo.append(poly_geo)
px = 0.5 * (xmin + xmax) + margin
py = 0.5 * (ymin + ymax) + margin
lenghtx = (xmax - xmin) + (margin * 2)
lenghty = (ymax - ymin) + (margin * 2)
gapsize = gapsize / 2 + (dia / 2) gapsize = gapsize / 2 + (dia / 2)
def geo_init(geo_obj, app_obj): def geo_init(geo_obj, app_obj):
if isinstance(geo, list): solid_geo = []
solid_geo = []
for subgeo in geo: for poly in cutout_obj.solid_geometry:
solid_geo.append(subgeo.buffer(margin + abs(dia / 2))) xmin, ymin, xmax, ymax = poly.bounds
geo_obj.solid_geometry = deepcopy(solid_geo) geo = box(xmin, ymin, xmax, ymax)
else:
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2)) # if Gerber create a buffer at a distance
# if Geometry then cut through the geometry
if isinstance(cutout_obj, FlatCAMGerber):
geo = geo.buffer(margin + abs(dia / 2))
px = 0.5 * (xmin + xmax) + margin
py = 0.5 * (ymin + ymax) + margin
lenx = (xmax - xmin) + (margin * 2)
leny = (ymax - ymin) + (margin * 2)
if gaps == '8' or gaps == '2LR':
geo = self.subtract_poly_from_geo(geo,
xmin - gapsize, # botleft_x
py - gapsize + leny / 4, # botleft_y
xmax + gapsize, # topright_x
py + gapsize + leny / 4) # topright_y
geo = self.subtract_poly_from_geo(geo,
xmin - gapsize,
py - gapsize - leny / 4,
xmax + gapsize,
py + gapsize - leny / 4)
if gaps == '8' or gaps == '2TB':
geo = self.subtract_poly_from_geo(geo,
px - gapsize + lenx / 4,
ymin - gapsize,
px + gapsize + lenx / 4,
ymax + gapsize)
geo = self.subtract_poly_from_geo(geo,
px - gapsize - lenx / 4,
ymin - gapsize,
px + gapsize - lenx / 4,
ymax + gapsize)
if gaps == '4' or gaps == 'LR':
geo = self.subtract_poly_from_geo(geo,
xmin - gapsize,
py - gapsize,
xmax + gapsize,
py + gapsize)
if gaps == '4' or gaps == 'TB':
geo = self.subtract_poly_from_geo(geo,
px - gapsize,
ymin - gapsize,
px + gapsize,
ymax + gapsize)
try:
for g in geo:
solid_geo.append(g)
except TypeError:
solid_geo.append(geo)
geo_obj.solid_geometry = deepcopy(solid_geo)
outname = cutout_obj.options["name"] + "_cutout" outname = cutout_obj.options["name"] + "_cutout"
self.app.new_object('geometry', outname, geo_init) self.app.new_object('geometry', outname, geo_init)
cutout_obj = self.app.collection.get_by_name(outname) # cutout_obj.plot()
if gaps == '8' or gaps == '2LR':
subtract_rectangle(cutout_obj,
xmin - gapsize, # botleft_x
py - gapsize + lenghty / 4, # botleft_y
xmax + gapsize, # topright_x
py + gapsize + lenghty / 4) # topright_y
subtract_rectangle(cutout_obj,
xmin - gapsize,
py - gapsize - lenghty / 4,
xmax + gapsize,
py + gapsize - lenghty / 4)
if gaps == '8' or gaps == '2TB':
subtract_rectangle(cutout_obj,
px - gapsize + lenghtx / 4,
ymin - gapsize,
px + gapsize + lenghtx / 4,
ymax + gapsize)
subtract_rectangle(cutout_obj,
px - gapsize - lenghtx / 4,
ymin - gapsize,
px + gapsize - lenghtx / 4,
ymax + gapsize)
if gaps == '4' or gaps == 'LR':
subtract_rectangle(cutout_obj,
xmin - gapsize,
py - gapsize,
xmax + gapsize,
py + gapsize)
if gaps == '4' or gaps == 'TB':
subtract_rectangle(cutout_obj,
px - gapsize,
ymin - gapsize,
px + gapsize,
ymax + gapsize)
cutout_obj.plot()
self.app.inform.emit(_("[success] Any form CutOut operation finished.")) self.app.inform.emit(_("[success] Any form CutOut operation finished."))
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab) self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
self.app.should_we_save = True self.app.should_we_save = True
@@ -847,58 +854,27 @@ class CutOut(FlatCAMTool):
self.app.geo_editor.tool_shape.clear(update=True) self.app.geo_editor.tool_shape.clear(update=True)
self.app.geo_editor.tool_shape.enabled = False self.app.geo_editor.tool_shape.enabled = False
def flatten(self, geometry=None, reset=True, pathonly=False): def subtract_poly_from_geo(self, solid_geo, x0, y0, x1, y1):
""" """
Creates a list of non-iterable linear geometry objects. Subtract polygon made from points from the given object.
Polygons are expanded into its exterior and interiors if specified. This only operates on the paths in the original geometry,
Results are placed in self.flat_geometry
:param geometry: Shapely type or list or list of list of such.
:param reset: Clears the contents of self.flat_geometry.
:param pathonly: Expands polygons into linear elements.
"""
if geometry is None:
geometry = self.solid_geometry
if reset:
self.flat_geometry = []
# If iterable, expand recursively.
try:
for geo in geometry:
if geo is not None:
self.flatten(geometry=geo,
reset=False,
pathonly=pathonly)
# Not iterable, do the actual indexing and add.
except TypeError:
if pathonly and type(geometry) == Polygon:
self.flat_geometry.append(geometry.exterior)
self.flatten(geometry=geometry.interiors,
reset=False,
pathonly=True)
else:
self.flat_geometry.append(geometry)
return self.flat_geometry
def subtract_poly_from_geo(self, solid_geo, points):
"""
Subtract polygon from the given object. This only operates on the paths in the original geometry,
i.e. it converts polygons into paths. i.e. it converts polygons into paths.
:param points: The vertices of the polygon. :param x0: x coord for lower left vertice of the polygon.
:param y0: y coord for lower left vertice of the polygon.
:param x1: x coord for upper right vertice of the polygon.
:param y1: y coord for upper right vertice of the polygon.
:param solid_geo: Geometry from which to substract. If none, use the solid_geomety property of the object :param solid_geo: Geometry from which to substract. If none, use the solid_geomety property of the object
:return: none :return: none
""" """
points = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
# pathonly should be allways True, otherwise polygons are not subtracted # pathonly should be allways True, otherwise polygons are not subtracted
flat_geometry = self.flatten(geometry=solid_geo, pathonly=True) flat_geometry = flatten(geometry=solid_geo)
log.debug("%d paths" % len(flat_geometry)) log.debug("%d paths" % len(flat_geometry))
polygon = Polygon(points) polygon = Polygon(points)
toolgeo = cascaded_union(polygon) toolgeo = cascaded_union(polygon)
diffs = [] diffs = []
@@ -908,7 +884,65 @@ class CutOut(FlatCAMTool):
else: else:
log.warning("Not implemented.") log.warning("Not implemented.")
return cascaded_union(diffs) return unary_union(diffs)
def reset_fields(self): def reset_fields(self):
self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
def flatten(geometry):
"""
Creates a list of non-iterable linear geometry objects.
Polygons are expanded into its exterior and interiors.
Results are placed in self.flat_geometry
:param geometry: Shapely type or list or list of list of such.
"""
flat_geo = []
try:
for geo in geometry:
if type(geo) == Polygon:
flat_geo.append(geo.exterior)
for subgeo in geo.interiors:
flat_geo.append(subgeo)
else:
flat_geo.append(geo)
except TypeError:
if type(geometry) == Polygon:
flat_geo.append(geometry.exterior)
for subgeo in geometry.interiors:
flat_geo.append(subgeo)
else:
flat_geo.append(geometry)
return flat_geo
def recursive_bounds(geometry):
"""
Returns coordinates of rectangular bounds
of geometry: (xmin, ymin, xmax, ymax).
"""
# now it can get bounds for nested lists of objects
def bounds_rec(obj):
try:
minx = Inf
miny = Inf
maxx = -Inf
maxy = -Inf
for k in obj:
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
return minx, miny, maxx, maxy
except TypeError:
# it's a Shapely object, return it's bounds
return obj.bounds
return bounds_rec(geometry)