- moved the ApertureMacro class from camlib to ParseGerber file
This commit is contained in:
parent
8762b115c9
commit
b05c71201e
|
@ -15,7 +15,8 @@ CAD program, and create G-Code for Isolation routing.
|
||||||
- working in adding to the Optimal Tool the rest of the distances found in the Gerber and the locations associated; added GUI
|
- working in adding to the Optimal Tool the rest of the distances found in the Gerber and the locations associated; added GUI
|
||||||
- added display of the results for the Rules Check Tool in a formatted way
|
- added display of the results for the Rules Check Tool in a formatted way
|
||||||
- made the Rules Check Tool document window Read Only
|
- made the Rules Check Tool document window Read Only
|
||||||
- made FlatCAMExcellon and FlatCAMGerber into their own files in the flatcamParser folder
|
- made Excellon and Gerber classes from camlib into their own files in the flatcamParser folder
|
||||||
|
- moved the ApertureMacro class from camlib to ParseGerber file
|
||||||
|
|
||||||
5.10.2019
|
5.10.2019
|
||||||
|
|
||||||
|
|
383
camlib.py
383
camlib.py
|
@ -49,7 +49,7 @@ import ezdxf
|
||||||
# TODO: Commented for FlatCAM packaging with cx_freeze
|
# TODO: Commented for FlatCAM packaging with cx_freeze
|
||||||
# from scipy.spatial import KDTree, Delaunay
|
# from scipy.spatial import KDTree, Delaunay
|
||||||
# from scipy.spatial import Delaunay
|
# from scipy.spatial import Delaunay
|
||||||
|
from flatcamParsers.ParseGerber import ApertureMacro
|
||||||
from flatcamParsers.ParseSVG import *
|
from flatcamParsers.ParseSVG import *
|
||||||
from flatcamParsers.ParseDXF import *
|
from flatcamParsers.ParseDXF import *
|
||||||
|
|
||||||
|
@ -1702,379 +1702,6 @@ class Geometry(object):
|
||||||
# origin=(px, py))
|
# origin=(px, py))
|
||||||
|
|
||||||
|
|
||||||
class ApertureMacro:
|
|
||||||
"""
|
|
||||||
Syntax of aperture macros.
|
|
||||||
|
|
||||||
<AM command>: AM<Aperture macro name>*<Macro content>
|
|
||||||
<Macro content>: {{<Variable definition>*}{<Primitive>*}}
|
|
||||||
<Variable definition>: $K=<Arithmetic expression>
|
|
||||||
<Primitive>: <Primitive code>,<Modifier>{,<Modifier>}|<Comment>
|
|
||||||
<Modifier>: $M|< Arithmetic expression>
|
|
||||||
<Comment>: 0 <Text>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# ## Regular expressions
|
|
||||||
am1_re = re.compile(r'^%AM([^\*]+)\*(.+)?(%)?$')
|
|
||||||
am2_re = re.compile(r'(.*)%$')
|
|
||||||
amcomm_re = re.compile(r'^0(.*)')
|
|
||||||
amprim_re = re.compile(r'^[1-9].*')
|
|
||||||
amvar_re = re.compile(r'^\$([0-9a-zA-z]+)=(.*)')
|
|
||||||
|
|
||||||
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
|
|
||||||
macro (in ``self.raw``) by replacing all variables by their
|
|
||||||
values iteratively and evaluating expressions. Results
|
|
||||||
are stored in ``self.primitives``.
|
|
||||||
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
# Cleanup
|
|
||||||
self.raw = self.raw.replace('\n', '').replace('\r', '').strip(" *")
|
|
||||||
self.primitives = []
|
|
||||||
|
|
||||||
# Separate parts
|
|
||||||
parts = self.raw.split('*')
|
|
||||||
|
|
||||||
# ### Every part in the macro ####
|
|
||||||
for part in parts:
|
|
||||||
# ## Comments. Ignored.
|
|
||||||
match = ApertureMacro.amcomm_re.search(part)
|
|
||||||
if match:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# ## Variables
|
|
||||||
# These are variables defined locally inside the macro. They can be
|
|
||||||
# numerical constant or defind in terms of previously define
|
|
||||||
# variables, which can be defined locally or in an aperture
|
|
||||||
# definition. All replacements ocurr here.
|
|
||||||
match = ApertureMacro.amvar_re.search(part)
|
|
||||||
if match:
|
|
||||||
var = match.group(1)
|
|
||||||
val = match.group(2)
|
|
||||||
|
|
||||||
# Replace variables in value
|
|
||||||
for v in self.locvars:
|
|
||||||
# replaced the following line with the next to fix Mentor custom apertures not parsed OK
|
|
||||||
# val = re.sub((r'\$'+str(v)+r'(?![0-9a-zA-Z])'), str(self.locvars[v]), val)
|
|
||||||
val = val.replace('$' + str(v), str(self.locvars[v]))
|
|
||||||
|
|
||||||
# Make all others 0
|
|
||||||
val = re.sub(r'\$[0-9a-zA-Z](?![0-9a-zA-Z])', "0", val)
|
|
||||||
# Change x with *
|
|
||||||
val = re.sub(r'[xX]', "*", val)
|
|
||||||
|
|
||||||
# Eval() and store.
|
|
||||||
self.locvars[var] = eval(val)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# ## Primitives
|
|
||||||
# Each is an array. The first identifies the primitive, while the
|
|
||||||
# rest depend on the primitive. All are strings representing a
|
|
||||||
# number and may contain variable definition. The values of these
|
|
||||||
# variables are defined in an aperture definition.
|
|
||||||
match = ApertureMacro.amprim_re.search(part)
|
|
||||||
if match:
|
|
||||||
# ## Replace all variables
|
|
||||||
for v in self.locvars:
|
|
||||||
# replaced the following line with the next to fix Mentor custom apertures not parsed OK
|
|
||||||
# part = re.sub(r'\$' + str(v) + r'(?![0-9a-zA-Z])', str(self.locvars[v]), part)
|
|
||||||
part = part.replace('$' + str(v), str(self.locvars[v]))
|
|
||||||
|
|
||||||
# Make all others 0
|
|
||||||
part = re.sub(r'\$[0-9a-zA-Z](?![0-9a-zA-Z])', "0", part)
|
|
||||||
|
|
||||||
# Change x with *
|
|
||||||
part = re.sub(r'[xX]', "*", part)
|
|
||||||
|
|
||||||
# ## Store
|
|
||||||
elements = part.split(",")
|
|
||||||
self.primitives.append([eval(x) for x in elements])
|
|
||||||
continue
|
|
||||||
|
|
||||||
log.warning("Unknown syntax of aperture macro part: %s" % str(part))
|
|
||||||
|
|
||||||
def append(self, data):
|
|
||||||
"""
|
|
||||||
Appends a string to the raw macro.
|
|
||||||
|
|
||||||
:param data: Part of the macro.
|
|
||||||
:type data: str
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self.raw += data
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def default2zero(n, mods):
|
|
||||||
"""
|
|
||||||
Pads the ``mods`` list with zeros resulting in an
|
|
||||||
list of length n.
|
|
||||||
|
|
||||||
:param n: Length of the resulting list.
|
|
||||||
:type n: int
|
|
||||||
:param mods: List to be padded.
|
|
||||||
:type mods: list
|
|
||||||
:return: Zero-padded list.
|
|
||||||
:rtype: list
|
|
||||||
"""
|
|
||||||
x = [0.0] * n
|
|
||||||
na = len(mods)
|
|
||||||
x[0:na] = mods
|
|
||||||
return x
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def make_circle(mods):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:param mods: (Exposure 0/1, Diameter >=0, X-coord, Y-coord)
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
pol, dia, x, y = ApertureMacro.default2zero(4, mods)
|
|
||||||
|
|
||||||
return {"pol": int(pol), "geometry": Point(x, y).buffer(dia/2)}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def make_vectorline(mods):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:param mods: (Exposure 0/1, Line width >= 0, X-start, Y-start, X-end, Y-end,
|
|
||||||
rotation angle around origin in degrees)
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
pol, width, xs, ys, xe, ye, angle = ApertureMacro.default2zero(7, mods)
|
|
||||||
|
|
||||||
line = LineString([(xs, ys), (xe, ye)])
|
|
||||||
box = line.buffer(width/2, cap_style=2)
|
|
||||||
box_rotated = affinity.rotate(box, angle, origin=(0, 0))
|
|
||||||
|
|
||||||
return {"pol": int(pol), "geometry": box_rotated}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def make_centerline(mods):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:param mods: (Exposure 0/1, width >=0, height >=0, x-center, y-center,
|
|
||||||
rotation angle around origin in degrees)
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
pol, width, height, x, y, angle = ApertureMacro.default2zero(6, mods)
|
|
||||||
|
|
||||||
box = shply_box(x-width/2, y-height/2, x+width/2, y+height/2)
|
|
||||||
box_rotated = affinity.rotate(box, angle, origin=(0, 0))
|
|
||||||
|
|
||||||
return {"pol": int(pol), "geometry": box_rotated}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def make_lowerleftline(mods):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:param mods: (exposure 0/1, width >=0, height >=0, x-lowerleft, y-lowerleft,
|
|
||||||
rotation angle around origin in degrees)
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
pol, width, height, x, y, angle = ApertureMacro.default2zero(6, mods)
|
|
||||||
|
|
||||||
box = shply_box(x, y, x+width, y+height)
|
|
||||||
box_rotated = affinity.rotate(box, angle, origin=(0, 0))
|
|
||||||
|
|
||||||
return {"pol": int(pol), "geometry": box_rotated}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def make_outline(mods):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:param mods:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
pol = mods[0]
|
|
||||||
n = mods[1]
|
|
||||||
points = [(0, 0)]*(n+1)
|
|
||||||
|
|
||||||
for i in range(n+1):
|
|
||||||
points[i] = mods[2*i + 2:2*i + 4]
|
|
||||||
|
|
||||||
angle = mods[2*n + 4]
|
|
||||||
|
|
||||||
poly = Polygon(points)
|
|
||||||
poly_rotated = affinity.rotate(poly, angle, origin=(0, 0))
|
|
||||||
|
|
||||||
return {"pol": int(pol), "geometry": poly_rotated}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def make_polygon(mods):
|
|
||||||
"""
|
|
||||||
Note: Specs indicate that rotation is only allowed if the center
|
|
||||||
(x, y) == (0, 0). I will tolerate breaking this rule.
|
|
||||||
|
|
||||||
:param mods: (exposure 0/1, n_verts 3<=n<=12, x-center, y-center,
|
|
||||||
diameter of circumscribed circle >=0, rotation angle around origin)
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
pol, nverts, x, y, dia, angle = ApertureMacro.default2zero(6, mods)
|
|
||||||
points = [(0, 0)]*nverts
|
|
||||||
|
|
||||||
for i in range(nverts):
|
|
||||||
points[i] = (x + 0.5 * dia * cos(2*pi * i/nverts),
|
|
||||||
y + 0.5 * dia * sin(2*pi * i/nverts))
|
|
||||||
|
|
||||||
poly = Polygon(points)
|
|
||||||
poly_rotated = affinity.rotate(poly, angle, origin=(0, 0))
|
|
||||||
|
|
||||||
return {"pol": int(pol), "geometry": poly_rotated}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def make_moire(mods):
|
|
||||||
"""
|
|
||||||
Note: Specs indicate that rotation is only allowed if the center
|
|
||||||
(x, y) == (0, 0). I will tolerate breaking this rule.
|
|
||||||
|
|
||||||
:param mods: (x-center, y-center, outer_dia_outer_ring, ring thickness,
|
|
||||||
gap, max_rings, crosshair_thickness, crosshair_len, rotation
|
|
||||||
angle around origin in degrees)
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
x, y, dia, thickness, gap, nrings, cross_th, cross_len, angle = ApertureMacro.default2zero(9, mods)
|
|
||||||
|
|
||||||
r = dia/2 - thickness/2
|
|
||||||
result = Point((x, y)).buffer(r).exterior.buffer(thickness/2.0)
|
|
||||||
ring = Point((x, y)).buffer(r).exterior.buffer(thickness/2.0) # Need a copy!
|
|
||||||
|
|
||||||
i = 1 # Number of rings created so far
|
|
||||||
|
|
||||||
# ## If the ring does not have an interior it means that it is
|
|
||||||
# ## a disk. Then stop.
|
|
||||||
while len(ring.interiors) > 0 and i < nrings:
|
|
||||||
r -= thickness + gap
|
|
||||||
if r <= 0:
|
|
||||||
break
|
|
||||||
ring = Point((x, y)).buffer(r).exterior.buffer(thickness/2.0)
|
|
||||||
result = cascaded_union([result, ring])
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# ## Crosshair
|
|
||||||
hor = LineString([(x - cross_len, y), (x + cross_len, y)]).buffer(cross_th/2.0, cap_style=2)
|
|
||||||
ver = LineString([(x, y-cross_len), (x, y + cross_len)]).buffer(cross_th/2.0, cap_style=2)
|
|
||||||
result = cascaded_union([result, hor, ver])
|
|
||||||
|
|
||||||
return {"pol": 1, "geometry": result}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def make_thermal(mods):
|
|
||||||
"""
|
|
||||||
Note: Specs indicate that rotation is only allowed if the center
|
|
||||||
(x, y) == (0, 0). I will tolerate breaking this rule.
|
|
||||||
|
|
||||||
:param mods: [x-center, y-center, diameter-outside, diameter-inside,
|
|
||||||
gap-thickness, rotation angle around origin]
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
x, y, dout, din, t, angle = ApertureMacro.default2zero(6, mods)
|
|
||||||
|
|
||||||
ring = Point((x, y)).buffer(dout/2.0).difference(Point((x, y)).buffer(din/2.0))
|
|
||||||
hline = LineString([(x - dout/2.0, y), (x + dout/2.0, y)]).buffer(t/2.0, cap_style=3)
|
|
||||||
vline = LineString([(x, y - dout/2.0), (x, y + dout/2.0)]).buffer(t/2.0, cap_style=3)
|
|
||||||
thermal = ring.difference(hline.union(vline))
|
|
||||||
|
|
||||||
return {"pol": 1, "geometry": thermal}
|
|
||||||
|
|
||||||
def make_geometry(self, modifiers):
|
|
||||||
"""
|
|
||||||
Runs the macro for the given modifiers and generates
|
|
||||||
the corresponding geometry.
|
|
||||||
|
|
||||||
:param modifiers: Modifiers (parameters) for this macro
|
|
||||||
:type modifiers: list
|
|
||||||
:return: Shapely geometry
|
|
||||||
:rtype: shapely.geometry.polygon
|
|
||||||
"""
|
|
||||||
|
|
||||||
# ## Primitive makers
|
|
||||||
makers = {
|
|
||||||
"1": ApertureMacro.make_circle,
|
|
||||||
"2": ApertureMacro.make_vectorline,
|
|
||||||
"20": ApertureMacro.make_vectorline,
|
|
||||||
"21": ApertureMacro.make_centerline,
|
|
||||||
"22": ApertureMacro.make_lowerleftline,
|
|
||||||
"4": ApertureMacro.make_outline,
|
|
||||||
"5": ApertureMacro.make_polygon,
|
|
||||||
"6": ApertureMacro.make_moire,
|
|
||||||
"7": ApertureMacro.make_thermal
|
|
||||||
}
|
|
||||||
|
|
||||||
# ## Store modifiers as local variables
|
|
||||||
modifiers = modifiers or []
|
|
||||||
modifiers = [float(m) for m in modifiers]
|
|
||||||
self.locvars = {}
|
|
||||||
for i in range(0, len(modifiers)):
|
|
||||||
self.locvars[str(i + 1)] = modifiers[i]
|
|
||||||
|
|
||||||
# ## Parse
|
|
||||||
self.primitives = [] # Cleanup
|
|
||||||
self.geometry = Polygon()
|
|
||||||
self.parse_content()
|
|
||||||
|
|
||||||
# ## Make the geometry
|
|
||||||
for primitive in self.primitives:
|
|
||||||
# Make the primitive
|
|
||||||
prim_geo = makers[str(int(primitive[0]))](primitive[1:])
|
|
||||||
|
|
||||||
# Add it (according to polarity)
|
|
||||||
# if self.geometry is None and prim_geo['pol'] == 1:
|
|
||||||
# self.geometry = prim_geo['geometry']
|
|
||||||
# continue
|
|
||||||
if prim_geo['pol'] == 1:
|
|
||||||
self.geometry = self.geometry.union(prim_geo['geometry'])
|
|
||||||
continue
|
|
||||||
if prim_geo['pol'] == 0:
|
|
||||||
self.geometry = self.geometry.difference(prim_geo['geometry'])
|
|
||||||
continue
|
|
||||||
|
|
||||||
return self.geometry
|
|
||||||
|
|
||||||
|
|
||||||
class AttrDict(dict):
|
class AttrDict(dict):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(AttrDict, self).__init__(*args, **kwargs)
|
super(AttrDict, self).__init__(*args, **kwargs)
|
||||||
|
@ -5181,7 +4808,7 @@ def arc(center, radius, start, stop, direction, steps_per_circ):
|
||||||
|
|
||||||
angle = abs(stop - start)
|
angle = abs(stop - start)
|
||||||
|
|
||||||
#angle = stop-start
|
# angle = stop-start
|
||||||
steps = max([int(ceil(angle / (2 * pi) * steps_per_circ)), 2])
|
steps = max([int(ceil(angle / (2 * pi) * steps_per_circ)), 2])
|
||||||
delta_angle = da_sign[direction] * angle * 1.0 / steps
|
delta_angle = da_sign[direction] * angle * 1.0 / steps
|
||||||
for i in range(steps + 1):
|
for i in range(steps + 1):
|
||||||
|
@ -5502,8 +5129,10 @@ def parse_gerber_number(strnumber, int_digits, frac_digits, zeros):
|
||||||
# dangling_lines = []
|
# dangling_lines = []
|
||||||
# for i1, i2 in lineIndices_:
|
# for i1, i2 in lineIndices_:
|
||||||
# p = (i1, i2)
|
# p = (i1, i2)
|
||||||
# connections = filter(lambda k: p != k and (p[0] == k[0] or p[0] == k[1] or p[1] == k[0] or p[1] == k[1]), lineIndices_)
|
# connections = filter(lambda k: p != k and
|
||||||
# # connections = filter(lambda (i1_, i2_): (i1, i2) != (i1_, i2_) and (i1 == i1_ or i1 == i2_ or i2 == i1_ or i2 == i2_), lineIndices_)
|
# (p[0] == k[0] or p[0] == k[1] or p[1] == k[0] or p[1] == k[1]), lineIndices_)
|
||||||
|
# # connections = filter(lambda (i1_, i2_): (i1, i2) != (i1_, i2_) and
|
||||||
|
# (i1 == i1_ or i1 == i2_ or i2 == i1_ or i2 == i2_), lineIndices_)
|
||||||
# assert 1 <= len(connections) <= 2
|
# assert 1 <= len(connections) <= 2
|
||||||
# if len(connections) == 1:
|
# if len(connections) == 1:
|
||||||
# dangling_lines.append((i1, i2))
|
# dangling_lines.append((i1, i2))
|
||||||
|
|
|
@ -926,7 +926,6 @@ class Gerber(Geometry):
|
||||||
elif current_operation_code == 2:
|
elif current_operation_code == 2:
|
||||||
if len(path) > 1:
|
if len(path) > 1:
|
||||||
geo_s = None
|
geo_s = None
|
||||||
geo_f = None
|
|
||||||
|
|
||||||
geo_dict = dict()
|
geo_dict = dict()
|
||||||
# --- BUFFERED ---
|
# --- BUFFERED ---
|
||||||
|
@ -1910,8 +1909,7 @@ class Gerber(Geometry):
|
||||||
log.debug('camlib.Gerber.skew() Exception --> %s' % str(e))
|
log.debug('camlib.Gerber.skew() Exception --> %s' % str(e))
|
||||||
return 'fail'
|
return 'fail'
|
||||||
|
|
||||||
self.app.inform.emit('[success] %s' %
|
self.app.inform.emit('[success] %s' % _("Gerber Skew done."))
|
||||||
_("Gerber Skew done."))
|
|
||||||
self.app.proc_container.new_text = ''
|
self.app.proc_container.new_text = ''
|
||||||
|
|
||||||
def rotate(self, angle, point):
|
def rotate(self, angle, point):
|
||||||
|
@ -1974,3 +1972,376 @@ class Gerber(Geometry):
|
||||||
self.app.inform.emit('[success] %s' %
|
self.app.inform.emit('[success] %s' %
|
||||||
_("Gerber Rotate done."))
|
_("Gerber Rotate done."))
|
||||||
self.app.proc_container.new_text = ''
|
self.app.proc_container.new_text = ''
|
||||||
|
|
||||||
|
|
||||||
|
class ApertureMacro:
|
||||||
|
"""
|
||||||
|
Syntax of aperture macros.
|
||||||
|
|
||||||
|
<AM command>: AM<Aperture macro name>*<Macro content>
|
||||||
|
<Macro content>: {{<Variable definition>*}{<Primitive>*}}
|
||||||
|
<Variable definition>: $K=<Arithmetic expression>
|
||||||
|
<Primitive>: <Primitive code>,<Modifier>{,<Modifier>}|<Comment>
|
||||||
|
<Modifier>: $M|< Arithmetic expression>
|
||||||
|
<Comment>: 0 <Text>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ## Regular expressions
|
||||||
|
am1_re = re.compile(r'^%AM([^\*]+)\*(.+)?(%)?$')
|
||||||
|
am2_re = re.compile(r'(.*)%$')
|
||||||
|
amcomm_re = re.compile(r'^0(.*)')
|
||||||
|
amprim_re = re.compile(r'^[1-9].*')
|
||||||
|
amvar_re = re.compile(r'^\$([0-9a-zA-z]+)=(.*)')
|
||||||
|
|
||||||
|
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
|
||||||
|
macro (in ``self.raw``) by replacing all variables by their
|
||||||
|
values iteratively and evaluating expressions. Results
|
||||||
|
are stored in ``self.primitives``.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
# Cleanup
|
||||||
|
self.raw = self.raw.replace('\n', '').replace('\r', '').strip(" *")
|
||||||
|
self.primitives = []
|
||||||
|
|
||||||
|
# Separate parts
|
||||||
|
parts = self.raw.split('*')
|
||||||
|
|
||||||
|
# ### Every part in the macro ####
|
||||||
|
for part in parts:
|
||||||
|
# ## Comments. Ignored.
|
||||||
|
match = ApertureMacro.amcomm_re.search(part)
|
||||||
|
if match:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ## Variables
|
||||||
|
# These are variables defined locally inside the macro. They can be
|
||||||
|
# numerical constant or defind in terms of previously define
|
||||||
|
# variables, which can be defined locally or in an aperture
|
||||||
|
# definition. All replacements ocurr here.
|
||||||
|
match = ApertureMacro.amvar_re.search(part)
|
||||||
|
if match:
|
||||||
|
var = match.group(1)
|
||||||
|
val = match.group(2)
|
||||||
|
|
||||||
|
# Replace variables in value
|
||||||
|
for v in self.locvars:
|
||||||
|
# replaced the following line with the next to fix Mentor custom apertures not parsed OK
|
||||||
|
# val = re.sub((r'\$'+str(v)+r'(?![0-9a-zA-Z])'), str(self.locvars[v]), val)
|
||||||
|
val = val.replace('$' + str(v), str(self.locvars[v]))
|
||||||
|
|
||||||
|
# Make all others 0
|
||||||
|
val = re.sub(r'\$[0-9a-zA-Z](?![0-9a-zA-Z])', "0", val)
|
||||||
|
# Change x with *
|
||||||
|
val = re.sub(r'[xX]', "*", val)
|
||||||
|
|
||||||
|
# Eval() and store.
|
||||||
|
self.locvars[var] = eval(val)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ## Primitives
|
||||||
|
# Each is an array. The first identifies the primitive, while the
|
||||||
|
# rest depend on the primitive. All are strings representing a
|
||||||
|
# number and may contain variable definition. The values of these
|
||||||
|
# variables are defined in an aperture definition.
|
||||||
|
match = ApertureMacro.amprim_re.search(part)
|
||||||
|
if match:
|
||||||
|
# ## Replace all variables
|
||||||
|
for v in self.locvars:
|
||||||
|
# replaced the following line with the next to fix Mentor custom apertures not parsed OK
|
||||||
|
# part = re.sub(r'\$' + str(v) + r'(?![0-9a-zA-Z])', str(self.locvars[v]), part)
|
||||||
|
part = part.replace('$' + str(v), str(self.locvars[v]))
|
||||||
|
|
||||||
|
# Make all others 0
|
||||||
|
part = re.sub(r'\$[0-9a-zA-Z](?![0-9a-zA-Z])', "0", part)
|
||||||
|
|
||||||
|
# Change x with *
|
||||||
|
part = re.sub(r'[xX]', "*", part)
|
||||||
|
|
||||||
|
# ## Store
|
||||||
|
elements = part.split(",")
|
||||||
|
self.primitives.append([eval(x) for x in elements])
|
||||||
|
continue
|
||||||
|
|
||||||
|
log.warning("Unknown syntax of aperture macro part: %s" % str(part))
|
||||||
|
|
||||||
|
def append(self, data):
|
||||||
|
"""
|
||||||
|
Appends a string to the raw macro.
|
||||||
|
|
||||||
|
:param data: Part of the macro.
|
||||||
|
:type data: str
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
self.raw += data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def default2zero(n, mods):
|
||||||
|
"""
|
||||||
|
Pads the ``mods`` list with zeros resulting in an
|
||||||
|
list of length n.
|
||||||
|
|
||||||
|
:param n: Length of the resulting list.
|
||||||
|
:type n: int
|
||||||
|
:param mods: List to be padded.
|
||||||
|
:type mods: list
|
||||||
|
:return: Zero-padded list.
|
||||||
|
:rtype: list
|
||||||
|
"""
|
||||||
|
x = [0.0] * n
|
||||||
|
na = len(mods)
|
||||||
|
x[0:na] = mods
|
||||||
|
return x
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_circle(mods):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param mods: (Exposure 0/1, Diameter >=0, X-coord, Y-coord)
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
pol, dia, x, y = ApertureMacro.default2zero(4, mods)
|
||||||
|
|
||||||
|
return {"pol": int(pol), "geometry": Point(x, y).buffer(dia/2)}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_vectorline(mods):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param mods: (Exposure 0/1, Line width >= 0, X-start, Y-start, X-end, Y-end,
|
||||||
|
rotation angle around origin in degrees)
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
pol, width, xs, ys, xe, ye, angle = ApertureMacro.default2zero(7, mods)
|
||||||
|
|
||||||
|
line = LineString([(xs, ys), (xe, ye)])
|
||||||
|
box = line.buffer(width/2, cap_style=2)
|
||||||
|
box_rotated = affinity.rotate(box, angle, origin=(0, 0))
|
||||||
|
|
||||||
|
return {"pol": int(pol), "geometry": box_rotated}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_centerline(mods):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param mods: (Exposure 0/1, width >=0, height >=0, x-center, y-center,
|
||||||
|
rotation angle around origin in degrees)
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
pol, width, height, x, y, angle = ApertureMacro.default2zero(6, mods)
|
||||||
|
|
||||||
|
box = shply_box(x-width/2, y-height/2, x+width/2, y+height/2)
|
||||||
|
box_rotated = affinity.rotate(box, angle, origin=(0, 0))
|
||||||
|
|
||||||
|
return {"pol": int(pol), "geometry": box_rotated}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_lowerleftline(mods):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param mods: (exposure 0/1, width >=0, height >=0, x-lowerleft, y-lowerleft,
|
||||||
|
rotation angle around origin in degrees)
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
pol, width, height, x, y, angle = ApertureMacro.default2zero(6, mods)
|
||||||
|
|
||||||
|
box = shply_box(x, y, x+width, y+height)
|
||||||
|
box_rotated = affinity.rotate(box, angle, origin=(0, 0))
|
||||||
|
|
||||||
|
return {"pol": int(pol), "geometry": box_rotated}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_outline(mods):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param mods:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
pol = mods[0]
|
||||||
|
n = mods[1]
|
||||||
|
points = [(0, 0)]*(n+1)
|
||||||
|
|
||||||
|
for i in range(n+1):
|
||||||
|
points[i] = mods[2*i + 2:2*i + 4]
|
||||||
|
|
||||||
|
angle = mods[2*n + 4]
|
||||||
|
|
||||||
|
poly = Polygon(points)
|
||||||
|
poly_rotated = affinity.rotate(poly, angle, origin=(0, 0))
|
||||||
|
|
||||||
|
return {"pol": int(pol), "geometry": poly_rotated}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_polygon(mods):
|
||||||
|
"""
|
||||||
|
Note: Specs indicate that rotation is only allowed if the center
|
||||||
|
(x, y) == (0, 0). I will tolerate breaking this rule.
|
||||||
|
|
||||||
|
:param mods: (exposure 0/1, n_verts 3<=n<=12, x-center, y-center,
|
||||||
|
diameter of circumscribed circle >=0, rotation angle around origin)
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
pol, nverts, x, y, dia, angle = ApertureMacro.default2zero(6, mods)
|
||||||
|
points = [(0, 0)]*nverts
|
||||||
|
|
||||||
|
for i in range(nverts):
|
||||||
|
points[i] = (x + 0.5 * dia * cos(2*pi * i/nverts),
|
||||||
|
y + 0.5 * dia * sin(2*pi * i/nverts))
|
||||||
|
|
||||||
|
poly = Polygon(points)
|
||||||
|
poly_rotated = affinity.rotate(poly, angle, origin=(0, 0))
|
||||||
|
|
||||||
|
return {"pol": int(pol), "geometry": poly_rotated}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_moire(mods):
|
||||||
|
"""
|
||||||
|
Note: Specs indicate that rotation is only allowed if the center
|
||||||
|
(x, y) == (0, 0). I will tolerate breaking this rule.
|
||||||
|
|
||||||
|
:param mods: (x-center, y-center, outer_dia_outer_ring, ring thickness,
|
||||||
|
gap, max_rings, crosshair_thickness, crosshair_len, rotation
|
||||||
|
angle around origin in degrees)
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
x, y, dia, thickness, gap, nrings, cross_th, cross_len, angle = ApertureMacro.default2zero(9, mods)
|
||||||
|
|
||||||
|
r = dia/2 - thickness/2
|
||||||
|
result = Point((x, y)).buffer(r).exterior.buffer(thickness/2.0)
|
||||||
|
ring = Point((x, y)).buffer(r).exterior.buffer(thickness/2.0) # Need a copy!
|
||||||
|
|
||||||
|
i = 1 # Number of rings created so far
|
||||||
|
|
||||||
|
# ## If the ring does not have an interior it means that it is
|
||||||
|
# ## a disk. Then stop.
|
||||||
|
while len(ring.interiors) > 0 and i < nrings:
|
||||||
|
r -= thickness + gap
|
||||||
|
if r <= 0:
|
||||||
|
break
|
||||||
|
ring = Point((x, y)).buffer(r).exterior.buffer(thickness/2.0)
|
||||||
|
result = cascaded_union([result, ring])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# ## Crosshair
|
||||||
|
hor = LineString([(x - cross_len, y), (x + cross_len, y)]).buffer(cross_th/2.0, cap_style=2)
|
||||||
|
ver = LineString([(x, y-cross_len), (x, y + cross_len)]).buffer(cross_th/2.0, cap_style=2)
|
||||||
|
result = cascaded_union([result, hor, ver])
|
||||||
|
|
||||||
|
return {"pol": 1, "geometry": result}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_thermal(mods):
|
||||||
|
"""
|
||||||
|
Note: Specs indicate that rotation is only allowed if the center
|
||||||
|
(x, y) == (0, 0). I will tolerate breaking this rule.
|
||||||
|
|
||||||
|
:param mods: [x-center, y-center, diameter-outside, diameter-inside,
|
||||||
|
gap-thickness, rotation angle around origin]
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
x, y, dout, din, t, angle = ApertureMacro.default2zero(6, mods)
|
||||||
|
|
||||||
|
ring = Point((x, y)).buffer(dout/2.0).difference(Point((x, y)).buffer(din/2.0))
|
||||||
|
hline = LineString([(x - dout/2.0, y), (x + dout/2.0, y)]).buffer(t/2.0, cap_style=3)
|
||||||
|
vline = LineString([(x, y - dout/2.0), (x, y + dout/2.0)]).buffer(t/2.0, cap_style=3)
|
||||||
|
thermal = ring.difference(hline.union(vline))
|
||||||
|
|
||||||
|
return {"pol": 1, "geometry": thermal}
|
||||||
|
|
||||||
|
def make_geometry(self, modifiers):
|
||||||
|
"""
|
||||||
|
Runs the macro for the given modifiers and generates
|
||||||
|
the corresponding geometry.
|
||||||
|
|
||||||
|
:param modifiers: Modifiers (parameters) for this macro
|
||||||
|
:type modifiers: list
|
||||||
|
:return: Shapely geometry
|
||||||
|
:rtype: shapely.geometry.polygon
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ## Primitive makers
|
||||||
|
makers = {
|
||||||
|
"1": ApertureMacro.make_circle,
|
||||||
|
"2": ApertureMacro.make_vectorline,
|
||||||
|
"20": ApertureMacro.make_vectorline,
|
||||||
|
"21": ApertureMacro.make_centerline,
|
||||||
|
"22": ApertureMacro.make_lowerleftline,
|
||||||
|
"4": ApertureMacro.make_outline,
|
||||||
|
"5": ApertureMacro.make_polygon,
|
||||||
|
"6": ApertureMacro.make_moire,
|
||||||
|
"7": ApertureMacro.make_thermal
|
||||||
|
}
|
||||||
|
|
||||||
|
# ## Store modifiers as local variables
|
||||||
|
modifiers = modifiers or []
|
||||||
|
modifiers = [float(m) for m in modifiers]
|
||||||
|
self.locvars = {}
|
||||||
|
for i in range(0, len(modifiers)):
|
||||||
|
self.locvars[str(i + 1)] = modifiers[i]
|
||||||
|
|
||||||
|
# ## Parse
|
||||||
|
self.primitives = [] # Cleanup
|
||||||
|
self.geometry = Polygon()
|
||||||
|
self.parse_content()
|
||||||
|
|
||||||
|
# ## Make the geometry
|
||||||
|
for primitive in self.primitives:
|
||||||
|
# Make the primitive
|
||||||
|
prim_geo = makers[str(int(primitive[0]))](primitive[1:])
|
||||||
|
|
||||||
|
# Add it (according to polarity)
|
||||||
|
# if self.geometry is None and prim_geo['pol'] == 1:
|
||||||
|
# self.geometry = prim_geo['geometry']
|
||||||
|
# continue
|
||||||
|
if prim_geo['pol'] == 1:
|
||||||
|
self.geometry = self.geometry.union(prim_geo['geometry'])
|
||||||
|
continue
|
||||||
|
if prim_geo['pol'] == 0:
|
||||||
|
self.geometry = self.geometry.difference(prim_geo['geometry'])
|
||||||
|
continue
|
||||||
|
|
||||||
|
return self.geometry
|
||||||
|
|
Loading…
Reference in New Issue