From 9308454ad4812715b7fe526849950aa8dea1f79b Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 15 Oct 2017 13:48:55 -0700 Subject: [PATCH] allow cutting any shape, always require pyclipper --- README.md | 7 +++--- masque/__init__.py | 1 + masque/shapes/polygon.py | 52 +++++++++------------------------------- masque/shapes/shape.py | 49 +++++++++++++++++++++++++++++++++++++ setup.py | 4 ++-- 5 files changed, 67 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 89c7b44..2281ef7 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,9 @@ E-beam doses, and the ability to output to multiple formats. ## Installation Requirements: -* python 3 (written and tested with 3.5) +* python >= 3.5 (written and tested with 3.6) * numpy +* pyclipper * matplotlib (optional, used for visualization functions and text) * python-gdsii (optional, used for gdsii i/o) * svgwrite (optional, used for svg output) @@ -27,5 +28,5 @@ pip install git+https://mpxd.net/gogs/jan/masque.git@release * Polygon de-embedding ### Maybe * Construct from bitmap -* Boolean operations on polygons (eg. using pyclipper) -* Output to OASIS +* Boolean operations on polygons (using pyclipper) +* Output to OASIS (using fatamorgana) diff --git a/masque/__init__.py b/masque/__init__.py index 25652dc..53eab35 100644 --- a/masque/__init__.py +++ b/masque/__init__.py @@ -18,6 +18,7 @@ Dependencies: - numpy + - pyclipper - matplotlib [Pattern.visualize(...)] - python-gdsii [masque.file.gdsii] - svgwrite [masque.file.svgwrite] diff --git a/masque/shapes/polygon.py b/masque/shapes/polygon.py index 60eba42..34f6285 100644 --- a/masque/shapes/polygon.py +++ b/masque/shapes/polygon.py @@ -2,6 +2,8 @@ from typing import List import copy import numpy from numpy import pi +import pyclipper +from pyclipper import scale_to_clipper, scale_from_clipper from . import Shape, normalized_shape_tuple from .. import PatternError @@ -173,49 +175,17 @@ class Polygon(Shape): (offset, scale/norm_value, rotation, self.dose), \ lambda: Polygon(reordered_vertices*norm_value, layer=self.layer) - def cut(self, - cut_xs: numpy.ndarray = None, - cut_ys: numpy.ndarray = None - ) -> List['Polygon']: + def clean_vertices(self) -> 'Polygon': """ - Decomposes the polygon into a list of constituents by cutting along the - specified x and/or y coordinates. + Removes duplicate, co-linear and otherwise redundant vertices. - :param cut_xs: list of x-coordinates to cut along (e.g., [1, 1.4, 6]) - :param cut_ys: list of y-coordinates to cut along (e.g., [1, 3, 5.4]) - :return: List of Polygon objects + :returns: self """ - import pyclipper - from pyclipper import scale_to_clipper, scale_from_clipper + self.vertices = scale_from_clipper( + pyclipper.CleanPolygon( + scale_to_clipper( + self.vertices + ))) + return self - min_x, min_y = numpy.min(self.vertices, axis=0) - max_x, max_y = numpy.max(self.vertices, axis=0) - range_x = max_x - min_x - range_y = max_y - min_y - edge_xs = (min_x - range_x - 1,) + tuple(cut_xs) + (max_x + range_x + 1,) - edge_ys = (min_y - range_y - 1,) + tuple(cut_ys) + (max_y + range_y + 1,) - - clipped_shapes = [] - for i in range(2): - for j in range(2): - clipper = pyclipper.Pyclipper() - clipper.AddPath(scale_to_clipper(self.vertices), pyclipper.PT_SUBJECT, True) - - for start_x, stop_x in zip(edge_xs[i::2], edge_xs[(i+1)::2]): - for start_y, stop_y in zip(edge_ys[j::2], edge_ys[(j+1)::2]): - clipper.AddPath(scale_to_clipper(( - (start_x, start_y), - (start_x, stop_y), - (stop_x, stop_y), - (stop_x, start_y), - )), pyclipper.PT_CLIP, True) - - clipped_parts = scale_from_clipper(clipper.Execute(pyclipper.CT_INTERSECTION, - pyclipper.PFT_EVENODD, - pyclipper.PFT_EVENODD)) - for part in clipped_parts: - poly = self.copy() - poly.vertices = part - clipped_shapes.append(poly) - return clipped_shapes diff --git a/masque/shapes/shape.py b/masque/shapes/shape.py index b795ba2..1036068 100644 --- a/masque/shapes/shape.py +++ b/masque/shapes/shape.py @@ -3,6 +3,9 @@ from abc import ABCMeta, abstractmethod import copy import numpy +import pyclipper +from pyclipper import scale_to_clipper, scale_from_clipper + from .. import PatternError from ..utils import is_scalar, rotation_matrix_2d, vector2 @@ -271,3 +274,49 @@ class Shape(metaclass=ABCMeta): return manhattan_polygons + def cut(self, + cut_xs: numpy.ndarray = None, + cut_ys: numpy.ndarray = None + ) -> List['Polygon']: + """ + Decomposes the shape into a list of constituent polygons by polygonizing and + then cutting along the specified x and/or y coordinates. + + :param cut_xs: list of x-coordinates to cut along (e.g., [1, 1.4, 6]) + :param cut_ys: list of y-coordinates to cut along (e.g., [1, 3, 5.4]) + :return: List of Polygon objects + """ + from . import Polygon + + clipped_shapes = [] + for polygon in self.to_polygons(): + min_x, min_y = numpy.min(polygon.vertices, axis=0) + max_x, max_y = numpy.max(polygon.vertices, axis=0) + range_x = max_x - min_x + range_y = max_y - min_y + + edge_xs = (min_x - range_x - 1,) + tuple(cut_xs) + (max_x + range_x + 1,) + edge_ys = (min_y - range_y - 1,) + tuple(cut_ys) + (max_y + range_y + 1,) + + for i in range(2): + for j in range(2): + clipper = pyclipper.Pyclipper() + clipper.AddPath(scale_to_clipper(polygon.vertices), pyclipper.PT_SUBJECT, True) + + for start_x, stop_x in zip(edge_xs[i::2], edge_xs[(i+1)::2]): + for start_y, stop_y in zip(edge_ys[j::2], edge_ys[(j+1)::2]): + clipper.AddPath(scale_to_clipper(( + (start_x, start_y), + (start_x, stop_y), + (stop_x, stop_y), + (stop_x, start_y), + )), pyclipper.PT_CLIP, True) + + clipped_parts = scale_from_clipper(clipper.Execute(pyclipper.CT_INTERSECTION, + pyclipper.PFT_EVENODD, + pyclipper.PFT_EVENODD)) + for part in clipped_parts: + poly = polygon.copy() + poly.vertices = part + clipped_shapes.append(poly) + return clipped_shapes diff --git a/setup.py b/setup.py index 3d68302..af04fee 100644 --- a/setup.py +++ b/setup.py @@ -10,14 +10,14 @@ setup(name='masque', url='https://mpxd.net/gogs/jan/masque', packages=find_packages(), install_requires=[ - 'numpy' + 'numpy', + 'pyclipper', ], extras_require={ 'visualization': ['matplotlib'], 'gdsii': ['python-gdsii'], 'svg': ['svgwrite'], 'text': ['freetype-py', 'matplotlib'], - 'clipping': ['pyclipper'], }, )