Serialization of ApertureMacro. Change scale, offset and mirror in Gerber to act only upon its resulting geometry, not its source data.

This commit is contained in:
Juan Pablo Caram
2014-03-13 22:37:59 -04:00
parent 21da78d654
commit 9d9c3f819d
10 changed files with 220 additions and 65 deletions

218
camlib.py
View File

@@ -192,10 +192,38 @@ class ApertureMacro:
def __init__(self, name=None):
self.name = name
self.raw = ""
## These below are recomputed for every aperture
## definition, in other words, are temporary variables.
self.primitives = []
self.locvars = {}
self.geometry = None
def to_dict(self):
"""
Returns the object in a serializable form. Only the name and
raw are required.
:return: Dictionary representing the object. JSON ready.
:rtype: dict
"""
return {
'name': self.name,
'raw': self.raw
}
def from_dict(self, d):
"""
Populates the object from a serial representation created
with ``self.to_dict()``.
:param d: Serial representation of an ApertureMacro object.
:return: None
"""
for attr in ['name', 'raw']:
setattr(self, attr, d[attr])
def parse_content(self):
"""
Creates numerical lists for all primitives in the aperture
@@ -632,7 +660,7 @@ class Gerber (Geometry):
# from Geometry.
self.ser_attrs += ['int_digits', 'frac_digits', 'apertures', 'paths',
'buffered_paths', 'regions', 'flashes',
'flash_geometry']
'flash_geometry', 'aperture_macros']
#### Parser patterns ####
# FS - Format Specification
@@ -714,55 +742,74 @@ class Gerber (Geometry):
Scales the objects' geometry on the XY plane by a given factor.
These are:
* ``apertures``
* ``paths``
* ``buffered_paths``
* ``flash_geometry``
* ``solid_geometry``
* ``regions``
* ``flashes``
Then ``buffered_paths``, ``flash_geometry`` and ``solid_geometry``
are re-created with ``self.create_geometry()``.
NOTE:
Does not modify the data used to create these elements. If these
are recreated, the scaling will be lost. This behavior was modified
because of the complexity reached in this class.
:param factor: Number by which to scale.
:type factor: float
:rtype : None
"""
## Apertures
# List of the non-dimension aperture parameters
nonDimensions = ["type", "nVertices", "rotation"]
for apid in self.apertures:
for param in self.apertures[apid]:
if param not in nonDimensions: # All others are dimensions.
print "Tool:", apid, "Parameter:", param
self.apertures[apid][param] *= factor
## Paths
for path in self.paths:
path['linestring'] = affinity.scale(path['linestring'],
factor, factor, origin=(0, 0))
## Flashes
for fl in self.flashes:
fl['loc'] = affinity.scale(fl['loc'], factor, factor, origin=(0, 0))
# ## Apertures
# # List of the non-dimension aperture parameters
# nonDimensions = ["type", "nVertices", "rotation"]
# for apid in self.apertures:
# for param in self.apertures[apid]:
# if param not in nonDimensions: # All others are dimensions.
# print "Tool:", apid, "Parameter:", param
# self.apertures[apid][param] *= factor
#
# ## Paths
# for path in self.paths:
# path['linestring'] = affinity.scale(path['linestring'],
# factor, factor, origin=(0, 0))
#
# ## Flashes
# for fl in self.flashes:
# fl['loc'] = affinity.scale(fl['loc'], factor, factor, origin=(0, 0))
## Regions
for reg in self.regions:
reg['polygon'] = affinity.scale(reg['polygon'], factor, factor,
origin=(0, 0))
# Now buffered_paths, flash_geometry and solid_geometry
self.create_geometry()
## Flashes
for flash in self.flash_geometry:
flash = affinity.scale(flash, factor, factor, origin=(0, 0))
## Buffered paths
for bp in self.buffered_paths:
bp = affinity.scale(bp, factor, factor, origin=(0, 0))
## solid_geometry ???
# It's a cascaded union of objects.
self.solid_geometry = affinity.scale(self.solid_geometry, factor,
factor, origin=(0, 0))
# # Now buffered_paths, flash_geometry and solid_geometry
# self.create_geometry()
def offset(self, vect):
"""
Offsets the objects' geometry on the XY plane by a given vector.
These are:
* ``paths``
* ``buffered_paths``
* ``flash_geometry``
* ``solid_geometry``
* ``regions``
* ``flashes``
Then ``buffered_paths``, ``flash_geometry`` and ``solid_geometry``
are re-created with ``self.create_geometry()``.
NOTE:
Does not modify the data used to create these elements. If these
are recreated, the scaling will be lost. This behavior was modified
because of the complexity reached in this class.
:param vect: (x, y) offset vector.
:type vect: tuple
@@ -771,25 +818,48 @@ class Gerber (Geometry):
dx, dy = vect
## Paths
for path in self.paths:
path['linestring'] = affinity.translate(path['linestring'],
xoff=dx, yoff=dy)
## Flashes
for fl in self.flashes:
fl['loc'] = affinity.translate(fl['loc'], xoff=dx, yoff=dy)
# ## Paths
# for path in self.paths:
# path['linestring'] = affinity.translate(path['linestring'],
# xoff=dx, yoff=dy)
#
# ## Flashes
# for fl in self.flashes:
# fl['loc'] = affinity.translate(fl['loc'], xoff=dx, yoff=dy)
## Regions
for reg in self.regions:
reg['polygon'] = affinity.translate(reg['polygon'],
xoff=dx, yoff=dy)
# Now buffered_paths, flash_geometry and solid_geometry
self.create_geometry()
## Buffered paths
for bp in self.buffered_paths:
bp = affinity.translate(bp, xoff=dx, yoff=dy)
## Flash geometry
for fl in self.flash_geometry:
fl = affinity.translate(fl, xoff=dx, yoff=dy)
## Solid geometry
self.solid_geometry = affinity.translate(self.solid_geometry, xoff=dx, yoff=dy)
# # Now buffered_paths, flash_geometry and solid_geometry
# self.create_geometry()
def mirror(self, axis, point):
"""
Mirrors the object around a specified axis passign through
the given point. What is affected:
* ``buffered_paths``
* ``flash_geometry``
* ``solid_geometry``
* ``regions``
NOTE:
Does not modify the data used to create these elements. If these
are recreated, the scaling will be lost. This behavior was modified
because of the complexity reached in this class.
:param axis: "X" or "Y" indicates around which axis to mirror.
:type axis: str
@@ -801,22 +871,35 @@ class Gerber (Geometry):
px, py = point
xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
## Paths
for path in self.paths:
path['linestring'] = affinity.scale(path['linestring'], xscale, yscale,
origin=(px, py))
## Flashes
for fl in self.flashes:
fl['loc'] = affinity.scale(fl['loc'], xscale, yscale, origin=(px, py))
# ## Paths
# for path in self.paths:
# path['linestring'] = affinity.scale(path['linestring'], xscale, yscale,
# origin=(px, py))
#
# ## Flashes
# for fl in self.flashes:
# fl['loc'] = affinity.scale(fl['loc'], xscale, yscale, origin=(px, py))
## Regions
for reg in self.regions:
reg['polygon'] = affinity.scale(reg['polygon'], xscale, yscale,
origin=(px, py))
# Now buffered_paths, flash_geometry and solid_geometry
self.create_geometry()
## Flashes
for flash in self.flash_geometry:
flash = affinity.scale(flash, xscale, yscale, origin=(px, py))
## Buffered paths
for bp in self.buffered_paths:
bp = affinity.scale(bp, xscale, yscale, origin=(px, py))
## solid_geometry ???
# It's a cascaded union of objects.
self.solid_geometry = affinity.scale(self.solid_geometry,
xscale, yscale, origin=(px, py))
# # Now buffered_paths, flash_geometry and solid_geometry
# self.create_geometry()
def fix_regions(self):
"""
@@ -2266,27 +2349,44 @@ def find_polygon(poly_set, point):
return None
def to_dict(geo):
def to_dict(obj):
"""
Makes a Shapely geometry object into serializeable form.
:param geo: Shapely geometry.
:type geo: BaseGeometry
:return: Dictionary with serializable form if ``geo`` was
BaseGeometry, otherwise returns ``geo``.
:param obj: Shapely geometry.
:type obj: BaseGeometry
:return: Dictionary with serializable form if ``obj`` was
BaseGeometry or ApertureMacro, otherwise returns ``obj``.
"""
if isinstance(geo, BaseGeometry):
if isinstance(obj, ApertureMacro):
return {
"__class__": "ApertureMacro",
"__inst__": obj.to_dict()
}
if isinstance(obj, BaseGeometry):
return {
"__class__": "Shply",
"__inst__": sdumps(geo)
"__inst__": sdumps(obj)
}
return geo
return obj
def dict2obj(d):
"""
Default deserializer.
:param d: Serializable dictionary representation of an object
to be reconstructed.
:return: Reconstructed object.
"""
if '__class__' in d and '__inst__' in d:
# For now assume all classes are Shapely geometry.
return sloads(d['__inst__'])
if d['__class__'] == "Shply":
return sloads(d['__inst__'])
if d['__class__'] == "ApertureMacro":
am = ApertureMacro()
am.from_dict(d['__inst__'])
return am
return d
else:
return d