Jelajahi Sumber

Changed project name to FlatCAM.

Juan Pablo Caram 12 tahun lalu
induk
melakukan
e930de1793
3 mengubah file dengan 206 tambahan dan 105 penghapusan
  1. 146 56
      FlatCAM.py
  2. 1 0
      FlatCAM.ui
  3. 59 49
      camlib.py

+ 146 - 56
cirkuix.py → FlatCAM.py

@@ -13,11 +13,11 @@ import sys
 
 
 ########################################
-##            CirkuixObj              ##
+##            FlatCAMObj              ##
 ########################################
-class CirkuixObj:
+class FlatCAMObj:
     """
-    Base type of objects handled in Cirkuix. These become interactive
+    Base type of objects handled in FlatCAM. These become interactive
     in the GUI, can be plotted, and their options can be modified
     by the user in their respective forms.
     """
@@ -178,25 +178,25 @@ class CirkuixObj:
     def deserialize(self, obj_dict):
         """
         Re-builds an object from its serialized version.
-        @param obj_dict: Dictionary representing a CirkuixObj
+        @param obj_dict: Dictionary representing a FlatCAMObj
         @type obj_dict: dict
         @return None
         """
         return
 
 
-class CirkuixGerber(CirkuixObj, Gerber):
+class FlatCAMGerber(FlatCAMObj, Gerber):
     """
     Represents Gerber code.
     """
 
     def __init__(self, name):
         Gerber.__init__(self)
-        CirkuixObj.__init__(self, name)
+        FlatCAMObj.__init__(self, name)
 
         self.kind = "gerber"
 
-        # The 'name' is already in self.options from CirkuixObj
+        # The 'name' is already in self.options from FlatCAMObj
         self.options.update({
             "plot": True,
             "mergepolys": True,
@@ -211,7 +211,7 @@ class CirkuixGerber(CirkuixObj, Gerber):
             "bboxrounded": False
         })
 
-        # The 'name' is already in self.form_kinds from CirkuixObj
+        # The 'name' is already in self.form_kinds from FlatCAMObj
         self.form_kinds.update({
             "plot": "cb",
             "mergepolys": "cb",
@@ -235,6 +235,15 @@ class CirkuixGerber(CirkuixObj, Gerber):
         self.ser_attrs += ['options', 'kind']
 
     def convert_units(self, units):
+        """
+        Converts the units of the object by scaling dimensions in all geometry
+        and options.
+
+        :param units: Units to which to convert the object: "IN" or "MM".
+        :type units: str
+        :return: None
+        :rtype: None
+        """
         factor = Gerber.convert_units(self, units)
 
         self.options['isotooldia'] *= factor
@@ -244,7 +253,7 @@ class CirkuixGerber(CirkuixObj, Gerber):
         self.options['bboxmargin'] *= factor
 
     def plot(self, figure):
-        CirkuixObj.plot(self, figure)
+        FlatCAMObj.plot(self, figure)
 
         self.create_geometry()
 
@@ -276,14 +285,14 @@ class CirkuixGerber(CirkuixObj, Gerber):
         }
 
 
-class CirkuixExcellon(CirkuixObj, Excellon):
+class FlatCAMExcellon(FlatCAMObj, Excellon):
     """
     Represents Excellon code.
     """
 
     def __init__(self, name):
         Excellon.__init__(self)
-        CirkuixObj.__init__(self, name)
+        FlatCAMObj.__init__(self, name)
 
         self.kind = "excellon"
 
@@ -323,7 +332,7 @@ class CirkuixExcellon(CirkuixObj, Excellon):
         self.options['feedrate'] *= factor
 
     def plot(self, figure):
-        CirkuixObj.plot(self, figure)
+        FlatCAMObj.plot(self, figure)
         #self.setup_axes(figure)
         self.create_geometry()
 
@@ -344,7 +353,7 @@ class CirkuixExcellon(CirkuixObj, Excellon):
         box.set_orientation(Gtk.Orientation(1))
         win.add(box)
         for tool in self.tools:
-            self.tool_cbs[tool] = Gtk.CheckButton(label=tool + ": " + self.tools[tool])
+            self.tool_cbs[tool] = Gtk.CheckButton(label=tool + ": " + str(self.tools[tool]))
             box.pack_start(self.tool_cbs[tool], False, False, 1)
         button = Gtk.Button(label="Accept")
         box.pack_start(button, False, False, 1)
@@ -363,7 +372,7 @@ class CirkuixExcellon(CirkuixObj, Excellon):
         button.connect("clicked", on_accept)
 
 
-class CirkuixCNCjob(CirkuixObj, CNCjob):
+class FlatCAMCNCjob(FlatCAMObj, CNCjob):
     """
     Represents G-Code.
     """
@@ -372,7 +381,7 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
                  feedrate=3.0, z_cut=-0.002, tooldia=0.0):
         CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
                         feedrate=feedrate, z_cut=z_cut, tooldia=tooldia)
