diff --git a/FlatCAM.py b/FlatCAM.py index 5fde5421..50fc24ea 100644 --- a/FlatCAM.py +++ b/FlatCAM.py @@ -266,9 +266,16 @@ class FlatCAMGerber(FlatCAMObj, Gerber): self.options['bboxmargin'] *= factor def plot(self, figure): + """ + Plots the object on to the specified figure. + + :param figure: Matplotlib figure on which to plot. + """ + FlatCAMObj.plot(self, figure) - #self.create_geometry() + if not self.options["plot"]: + return if self.options["mergepolys"]: geometry = self.solid_geometry @@ -282,12 +289,22 @@ class FlatCAMGerber(FlatCAMObj, Gerber): else: linespec = 'k-' - for poly in geometry: - x, y = poly.exterior.xy - self.axes.plot(x, y, linespec) - for ints in poly.interiors: - x, y = ints.coords.xy + if self.options["solid"]: + for poly in geometry: + # TODO: Too many things hardcoded. + patch = PolygonPatch(poly, + facecolor="#BBF268", + edgecolor="#006E20", + alpha=0.75, + zorder=2) + self.axes.add_patch(patch) + else: + for poly in geometry: + x, y = poly.exterior.xy self.axes.plot(x, y, linespec) + for ints in poly.interiors: + x, y = ints.coords.xy + self.axes.plot(x, y, linespec) self.app.canvas.queue_draw() @@ -346,8 +363,9 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): def plot(self, figure): FlatCAMObj.plot(self, figure) - #self.setup_axes(figure) - #self.create_geometry() + + if not self.options["plot"]: + return # Plot excellon for geo in self.solid_geometry: @@ -400,15 +418,11 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): self.options.update({ "plot": True, - "solid": False, - "multicolored": False, "tooldia": 0.4 / 25.4 # 0.4mm in inches }) self.form_kinds.update({ "plot": "cb", - "solid": "cb", - "multicolored": "cb", "tooldia": "entry_eval" }) @@ -418,8 +432,11 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): self.ser_attrs += ['options', 'kind'] def plot(self, figure): - FlatCAMObj.plot(self, figure) - #self.setup_axes(figure) + FlatCAMObj.plot(self, figure) # Only sets up axes + + if not self.options["plot"]: + return + self.plot2(self.axes, tooldia=self.options["tooldia"]) self.app.on_zoom_fit(None) self.app.canvas.queue_draw() @@ -494,7 +511,9 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): def plot(self, figure): FlatCAMObj.plot(self, figure) - #self.setup_axes(figure) + + if not self.options["plot"]: + return try: _ = iter(self.solid_geometry) @@ -1262,6 +1281,11 @@ class App: ######################################## ## EVENT HANDLERS ## ######################################## + def on_cb_plot_toggled(self, widget): + self.get_current().read_form() + self.get_current().plot(self.figure) + self.on_zoom_fit(None) # TODO: Does not update correctly otherwise. + def on_about(self, widget): """ Opens the 'About' dialog box. @@ -1269,11 +1293,11 @@ class App: :param widget: Ignored. :return: None """ + about = self.builder.get_object("aboutdialog") response = about.run() about.destroy() - def on_create_mirror(self, widget): """ Creates a mirror image of a Gerber object to be used as a bottom @@ -1322,6 +1346,7 @@ class App: :param widget: Ignored. :return: None """ + # Mirror axis. Same as in on_create_mirror. axis = self.get_radio_value({"rb_mirror_x": "X", "rb_mirror_y": "Y"}) @@ -1445,6 +1470,7 @@ class App: :param widget: Ignored. :return: None """ + if self.toggle_units_ignore: return @@ -1522,6 +1548,7 @@ class App: :param param: Ignored. :return: None """ + def on_success(app_obj, filename): app_obj.open_project(filename) @@ -1536,6 +1563,7 @@ class App: :param param: Ignored. :return: None """ + if self.project_filename is None: self.on_file_saveprojectas(None) else: @@ -1551,6 +1579,7 @@ class App: :param param: Ignored. :return: None """ + def on_success(app_obj, filename): assert isinstance(app_obj, App) app_obj.save_project(filename) @@ -1569,6 +1598,7 @@ class App: :param param: Ignore. :return: None """ + def on_success(app_obj, filename): assert isinstance(app_obj, App) app_obj.save_project(filename) @@ -2016,7 +2046,8 @@ class App: # Object initialization function for app.new_object() def job_init(job_obj, app_obj): assert isinstance(job_obj, FlatCAMCNCjob) - geometry = app_obj.stuff[app_obj.selected_item_name] + #geometry = app_obj.stuff[app_obj.selected_item_name] + geometry = app_obj.get_current() assert isinstance(geometry, FlatCAMGeometry) geometry.read_form() @@ -2027,13 +2058,15 @@ class App: job_obj.options["tooldia"] = geometry.options["cnctooldia"] GLib.idle_add(lambda: app_obj.set_progress_bar(0.4, "Analyzing Geometry...")) - job_obj.generate_from_geometry(geometry) + # TODO: The tolerance should not be hard coded. Just for testing. + job_obj.generate_from_geometry(geometry, tolerance=0.001) GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Parsing G-Code...")) job_obj.gcode_parse() - GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Creating New Geometry...")) - job_obj.create_geometry() + # TODO: job_obj.create_geometry creates stuff that is not used. + #GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Creating New Geometry...")) + #job_obj.create_geometry() GLib.idle_add(lambda: app_obj.set_progress_bar(0.8, "Plotting...")) @@ -2119,13 +2152,16 @@ class App: # Update UI self.build_list() # Update the items list - def on_replot(self, widget): + def on_toolbar_replot(self, widget): """ Callback for toolbar button. Re-plots all objects. :param widget: The widget from which this was called. :return: None """ + + self.get_current().read_form() + self.plot_all() def on_clear_plots(self, widget): diff --git a/FlatCAM.ui b/FlatCAM.ui index 54e42ead..f4d8f815 100644 --- a/FlatCAM.ui +++ b/FlatCAM.ui @@ -567,6 +567,7 @@ THE SOFTWARE. 0 True True + False @@ -575,34 +576,10 @@ THE SOFTWARE. - - Solid - True - True - False - 0 - True - - - False - True - 4 - + - - Multi-colored - True - True - False - 0 - True - - - False - True - 5 - + @@ -874,6 +851,7 @@ THE SOFTWARE. 0 True True + False @@ -1301,6 +1279,7 @@ THE SOFTWARE. 0 True True + False @@ -1836,6 +1815,7 @@ THE SOFTWARE. 0 True True + False @@ -2813,7 +2793,7 @@ to application defaults. Re-plot True gtk-redo - + False @@ -4078,36 +4058,10 @@ to application defaults. - - Solid - True - True - False - 0 - True - - - - False - True - 37 - + - - Multi-colored - True - True - False - 0 - True - - - - False - True - 38 - + diff --git a/camlib.py b/camlib.py index 9253ea5f..bdf9e018 100644 --- a/camlib.py +++ b/camlib.py @@ -925,11 +925,13 @@ class CNCjob(Geometry): """ Creates gcode for this object from an Excellon object for the specified tools. - @param exobj: Excellon object to process - @type exobj: Excellon - @param tools: Comma separated tool names - @type: tools: str - @return: None + + :param exobj: Excellon object to process + :type exobj: Excellon + :param tools: Comma separated tool names + :type: tools: str + :return: None + :rtype: None """ print "Creating CNC Job from Excellon..." if tools == "all": @@ -970,9 +972,21 @@ class CNCjob(Geometry): self.gcode = gcode - def generate_from_geometry(self, geometry, append=True, tooldia=None): + def generate_from_geometry(self, geometry, append=True, tooldia=None, tolerance=0): """ - Generates G-Code from a Geometry object. + Generates G-Code from a Geometry object. Stores in ``self.gcode``. + + :param geometry: Geometry defining the toolpath + :type geometry: Geometry + :param append: Wether to append to self.gcode or re-write it. + :type append: bool + :param tooldia: If given, sets the tooldia property but does + not affect the process in any other way. + :type tooldia: bool + :param tolerance: All points in the simplified object will be within the + tolerance distance of the original geometry. + :return: None + :rtype: None """ if tooldia is not None: self.tooldia = tooldia @@ -993,11 +1007,11 @@ class CNCjob(Geometry): for geo in geometry.solid_geometry: if type(geo) == Polygon: - self.gcode += self.polygon2gcode(geo) + self.gcode += self.polygon2gcode(geo, tolerance=tolerance) continue if type(geo) == LineString or type(geo) == LinearRing: - self.gcode += self.linear2gcode(geo) + self.gcode += self.linear2gcode(geo, tolerance=tolerance) continue if type(geo) == Point: @@ -1006,7 +1020,7 @@ class CNCjob(Geometry): if type(geo) == MultiPolygon: for poly in geo: - self.gcode += self.polygon2gcode(poly) + self.gcode += self.polygon2gcode(poly, tolerance=tolerance) continue print "WARNING: G-code generation not implemented for %s" % (str(type(geo))) @@ -1017,7 +1031,10 @@ class CNCjob(Geometry): def pre_parse(self, gtext): """ - gtext is a single string with g-code + Separates parts of the G-Code text into a list of dictionaries. + Used by ``self.gcode_parse()``. + + :param gtext: A single string with g-code """ # Units: G20-inches, G21-mm @@ -1177,9 +1194,18 @@ class CNCjob(Geometry): def plot2(self, axes, tooldia=None, dpi=75, margin=0.1, color={"T": ["#F0E24D", "#B5AB3A"], "C": ["#5E6CFF", "#4650BD"]}, - alpha={"T": 0.3, "C": 1.0}): + alpha={"T": 0.3, "C": 1.0}, tool_tolerance=0.001): """ Plots the G-code job onto the given axes. + + :param axes: Matplotlib axes on which to plot. + :param tooldia: Tool diameter. + :param dpi: Not used! + :param margin: Not used! + :param color: Color specification. + :param alpha: Transparency specification. + :param tool_tolerance: Tolerance when drawing the toolshape. + :return: None """ if tooldia is None: tooldia = self.tooldia @@ -1194,32 +1220,44 @@ class CNCjob(Geometry): axes.plot(x, y, linespec, color=linecolor) else: for geo in self.gcode_parsed: - poly = geo['geom'].buffer(tooldia/2.0) + poly = geo['geom'].buffer(tooldia/2.0).simplify(tool_tolerance) patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0], edgecolor=color[geo['kind'][0]][1], alpha=alpha[geo['kind'][0]], zorder=2) axes.add_patch(patch) def create_geometry(self): + # TODO: This takes forever. Too much data? self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed]) - def polygon2gcode(self, polygon): + def polygon2gcode(self, polygon, tolerance=0): """ Creates G-Code for the exterior and all interior paths of a polygon. :param polygon: A Shapely.Polygon :type polygon: Shapely.Polygon + :param tolerance: All points in the simplified object will be within the + tolerance distance of the original geometry. + :type tolerance: float + :return: G-code to cut along polygon. + :rtype: str """ + + if tolerance > 0: + target_polygon = polygon.simplify(tolerance) + else: + target_polygon = polygon + gcode = "" t = "G0%d X%.4fY%.4f\n" - path = list(polygon.exterior.coords) # Polygon exterior + path = list(target_polygon.exterior.coords) # Polygon exterior gcode += t % (0, path[0][0], path[0][1]) # Move to first point gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting for pt in path[1:]: gcode += t % (1, pt[0], pt[1]) # Linear motion to point gcode += "G00 Z%.4f\n" % self.z_move # Stop cutting - for ints in polygon.interiors: # Polygon interiors + for ints in target_polygon.interiors: # Polygon interiors path = list(ints.coords) gcode += t % (0, path[0][0], path[0][1]) # Move to first point gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting @@ -1228,11 +1266,28 @@ class CNCjob(Geometry): gcode += "G00 Z%.4f\n" % self.z_move # Stop cutting return gcode - def linear2gcode(self, linear): + def linear2gcode(self, linear, tolerance=0): + """ + Generates G-code to cut along the linear feature. + + :param linear: The path to cut along. + :type: Shapely.LinearRing or Shapely.Linear String + :param tolerance: All points in the simplified object will be within the + tolerance distance of the original geometry. + :type tolerance: float + :return: G-code to cut alon the linear feature. + :rtype: str + """ + + if tolerance > 0: + target_linear = linear.simplify(tolerance) + else: + target_linear = linear + gcode = "" t = "G0%d X%.4fY%.4f\n" - path = list(linear.coords) - gcode += t%(0, path[0][0], path[0][1]) # Move to first point + path = list(target_linear.coords) + gcode += t % (0, path[0][0], path[0][1]) # Move to first point gcode += "G01 Z%.4f\n" % self.z_cut # Start cutting for pt in path[1:]: gcode += t % (1, pt[0], pt[1]) # Linear motion to point diff --git a/defaults.json b/defaults.json index 1364be29..4d942731 100644 --- a/defaults.json +++ b/defaults.json @@ -1 +1 @@ -{"cncjob_multicolored": false, "geometry_paintoverlap": 0.15, "geometry_plot": true, "cncjob_solid": false, "gerber_isotooldia": 0.016, "gerber_plot": true, "gerber_mergepolys": true, "gerber_cutoutgapsize": 0.15, "geometry_feedrate": 5.0, "units": "IN", "excellon_travelz": 0.1, "gerber_multicolored": false, "gerber_solid": false, "excellon_plot": true, "excellon_feedrate": 5.0, "cncjob_tooldia": 0.016, "geometry_travelz": 0.1, "gerber_cutoutmargin": 0.2, "excellon_solid": false, "geometry_paintmargin": 0.01, "geometry_cutz": -0.002, "gerber_noncoppermargin": 0.0, "gerber_gaps": "4", "excellon_multicolored": false, "geometry_painttooldia": 0.0625, "cncjob_plot": true, "excellon_drillz": -0.1, "gerber_bboxrounded": false, "geometry_multicolored": false, "geometry_cnctooldia": 0.016, "geometry_solid": false, "gerber_bboxmargin": 0.0} \ No newline at end of file +{"geometry_paintoverlap": 0.15, "geometry_plot": true, "excellon_feedrate": 5.0, "gerber_plot": true, "gerber_mergepolys": true, "excellon_drillz": -0.1, "geometry_feedrate": 5.0, "units": "IN", "excellon_travelz": 0.1, "gerber_multicolored": false, "gerber_solid": true, "excellon_plot": true, "gerber_isotooldia": 0.016, "gerber_bboxmargin": 0.0, "cncjob_tooldia": 0.016, "geometry_travelz": 0.1, "gerber_cutoutmargin": 0.2, "excellon_solid": false, "geometry_paintmargin": 0.01, "geometry_cutz": -0.002, "geometry_cnctooldia": 0.016, "gerber_gaps": "4", "excellon_multicolored": false, "geometry_painttooldia": 0.0625, "cncjob_plot": true, "gerber_cutoutgapsize": 0.15, "gerber_bboxrounded": false, "geometry_multicolored": false, "gerber_noncoppermargin": 0.0, "geometry_solid": false} \ No newline at end of file