- Gerber Editor - working in conversion to the new data format

This commit is contained in:
Marius Stanciu
2019-05-10 15:40:09 +03:00
committed by Marius
parent a614e2b73e
commit ce0ed2208f
2 changed files with 369 additions and 249 deletions

View File

@@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing.
================================================= =================================================
10.05.2019
- Gerber Editor - working in conversion to the new data format
9.05.2019 9.05.2019
- rework the Gerber parser - rework the Gerber parser

View File

@@ -14,7 +14,6 @@ from copy import copy, deepcopy
from camlib import * from camlib import *
from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, LengthEntry, RadioSet, \ from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, LengthEntry, RadioSet, \
SpinBoxDelegate, EvalEntry, EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox SpinBoxDelegate, EvalEntry, EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox
from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
from FlatCAMObj import FlatCAMGerber from FlatCAMObj import FlatCAMGerber
from FlatCAMTool import FlatCAMTool from FlatCAMTool import FlatCAMTool
@@ -32,6 +31,148 @@ if '_' not in builtins.__dict__:
_ = gettext.gettext _ = gettext.gettext
class DrawToolShape(object):
"""
Encapsulates "shapes" under a common class.
"""
tolerance = None
@staticmethod
def get_pts(o):
"""
Returns a list of all points in the object, where
the object can be a Polygon, Not a polygon, or a list
of such. Search is done recursively.
:param: geometric object
:return: List of points
:rtype: list
"""
pts = []
## Iterable: descend into each item.
try:
for subo in o:
pts += DrawToolShape.get_pts(subo)
## Non-iterable
except TypeError:
if o is not None:
## DrawToolShape: descend into .geo.
if isinstance(o, DrawToolShape):
pts += DrawToolShape.get_pts(o.geo)
## Descend into .exerior and .interiors
elif type(o) == Polygon:
pts += DrawToolShape.get_pts(o.exterior)
for i in o.interiors:
pts += DrawToolShape.get_pts(i)
elif type(o) == MultiLineString:
for line in o:
pts += DrawToolShape.get_pts(line)
## Has .coords: list them.
else:
if DrawToolShape.tolerance is not None:
pts += list(o.simplify(DrawToolShape.tolerance).coords)
else:
pts += list(o.coords)
else:
return
return pts
def __init__(self, geo={}):
# Shapely type or list of such
self.geo = geo
self.utility = False
class DrawToolUtilityShape(DrawToolShape):
"""
Utility shapes are temporary geometry in the editor
to assist in the creation of shapes. For example it
will show the outline of a rectangle from the first
point to the current mouse pointer before the second
point is clicked and the final geometry is created.
"""
def __init__(self, geo={}):
super(DrawToolUtilityShape, self).__init__(geo=geo)
self.utility = True
class DrawTool(object):
"""
Abstract Class representing a tool in the drawing
program. Can generate geometry, including temporary
utility geometry that is updated on user clicks
and mouse motion.
"""
def __init__(self, draw_app):
self.draw_app = draw_app
self.complete = False
self.points = []
self.geometry = None # DrawToolShape or None
def click(self, point):
"""
:param point: [x, y] Coordinate pair.
"""
return ""
def click_release(self, point):
"""
:param point: [x, y] Coordinate pair.
"""
return ""
def on_key(self, key):
return None
def utility_geometry(self, data=None):
return None
def bounds(self, obj):
def bounds_rec(o):
if type(o) is list:
minx = Inf
miny = Inf
maxx = -Inf
maxy = -Inf
for k in o:
try:
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
except Exception as e:
log.debug("camlib.Gerber.bounds() --> %s" % str(e))
return
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
return minx, miny, maxx, maxy
else:
# it's a Shapely object, return it's bounds
return o.geo.bounds
bounds_coords = bounds_rec(obj)
return bounds_coords
class FCShapeTool(DrawTool):
"""
Abstract class for tools that create a shape.
"""
def __init__(self, draw_app):
DrawTool.__init__(self, draw_app)
def make(self):
pass
class FCPad(FCShapeTool): class FCPad(FCShapeTool):
""" """
Resulting type: Polygon Resulting type: Polygon
@@ -2159,9 +2300,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
# this var will store the state of the toolbar before starting the editor # this var will store the state of the toolbar before starting the editor
self.toolbar_old_state = False self.toolbar_old_state = False
# holds flattened geometry
self.flat_geometry = []
# Init GUI # Init GUI
self.apdim_lbl.hide() self.apdim_lbl.hide()
self.apdim_entry.hide() self.apdim_entry.hide()
@@ -2660,11 +2798,19 @@ class FlatCAMGrbEditor(QtCore.QObject):
else: else:
# aperture code is already in use so we move the pads from the prior tool to the new tool # aperture code is already in use so we move the pads from the prior tool to the new tool
factor = current_table_dia_edited / dia_changed factor = current_table_dia_edited / dia_changed
for shape in self.storage_dict[dia_changed].get_objects(): geometry = []
geometry.append(DrawToolShape( for geo_el in self.storage_dict[dia_changed]:
MultiLineString([affinity.scale(subgeo, xfact=factor, yfact=factor) for subgeo in shape.geo]))) geometric_data = geo_el.geo
new_geo_el = dict()
if 'solid' in geometric_data:
new_geo_el['solid'] = deepcopy(geometric_data['solid'])
if 'follow' in geometric_data:
new_geo_el['follow'] = deepcopy(geometric_data['follow'])
if 'clear' in geometric_data:
new_geo_el['clear'] = deepcopy(geometric_data['clear'])
# geometry.append(DrawToolShape(
# MultiLineString([affinity.scale(subgeo, xfact=factor, yfact=factor) for subgeo in shape.geo])))
self.points_edit[current_table_dia_edited].append((0, 0))
self.add_gerber_shape(geometry, self.storage_dict[current_table_dia_edited]) self.add_gerber_shape(geometry, self.storage_dict[current_table_dia_edited])
self.on_aperture_delete(apid=dia_changed) self.on_aperture_delete(apid=dia_changed)
@@ -2914,37 +3060,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.shapes.clear(update=True) self.shapes.clear(update=True)
self.tool_shape.clear(update=True) self.tool_shape.clear(update=True)
def flatten(self, geometry=None, reset=True, pathonly=False):
"""
Creates a list of non-iterable linear geometry objects.
Polygons are expanded into its exterior pathonly param if specified.
Results are placed in 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 from the exterior attribute.
"""
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 edit_fcgerber(self, orig_grb_obj): def edit_fcgerber(self, orig_grb_obj):
""" """
Imports the geometry found in self.apertures from the given FlatCAM Gerber object Imports the geometry found in self.apertures from the given FlatCAM Gerber object
@@ -2983,17 +3098,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
for k, v in self.gerber_obj.apertures[apid].items(): for k, v in self.gerber_obj.apertures[apid].items():
try: try:
if k == 'geometry': if k == 'geometry':
for el_dict in v: for geo_el in v:
if el_dict: if geo_el:
new_el_dict = dict() self.add_gerber_shape(DrawToolShape(geo_el), storage_elem)
if 'solid' in el_dict:
new_el_dict['solid'] =DrawToolShape(deepcopy(el_dict['solid']))
if 'follow' in el_dict:
new_el_dict['follow'] =DrawToolShape(deepcopy(el_dict['follow']))
if 'clear' in el_dict:
new_el_dict['clear'] =DrawToolShape(deepcopy(el_dict['clear']))
storage_elem.append(new_el_dict)
self.add_gerber_shape(DrawToolShape(new_el_dict['solid']))
self.storage_dict[apid][k] = storage_elem self.storage_dict[apid][k] = storage_elem
else: else:
self.storage_dict[apid][k] = self.gerber_obj.apertures[apid][k] self.storage_dict[apid][k] = self.gerber_obj.apertures[apid][k]
@@ -3101,26 +3208,27 @@ class FlatCAMGrbEditor(QtCore.QObject):
grb_obj.apertures[storage_apid] = {} grb_obj.apertures[storage_apid] = {}
for k, v in storage_val.items(): for k, v in storage_val.items():
if k == 'solid_geometry': if k == 'geometry':
grb_obj.apertures[storage_apid][k] = [] grb_obj.apertures[storage_apid][k] = []
for geo in v: for geo_el in v:
new_geo = deepcopy(geo.geo) new_geo = dict()
grb_obj.apertures[storage_apid][k].append(new_geo) geometric_data = geo_el.geo
poly_buffer.append(new_geo) for key in geometric_data:
if key == 'solid':
elif k == 'follow_geometry': new_geo[key] = geometric_data['solid']
grb_obj.apertures[storage_apid][k] = [] poly_buffer.append(deepcopy(new_geo['solid']))
for geo_f in v: if key == 'follow':
if isinstance(geo_f.geo, Polygon): if isinstance(geometric_data[key], Polygon):
buff_val = -(int(storage_apid) / 2) buff_val = -(int(storage_apid) / 2)
geo_f = geo_f.geo.buffer(buff_val).exterior geo_f = geo_el.geo.buffer(buff_val).exterior
new_geo = deepcopy(geo_f) new_geo[key] = geo_f
else: else:
new_geo = deepcopy(geo_f.geo) new_geo[key] = geometric_data[key]
grb_obj.apertures[storage_apid][k].append(new_geo) follow_buffer.append(deepcopy(new_geo['follow']))
follow_buffer.append(new_geo) if key == 'clear':
else: new_geo[key] = geometric_data['clear']
grb_obj.apertures[storage_apid][k] = deepcopy(v)
grb_obj.apertures[storage_apid][k].append(deepcopy(new_geo))
grb_obj.aperture_macros = deepcopy(self.gerber_obj.aperture_macros) grb_obj.aperture_macros = deepcopy(self.gerber_obj.aperture_macros)
@@ -3218,7 +3326,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
selected_apid = self.apertures_table.item(row, 1).text() selected_apid = self.apertures_table.item(row, 1).text()
self.last_aperture_selected = copy(selected_apid) self.last_aperture_selected = copy(selected_apid)
for obj in self.storage_dict[selected_apid]['solid_geometry']: for obj in self.storage_dict[selected_apid]['geometry']:
self.selected.append(obj) self.selected.append(obj)
except Exception as e: except Exception as e:
self.app.log.debug(str(e)) self.app.log.debug(str(e))
@@ -3230,7 +3338,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
return self.options[key] return self.options[key]
def on_grb_shape_complete(self, storage=None, specific_shape=None, noplot=False): def on_grb_shape_complete(self, storage=None, specific_shape=None, noplot=False):
self.app.log.debug("on_shape_complete()") self.app.log.debug("on_grb_shape_complete()")
if specific_shape: if specific_shape:
geo = specific_shape geo = specific_shape
@@ -3243,7 +3351,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
# Add shape # Add shape
self.add_gerber_shape(geo, storage) self.add_gerber_shape(geo, storage)
else: else:
stora = self.storage_dict[self.last_aperture_selected]['solid_geometry'] stora = self.storage_dict[self.last_aperture_selected]['geometry']
self.add_gerber_shape(geo, storage=stora) self.add_gerber_shape(geo, storage=stora)
# Remove any utility shapes # Remove any utility shapes
@@ -3254,33 +3362,36 @@ class FlatCAMGrbEditor(QtCore.QObject):
# Replot and reset tool. # Replot and reset tool.
self.plot_all() self.plot_all()
def add_gerber_shape(self, shape): def add_gerber_shape(self, shape_element, storage):
""" """
Adds a shape to the shape storage. Adds a shape to the shape storage.
:param shape: Shape to be added. :param shape_element: Shape to be added.
:type shape: DrawToolShape :type shape_element: DrawToolShape or DrawToolUtilityShape Geometry is stored as a dict with keys: solid,
follow, clear, each value being a list of Shapely objects. The dict can have at least one of the mentioned keys
:return: None :return: None
""" """
# List of DrawToolShape? # List of DrawToolShape?
if isinstance(shape, list): if isinstance(shape_element, list):
for subshape in shape: for subshape in shape_element:
self.add_gerber_shape(subshape) self.add_gerber_shape(subshape, storage)
return return
assert isinstance(shape, DrawToolShape), \ assert isinstance(shape_element, DrawToolShape), \
"Expected a DrawToolShape, got %s" % str(type(shape)) "Expected a DrawToolShape, got %s" % str(type(shape_element))
assert shape.geo is not None, \ assert shape_element.geo is not None, \
"Shape object has empty geometry (None)" "Shape object has empty geometry (None)"
assert (isinstance(shape.geo, list) and len(shape.geo) > 0) or \ assert (isinstance(shape_element.geo, list) and len(shape_element.geo) > 0) or \
not isinstance(shape.geo, list), \ not isinstance(shape_element.geo, list), \
"Shape objects has empty geometry ([])" "Shape objects has empty geometry ([])"
if isinstance(shape, DrawToolUtilityShape): if isinstance(shape_element, DrawToolUtilityShape):
self.utility.append(shape) self.utility.append(shape_element)
else:
storage.append(shape_element)
def on_canvas_click(self, event): def on_canvas_click(self, event):
""" """
@@ -3435,9 +3546,10 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.delete_selection_shape() self.app.delete_selection_shape()
for storage in self.storage_dict: for storage in self.storage_dict:
try: try:
for obj in self.storage_dict[storage]['solid_geometry']: for obj in self.storage_dict[storage]['geometry']:
if (sel_type is True and poly_selection.contains(obj.geo)) or \ geometric_data = obj.geo['solid']
(sel_type is False and poly_selection.intersects(obj.geo)): if (sel_type is True and poly_selection.contains(geometric_data)) or \
(sel_type is False and poly_selection.intersects(geometric_data)):
if self.key == self.app.defaults["global_mselect_key"]: if self.key == self.app.defaults["global_mselect_key"]:
if obj in self.selected: if obj in self.selected:
self.selected.remove(obj) self.selected.remove(obj)
@@ -3557,15 +3669,17 @@ class FlatCAMGrbEditor(QtCore.QObject):
def draw_utility_geometry(self, geo): def draw_utility_geometry(self, geo):
if type(geo.geo) == list: if type(geo.geo) == list:
for el in geo.geo: for el in geo.geo:
geometric_data = el['solid']
# Add the new utility shape # Add the new utility shape
self.tool_shape.add( self.tool_shape.add(
shape=el, color=(self.app.defaults["global_draw_color"] + '80'), shape=geometric_data, color=(self.app.defaults["global_draw_color"] + '80'),
# face_color=self.app.defaults['global_alt_sel_fill'], # face_color=self.app.defaults['global_alt_sel_fill'],
update=False, layer=0, tolerance=None) update=False, layer=0, tolerance=None)
else: else:
geometric_data = geo.geo['solid']
# Add the new utility shape # Add the new utility shape
self.tool_shape.add( self.tool_shape.add(
shape=geo.geo, shape=geometric_data,
color=(self.app.defaults["global_draw_color"] + '80'), color=(self.app.defaults["global_draw_color"] + '80'),
# face_color=self.app.defaults['global_alt_sel_fill'], # face_color=self.app.defaults['global_alt_sel_fill'],
update=False, layer=0, tolerance=None) update=False, layer=0, tolerance=None)
@@ -3586,32 +3700,33 @@ class FlatCAMGrbEditor(QtCore.QObject):
for storage in self.storage_dict: for storage in self.storage_dict:
try: try:
for elem in self.storage_dict[storage]['geometry']: for elem in self.storage_dict[storage]['geometry']:
if elem['solid'].geo is None: geometric_data = elem.geo['solid']
if geometric_data is None:
continue continue
if elem['solid'] in self.selected: if elem in self.selected:
self.plot_shape(geometry=elem['solid'].geo, self.plot_shape(geometry=geometric_data,
color=self.app.defaults['global_sel_draw_color'], color=self.app.defaults['global_sel_draw_color'])
linewidth=2)
continue continue
self.plot_shape(geometry=elem['solid'].geo, color=self.app.defaults['global_draw_color']) self.plot_shape(geometry=geometric_data,
color=self.app.defaults['global_draw_color'])
except KeyError: except KeyError:
pass pass
for elem in self.utility: for elem in self.utility:
self.plot_shape(geometry=elem['solid'].geo, linewidth=1) geometric_data = elem.geo['solid']
self.plot_shape(geometry=geometric_data)
continue continue
self.shapes.redraw() self.shapes.redraw()
def plot_shape(self, geometry=None, color='black', linewidth=1): def plot_shape(self, geometry=None, color='black'):
""" """
Plots a geometric object or list of objects without rendering. Plotted objects Plots a geometric object or list of objects without rendering. Plotted objects
are returned as a list. This allows for efficient/animated rendering. are returned as a list. This allows for efficient/animated rendering.
:param geometry: Geometry to be plotted (Any Shapely.geom kind or list of such) :param geometry: Geometry to be plotted (Any Shapely.geom kind or list of such)
:param color: Shape color :param color: Shape color
:param linewidth: Width of lines in # of pixels.
:return: List of plotted elements. :return: List of plotted elements.
""" """
# plot_elements = [] # plot_elements = []
@@ -3667,20 +3782,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()
def on_shape_complete(self):
self.app.log.debug("on_shape_complete()")
# Add shape
self.add_gerber_shape(self.active_tool.geometry)
# Remove any utility shapes
self.delete_utility_geometry()
self.tool_shape.clear(update=True)
# Replot and reset tool.
self.plot_all()
# self.active_tool = type(self.active_tool)(self)
def get_selected(self): def get_selected(self):
""" """
Returns list of shapes that are selected in the editor. Returns list of shapes that are selected in the editor.
@@ -3831,15 +3932,16 @@ class FlatCAMGrbEditor(QtCore.QObject):
return geoms return geoms
else: else:
if geom_el in selection: if geom_el in selection:
geometric_data = geom_el.geo
buffered_geom_el = dict() buffered_geom_el = dict()
if 'solid' in geom_el: if 'solid' in geom_el:
buffered_geom_el['solid'] = DrawToolShape(geom_el['solid'].geo.buffer(buff_value, buffered_geom_el['solid'] = DrawToolShape(geometric_data['solid'].buffer(buff_value,
join_style=join_style)) join_style=join_style))
if 'follow' in geom_el: if 'follow' in geom_el:
buffered_geom_el['follow'] = DrawToolShape(geom_el['follow'].geo.buffer(buff_value, buffered_geom_el['follow'] = DrawToolShape(geometric_data['follow'].buffer(buff_value,
join_style=join_style)) join_style=join_style))
if 'clear' in geom_el: if 'clear' in geom_el:
buffered_geom_el['clear'] = DrawToolShape(geom_el['clear'].geo.buffer(buff_value, buffered_geom_el['clear'] = DrawToolShape(geometric_data['clear'].buffer(buff_value,
join_style=join_style)) join_style=join_style))
return buffered_geom_el return buffered_geom_el
else: else:
@@ -3888,16 +3990,17 @@ class FlatCAMGrbEditor(QtCore.QObject):
return geoms return geoms
else: else:
if geom_el in selection: if geom_el in selection:
geometric_data = geom_el.geo
scaled_geom_el = dict() scaled_geom_el = dict()
if 'solid' in geom_el: if 'solid' in geom_el:
scaled_geom_el['solid'] = DrawToolShape( scaled_geom_el['solid'] = DrawToolShape(
affinity.scale(geom_el['solid'].geo, scale_factor, scale_factor, origin='center')) affinity.scale(geometric_data['solid'], scale_factor, scale_factor, origin='center'))
if 'follow' in geom_el: if 'follow' in geom_el:
scaled_geom_el['follow'] = DrawToolShape( scaled_geom_el['follow'] = DrawToolShape(
affinity.scale(geom_el['follow'].geo, scale_factor, scale_factor, origin='center')) affinity.scale(geometric_data['follow'], scale_factor, scale_factor, origin='center'))
if 'clear' in geom_el: if 'clear' in geom_el:
scaled_geom_el['clear'] = DrawToolShape( scaled_geom_el['clear'] = DrawToolShape(
affinity.scale(geom_el['clear'].geo, scale_factor, scale_factor, origin='center')) affinity.scale(geometric_data['clear'], scale_factor, scale_factor, origin='center'))
return scaled_geom_el return scaled_geom_el
else: else:
@@ -4607,13 +4710,14 @@ class TransformEditorTool(FlatCAMTool):
if not elem_list: if not elem_list:
self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to rotate!")) self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to rotate!"))
return return
else:
with self.app.proc_container.new(_("Appying Rotate")): with self.app.proc_container.new(_("Appying Rotate")):
try: try:
# first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest # first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest
# bounding box # bounding box
for geo_el in elem_list: for el in elem_list:
xmin, ymin, xmax, ymax = geo_el..bounds() if 'solid' in el:
xmin, ymin, xmax, ymax = el['solid'].bounds()
xminlist.append(xmin) xminlist.append(xmin)
yminlist.append(ymin) yminlist.append(ymin)
xmaxlist.append(xmax) xmaxlist.append(xmax)
@@ -4626,36 +4730,35 @@ class TransformEditorTool(FlatCAMTool):
ymaximal = max(ymaxlist) ymaximal = max(ymaxlist)
self.app.progress.emit(20) self.app.progress.emit(20)
for sel_sha in shape_list:
px = 0.5 * (xminimal + xmaximal) px = 0.5 * (xminimal + xmaximal)
py = 0.5 * (yminimal + ymaximal) py = 0.5 * (yminimal + ymaximal)
sel_sha.rotate(-num, point=(px, py)) for sel_el in elem_list:
if 'solid' in sel_el:
sel_el['solid'].rotate(-num, point=(px, py))
if 'follow' in sel_el:
sel_el['follow'].rotate(-num, point=(px, py))
if 'clear' in sel_el:
sel_el['clear'].rotate(-num, point=(px, py))
self.draw_app.plot_all() self.draw_app.plot_all()
# self.draw_app.add_shape(DrawToolShape(sel_sha.geo))
# self.draw_app.transform_complete.emit()
self.app.inform.emit(_("[success] Done. Rotate completed.")) self.app.inform.emit(_("[success] Done. Rotate completed."))
self.app.progress.emit(100) self.app.progress.emit(100)
except Exception as e: except Exception as e:
self.app.inform.emit(_("[ERROR_NOTCL] Due of %s, rotation movement was not executed.") % str(e)) self.app.inform.emit(_("[ERROR_NOTCL] Due of %s, rotation movement was not executed.") % str(e))
return return
def on_flip(self, axis): def on_flip(self, axis):
shape_list = self.draw_app.selected elem_list = self.draw_app.selected
xminlist = [] xminlist = []
yminlist = [] yminlist = []
xmaxlist = [] xmaxlist = []
ymaxlist = [] ymaxlist = []
if not shape_list: if not elem_list:
self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to flip!")) self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to flip!"))
return return
else:
with self.app.proc_container.new(_("Applying Flip")): with self.app.proc_container.new(_("Applying Flip")):
try: try:
# get mirroring coords from the point entry # get mirroring coords from the point entry
@@ -4663,9 +4766,11 @@ class TransformEditorTool(FlatCAMTool):
px, py = eval('{}'.format(self.flip_ref_entry.text())) px, py = eval('{}'.format(self.flip_ref_entry.text()))
# get mirroing coords from the center of an all-enclosing bounding box # get mirroing coords from the center of an all-enclosing bounding box
else: else:
# first get a bounding box to fit all # first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest
for sha in shape_list: # bounding box
xmin, ymin, xmax, ymax = sha.bounds() for el in elem_list:
if 'solid' in el:
xmin, ymin, xmax, ymax = el['solid'].bounds()
xminlist.append(xmin) xminlist.append(xmin)
yminlist.append(ymin) yminlist.append(ymin)
xmaxlist.append(xmax) xmaxlist.append(xmax)
@@ -4683,19 +4788,24 @@ class TransformEditorTool(FlatCAMTool):
self.app.progress.emit(20) self.app.progress.emit(20)
# execute mirroring # execute mirroring
for sha in shape_list: for sel_el in elem_list:
if axis is 'X': if axis is 'X':
sha.mirror('X', (px, py)) if 'solid' in sel_el:
sel_el['solid'].mirror('X', (px, py))
if 'follow' in sel_el:
sel_el['follow'].mirror('X', (px, py))
if 'clear' in sel_el:
sel_el['clear'].mirror('X', (px, py))
self.app.inform.emit(_('[success] Flip on the Y axis done ...')) self.app.inform.emit(_('[success] Flip on the Y axis done ...'))
elif axis is 'Y': elif axis is 'Y':
sha.mirror('Y', (px, py)) if 'solid' in sel_el:
sel_el['solid'].mirror('Y', (px, py))
if 'follow' in sel_el:
sel_el['follow'].mirror('Y', (px, py))
if 'clear' in sel_el:
sel_el['clear'].mirror('Y', (px, py))
self.app.inform.emit(_('[success] Flip on the X axis done ...')) self.app.inform.emit(_('[success] Flip on the X axis done ...'))
self.draw_app.plot_all() self.draw_app.plot_all()
# self.draw_app.add_shape(DrawToolShape(sha.geo))
#
# self.draw_app.transform_complete.emit()
self.app.progress.emit(100) self.app.progress.emit(100)
except Exception as e: except Exception as e:
@@ -4703,19 +4813,21 @@ class TransformEditorTool(FlatCAMTool):
return return
def on_skew(self, axis, num): def on_skew(self, axis, num):
shape_list = self.draw_app.selected elem_list = self.draw_app.selected
xminlist = [] xminlist = []
yminlist = [] yminlist = []
if not shape_list: if not elem_list:
self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to shear/skew!")) self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to shear/skew!"))
return return
else: else:
with self.app.proc_container.new(_("Applying Skew")): with self.app.proc_container.new(_("Applying Skew")):
try: try:
# first get a bounding box to fit all # first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest
for sha in shape_list: # bounding box
xmin, ymin, xmax, ymax = sha.bounds() for el in elem_list:
if 'solid' in el:
xmin, ymin, xmax, ymax = el['solid'].bounds()
xminlist.append(xmin) xminlist.append(xmin)
yminlist.append(ymin) yminlist.append(ymin)
@@ -4725,17 +4837,23 @@ class TransformEditorTool(FlatCAMTool):
self.app.progress.emit(20) self.app.progress.emit(20)
for sha in shape_list: for sel_el in elem_list:
if axis is 'X': if axis is 'X':
sha.skew(num, 0, point=(xminimal, yminimal)) if 'solid' in sel_el:
sel_el['solid'].skew(num, 0, point=(xminimal, yminimal))
if 'follow' in sel_el:
sel_el['follow'].skew(num, 0, point=(xminimal, yminimal))
if 'clear' in sel_el:
sel_el['clear'].skew(num, 0, point=(xminimal, yminimal))
elif axis is 'Y': elif axis is 'Y':
sha.skew(0, num, point=(xminimal, yminimal)) if 'solid' in sel_el:
sel_el['solid'].skew(0, num, point=(xminimal, yminimal))
if 'follow' in sel_el:
sel_el['follow'].skew(0, num, point=(xminimal, yminimal))
if 'clear' in sel_el:
sel_el['clear'].skew(0, num, point=(xminimal, yminimal))
self.draw_app.plot_all() self.draw_app.plot_all()
# self.draw_app.add_shape(DrawToolShape(sha.geo))
#
# self.draw_app.transform_complete.emit()
self.app.inform.emit(_('[success] Skew on the %s axis done ...') % str(axis)) self.app.inform.emit(_('[success] Skew on the %s axis done ...') % str(axis))
self.app.progress.emit(100) self.app.progress.emit(100)
@@ -4744,21 +4862,23 @@ class TransformEditorTool(FlatCAMTool):
return return
def on_scale(self, axis, xfactor, yfactor, point=None): def on_scale(self, axis, xfactor, yfactor, point=None):
shape_list = self.draw_app.selected elem_list = self.draw_app.selected
xminlist = [] xminlist = []
yminlist = [] yminlist = []
xmaxlist = [] xmaxlist = []
ymaxlist = [] ymaxlist = []
if not shape_list: if not elem_list:
self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to scale!")) self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to scale!"))
return return
else: else:
with self.app.proc_container.new(_("Applying Scale")): with self.app.proc_container.new(_("Applying Scale")):
try: try:
# first get a bounding box to fit all # first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest
for sha in shape_list: # bounding box
xmin, ymin, xmax, ymax = sha.bounds() for el in elem_list:
if 'solid' in el:
xmin, ymin, xmax, ymax = el['solid'].bounds()
xminlist.append(xmin) xminlist.append(xmin)
yminlist.append(ymin) yminlist.append(ymin)
xmaxlist.append(xmax) xmaxlist.append(xmax)
@@ -4779,14 +4899,15 @@ class TransformEditorTool(FlatCAMTool):
px = 0 px = 0
py = 0 py = 0
for sha in shape_list: for sel_el in elem_list:
sha.scale(xfactor, yfactor, point=(px, py)) if 'solid' in sel_el:
sel_el['solid'].scale(xfactor, yfactor, point=(px, py))
if 'follow' in sel_el:
sel_el['follow'].scale(xfactor, yfactor, point=(px, py))
if 'clear' in sel_el:
sel_el['clear'].scale(xfactor, yfactor, point=(px, py))
self.draw_app.plot_all() self.draw_app.plot_all()
# self.draw_app.add_shape(DrawToolShape(sha.geo))
#
# self.draw_app.transform_complete.emit()
self.app.inform.emit(_('[success] Scale on the %s axis done ...') % str(axis)) self.app.inform.emit(_('[success] Scale on the %s axis done ...') % str(axis))
self.app.progress.emit(100) self.app.progress.emit(100)
except Exception as e: except Exception as e:
@@ -4794,38 +4915,33 @@ class TransformEditorTool(FlatCAMTool):
return return
def on_offset(self, axis, num): def on_offset(self, axis, num):
shape_list = self.draw_app.selected elem_list = self.draw_app.selected
xminlist = []
yminlist = []
if not shape_list: if not elem_list:
self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to offset!")) self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to offset!"))
return return
else: else:
with self.app.proc_container.new(_("Applying Offset")): with self.app.proc_container.new(_("Applying Offset")):
try: try:
# first get a bounding box to fit all
for sha in shape_list:
xmin, ymin, xmax, ymax = sha.bounds()
xminlist.append(xmin)
yminlist.append(ymin)
# get the minimum x,y and maximum x,y for all objects selected
xminimal = min(xminlist)
yminimal = min(yminlist)
self.app.progress.emit(20) self.app.progress.emit(20)
for sha in shape_list: for sel_el in elem_list:
if axis is 'X': if axis is 'X':
sha.offset((num, 0)) if 'solid' in sel_el:
sel_el['solid'].offset((num, 0))
if 'follow' in sel_el:
sel_el['follow'].offset((num, 0))
if 'clear' in sel_el:
sel_el['clear'].offset((num, 0))
elif axis is 'Y': elif axis is 'Y':
sha.offset((0, num)) if 'solid' in sel_el:
sel_el['solid'].offset((0, num))
if 'follow' in sel_el:
sel_el['follow'].offset((0, num))
if 'clear' in sel_el:
sel_el['clear'].offset((0, num))
self.draw_app.plot_all() self.draw_app.plot_all()
# self.draw_app.add_shape(DrawToolShape(sha.geo))
#
# self.draw_app.transform_complete.emit()
self.app.inform.emit(_('[success] Offset on the %s axis done ...') % str(axis)) self.app.inform.emit(_('[success] Offset on the %s axis done ...') % str(axis))
self.app.progress.emit(100) self.app.progress.emit(100)