-        CirkuixObj.__init__(self, name)
+        FlatCAMObj.__init__(self, name)
 
         self.kind = "cncjob"
 
@@ -396,21 +405,25 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
         self.ser_attrs += ['options', 'kind']
 
     def plot(self, figure):
-        CirkuixObj.plot(self, figure)
+        FlatCAMObj.plot(self, figure)
         #self.setup_axes(figure)
         self.plot2(self.axes, tooldia=self.options["tooldia"])
         self.app.on_zoom_fit(None)
         self.app.canvas.queue_draw()
 
+    def convert_units(self, units):
+        factor = CNCjob.convert_units(self, units)
+        print "FlatCAMCNCjob.convert_units()"
+        self.options["tooldia"] *= factor
 
-class CirkuixGeometry(CirkuixObj, Geometry):
+class FlatCAMGeometry(FlatCAMObj, Geometry):
     """
     Geometric object not associated with a specific
     format.
     """
 
     def __init__(self, name):
-        CirkuixObj.__init__(self, name)
+        FlatCAMObj.__init__(self, name)
         Geometry.__init__(self)
 
         self.kind = "geometry"
@@ -467,7 +480,7 @@ class CirkuixGeometry(CirkuixObj, Geometry):
         return factor
 
     def plot(self, figure):
-        CirkuixObj.plot(self, figure)
+        FlatCAMObj.plot(self, figure)
         #self.setup_axes(figure)
 
         try:
@@ -525,11 +538,11 @@ class App:
         GObject.threads_init()
 
         ## GUI ##
-        self.gladefile = "cirkuix.ui"
+        self.gladefile = "FlatCAM.ui"
         self.builder = Gtk.Builder()
         self.builder.add_from_file(self.gladefile)
         self.window = self.builder.get_object("window1")
-        self.window.set_title("Cirkuix")
+        self.window.set_title("FlatCAM")
         self.position_label = self.builder.get_object("label3")
         self.grid = self.builder.get_object("grid1")
         self.notebook = self.builder.get_object("notebook1")
@@ -560,7 +573,7 @@ class App:
 
         #### DATA ####
         self.setup_obj_classes()
-        self.stuff = {}    # CirkuixObj's by name
+        self.stuff = {}    # FlatCAMObj's by name
         self.mouse = None  # Mouse coordinates over plot
 
         # What is selected by the user. It is
@@ -571,6 +584,8 @@ class App:
         # the options are being changed by the program and not the user.
         self.options_update_ignore = False
 
+        self.toggle_units_ignore = False
+
         self.defaults = {
             "units": "in"
         }  # Application defaults
@@ -590,10 +605,10 @@ class App:
 
         # self.combos = []
 
-        # Options for each kind of CirkuixObj.
+        # Options for each kind of FlatCAMObj.
         # Example: 'gerber_plot': 'cb'. The widget name would be: 'cb_app_gerber_plot'
