# -*- coding: utf-8 -*-
"""
Utility functions for geometric transformations and layer manipulation.
.. note::
Copyright 2009-2012 Lucas Heitzmann Gabrielli
Copyright 2013 Andrew G. Mark
gdsCAD (based on gdspy) is released under the terms of the GNU GPL
"""
import numpy as np
from core import (Cell, CellReference, CellArray,
ElementBase, Elements, ReferenceBase)
[docs]def translate(obj, displacement):
"""
Translate an object, 2D vector, or a sequence of 2D vectors by the given vector
:param obj: an object, a 2D vector, or a sequence of 2D vectors
:param displacment: the vector by which to move ``obj``
:returns: A moved copy of the ``obj``
"""
if isinstance(obj, (ElementBase, Elements)):
obj=obj.copy()
obj.translate(displacement)
return obj
return np.array(obj)+np.array(displacement)
[docs]def rotate(obj, theta, center=(0,0)):
"""
Rotate an object by given angle
:param obj: an object, a 2D vector, or a sequence of 2D vectors
:param theta: angle by which to rotate points in deg
:param center: optional, pt about which to perform the rotation
:return: A rotated copy of ``obj``
"""
if isinstance(obj, (ElementBase, Elements)):
obj=obj.copy()
obj.rotate(theta, center)
return obj
pts=np.array(obj)
ang = theta * np.pi/180
m=np.array([[np.cos(ang), -np.sin(ang)], [np.sin(ang), np.cos(ang)]])
if isinstance(center, str) and center.lower()=='com':
center=pts.mean(0)
else:
center=np.array(center)
return m.dot((np.array(pts)-center).T).T+center
[docs]def reflect(obj, axis, origin=(0,0), reverse_seq=True):
"""
Reflect an object in the x or y axis
:param obj: a geometric object, 2D vector, or a sequence of 2D vectors.
:param axis: string 'x' or 'y' indcating which axis in which to make the refln.
:param origin: optional, pt about which to perform the rotation.
:param reverse_seq: if ``True`` reverses the order of a sequence.
:returns: a reflected copy of ``obj``
Sequences of points are reversed to maintain the same sense as the
original sequence.
"""
if isinstance(obj, (ElementBase, Elements)):
obj=obj.copy()
obj.reflect(axis, origin)
return obj
if axis=='x':
return scale(obj, [1,-1], origin, reverse_seq)
elif axis=='y':
return scale(obj, [-1,1], origin, reverse_seq)
else:
raise ValueError('Unknown axis %s'%str(axis))
[docs]def scale(obj, k, origin=(0,0), reverse_seq=True):
"""
Scale the pt or sequence of pts by the factor k
:param obj: a geometric object, 2D vector, or a sequence of 2D vectors.
:param k: factor by which to scale ``obj``
:param origin: pt which remains invariant under scale
:param reverse_seq: if ``True`` reverses the order of a sequence.
:returns: a scaled copy of ``obj``
The factor k can be a scalar or 2D vector allowing non-uniform scaling
Optional origin can be a 2D vector or 'COM' indicating that scaling should
be made about the pts centre of mass. The center of mass is calculated
only for the points, not by the area of the polygon they define.
Sequences of points are reversed if necessary to maintain the
same sense as the original sequence.
"""
if isinstance(obj, (ElementBase, Elements)):
obj=obj.copy()
obj.scale(k, origin)
return obj
pts=np.array(obj)
if isinstance(origin, str) and origin.lower()=='com':
origin=pts.mean(0)
else:
origin=np.array(origin)
k=np.array(k)
if reverse_seq and ((k.prod()>=0) or k.shape==(2,)): #even parity or single point
return (pts-origin)*k+origin
else:
return ((pts-origin)*k+origin)[::-1]
[docs]def split_layers(cell, old_layers):
"""
Split the artwork in a cell between two copies according by layer.
:param cells: The :class:`Cell` to split
:param old_layers: A list of layers whose artwork will be moved to the second cell
:returns: A tuple of two cells
TODO: use same labelling scheme found in GdsImport
:func:`split_layers` provides a convenient method of separating layers in
a ``Cell`` into different Cells. It creates two copies of ``cell`` based on
the list of layers in ``old_layers``. Any artwork found on the layers of
``old_layers`` is moved to the first cell, and any artwork not found on the
layers of ``old_layers`` is moved to the second layer. The existing heirarchy
is maintained, however any empty ``Cells`` or references are removed.
"""
subA=cell.copy()
subB=cell.copy()
#identify all art in subA that should be removed
blacklist=set()
deps=subA.get_dependencies(True)
print 'DEPENDENCY LIST HAS LENGTH: ',len(deps)
for e in deps:
if not isinstance(e, (Cell, CellReference, CellArray)):
if e.layer in old_layers:
blacklist.add(e)
#remove references to removed art
for c in [subA]+deps:
if isinstance(c, Cell):
c._objects=[e for e in c.objects if e not in blacklist]
#clean heirarchy
subA.prune()
#identify all art in subB that should be removed
blacklist=set()
deps=subB.get_dependencies(True)
for e in deps:
if not isinstance(e, (Cell, CellReference, CellArray)):
if e.layer not in old_layers:
blacklist.add(e)
#remove references to removed art
for c in [subB]+deps:
if isinstance(c, Cell):
c._objects=[e for e in c.objects if e not in blacklist]
#clean heirarchy
subB.prune()
return (subA, subB)
[docs]def relayer(cell, old_layers, new_layer):
"""
Move any elements in old_layers to new_layer
:param cell: The :class:`Cell` to process
:param old_layers: A list of layers to be relayed
:param new_layer: The layer that ``old_layers`` will be moved to
:returns: A copy of the relayered ``Cell``
TODO: use same labelling scheme found in GdsImport
"""
new_cell=cell.copy()
#change layer of art
for e in new_cell.get_dependencies(True):
if not isinstance(e, (Cell, CellReference, CellArray)):
if e.layer in old_layers:
e.layer=new_layer
return new_cell
#===========================================
#functions below here probably no longer work
[docs]def chop(polygon, position, axis):
"""
**BROKEN**: Slice polygon at a given position along a given axis.
Parameters
polygon : array-like[N][2]
Coordinates of the vertices of the polygon.
position : number
Position to perform the slicing operation along the specified
axis.
axis : 0 or 1
Axis along which the polygon will be sliced.
Returns
out : tuple[2]
Each element is a list of polygons (array-like[N][2]). The first
list contains the polygons left before the slicing position, and
the second, the polygons left after that position.
"""
out_polygons = ([], [])
polygon = list(polygon)
while polygon[-1][axis] == position:
polygon = [polygon[-1]] + polygon[:-1]
cross = list(numpy.sign(numpy.array(polygon)[:, axis] - position))
bnd = ([], [])
i = 0
while i < len(cross):
if cross[i - 1] * cross[i] < 0:
if axis == 0:
polygon.insert(i, [position, polygon[i - 1][1] + (position - polygon[i - 1][0]) * float(polygon[i][1] - polygon[i - 1][1]) / (polygon[i][0] - polygon[i - 1][0])])
else:
polygon.insert(i, [polygon[i - 1][0] + (position - polygon[i - 1][1]) * float(polygon[i][0] - polygon[i - 1][0]) / (polygon[i][1] - polygon[i - 1][1]), position])
cross.insert(i, 0)
bnd[1 * (cross[i + 1] > 0)].append(i)
i += 2
elif cross[i] == 0:
j = i + 1
while cross[j] == 0:
j += 1
if cross[i - 1] * cross[j] < 0:
bnd[1 * (cross[j] > 0)].append(j - 1)
i = j + 1
else:
i += 1
if len(bnd[0]) == 0:
out_polygons[1 * (numpy.sum(cross) > 0)].append(polygon)
return out_polygons
bnd = (numpy.array(bnd[0]), numpy.array(bnd[1]))
bnd = (list(bnd[0][numpy.argsort(numpy.array(polygon)[bnd[0], 1 - axis])]),
list(bnd[1][numpy.argsort(numpy.array(polygon)[bnd[1], 1 - axis])]))
cross = numpy.ones(len(polygon), dtype=int)
cross[bnd[0]] = -2
cross[bnd[1]] = -1
i = 0
while i < len(polygon):
if cross[i] > 0 and polygon[i][axis] != position:
start = i
side = 1 * (polygon[i][axis] > position)
out_polygons[side].append([polygon[i]])
cross[i] = 0
nxt = i + 1
if nxt == len(polygon):
nxt = 0
boundary = True
while nxt != start:
out_polygons[side][-1].append(polygon[nxt])
if cross[nxt] > 0:
cross[nxt] = 0
if cross[nxt] < 0 and boundary:
j = bnd[cross[nxt] + 2].index(nxt)
nxt = bnd[-cross[nxt] - 1][j]
boundary = False
else:
nxt += 1
if nxt == len(polygon):
nxt = 0
boundary = True
i += 1
return out_polygons
[docs]def slice(layer, objects, position, axis, datatype=0):
"""
**BROKEN** Slice polygons and polygon sets at given positions along an axis.
Parameters
layer : integer, list
The GDSII layer numbers for the elements between each division. If
the number of layers in the list is less than the number of divided
regions, the list is repeated.
objects : ``Polygon``, ``PolygonSet``, or list
Operand of the slice operation. If this is a list, each element
must be a ``Polygon``, ``PolygonSet``, ``CellReference``,
``CellArray``, or an array-like[N][2] of vertices of a polygon.
position : number or list of numbers
Positions to perform the slicing operation along the specified
axis.
axis : 0 or 1
Axis along which the polygon will be sliced.
datatype : integer
The GDSII datatype for the resulting element (between 0 and 255).
Returns
out : list[N] of PolygonSet
Result of the slicing operation, with N = len(positions) + 1. Each
PolygonSet comprises all polygons between 2 adjacent slicing
positions, in crescent order.
Examples
>>> ring = gdspy.Round(1, (0, 0), 10, inner_radius = 5)
>>> result = gdspy.slice(1, ring, [-7, 7], 0)
>>> cell.add(result[1])
"""
if (layer.__class__ != [].__class__):
layer = [layer]
if (objects.__class__ != [].__class__):
objects = [objects]
if (position.__class__ != [].__class__):
position = [position]
position.sort()
result = [[] for i in range(len(position) + 1)]
polygons = []
for obj in objects:
if isinstance(obj, Boundary):
polygons.append(obj.points)
elif isinstance(obj, Elements):
polygons += obj.polygons
elif isinstance(obj, CellReference) or isinstance(obj, CellArray):
polygons += obj.get_polygons()
else:
polygons.append(obj)
for i, p in enumerate(position):
nxt_polygons = []
for pol in polygons:
(pol1, pol2) = chop(pol, p, axis)
result[i] += pol1
nxt_polygons += pol2
polygons = nxt_polygons
result[-1] = polygons
for i in range(len(result)):
result[i] = Elements(layer[i % len(layer)], result[i], datatype)
return result
[docs]def boolean(layer, objects, operation, max_points=199, datatype=0, eps=1e-13):
"""
**BROKEN** Execute any boolean operation on polygons and polygon sets.
Parameters
layer : integer
The GDSII layer number for the resulting element.
objects : array-like
Operands of the boolean operation. Each element of this array must
be a ``Polygon``, ``PolygonSet``, ``CellReference``, ``CellArray``,
or an array-like[N][2] of vertices of a polygon.
operation : function
Function that accepts as input ``len(objects)`` integers. Each
integer represents the incidence of the corresponding ``object``.
The function must return a bool or integer (interpreted as bool).
max_points : integer
If greater than 4, fracture the resulting polygons to ensure they
have at most ``max_points`` vertices. This is not a tessellating
function, so this number should be as high as possible. For
example, it should be set to 199 for polygons being drawn in GDSII
files.
datatype : integer
The GDSII datatype for the resulting element (between 0 and 255).
eps : positive number
Small number to be used as tolerance in intersection and overlap
calculations.
Returns
out : PolygonSet
Result of the boolean operation.
Notes
Since ``operation`` receives a list of integers as input, it can be
somewhat more general than boolean operations only. See the examples
below.
Because of roundoff errors there are a few cases when this function
can cause segmentation faults. If that happens, increasing the value
of ``eps`` might help.
Examples
>>> circle = gdspy.Round(0, (0, 0), 10)
>>> triangle = gdspy.Round(0, (0, 0), 12, number_of_points=3)
>>> bad_poly = gdspy.L1Path(1, (0, 0), '+y', 2,
[6, 4, 4, 8, 4, 5, 10], [-1, -1, -1, 1, 1, 1])
>>> union = gdspy.boolean(1, [circle, triangle],
lambda cir, tri: cir or tri)
>>> intersection = gdspy.boolean(1, [circle, triangle],
lambda cir, tri: cir and tri)
>>> subtraction = gdspy.boolean(1, [circle, triangle],
lambda cir, tri: cir and not tri)
>>> multi_xor = gdspy.boolean(1, [badPath], lambda p: p % 2)
"""
polygons = []
indices = [0]
special_function = False
for obj in objects:
if isinstance(obj, ElementBase):
polygons.append(obj.points)
indices.append(indices[-1] + 1)
elif isinstance(obj, Elements):
special_function = True
polygons += obj.polygons
indices.append(indices[-1] + len(obj.polygons))
elif isinstance(obj, CellReference) or isinstance(obj, CellArray):
special_function = True
a = obj.get_polygons()
polygons += a
indices.append(indices[-1] + len(a))
else:
polygons.append(obj)
indices.append(indices[-1] + 1)
if special_function:
result = boolext.clip(polygons, lambda *p: operation(*[sum(p[indices[ia]:indices[ia + 1]]) for ia in range(len(indices) - 1)]), eps)
else:
result = boolext.clip(polygons, operation, eps)
return None if result is None else Elements(layer, result, datatype, False).fracture(max_points)