Object serialization and project saving.
This commit is contained in:
63
camlib.py
63
camlib.py
@@ -8,10 +8,14 @@ from shapely.geometry import MultiPoint, MultiPolygon
|
|||||||
from shapely.geometry import box as shply_box
|
from shapely.geometry import box as shply_box
|
||||||
from shapely.ops import cascaded_union
|
from shapely.ops import cascaded_union
|
||||||
import shapely.affinity as affinity
|
import shapely.affinity as affinity
|
||||||
|
from shapely.wkt import loads as sloads
|
||||||
|
from shapely.wkt import dumps as sdumps
|
||||||
|
from shapely.geometry.base import BaseGeometry
|
||||||
|
|
||||||
# Used for solid polygons in Matplotlib
|
# Used for solid polygons in Matplotlib
|
||||||
from descartes.patch import PolygonPatch
|
from descartes.patch import PolygonPatch
|
||||||
|
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
class Geometry:
|
class Geometry:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -20,6 +24,9 @@ class Geometry:
|
|||||||
|
|
||||||
# Final geometry: MultiPolygon
|
# Final geometry: MultiPolygon
|
||||||
self.solid_geometry = None
|
self.solid_geometry = None
|
||||||
|
|
||||||
|
# Attributes to be included in serialization
|
||||||
|
self.ser_attrs = ['units', 'solid_geometry']
|
||||||
|
|
||||||
def isolation_geometry(self, offset):
|
def isolation_geometry(self, offset):
|
||||||
"""
|
"""
|
||||||
@@ -113,7 +120,25 @@ class Geometry:
|
|||||||
self.units = units
|
self.units = units
|
||||||
self.scale(factor)
|
self.scale(factor)
|
||||||
return factor
|
return factor
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""
|
||||||
|
Returns a respresentation of the object as a dictionary.
|
||||||
|
``self.solid_geometry`` has been converted using the ``to_dict``
|
||||||
|
function. Attributes to include are listed in
|
||||||
|
``self.ser_attrs``.
|
||||||
|
|
||||||
|
:return: A dictionary-encoded copy of the object.
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
d = {}
|
||||||
|
for attr in self.ser_attrs:
|
||||||
|
d[attr] = getattr(self, attr)
|
||||||
|
return d
|
||||||
|
|
||||||
|
def from_dict(self, d):
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
class Gerber (Geometry):
|
class Gerber (Geometry):
|
||||||
"""
|
"""
|
||||||
@@ -207,6 +232,13 @@ class Gerber (Geometry):
|
|||||||
# Geometry from flashes
|
# Geometry from flashes
|
||||||
self.flash_geometry = []
|
self.flash_geometry = []
|
||||||
|
|
||||||
|
# Attributes to be included in serialization
|
||||||
|
# Always append to it because it carries contents
|
||||||
|
# from Geometry.
|
||||||
|
self.ser_attrs += ['digits', 'fraction', 'apertures', 'paths',
|
||||||
|
'buffered_paths', 'regions', 'flashes',
|
||||||
|
'flash_geometry']
|
||||||
|
|
||||||
def scale(self, factor):
|
def scale(self, factor):
|
||||||
"""
|
"""
|
||||||
Scales the objects' geometry on the XY plane by a given factor.
|
Scales the objects' geometry on the XY plane by a given factor.
|
||||||
@@ -458,6 +490,11 @@ class Excellon(Geometry):
|
|||||||
|
|
||||||
# Trailing "T" or leading "L"
|
# Trailing "T" or leading "L"
|
||||||
self.zeros = ""
|
self.zeros = ""
|
||||||
|
|
||||||
|
# Attributes to be included in serialization
|
||||||
|
# Always append to it because it carries contents
|
||||||
|
# from Geometry.
|
||||||
|
self.ser_attrs += ['tools', 'drills', 'zeros']
|
||||||
|
|
||||||
def parse_file(self, filename):
|
def parse_file(self, filename):
|
||||||
efile = open(filename, 'r')
|
efile = open(filename, 'r')
|
||||||
@@ -592,6 +629,13 @@ class CNCjob(Geometry):
|
|||||||
self.input_geometry_bounds = None
|
self.input_geometry_bounds = None
|
||||||
self.gcode_parsed = None
|
self.gcode_parsed = None
|
||||||
self.steps_per_circ = 20 # Used when parsing G-code arcs
|
self.steps_per_circ = 20 # Used when parsing G-code arcs
|
||||||
|
|
||||||
|
# Attributes to be included in serialization
|
||||||
|
# Always append to it because it carries contents
|
||||||
|
# from Geometry.
|
||||||
|
self.ser_attrs += ['kind', 'z_cut', 'z_move', 'feedrate', 'tooldia',
|
||||||
|
'gcode', 'input_geometry_bounds', 'gcode_parsed',
|
||||||
|
'steps_per_circ']
|
||||||
|
|
||||||
def generate_from_excellon(self, exobj):
|
def generate_from_excellon(self, exobj):
|
||||||
"""
|
"""
|
||||||
@@ -949,6 +993,7 @@ class CNCjob(Geometry):
|
|||||||
return gcode
|
return gcode
|
||||||
|
|
||||||
def point2gcode(self, point):
|
def point2gcode(self, point):
|
||||||
|
# TODO: This is not doing anything.
|
||||||
gcode = ""
|
gcode = ""
|
||||||
t = "G0%d X%.4fY%.4f\n"
|
t = "G0%d X%.4fY%.4f\n"
|
||||||
path = list(point.coords)
|
path = list(point.coords)
|
||||||
@@ -1067,6 +1112,22 @@ def find_polygon(poly_set, point):
|
|||||||
return poly
|
return poly
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def to_dict(geo):
|
||||||
|
output = ''
|
||||||
|
if isinstance(geo, BaseGeometry):
|
||||||
|
return {
|
||||||
|
"__class__": "Shply",
|
||||||
|
"__inst__": sdumps(geo)
|
||||||
|
}
|
||||||
|
return geo
|
||||||
|
|
||||||
|
def dict2obj(d):
|
||||||
|
if '__class__' in d and '__inst__' in d:
|
||||||
|
# For now assume all classes are Shapely geometry.
|
||||||
|
return sloads(d['__inst__'])
|
||||||
|
else:
|
||||||
|
return d
|
||||||
|
|
||||||
############### cam.py ####################
|
############### cam.py ####################
|
||||||
def coord(gstr, digits, fraction):
|
def coord(gstr, digits, fraction):
|
||||||
"""
|
"""
|
||||||
|
|||||||
106
cirkuix.py
106
cirkuix.py
@@ -39,9 +39,11 @@ class CirkuixObj:
|
|||||||
1) Creates axes if they don't exist. 2) Clears axes. 3) Attaches
|
1) Creates axes if they don't exist. 2) Clears axes. 3) Attaches
|
||||||
them to figure if not part of the figure. 4) Sets transparent
|
them to figure if not part of the figure. 4) Sets transparent
|
||||||
background. 5) Sets 1:1 scale aspect ratio.
|
background. 5) Sets 1:1 scale aspect ratio.
|
||||||
@param figure: A Matplotlib.Figure on which to add/configure axes.
|
|
||||||
@type figure: matplotlib.figure.Figure
|
:param figure: A Matplotlib.Figure on which to add/configure axes.
|
||||||
@return: None
|
:type figure: matplotlib.figure.Figure
|
||||||
|
:return: None
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
if self.axes is None:
|
if self.axes is None:
|
||||||
print "New axes"
|
print "New axes"
|
||||||
@@ -63,19 +65,21 @@ class CirkuixObj:
|
|||||||
self.axes.patch.set_visible(False) # No background
|
self.axes.patch.set_visible(False) # No background
|
||||||
self.axes.set_aspect(1)
|
self.axes.set_aspect(1)
|
||||||
|
|
||||||
def set_options(self, options):
|
|
||||||
for name in options:
|
|
||||||
self.options[name] = options[name]
|
|
||||||
return
|
|
||||||
|
|
||||||
def to_form(self):
|
def to_form(self):
|
||||||
|
"""
|
||||||
|
Copies options to the UI form.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
for option in self.options:
|
for option in self.options:
|
||||||
self.set_form_item(option)
|
self.set_form_item(option)
|
||||||
|
|
||||||
def read_form(self):
|
def read_form(self):
|
||||||
"""
|
"""
|
||||||
Reads form into self.options
|
Reads form into ``self.options``.
|
||||||
@rtype : None
|
|
||||||
|
:return: None
|
||||||
|
:rtype : None
|
||||||
"""
|
"""
|
||||||
for option in self.options:
|
for option in self.options:
|
||||||
self.read_form_item(option)
|
self.read_form_item(option)
|
||||||
@@ -83,8 +87,9 @@ class CirkuixObj:
|
|||||||
def build_ui(self):
|
def build_ui(self):
|
||||||
"""
|
"""
|
||||||
Sets up the UI/form for this object.
|
Sets up the UI/form for this object.
|
||||||
@return: None
|
|
||||||
@rtype : None
|
:return: None
|
||||||
|
:rtype : None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Where the UI for this object is drawn
|
# Where the UI for this object is drawn
|
||||||
@@ -110,6 +115,13 @@ class CirkuixObj:
|
|||||||
sw.show()
|
sw.show()
|
||||||
|
|
||||||
def set_form_item(self, option):
|
def set_form_item(self, option):
|
||||||
|
"""
|
||||||
|
Copies the specified options to the UI form.
|
||||||
|
|
||||||
|
:param option: Name of the option (Key in ``self.options``).
|
||||||
|
:type option: str
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
fkind = self.form_kinds[option]
|
fkind = self.form_kinds[option]
|
||||||
fname = fkind + "_" + self.kind + "_" + option
|
fname = fkind + "_" + self.kind + "_" + option
|
||||||
|
|
||||||
@@ -217,6 +229,11 @@ class CirkuixGerber(CirkuixObj, Gerber):
|
|||||||
self.radios = {"gaps": {"rb_2tb": "tb", "rb_2lr": "lr", "rb_4": "4"}}
|
self.radios = {"gaps": {"rb_2tb": "tb", "rb_2lr": "lr", "rb_4": "4"}}
|
||||||
self.radios_inv = {"gaps": {"tb": "rb_2tb", "lr": "rb_2lr", "4": "rb_4"}}
|
self.radios_inv = {"gaps": {"tb": "rb_2tb", "lr": "rb_2lr", "4": "rb_4"}}
|
||||||
|
|
||||||
|
# Attributes to be included in serialization
|
||||||
|
# Always append to it because it carries contents
|
||||||
|
# from predecessors.
|
||||||
|
self.ser_attrs += ['options']
|
||||||
|
|
||||||
def convert_units(self, units):
|
def convert_units(self, units):
|
||||||
factor = Gerber.convert_units(self, units)
|
factor = Gerber.convert_units(self, units)
|
||||||
|
|
||||||
@@ -290,8 +307,14 @@ class CirkuixExcellon(CirkuixObj, Excellon):
|
|||||||
"toolselection": "entry_text"
|
"toolselection": "entry_text"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# TODO: Document this.
|
||||||
self.tool_cbs = {}
|
self.tool_cbs = {}
|
||||||
|
|
||||||
|
# Attributes to be included in serialization
|
||||||
|
# Always append to it because it carries contents
|
||||||
|
# from predecessors.
|
||||||
|
self.ser_attrs += ['options']
|
||||||
|
|
||||||
def convert_units(self, units):
|
def convert_units(self, units):
|
||||||
factor = Excellon.convert_units(self, units)
|
factor = Excellon.convert_units(self, units)
|
||||||
|
|
||||||
@@ -339,10 +362,6 @@ class CirkuixExcellon(CirkuixObj, Excellon):
|
|||||||
button.connect("activate", on_accept)
|
button.connect("activate", on_accept)
|
||||||
button.connect("clicked", on_accept)
|
button.connect("clicked", on_accept)
|
||||||
|
|
||||||
# def parse_lines(self, elines):
|
|
||||||
# Excellon.parse_lines(self, elines)
|
|
||||||
# self.options["units"] = self.units
|
|
||||||
|
|
||||||
|
|
||||||
class CirkuixCNCjob(CirkuixObj, CNCjob):
|
class CirkuixCNCjob(CirkuixObj, CNCjob):
|
||||||
"""
|
"""
|
||||||
@@ -371,6 +390,11 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
|
|||||||
"tooldia": "entry_eval"
|
"tooldia": "entry_eval"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Attributes to be included in serialization
|
||||||
|
# Always append to it because it carries contents
|
||||||
|
# from predecessors.
|
||||||
|
self.ser_attrs += ['options']
|
||||||
|
|
||||||
def plot(self, figure):
|
def plot(self, figure):
|
||||||
CirkuixObj.plot(self, figure)
|
CirkuixObj.plot(self, figure)
|
||||||
#self.setup_axes(figure)
|
#self.setup_axes(figure)
|
||||||
@@ -417,6 +441,11 @@ class CirkuixGeometry(CirkuixObj, Geometry):
|
|||||||
"paintmargin": "entry_eval"
|
"paintmargin": "entry_eval"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Attributes to be included in serialization
|
||||||
|
# Always append to it because it carries contents
|
||||||
|
# from predecessors.
|
||||||
|
self.ser_attrs += ['options']
|
||||||
|
|
||||||
def scale(self, factor):
|
def scale(self, factor):
|
||||||
if type(self.solid_geometry) == list:
|
if type(self.solid_geometry) == list:
|
||||||
self.solid_geometry = [affinity.scale(g, factor, factor, origin=(0, 0))
|
self.solid_geometry = [affinity.scale(g, factor, factor, origin=(0, 0))
|
||||||
@@ -510,7 +539,8 @@ class App:
|
|||||||
self.units_label = self.builder.get_object("label_units")
|
self.units_label = self.builder.get_object("label_units")
|
||||||
|
|
||||||
# White (transparent) background on the "Options" tab.
|
# White (transparent) background on the "Options" tab.
|
||||||
self.builder.get_object("vp_options").override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
|
self.builder.get_object("vp_options").override_background_color(Gtk.StateType.NORMAL,
|
||||||
|
Gdk.RGBA(1, 1, 1, 1))
|
||||||
|
|
||||||
# Combo box to choose between project and application options.
|
# Combo box to choose between project and application options.
|
||||||
self.combo_options = self.builder.get_object("combo_options")
|
self.combo_options = self.builder.get_object("combo_options")
|
||||||
@@ -1100,9 +1130,51 @@ class App:
|
|||||||
return
|
return
|
||||||
print "Unknown kind of form item:", fkind
|
print "Unknown kind of form item:", fkind
|
||||||
|
|
||||||
|
def save_project(self, filename):
|
||||||
|
"""
|
||||||
|
Saves the current project to the specified file.
|
||||||
|
:param filename: Name of the file in which to save.
|
||||||
|
:type filename: str
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
d = {"objs": [self.stuff[o].to_dict() for o in self.stuff],
|
||||||
|
"options": self.options}
|
||||||
|
|
||||||
|
try:
|
||||||
|
f = open(filename, 'w')
|
||||||
|
except:
|
||||||
|
print "ERROR: Failed to open file for saving:", filename
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
json.dump(d, f, default=to_dict)
|
||||||
|
except:
|
||||||
|
print "ERROR: File open but failed to write:", filename
|
||||||
|
f.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
## EVENT HANDLERS ##
|
## EVENT HANDLERS ##
|
||||||
########################################
|
########################################
|
||||||
|
def on_file_saveproject(self, param):
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_file_saveprojectas(self, param):
|
||||||
|
def on_success(app_obj, filename):
|
||||||
|
assert isinstance(app_obj, App)
|
||||||
|
app_obj.save_project(filename)
|
||||||
|
app_obj.info("Project saved to: " + filename)
|
||||||
|
|
||||||
|
self.file_chooser_save_action(on_success)
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_file_saveprojectcopy(self, param):
|
||||||
|
return
|
||||||
|
|
||||||
def on_options_app2project(self, param):
|
def on_options_app2project(self, param):
|
||||||
"""
|
"""
|
||||||
Callback for Options->Transfer Options->App=>Project. Copies options
|
Callback for Options->Transfer Options->App=>Project. Copies options
|
||||||
|
|||||||
51
cirkuix.ui
51
cirkuix.ui
@@ -26,6 +26,21 @@
|
|||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="stock">gtk-jump-to</property>
|
<property name="stock">gtk-jump-to</property>
|
||||||
</object>
|
</object>
|
||||||
|
<object class="GtkImage" id="image6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="stock">gtk-save</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkImage" id="image7">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="stock">gtk-save-as</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkImage" id="image8">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="stock">gtk-save-as</property>
|
||||||
|
</object>
|
||||||
<object class="GtkOffscreenWindow" id="offscrwindow_cncjob">
|
<object class="GtkOffscreenWindow" id="offscrwindow_cncjob">
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<child>
|
<child>
|
||||||
@@ -2063,6 +2078,42 @@
|
|||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImageMenuItem" id="imagemenuitem6">
|
||||||
|
<property name="label" translatable="yes">Save Project</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="image">image6</property>
|
||||||
|
<property name="use_stock">False</property>
|
||||||
|
<signal name="activate" handler="on_file_saveproject" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImageMenuItem" id="imagemenuitem7">
|
||||||
|
<property name="label" translatable="yes">Save Project As ...</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="image">image7</property>
|
||||||
|
<property name="use_stock">False</property>
|
||||||
|
<signal name="activate" handler="on_file_saveprojectas" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImageMenuItem" id="imagemenuitem8">
|
||||||
|
<property name="label" translatable="yes">Save a Project copy ...</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="image">image8</property>
|
||||||
|
<property name="use_stock">False</property>
|
||||||
|
<signal name="activate" handler="on_file_saveprojectcopy" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSeparatorMenuItem" id="separatormenuitem3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImageMenuItem" id="imagemenuitem5">
|
<object class="GtkImageMenuItem" id="imagemenuitem5">
|
||||||
<property name="label">gtk-quit</property>
|
<property name="label">gtk-quit</property>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"cncjob_multicolored": false, "geometry_paintoverlap": 0.15, "geometry_plot": true, "cncjob_solid": false, "gerber_isotooldia": 0.16, "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.16, "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, "gerber_bboxmargin": 0.0, "cncjob_plot": true, "excellon_drillz": -0.1, "gerber_bboxrounded": false, "geometry_multicolored": false, "geometry_cnctooldia": 0.16, "geometry_solid": false, "geometry_painttooldia": 0.0625}
|
{"cncjob_multicolored": false, "geometry_paintoverlap": 0.15, "geometry_plot": true, "cncjob_solid": false, "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": false, "excellon_plot": true, "gerber_isotooldia": 0.016, "cncjob_tooldia": 0.16, "geometry_travelz": 0.1, "gerber_cutoutmargin": 0.2, "excellon_solid": false, "geometry_paintmargin": 0.01, "geometry_cutz": -0.002, "geometry_cnctooldia": 0.16, "geometry_painttooldia": 0.0625, "gerber_gaps": "4", "excellon_multicolored": false, "gerber_bboxmargin": 0.0, "cncjob_plot": true, "gerber_cutoutgapsize": 0.15, "gerber_bboxrounded": false, "geometry_multicolored": false, "gerber_noncoppermargin": 0.0, "geometry_solid": false}
|
||||||
Reference in New Issue
Block a user