-        for CirkuixClass in [CirkuixExcellon, CirkuixGeometry, CirkuixGerber, CirkuixCNCjob]:
-            obj = CirkuixClass("no_name")
+        for FlatCAMClass in [FlatCAMExcellon, FlatCAMGeometry, FlatCAMGerber, FlatCAMCNCjob]:
+            obj = FlatCAMClass("no_name")
             for option in obj.form_kinds:
                 self.form_kinds[obj.kind + "_" + option] = obj.form_kinds[option]
                 # if obj.form_kinds[option] == "radio":
@@ -657,11 +672,11 @@ class App:
 
     def setup_obj_classes(self):
         """
-        Sets up application specifics on the CirkuixObj class.
+        Sets up application specifics on the FlatCAMObj class.
 
         :return: None
         """
-        CirkuixObj.app = self
+        FlatCAMObj.app = self
 
     def setup_project_list(self):
         """
@@ -873,7 +888,7 @@ class App:
 
     def new_object(self, kind, name, initialize):
         """
-        Creates a new specalized CirkuixObj and attaches it to the application,
+        Creates a new specalized FlatCAMObj and attaches it to the application,
         this is, updates the GUI accordingly, any other records and plots it.
 
         :param kind: The kind of object to create. One of 'gerber',
@@ -896,12 +911,13 @@ class App:
 
         # Create object
         classdict = {
-            "gerber": CirkuixGerber,
-            "excellon": CirkuixExcellon,
-            "cncjob": CirkuixCNCjob,
-            "geometry": CirkuixGeometry
+            "gerber": FlatCAMGerber,
+            "excellon": FlatCAMExcellon,
+            "cncjob": FlatCAMCNCjob,
+            "geometry": FlatCAMGeometry
         }
         obj = classdict[kind](name)
+        obj.units = self.options["units"]  # TODO: The constructor should look at defaults.
 
         # Initialize as per user request
         # User must take care to implement initialize
@@ -958,10 +974,10 @@ class App:
 
     def get_current(self):
         """
-        Returns the currently selected CirkuixObj in the application.
+        Returns the currently selected FlatCAMObj in the application.
 
-        :return: Currently selected CirkuixObj in the application.
-        :rtype: CirkuixObj or None
+        :return: Currently selected FlatCAMObj in the application.
+        :rtype: FlatCAMObj or None
         """
         try:
             return self.stuff[self.selected_item_name]
@@ -1095,6 +1111,7 @@ class App:
 
         # Set the on-change callback to do nothing while we do the changes.
         self.options_update_ignore = True
+        self.toggle_units_ignore = True
 
         combo_sel = self.combo_options.get_active()
         options_set = [self.options, self.defaults][combo_sel]
@@ -1102,6 +1119,7 @@ class App:
             self.set_form_item(option, options_set[option])
 
         self.options_update_ignore = False
