- 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
- 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_label = QtWidgets.QLabel(_("Convex Sh.:"))
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)
@@ -327,9 +328,9 @@ class CutOut(FlatCAMTool):
def on_freeform_cutout(self):
def subtract_rectangle(obj_, x0, y0, x1, y1):
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
obj_.subtract_polygon(pts)
# def subtract_rectangle(obj_, x0, y0, x1, y1):
# pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
# obj_.subtract_polygon(pts)
name = self.obj_combo.currentText()
@@ -400,77 +401,83 @@ class CutOut(FlatCAMTool):
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)
if isinstance(cutout_obj, FlatCAMGeometry):
# rename the obj name so it can be identified as cutout
cutout_obj.options["name"] += "_cutout"
else:
def geo_init(geo_obj, app_obj):
def geo_init(geo_obj, app_obj):
solid_geo = []
if isinstance(cutout_obj, FlatCAMGerber):
if convex_box:
geo = cutout_obj.solid_geometry.convex_hull
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
object_geo = cutout_obj.solid_geometry.convex_hull
else:
geo = cutout_obj.solid_geometry
geo = geo.buffer(margin + abs(dia / 2))
object_geo = cutout_obj.solid_geometry
else:
object_geo = cutout_obj.solid_geometry
if isinstance(geo, Polygon):
geo_obj.solid_geometry = deepcopy(geo.exterior)
elif isinstance(geo, MultiPolygon):
solid_geo = []
for poly in geo:
solid_geo.append(poly.exterior)
geo_obj.solid_geometry = deepcopy(solid_geo)
try:
_ = iter(object_geo)
except TypeError:
object_geo = [object_geo]
outname = cutout_obj.options["name"] + "_cutout"
self.app.new_object('geometry', outname, geo_init)
for geo in object_geo:
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':
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 == '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':
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 == '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':
subtract_rectangle(cutout_obj,
xmin - gapsize,
py - gapsize,
xmax + gapsize,
py + 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':
subtract_rectangle(cutout_obj,
px - gapsize,
ymin - gapsize,
px + gapsize,
ymax + 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"
self.app.new_object('geometry', outname, geo_init)
cutout_obj.plot()
self.app.inform.emit(_("[success] Any form CutOut operation finished."))
@@ -479,9 +486,9 @@ class CutOut(FlatCAMTool):
def on_rectangular_cutout(self):
def subtract_rectangle(obj_, x0, y0, x1, y1):
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
obj_.subtract_polygon(pts)
# def subtract_rectangle(obj_, x0, y0, x1, y1):
# pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
# obj_.subtract_polygon(pts)
name = self.obj_combo.currentText()
@@ -550,75 +557,75 @@ class CutOut(FlatCAMTool):
return
# 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)
def geo_init(geo_obj, app_obj):
if isinstance(geo, list):
solid_geo = []
for subgeo in geo:
solid_geo.append(subgeo.buffer(margin + abs(dia / 2)))
geo_obj.solid_geometry = deepcopy(solid_geo)
else:
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
solid_geo = []
for poly in cutout_obj.solid_geometry:
xmin, ymin, xmax, ymax = poly.bounds
geo = box(xmin, ymin, xmax, ymax)
# 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"
self.app.new_object('geometry', outname, geo_init)
cutout_obj = self.app.collection.get_by_name(outname)
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()
# cutout_obj.plot()
self.app.inform.emit(_("[success] Any form CutOut operation finished."))
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
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.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.
Polygons are expanded into its exterior and interiors if specified.
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,
Subtract polygon made from points from the given object.
This only operates on the paths in the original geometry,
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
:return: none
"""
points = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
# 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))
polygon = Polygon(points)
toolgeo = cascaded_union(polygon)
diffs = []
@@ -908,7 +884,65 @@ class CutOut(FlatCAMTool):
else:
log.warning("Not implemented.")
return cascaded_union(diffs)
return unary_union(diffs)
def reset_fields(self):
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)