diff --git a/README.md b/README.md index 4e7c6b0b..0b6f14e9 100644 --- a/README.md +++ b/README.md @@ -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 - rework the Gerber parser diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py index de5016e2..459da094 100644 --- a/flatcamEditors/FlatCAMGrbEditor.py +++ b/flatcamEditors/FlatCAMGrbEditor.py @@ -14,7 +14,6 @@ from copy import copy, deepcopy from camlib import * from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, LengthEntry, RadioSet, \ SpinBoxDelegate, EvalEntry, EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox -from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor from FlatCAMObj import FlatCAMGerber from FlatCAMTool import FlatCAMTool @@ -32,6 +31,148 @@ if '_' not in builtins.__dict__: _ = 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): """ Resulting type: Polygon @@ -2159,9 +2300,6 @@ class FlatCAMGrbEditor(QtCore.QObject): # this var will store the state of the toolbar before starting the editor self.toolbar_old_state = False - # holds flattened geometry - self.flat_geometry = [] - # Init GUI self.apdim_lbl.hide() self.apdim_entry.hide() @@ -2660,11 +2798,19 @@ class FlatCAMGrbEditor(QtCore.QObject): else: # 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 - for shape in self.storage_dict[dia_changed].get_objects(): - geometry.append(DrawToolShape( - MultiLineString([affinity.scale(subgeo, xfact=factor, yfact=factor) for subgeo in shape.geo]))) + geometry = [] + for geo_el in self.storage_dict[dia_changed]: + 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.on_aperture_delete(apid=dia_changed) @@ -2914,37 +3060,6 @@ class FlatCAMGrbEditor(QtCore.QObject): self.shapes.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): """ 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(): try: if k == 'geometry': - for el_dict in v: - if el_dict: - new_el_dict = dict() - 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'])) + for geo_el in v: + if geo_el: + self.add_gerber_shape(DrawToolShape(geo_el), storage_elem) self.storage_dict[apid][k] = storage_elem else: self.storage_dict[apid][k] = self.gerber_obj.apertures[apid][k] @@ -3101,26 +3208,27 @@ class FlatCAMGrbEditor(QtCore.QObject): grb_obj.apertures[storage_apid] = {} for k, v in storage_val.items(): - if k == 'solid_geometry': + if k == 'geometry': grb_obj.apertures[storage_apid][k] = [] - for geo in v: - new_geo = deepcopy(geo.geo) - grb_obj.apertures[storage_apid][k].append(new_geo) - poly_buffer.append(new_geo) + for geo_el in v: + new_geo = dict() + geometric_data = geo_el.geo + for key in geometric_data: + if key == 'solid': + new_geo[key] = geometric_data['solid'] + poly_buffer.append(deepcopy(new_geo['solid'])) + if key == 'follow': + if isinstance(geometric_data[key], Polygon): + buff_val = -(int(storage_apid) / 2) + geo_f = geo_el.geo.buffer(buff_val).exterior + new_geo[key] = geo_f + else: + new_geo[key] = geometric_data[key] + follow_buffer.append(deepcopy(new_geo['follow'])) + if key == 'clear': + new_geo[key] = geometric_data['clear'] - elif k == 'follow_geometry': - grb_obj.apertures[storage_apid][k] = [] - for geo_f in v: - if isinstance(geo_f.geo, Polygon): - buff_val = -(int(storage_apid) / 2) - geo_f = geo_f.geo.buffer(buff_val).exterior - new_geo = deepcopy(geo_f) - else: - new_geo = deepcopy(geo_f.geo) - grb_obj.apertures[storage_apid][k].append(new_geo) - follow_buffer.append(new_geo) - else: - 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) @@ -3218,7 +3326,7 @@ class FlatCAMGrbEditor(QtCore.QObject): selected_apid = self.apertures_table.item(row, 1).text() 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) except Exception as e: self.app.log.debug(str(e)) @@ -3230,7 +3338,7 @@ class FlatCAMGrbEditor(QtCore.QObject): return self.options[key] 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: geo = specific_shape @@ -3243,7 +3351,7 @@ class FlatCAMGrbEditor(QtCore.QObject): # Add shape self.add_gerber_shape(geo, storage) 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) # Remove any utility shapes @@ -3254,33 +3362,36 @@ class FlatCAMGrbEditor(QtCore.QObject): # Replot and reset tool. self.plot_all() - def add_gerber_shape(self, shape): + def add_gerber_shape(self, shape_element, storage): """ Adds a shape to the shape storage. - :param shape: Shape to be added. - :type shape: DrawToolShape + :param shape_element: Shape to be added. + :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 """ # List of DrawToolShape? - if isinstance(shape, list): - for subshape in shape: - self.add_gerber_shape(subshape) + if isinstance(shape_element, list): + for subshape in shape_element: + self.add_gerber_shape(subshape, storage) return - assert isinstance(shape, DrawToolShape), \ - "Expected a DrawToolShape, got %s" % str(type(shape)) + assert isinstance(shape_element, DrawToolShape), \ + "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)" - assert (isinstance(shape.geo, list) and len(shape.geo) > 0) or \ - not isinstance(shape.geo, list), \ + assert (isinstance(shape_element.geo, list) and len(shape_element.geo) > 0) or \ + not isinstance(shape_element.geo, list), \ "Shape objects has empty geometry ([])" - if isinstance(shape, DrawToolUtilityShape): - self.utility.append(shape) + if isinstance(shape_element, DrawToolUtilityShape): + self.utility.append(shape_element) + else: + storage.append(shape_element) def on_canvas_click(self, event): """ @@ -3435,9 +3546,10 @@ class FlatCAMGrbEditor(QtCore.QObject): self.app.delete_selection_shape() for storage in self.storage_dict: try: - for obj in self.storage_dict[storage]['solid_geometry']: - if (sel_type is True and poly_selection.contains(obj.geo)) or \ - (sel_type is False and poly_selection.intersects(obj.geo)): + for obj in self.storage_dict[storage]['geometry']: + geometric_data = obj.geo['solid'] + 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 obj in self.selected: self.selected.remove(obj) @@ -3557,15 +3669,17 @@ class FlatCAMGrbEditor(QtCore.QObject): def draw_utility_geometry(self, geo): if type(geo.geo) == list: for el in geo.geo: + geometric_data = el['solid'] # Add the new utility shape 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'], update=False, layer=0, tolerance=None) else: + geometric_data = geo.geo['solid'] # Add the new utility shape self.tool_shape.add( - shape=geo.geo, + shape=geometric_data, color=(self.app.defaults["global_draw_color"] + '80'), # face_color=self.app.defaults['global_alt_sel_fill'], update=False, layer=0, tolerance=None) @@ -3586,32 +3700,33 @@ class FlatCAMGrbEditor(QtCore.QObject): for storage in self.storage_dict: try: 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 - if elem['solid'] in self.selected: - self.plot_shape(geometry=elem['solid'].geo, - color=self.app.defaults['global_sel_draw_color'], - linewidth=2) + if elem in self.selected: + self.plot_shape(geometry=geometric_data, + color=self.app.defaults['global_sel_draw_color']) 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: pass 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 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 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 color: Shape color - :param linewidth: Width of lines in # of pixels. :return: List of plotted elements. """ # plot_elements = [] @@ -3667,20 +3782,6 @@ class FlatCAMGrbEditor(QtCore.QObject): except Exception: 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): """ Returns list of shapes that are selected in the editor. @@ -3831,15 +3932,16 @@ class FlatCAMGrbEditor(QtCore.QObject): return geoms else: if geom_el in selection: + geometric_data = geom_el.geo buffered_geom_el = dict() 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)) 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)) 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)) return buffered_geom_el else: @@ -3888,16 +3990,17 @@ class FlatCAMGrbEditor(QtCore.QObject): return geoms else: if geom_el in selection: + geometric_data = geom_el.geo scaled_geom_el = dict() if 'solid' in geom_el: 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: 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: 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 else: @@ -4607,117 +4710,126 @@ class TransformEditorTool(FlatCAMTool): if not elem_list: self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to rotate!")) return - else: - with self.app.proc_container.new(_("Appying Rotate")): - try: - # first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest - # bounding box - for geo_el in elem_list: - xmin, ymin, xmax, ymax = geo_el..bounds() + + with self.app.proc_container.new(_("Appying Rotate")): + try: + # first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest + # bounding box + for el in elem_list: + if 'solid' in el: + xmin, ymin, xmax, ymax = el['solid'].bounds() xminlist.append(xmin) yminlist.append(ymin) xmaxlist.append(xmax) ymaxlist.append(ymax) + # get the minimum x,y and maximum x,y for all objects selected + xminimal = min(xminlist) + yminimal = min(yminlist) + xmaximal = max(xmaxlist) + ymaximal = max(ymaxlist) + + self.app.progress.emit(20) + px = 0.5 * (xminimal + xmaximal) + py = 0.5 * (yminimal + ymaximal) + + 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.app.inform.emit(_("[success] Done. Rotate completed.")) + self.app.progress.emit(100) + except Exception as e: + self.app.inform.emit(_("[ERROR_NOTCL] Due of %s, rotation movement was not executed.") % str(e)) + return + + def on_flip(self, axis): + elem_list = self.draw_app.selected + xminlist = [] + yminlist = [] + xmaxlist = [] + ymaxlist = [] + + if not elem_list: + self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to flip!")) + return + + with self.app.proc_container.new(_("Applying Flip")): + try: + # get mirroring coords from the point entry + if self.flip_ref_cb.isChecked(): + px, py = eval('{}'.format(self.flip_ref_entry.text())) + # get mirroing coords from the center of an all-enclosing bounding box + else: + # first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest + # bounding box + for el in elem_list: + if 'solid' in el: + xmin, ymin, xmax, ymax = el['solid'].bounds() + xminlist.append(xmin) + yminlist.append(ymin) + xmaxlist.append(xmax) + ymaxlist.append(ymax) + # get the minimum x,y and maximum x,y for all objects selected xminimal = min(xminlist) yminimal = min(yminlist) xmaximal = max(xmaxlist) ymaximal = max(ymaxlist) - self.app.progress.emit(20) + px = 0.5 * (xminimal + xmaximal) + py = 0.5 * (yminimal + ymaximal) - for sel_sha in shape_list: - px = 0.5 * (xminimal + xmaximal) - py = 0.5 * (yminimal + ymaximal) + self.app.progress.emit(20) - sel_sha.rotate(-num, point=(px, py)) - self.draw_app.plot_all() - # self.draw_app.add_shape(DrawToolShape(sel_sha.geo)) + # execute mirroring + for sel_el in elem_list: + if axis is 'X': + 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 ...')) + elif axis is 'Y': + 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.draw_app.plot_all() + self.app.progress.emit(100) - # self.draw_app.transform_complete.emit() - - self.app.inform.emit(_("[success] Done. Rotate completed.")) - - self.app.progress.emit(100) - - except Exception as e: - self.app.inform.emit(_("[ERROR_NOTCL] Due of %s, rotation movement was not executed.") % str(e)) - return - - def on_flip(self, axis): - shape_list = self.draw_app.selected - xminlist = [] - yminlist = [] - xmaxlist = [] - ymaxlist = [] - - if not shape_list: - self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to flip!")) - return - else: - with self.app.proc_container.new(_("Applying Flip")): - try: - # get mirroring coords from the point entry - if self.flip_ref_cb.isChecked(): - px, py = eval('{}'.format(self.flip_ref_entry.text())) - # get mirroing coords from the center of an all-enclosing bounding box - else: - # 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) - xmaxlist.append(xmax) - ymaxlist.append(ymax) - - # get the minimum x,y and maximum x,y for all objects selected - xminimal = min(xminlist) - yminimal = min(yminlist) - xmaximal = max(xmaxlist) - ymaximal = max(ymaxlist) - - px = 0.5 * (xminimal + xmaximal) - py = 0.5 * (yminimal + ymaximal) - - self.app.progress.emit(20) - - # execute mirroring - for sha in shape_list: - if axis is 'X': - sha.mirror('X', (px, py)) - self.app.inform.emit(_('[success] Flip on the Y axis done ...')) - elif axis is 'Y': - sha.mirror('Y', (px, py)) - self.app.inform.emit(_('[success] Flip on the X axis done ...')) - self.draw_app.plot_all() - - # self.draw_app.add_shape(DrawToolShape(sha.geo)) - # - # self.draw_app.transform_complete.emit() - - self.app.progress.emit(100) - - except Exception as e: - self.app.inform.emit(_("[ERROR_NOTCL] Due of %s, Flip action was not executed.") % str(e)) - return + except Exception as e: + self.app.inform.emit(_("[ERROR_NOTCL] Due of %s, Flip action was not executed.") % str(e)) + return def on_skew(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 shear/skew!")) return else: with self.app.proc_container.new(_("Applying Skew")): 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) + # first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest + # bounding box + for el in elem_list: + if 'solid' in el: + xmin, ymin, xmax, ymax = el['solid'].bounds() + xminlist.append(xmin) + yminlist.append(ymin) # get the minimum x,y and maximum x,y for all objects selected xminimal = min(xminlist) @@ -4725,16 +4837,22 @@ class TransformEditorTool(FlatCAMTool): self.app.progress.emit(20) - for sha in shape_list: + for sel_el in elem_list: 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': - sha.skew(0, num, point=(xminimal, yminimal)) - self.draw_app.plot_all() - - # self.draw_app.add_shape(DrawToolShape(sha.geo)) - # - # self.draw_app.transform_complete.emit() + 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.app.inform.emit(_('[success] Skew on the %s axis done ...') % str(axis)) self.app.progress.emit(100) @@ -4744,25 +4862,27 @@ class TransformEditorTool(FlatCAMTool): return def on_scale(self, axis, xfactor, yfactor, point=None): - shape_list = self.draw_app.selected + elem_list = self.draw_app.selected xminlist = [] yminlist = [] xmaxlist = [] ymaxlist = [] - if not shape_list: + if not elem_list: self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to scale!")) return else: with self.app.proc_container.new(_("Applying Scale")): 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) - xmaxlist.append(xmax) - ymaxlist.append(ymax) + # first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest + # bounding box + for el in elem_list: + if 'solid' in el: + xmin, ymin, xmax, ymax = el['solid'].bounds() + xminlist.append(xmin) + yminlist.append(ymin) + xmaxlist.append(xmax) + ymaxlist.append(ymax) # get the minimum x,y and maximum x,y for all objects selected xminimal = min(xminlist) @@ -4779,13 +4899,14 @@ class TransformEditorTool(FlatCAMTool): px = 0 py = 0 - for sha in shape_list: - sha.scale(xfactor, yfactor, point=(px, py)) - self.draw_app.plot_all() - - # self.draw_app.add_shape(DrawToolShape(sha.geo)) - # - # self.draw_app.transform_complete.emit() + for sel_el in elem_list: + 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.app.inform.emit(_('[success] Scale on the %s axis done ...') % str(axis)) self.app.progress.emit(100) @@ -4794,38 +4915,33 @@ class TransformEditorTool(FlatCAMTool): return def on_offset(self, axis, num): - shape_list = self.draw_app.selected - xminlist = [] - yminlist = [] + elem_list = self.draw_app.selected - if not shape_list: + if not elem_list: self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to offset!")) return else: with self.app.proc_container.new(_("Applying Offset")): 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) - for sha in shape_list: + for sel_el in elem_list: 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': - 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.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.progress.emit(100)