+        self.toggle_units_ignore = False
 
     def set_form_item(self, name, value):
         """
@@ -1201,6 +1219,7 @@ class App:
         # Project options
         self.options.update(d['options'])
         self.project_filename = filename
+        self.units_label.set_text(self.options["units"])
 
         # Re create objects
         for obj in d['objs']:
@@ -1213,6 +1232,76 @@ class App:
     ########################################
     ##         EVENT HANDLERS             ##
     ########################################
+    def on_toggle_units(self, widget):
+        if self.toggle_units_ignore:
+            return
+
+        combo_sel = self.combo_options.get_active()
+        options_set = [self.options, self.defaults][combo_sel]
+
+        # Options to scale
+        dimensions = ['gerber_isotooldia', 'gerber_cutoutmargin', 'gerber_cutoutgapsize',
+                      'gerber_noncoppermargin', 'gerber_bboxmargin', 'excellon_drillz',
+                      'excellon_travelz', 'excellon_feedrate', 'cncjob_tooldia',
+                      'geometry_cutz', 'geometry_travelz', 'geometry_feedrate',
+                      'geometry_cnctooldia', 'geometry_painttooldia', 'geometry_paintoverlap',
+                      'geometry_paintmargin']
+
+        def scale_options(factor):
+            for dim in dimensions:
+                options_set[dim] *= factor
+
+        factor = 1/25.4
+        if self.builder.get_object('rb_mm').get_active():
+            factor = 25.4
+
+        # App units. Convert without warning.
+        if combo_sel == 1:
+            self.read_form()
+            scale_options(factor)
+            self.options2form()
+            return
+
+        label = Gtk.Label("Changing the units of the project causes all geometrical \n" + \
+                            "properties of all objects to be scaled accordingly. Continue?")
+        dialog = Gtk.Dialog("Changing Project Units", self.window, 0,
+                            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+                             Gtk.STOCK_OK, Gtk.ResponseType.OK))
+        dialog.set_default_size(150, 100)
+        dialog.set_modal(True)
+        box = dialog.get_content_area()
+        box.set_border_width(10)
+        box.add(label)
+        dialog.show_all()
+        response = dialog.run()
+        dialog.destroy()
+
+        if response == Gtk.ResponseType.OK:
+            print "Converting units..."
+            print "Converting options..."
+            self.read_form()
+            scale_options(factor)
+            self.options2form()
+            for obj in self.stuff:
+                units = self.get_radio_value({"rb_mm": "MM", "rb_inch": "IN"})
+                print "Converting ", obj, " to ", units
+                self.stuff[obj].convert_units(units)
+            current = self.get_current()
+            if current is not None:
+                current.to_form()
+            self.plot_all()
+        else:
+            # Undo toggling
+            self.toggle_units_ignore = True
+            if self.builder.get_object('rb_mm').get_active():
+                self.builder.get_object('rb_inch').set_active(True)
+            else:
+                self.builder.get_object('rb_mm').set_active(True)
+            self.toggle_units_ignore = False
+
+        self.read_form()
+        self.units_label.set_text("[" + self.options["units"] + "]")
+
     def on_file_openproject(self, param):
         """
         Callback for menu item File->Open Project. Opens a file chooser and calls
@@ -1443,7 +1532,7 @@ class App:
         :return: None
         """
         obj = self.get_current()
-        assert isinstance(obj, CirkuixObj)
+        assert isinstance(obj, FlatCAMObj)
         factor = self.get_eval("entry_eval_" + obj.kind + "_scalefactor")
         obj.scale(factor)
         obj.to_form()
@@ -1480,7 +1569,7 @@ class App:
     def on_generate_gerber_bounding_box(self, widget):
         """
         Callback for request from the Gerber form to generate a bounding box for the
-        geometry in the object. Creates a CirkuixGeometry with the bounding box.
+        geometry in the object. Creates a FlatCAMGeometry with the bounding box.
 
         :param widget: Ignored.
         :return: None
@@ -1490,7 +1579,7 @@ class App:
         name = self.selected_item_name + "_bbox"
 
         def geo_init(geo_obj, app_obj):
-            assert isinstance(geo_obj, CirkuixGeometry)
+            assert isinstance(geo_obj, FlatCAMGeometry)
             bounding_box = gerber.solid_geometry.envelope.buffer(gerber.options["bboxmargin"])
             if not gerber.options["bboxrounded"]:
                 bounding_box = bounding_box.envelope
@@ -1535,14 +1624,14 @@ class App:
 
         job_name = self.selected_item_name + "_cnc"
         excellon = self.get_current()
-        assert isinstance(excellon, CirkuixExcellon)
+        assert isinstance(excellon, FlatCAMExcellon)
         excellon.read_form()
 
         # Object initialization function for app.new_object()
         def job_init(job_obj, app_obj):
             excellon_ = self.get_current()
-            assert isinstance(excellon_, CirkuixExcellon)
-            assert isinstance(job_obj, CirkuixCNCjob)
+            assert isinstance(excellon_, FlatCAMExcellon)
+            assert isinstance(job_obj, FlatCAMCNCjob)
 
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Creating CNC Job..."))
             job_obj.z_cut = excellon_.options["drillz"]
@@ -1581,7 +1670,7 @@ class App:
         :return: None
         """
         excellon = self.get_current()
-        assert isinstance(excellon, CirkuixExcellon)
+        assert isinstance(excellon, FlatCAMExcellon)
         excellon.show_tool_chooser()
 
     def on_entry_eval_activate(self, widget):
@@ -1595,7 +1684,7 @@ class App:
         """
         self.on_eval_update(widget)
         obj = self.get_current()
-        assert isinstance(obj, CirkuixObj)
+        assert isinstance(obj, FlatCAMObj)
         obj.read_form()
 
     def on_gerber_generate_noncopper(self, widget):
@@ -1610,9 +1699,9 @@ class App:
         name = self.selected_item_name + "_noncopper"
 
         def geo_init(geo_obj, app_obj):
-            assert isinstance(geo_obj, CirkuixGeometry)
+            assert isinstance(geo_obj, FlatCAMGeometry)
             gerber = app_obj.stuff[app_obj.selected_item_name]
-            assert isinstance(gerber, CirkuixGerber)
+            assert isinstance(gerber, FlatCAMGerber)
             gerber.read_form()
             bounding_box = gerber.solid_geometry.envelope.buffer(gerber.options["noncoppermargin"])
             non_copper = bounding_box.difference(gerber.solid_geometry)
@@ -1713,9 +1802,9 @@ class App:
 
         # Object initialization function for app.new_object()
         def job_init(job_obj, app_obj):
-            assert isinstance(job_obj, CirkuixCNCjob)
+            assert isinstance(job_obj, FlatCAMCNCjob)
             geometry = app_obj.stuff[app_obj.selected_item_name]
-            assert isinstance(geometry, CirkuixGeometry)
+            assert isinstance(geometry, FlatCAMGeometry)
             geometry.read_form()
 
             GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Creating CNC Job..."))
@@ -1752,7 +1841,7 @@ class App:
         Subscribes to the "Click on plot" event and continues
         after the click. Finds the polygon containing
         the clicked point and runs clear_poly() on it, resulting
-        in a new CirkuixGeometry object.
+        in a new FlatCAMGeometry object.
 
         :param widget: The  widget from which this was called.
         :return: None
@@ -1772,7 +1861,7 @@ class App:
 
             # Initializes the new geometry object
             def gen_paintarea(geo_obj, app_obj):
-                assert isinstance(geo_obj, CirkuixGeometry)
+                assert isinstance(geo_obj, FlatCAMGeometry)
                 assert isinstance(app_obj, App)
                 cp = clear_poly(poly.buffer(-geo.options["paintmargin"]), tooldia, overlap)
                 geo_obj.solid_geometry = cp
@@ -1800,7 +1889,7 @@ class App:
 
     def on_delete(self, widget):
         """
-        Delete the currently selected CirkuixObj.
+        Delete the currently selected FlatCAMObj.
 
         :param widget: The widget from which this was called.
         :return: None
@@ -1862,7 +1951,7 @@ class App:
     def on_tree_selection_changed(self, selection):
         """
         Callback for selection change in the project list. This changes
-        the currently selected CirkuixObj.
+        the currently selected FlatCAMObj.
 
         :param selection: Selection associated to the project tree or list
         :type selection: Gtk.TreeSelection
@@ -1991,7 +2080,7 @@ class App:
     def on_fileopengerber(self, param):
         """
         Callback for menu item File->Open Gerber. Defines a function that is then passed
-        to ``self.file_chooser_action()``. It requests the creation of a CirkuixGerber object
+        to ``self.file_chooser_action()``. It requests the creation of a FlatCAMGerber object
         and updates the progress bar throughout the process.
 
         :param param: Ignore
@@ -2021,7 +2110,7 @@ class App:
     def on_fileopenexcellon(self, param):
         """
         Callback for menu item File->Open Excellon. Defines a function that is then passed
-        to ``self.file_chooser_action()``. It requests the creation of a CirkuixExcellon object
+        to ``self.file_chooser_action()``. It requests the creation of a FlatCAMExcellon object
         and updates the progress bar throughout the process.
 
         :param param: Ignore
@@ -2051,7 +2140,7 @@ class App:
     def on_fileopengcode(self, param):
         """
         Callback for menu item File->Open G-Code. Defines a function that is then passed
-        to ``self.file_chooser_action()``. It requests the creation of a CirkuixCNCjob object
+        to ``self.file_chooser_action()``. It requests the creation of a FlatCAMCNCjob object
         and updates the progress bar throughout the process.
 
         :param param: Ignore
@@ -2113,8 +2202,9 @@ class App:
         by the Matplotlib backend and has been registered in ``self.__init__()``.
         For details, see: http://matplotlib.org/users/event_handling.html
 
-        :param event:
-        :return:
+        :param event: Contains information about the event, like which button
+            was clicked, the pixel coordinates and the axes coordinates.
+        :return: None
         """
         # For key presses
         self.canvas.grab_focus()

+ 1 - 0
cirkuix.ui → FlatCAM.ui

@@ -2546,6 +2546,7 @@ to application defaults.</property>
                                             <property name="xalign">0</property>
                                             <property name="active">True</property>
                                             <property name="draw_indicator">True</property>
+                                            <signal name="toggled" handler="on_toggle_units" swapped="no"/>
                                           </object>
                                           <packing>
                                             <property name="expand">False</property>

+ 59 - 49
camlib.py

@@ -45,6 +45,7 @@ class Geometry:
             return (0, 0, 0, 0)
             
         if type(self.solid_geometry) == list:
+            # TODO: This can be done faster. See comment from Shapely mailing lists.
             return cascaded_union(self.solid_geometry).bounds
         else:
             return self.solid_geometry.bounds
@@ -98,13 +99,15 @@ class Geometry:
     def convert_units(self, units):
         """
         Converts the units of the object to ``units`` by scaling all
-        the geometry appropriately.
+        the geometry appropriately. This call ``scale()``. Don't call
+        it again in descendents.
 
         :param units: "IN" or "MM"
         :type units: str
         :return: Scaling factor resulting from unit change.
         :rtype: float
         """
+        print "Geometry.convert_units()"
 
         if units.upper() == self.units.upper():
             return 1.0
@@ -578,6 +581,7 @@ class Excellon(Geometry):
         """
         Scales geometry on the XY plane in the object by a given factor.
         Tool sizes, feedrates an Z-plane dimensions are untouched.
+
         :param factor: Number by which to scale the object.
         :type factor: float
         :return: None
@@ -586,7 +590,7 @@ class Excellon(Geometry):
 
         # Drills
         for drill in self.drills:
-            drill.point = affinity.scale(drill.point, factor, factor, origin=(0, 0))
+            drill['point'] = affinity.scale(drill['point'], factor, factor, origin=(0, 0))
 
     def convert_units(self, units):
         factor = Geometry.convert_units(self, units)
@@ -639,7 +643,18 @@ class CNCjob(Geometry):
         self.ser_attrs += ['kind', 'z_cut', 'z_move', 'feedrate', 'tooldia',
                            'gcode', 'input_geometry_bounds', 'gcode_parsed',
                            'steps_per_circ']
-        
+
+    def convert_units(self, units):
+        factor = Geometry.convert_units(self, units)
+        print "CNCjob.convert_units()"
+
+        self.z_cut *= factor
+        self.z_move *= factor
+        self.feedrate *= factor
+        self.tooldia *= factor
+
+        return factor
+
     def generate_from_excellon(self, exobj):
         """
         Generates G-code for drilling from Excellon object.
@@ -897,44 +912,44 @@ class CNCjob(Geometry):
         self.gcode_parsed = geometry
         return geometry
         
-    def plot(self, tooldia=None, dpi=75, margin=0.1,
-             color={"T": ["#F0E24D", "#B5AB3A"], "C": ["#5E6CFF", "#4650BD"]},
-             alpha={"T": 0.3, "C": 1.0}):
-        """
-        Creates a Matplotlib figure with a plot of the
-        G-code job.
-        """
-        if tooldia is None:
-            tooldia = self.tooldia
-            
-        fig = Figure(dpi=dpi)
-        ax = fig.add_subplot(111)
-        ax.set_aspect(1)
-        xmin, ymin, xmax, ymax = self.input_geometry_bounds
-        ax.set_xlim(xmin-margin, xmax+margin)
-        ax.set_ylim(ymin-margin, ymax+margin)
-        
-        if tooldia == 0:
-            for geo in self.gcode_parsed:
-                linespec = '--'
-                linecolor = color[geo['kind'][0]][1]
-                if geo['kind'][0] == 'C':
-                    linespec = 'k-'
-                x, y = geo['geom'].coords.xy
-                ax.plot(x, y, linespec, color=linecolor)
-        else:
-            for geo in self.gcode_parsed:
-                poly = geo['geom'].buffer(tooldia/2.0)
-                patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0],
-                                     edgecolor=color[geo['kind'][0]][1],
-                                     alpha=alpha[geo['kind'][0]], zorder=2)
-                ax.add_patch(patch)
-        
-        return fig
+    # def plot(self, tooldia=None, dpi=75, margin=0.1,
+    #          color={"T": ["#F0E24D", "#B5AB3A"], "C": ["#5E6CFF", "#4650BD"]},
+    #          alpha={"T": 0.3, "C": 1.0}):
+    #     """
+    #     Creates a Matplotlib figure with a plot of the
+    #     G-code job.
+    #     """
+    #     if tooldia is None:
+    #         tooldia = self.tooldia
+    #
+    #     fig = Figure(dpi=dpi)
+    #     ax = fig.add_subplot(111)
+    #     ax.set_aspect(1)
+    #     xmin, ymin, xmax, ymax = self.input_geometry_bounds
+    #     ax.set_xlim(xmin-margin, xmax+margin)
+    #     ax.set_ylim(ymin-margin, ymax+margin)
+    #
+    #     if tooldia == 0:
+    #         for geo in self.gcode_parsed:
+    #             linespec = '--'
+    #             linecolor = color[geo['kind'][0]][1]
+    #             if geo['kind'][0] == 'C':
+    #                 linespec = 'k-'
+    #             x, y = geo['geom'].coords.xy
+    #             ax.plot(x, y, linespec, color=linecolor)
+    #     else:
+    #         for geo in self.gcode_parsed:
+    #             poly = geo['geom'].buffer(tooldia/2.0)
+    #             patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0],
+    #                                  edgecolor=color[geo['kind'][0]][1],
+    #                                  alpha=alpha[geo['kind'][0]], zorder=2)
+    #             ax.add_patch(patch)
+    #
+    #     return fig
         
     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}):
         """
         Plots the G-code job onto the given axes.
         """
@@ -964,8 +979,9 @@ class CNCjob(Geometry):
         """
         Creates G-Code for the exterior and all interior paths
         of a polygon.
-        @param polygon: A Shapely.Polygon
-        @type polygon: Shapely.Polygon
+
+        :param polygon: A Shapely.Polygon
+        :type polygon: Shapely.Polygon
         """
         gcode = ""
         t = "G0%d X%.4fY%.4f\n"
@@ -1009,23 +1025,17 @@ class CNCjob(Geometry):
         Scales all the geometry on the XY plane in the object by the
         given factor. Tool sizes, feedrates, or Z-axis dimensions are
         not altered.
+
         :param factor: Number by which to scale the object.
         :type factor: float
         :return: None
         :rtype: None
         """
+
         for g in self.gcode_parsed:
             g['geom'] = affinity.scale(g['geom'], factor, factor, origin=(0, 0))
 
-    def convert_units(self, units):
-        factor = Geometry.convert_units(self, units)
-
-        self.z_move *= factor
-        self.z_cut *= factor
-        self.feedrate *= factor
-        self.tooldia *= factor
-
-        return factor
+        self.create_geometry()
 
 
 def get_bounds(geometry_set):