summaryrefslogtreecommitdiff
path: root/gerber
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2021-06-13 15:00:17 +0200
committerjaseg <git@jaseg.de>2021-06-13 15:00:17 +0200
commit4eb0e063bcd34c21b737023aa6ed5baed80658d1 (patch)
tree3a56ef7d05f4f64cde930f2432119986e4aab49d /gerber
parent889ea37d9b66cbfb7a61795c7750b9f4311faa3f (diff)
downloadgerbonara-4eb0e063bcd34c21b737023aa6ed5baed80658d1.tar.gz
gerbonara-4eb0e063bcd34c21b737023aa6ed5baed80658d1.tar.bz2
gerbonara-4eb0e063bcd34c21b737023aa6ed5baed80658d1.zip
Repo re-org, make gerberex tests run
Diffstat (limited to 'gerber')
-rw-r--r--gerber/__init__.py28
-rw-r--r--gerber/__main__.py122
-rw-r--r--gerber/am_eval.py109
-rw-r--r--gerber/am_read.py255
-rw-r--r--gerber/am_statements.py1046
-rw-r--r--gerber/cam.py286
-rw-r--r--gerber/common.py71
-rwxr-xr-xgerber/excellon.py904
-rw-r--r--gerber/excellon_report/excellon_drr.py25
-rw-r--r--gerber/excellon_settings.py105
-rw-r--r--gerber/excellon_statements.py979
-rw-r--r--gerber/excellon_tool.py190
-rw-r--r--gerber/exceptions.py36
-rw-r--r--gerber/gerber_statements.py1189
-rw-r--r--gerber/ipc356.py485
-rw-r--r--gerber/layers.py295
-rw-r--r--gerber/ncparam/allegro.py25
-rw-r--r--gerber/operations.py126
-rw-r--r--gerber/pcb.py124
-rw-r--r--gerber/primitives.py1697
-rw-r--r--gerber/render/__init__.py31
-rw-r--r--gerber/render/cairo_backend.py616
-rw-r--r--gerber/render/excellon_backend.py188
-rw-r--r--gerber/render/render.py246
-rw-r--r--gerber/render/rs274x_backend.py510
-rw-r--r--gerber/render/theme.py112
-rw-r--r--gerber/rs274x.py800
-rw-r--r--gerber/tests/__init__.py0
-rw-r--r--gerber/tests/golden/example_am_exposure_modifier.pngbin10091 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_coincident_hole.pngbin47261 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_cutin_multiple.pngbin1348 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_flash_circle.pngbin5978 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_flash_obround.pngbin3443 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_flash_polygon.pngbin4087 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_flash_rectangle.pngbin1731 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_fully_coincident.pngbin71825 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_holes_dont_clear.pngbin11552 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_not_overlapping_contour.pngbin71825 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_not_overlapping_touching.pngbin96557 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_overlapping_contour.pngbin33301 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_overlapping_touching.pngbin33301 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_simple_contour.pngbin31830 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_single_contour.pngbin556 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_single_contour_3.pngbin2297 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_single_quadrant.gbr16
-rw-r--r--gerber/tests/golden/example_single_quadrant.pngbin9658 -> 0 bytes
-rw-r--r--gerber/tests/golden/example_two_square_boxes.gbr16
-rw-r--r--gerber/tests/golden/example_two_square_boxes.pngbin18219 -> 0 bytes
-rw-r--r--gerber/tests/resources/board_outline.GKO503
-rw-r--r--gerber/tests/resources/bottom_copper.GBL1811
-rw-r--r--gerber/tests/resources/bottom_mask.GBS66
-rw-r--r--gerber/tests/resources/bottom_silk.GBO6007
-rw-r--r--gerber/tests/resources/example_am_exposure_modifier.gbr16
-rw-r--r--gerber/tests/resources/example_coincident_hole.gbr24
-rw-r--r--gerber/tests/resources/example_cutin.gbr18
-rw-r--r--gerber/tests/resources/example_cutin_multiple.gbr28
-rw-r--r--gerber/tests/resources/example_flash_circle.gbr10
-rw-r--r--gerber/tests/resources/example_flash_obround.gbr10
-rw-r--r--gerber/tests/resources/example_flash_polygon.gbr10
-rw-r--r--gerber/tests/resources/example_flash_rectangle.gbr10
-rw-r--r--gerber/tests/resources/example_fully_coincident.gbr23
-rw-r--r--gerber/tests/resources/example_guess_by_content.g0166
-rw-r--r--gerber/tests/resources/example_holes_dont_clear.gbr13
-rw-r--r--gerber/tests/resources/example_level_holes.gbr39
-rw-r--r--gerber/tests/resources/example_not_overlapping_contour.gbr20
-rw-r--r--gerber/tests/resources/example_not_overlapping_touching.gbr20
-rw-r--r--gerber/tests/resources/example_overlapping_contour.gbr20
-rw-r--r--gerber/tests/resources/example_overlapping_touching.gbr20
-rw-r--r--gerber/tests/resources/example_simple_contour.gbr16
-rw-r--r--gerber/tests/resources/example_single_contour_1.gbr15
-rw-r--r--gerber/tests/resources/example_single_contour_2.gbr15
-rw-r--r--gerber/tests/resources/example_single_contour_3.gbr15
-rw-r--r--gerber/tests/resources/example_single_quadrant.gbr18
-rw-r--r--gerber/tests/resources/example_two_square_boxes.gbr19
-rw-r--r--gerber/tests/resources/ipc-d-356.ipc115
-rw-r--r--gerber/tests/resources/multiline_read.ger9
-rw-r--r--gerber/tests/resources/ncdrill.DRD51
-rw-r--r--gerber/tests/resources/top_copper.GTL27
-rw-r--r--gerber/tests/resources/top_mask.GTS162
-rw-r--r--gerber/tests/resources/top_silk.GTO2099
-rw-r--r--gerber/tests/test_am_statements.py395
-rw-r--r--gerber/tests/test_cairo_backend.py279
-rw-r--r--gerber/tests/test_cam.py151
-rw-r--r--gerber/tests/test_common.py38
-rw-r--r--gerber/tests/test_excellon.py366
-rw-r--r--gerber/tests/test_excellon_statements.py734
-rw-r--r--gerber/tests/test_gerber_statements.py959
-rw-r--r--gerber/tests/test_ipc356.py148
-rw-r--r--gerber/tests/test_layers.py158
-rw-r--r--gerber/tests/test_primitives.py1429
-rw-r--r--gerber/tests/test_rs274x.py55
-rw-r--r--gerber/tests/test_rs274x_backend.py232
-rw-r--r--gerber/tests/test_utils.py167
-rw-r--r--gerber/utils.py458
94 files changed, 0 insertions, 27596 deletions
diff --git a/gerber/__init__.py b/gerber/__init__.py
deleted file mode 100644
index 1faba53..0000000
--- a/gerber/__init__.py
+++ /dev/null
@@ -1,28 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2013-2014 Paulo Henrique Silva <ph.silva@gmail.com>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Gerber Tools
-============
-**Gerber Tools**
-
-gerber-tools provides utilities for working with Gerber (RS-274X) and Excellon
-files in python.
-"""
-
-from .common import read, loads
-from .layers import load_layer, load_layer_data
-from .pcb import PCB
diff --git a/gerber/__main__.py b/gerber/__main__.py
deleted file mode 100644
index 988adff..0000000
--- a/gerber/__main__.py
+++ /dev/null
@@ -1,122 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2013-2014 Paulo Henrique Silva <ph.silva@gmail.com>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-
-import os
-import argparse
-from .render import available_renderers
-from .render import theme
-from .pcb import PCB
-from . import load_layer
-
-
-def main():
- parser = argparse.ArgumentParser(
- description='Render gerber files to image',
- prog='gerber-render'
- )
- parser.add_argument(
- 'filenames', metavar='FILENAME', type=str, nargs='+',
- help='Gerber files to render. If a directory is provided, it should '
- 'be provided alone and should contain the gerber files for a '
- 'single PCB.'
- )
- parser.add_argument(
- '--outfile', '-o', type=str, nargs='?', default='out',
- help="Output Filename (extension will be added automatically)"
- )
- parser.add_argument(
- '--backend', '-b', choices=available_renderers.keys(), default='cairo',
- help='Choose the backend to use to generate the output.'
- )
- parser.add_argument(
- '--theme', '-t', choices=theme.THEMES.keys(), default='default',
- help='Select render theme.'
- )
- parser.add_argument(
- '--width', type=int, default=1920, help='Maximum width.'
- )
- parser.add_argument(
- '--height', type=int, default=1080, help='Maximum height.'
- )
- parser.add_argument(
- '--verbose', '-v', action='store_true', default=False,
- help='Increase verbosity of the output.'
- )
- # parser.add_argument(
- # '--quick', '-q', action='store_true', default=False,
- # help='Skip longer running rendering steps to produce lower quality'
- # ' output faster. This only has an effect for the freecad backend.'
- # )
- # parser.add_argument(
- # '--nox', action='store_true', default=False,
- # help='Run without using any GUI elements. This may produce suboptimal'
- # 'output. For the freecad backend, colors, transparancy, and '
- # 'visibility cannot be set without a GUI instance.'
- # )
-
- args = parser.parse_args()
-
- renderer = available_renderers[args.backend]()
-
- if args.backend in ['cairo', ]:
- outext = 'png'
- else:
- outext = None
-
- if os.path.exists(args.filenames[0]) and os.path.isdir(args.filenames[0]):
- directory = args.filenames[0]
- pcb = PCB.from_directory(directory)
-
- if args.backend in ['cairo', ]:
- top = pcb.top_layers
- bottom = pcb.bottom_layers
- copper = pcb.copper_layers
-
- outline = pcb.outline_layer
- if outline:
- top = [outline] + top
- bottom = [outline] + bottom
- copper = [outline] + copper + pcb.drill_layers
-
- renderer.render_layers(
- layers=top, theme=theme.THEMES[args.theme],
- max_height=args.height, max_width=args.width,
- filename='{0}.top.{1}'.format(args.outfile, outext)
- )
- renderer.render_layers(
- layers=bottom, theme=theme.THEMES[args.theme],
- max_height=args.height, max_width=args.width,
- filename='{0}.bottom.{1}'.format(args.outfile, outext)
- )
- renderer.render_layers(
- layers=copper, theme=theme.THEMES['Transparent Multilayer'],
- max_height=args.height, max_width=args.width,
- filename='{0}.copper.{1}'.format(args.outfile, outext))
- else:
- pass
- else:
- filenames = args.filenames
- for filename in filenames:
- layer = load_layer(filename)
- settings = theme.THEMES[args.theme].get(layer.layer_class, None)
- renderer.render_layer(layer, settings=settings)
- renderer.dump(filename='{0}.{1}'.format(args.outfile, outext))
-
-
-if __name__ == '__main__':
- main()
-
diff --git a/gerber/am_eval.py b/gerber/am_eval.py
deleted file mode 100644
index 3a7e1ed..0000000
--- a/gerber/am_eval.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-# copyright 2014 Paulo Henrique Silva <ph.silva@gmail.com>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-""" This module provides RS-274-X AM macro evaluation.
-"""
-
-
-class OpCode:
- PUSH = 1
- LOAD = 2
- STORE = 3
- ADD = 4
- SUB = 5
- MUL = 6
- DIV = 7
- PRIM = 8
-
- @staticmethod
- def str(opcode):
- if opcode == OpCode.PUSH:
- return "OPCODE_PUSH"
- elif opcode == OpCode.LOAD:
- return "OPCODE_LOAD"
- elif opcode == OpCode.STORE:
- return "OPCODE_STORE"
- elif opcode == OpCode.ADD:
- return "OPCODE_ADD"
- elif opcode == OpCode.SUB:
- return "OPCODE_SUB"
- elif opcode == OpCode.MUL:
- return "OPCODE_MUL"
- elif opcode == OpCode.DIV:
- return "OPCODE_DIV"
- elif opcode == OpCode.PRIM:
- return "OPCODE_PRIM"
- else:
- return "UNKNOWN"
-
-
-def eval_macro(instructions, parameters={}):
-
- if not isinstance(parameters, type({})):
- p = {}
- for i, val in enumerate(parameters):
- p[i + 1] = val
-
- parameters = p
-
- stack = []
-
- def pop():
- return stack.pop()
-
- def push(op):
- stack.append(op)
-
- def top():
- return stack[-1]
-
- def empty():
- return len(stack) == 0
-
- for opcode, argument in instructions:
- if opcode == OpCode.PUSH:
- push(argument)
-
- elif opcode == OpCode.LOAD:
- push(parameters.get(argument, 0))
-
- elif opcode == OpCode.STORE:
- parameters[argument] = pop()
-
- elif opcode == OpCode.ADD:
- op1 = pop()
- op2 = pop()
- push(op2 + op1)
-
- elif opcode == OpCode.SUB:
- op1 = pop()
- op2 = pop()
- push(op2 - op2)
-
- elif opcode == OpCode.MUL:
- op1 = pop()
- op2 = pop()
- push(op2 * op1)
-
- elif opcode == OpCode.DIV:
- op1 = pop()
- op2 = pop()
- push(op2 / op1)
-
- elif opcode == OpCode.PRIM:
- yield "%d,%s" % (argument, ",".join([str(x) for x in stack]))
- stack = []
diff --git a/gerber/am_read.py b/gerber/am_read.py
deleted file mode 100644
index 4aff00b..0000000
--- a/gerber/am_read.py
+++ /dev/null
@@ -1,255 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-# copyright 2014 Paulo Henrique Silva <ph.silva@gmail.com>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-""" This module provides RS-274-X AM macro modifiers parsing.
-"""
-
-from .am_eval import OpCode, eval_macro
-
-import string
-
-
-class Token:
- ADD = "+"
- SUB = "-"
- # compatibility as many gerber writes do use non compliant X
- MULT = ("x", "X")
- DIV = "/"
- OPERATORS = (ADD, SUB, MULT[0], MULT[1], DIV)
- LEFT_PARENS = "("
- RIGHT_PARENS = ")"
- EQUALS = "="
- EOF = "EOF"
-
-
-def token_to_opcode(token):
- if token == Token.ADD:
- return OpCode.ADD
- elif token == Token.SUB:
- return OpCode.SUB
- elif token in Token.MULT:
- return OpCode.MUL
- elif token == Token.DIV:
- return OpCode.DIV
- else:
- return None
-
-
-def precedence(token):
- if token == Token.ADD or token == Token.SUB:
- return 1
- elif token in Token.MULT or token == Token.DIV:
- return 2
- else:
- return 0
-
-
-def is_op(token):
- return token in Token.OPERATORS
-
-
-class Scanner:
-
- def __init__(self, s):
- self.buff = s
- self.n = 0
-
- def eof(self):
- return self.n == len(self.buff)
-
- def peek(self):
- if not self.eof():
- return self.buff[self.n]
-
- return Token.EOF
-
- def ungetc(self):
- if self.n > 0:
- self.n -= 1
-
- def getc(self):
- if self.eof():
- return ""
-
- c = self.buff[self.n]
- self.n += 1
- return c
-
- def readint(self):
- n = ""
- while not self.eof() and (self.peek() in string.digits):
- n += self.getc()
- return int(n)
-
- def readfloat(self):
- n = ""
- while not self.eof() and (self.peek() in string.digits or self.peek() == "."):
- n += self.getc()
- # weird case where zero is ommited inthe last modifider, like in ',0.'
- if n == ".":
- return 0
- return float(n)
-
- def readstr(self, end="*"):
- s = ""
- while not self.eof() and self.peek() != end:
- s += self.getc()
- return s.strip()
-
-
-def print_instructions(instructions):
- for opcode, argument in instructions:
- print("%s %s" % (OpCode.str(opcode),
- str(argument) if argument is not None else ""))
-
-
-def read_macro(macro):
- instructions = []
-
- for block in macro.split("*"):
-
- is_primitive = False
- is_equation = False
-
- found_equation_left_side = False
- found_primitive_code = False
-
- equation_left_side = 0
- primitive_code = 0
-
- unary_minus_allowed = False
- unary_minus = False
-
- if Token.EQUALS in block:
- is_equation = True
- else:
- is_primitive = True
-
- scanner = Scanner(block)
-
- # inlined here for compactness and convenience
- op_stack = []
-
- def pop():
- return op_stack.pop()
-
- def push(op):
- op_stack.append(op)
-
- def top():
- return op_stack[-1]
-
- def empty():
- return len(op_stack) == 0
-
- while not scanner.eof():
-
- c = scanner.getc()
-
- if c == ",":
- found_primitive_code = True
-
- # add all instructions on the stack to finish last modifier
- while not empty():
- instructions.append((token_to_opcode(pop()), None))
-
- unary_minus_allowed = True
-
- elif c in Token.OPERATORS:
- if c == Token.SUB and unary_minus_allowed:
- unary_minus = True
- unary_minus_allowed = False
- continue
-
- while not empty() and is_op(top()) and precedence(top()) >= precedence(c):
- instructions.append((token_to_opcode(pop()), None))
-
- push(c)
-
- elif c == Token.LEFT_PARENS:
- push(c)
-
- elif c == Token.RIGHT_PARENS:
- while not empty() and top() != Token.LEFT_PARENS:
- instructions.append((token_to_opcode(pop()), None))
-
- if empty():
- raise ValueError("unbalanced parentheses")
-
- # discard "("
- pop()
-
- elif c.startswith("$"):
- n = scanner.readint()
-
- if is_equation and not found_equation_left_side:
- equation_left_side = n
- else:
- instructions.append((OpCode.LOAD, n))
-
- elif c == Token.EQUALS:
- found_equation_left_side = True
-
- elif c == "0":
- if is_primitive and not found_primitive_code:
- instructions.append((OpCode.PUSH, scanner.readstr("*")))
- found_primitive_code = True
- else:
- # decimal or integer disambiguation
- if scanner.peek() not in '.' or scanner.peek() == Token.EOF:
- instructions.append((OpCode.PUSH, 0))
-
- elif c in "123456789.":
- scanner.ungetc()
-
- if is_primitive and not found_primitive_code:
- primitive_code = scanner.readint()
- else:
- n = scanner.readfloat()
- if unary_minus:
- unary_minus = False
- n *= -1
-
- instructions.append((OpCode.PUSH, n))
- else:
- # whitespace or unknown char
- pass
-
- # add all instructions on the stack to finish last modifier (if any)
- while not empty():
- instructions.append((token_to_opcode(pop()), None))
-
- # at end, we either have a primitive or a equation
- if is_primitive and found_primitive_code:
- instructions.append((OpCode.PRIM, primitive_code))
-
- if is_equation:
- instructions.append((OpCode.STORE, equation_left_side))
-
- return instructions
-
-if __name__ == '__main__':
- import sys
-
- instructions = read_macro(sys.argv[1])
-
- print("insructions:")
- print_instructions(instructions)
-
- print("eval:")
- for primitive in eval_macro(instructions):
- print(primitive)
diff --git a/gerber/am_statements.py b/gerber/am_statements.py
deleted file mode 100644
index 31c0ae4..0000000
--- a/gerber/am_statements.py
+++ /dev/null
@@ -1,1046 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2015 Hamilton Kibbe <ham@hamiltonkib.be> and Paulo Henrique Silva
-# <ph.silva@gmail.com>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from math import asin
-import math
-
-from .primitives import *
-from .utils import validate_coordinates, inch, metric, rotate_point
-
-
-
-# TODO: Add support for aperture macro variables
-__all__ = ['AMPrimitive', 'AMCommentPrimitive', 'AMCirclePrimitive',
- 'AMVectorLinePrimitive', 'AMOutlinePrimitive', 'AMPolygonPrimitive',
- 'AMMoirePrimitive', 'AMThermalPrimitive', 'AMCenterLinePrimitive',
- 'AMLowerLeftLinePrimitive', 'AMUnsupportPrimitive']
-
-
-class AMPrimitive(object):
- """ Aperture Macro Primitive Base Class
-
- Parameters
- ----------
- code : int
- primitive shape code
-
- exposure : str
- on or off Primitives with exposure on create a slid part of
- the macro aperture, and primitives with exposure off erase the
- solid part created previously in the aperture macro definition.
- .. note::
- The erasing effect is limited to the aperture definition in
- which it occurs.
-
- Returns
- -------
- primitive : :class: `gerber.am_statements.AMPrimitive`
-
- Raises
- ------
- TypeError, ValueError
- """
-
- def __init__(self, code, exposure=None):
- VALID_CODES = (0, 1, 2, 4, 5, 6, 7, 20, 21, 22, 9999)
- if not isinstance(code, int):
- raise TypeError('Aperture Macro Primitive code must be an integer')
- elif code not in VALID_CODES:
- raise ValueError('Invalid Code. Valid codes are %s.' %
- ', '.join(map(str, VALID_CODES)))
- if exposure is not None and exposure.lower() not in ('on', 'off'):
- raise ValueError('Exposure must be either on or off')
- self.code = code
- self.exposure = exposure.lower() if exposure is not None else None
-
- def to_inch(self):
- raise NotImplementedError('Subclass must implement `to-inch`')
-
- def to_metric(self):
- raise NotImplementedError('Subclass must implement `to-metric`')
-
- @property
- def _level_polarity(self):
- if self.exposure == 'off':
- return 'clear'
- return 'dark'
-
- def to_primitive(self, units):
- """ Return a Primitive instance based on the specified macro params.
- """
- print('Rendering {}s is not supported yet.'.format(str(self.__class__)))
-
- def __eq__(self, other):
- return self.__dict__ == other.__dict__
-
-
-class AMCommentPrimitive(AMPrimitive):
- """ Aperture Macro Comment primitive. Code 0
-
- The comment primitive has no image meaning. It is used to include human-
- readable comments into the AM command.
-
- .. seealso::
- `The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
- **Section 4.12.3.1:** Comment, primitive code 0
-
- Parameters
- ----------
- code : int
- Aperture Macro primitive code. 0 Indicates an AMCommentPrimitive
-
- comment : str
- The comment as a string.
-
- Returns
- -------
- CommentPrimitive : :class:`gerber.am_statements.AMCommentPrimitive`
- An Initialized AMCommentPrimitive
-
- Raises
- ------
- ValueError
- """
- @classmethod
- def from_gerber(cls, primitive):
- primitive = primitive.strip()
- code = int(primitive[0])
- comment = primitive[1:]
- return cls(code, comment)
-
- def __init__(self, code, comment):
- if code != 0:
- raise ValueError('Not a valid Aperture Macro Comment statement')
- super(AMCommentPrimitive, self).__init__(code)
- self.comment = comment.strip(' *')
-
- def to_inch(self):
- pass
-
- def to_metric(self):
- pass
-
- def to_gerber(self, settings=None):
- return '0 %s *' % self.comment
-
- def to_primitive(self, units):
- """
- Returns None - has not primitive representation
- """
- return None
-
- def __str__(self):
- return '<Aperture Macro Comment: %s>' % self.comment
-
-
-class AMCirclePrimitive(AMPrimitive):
- """ Aperture macro Circle primitive. Code 1
-
- A circle primitive is defined by its center point and diameter.
-
- .. seealso::
- `The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
- **Section 4.12.3.2:** Circle, primitive code 1
-
- Parameters
- ----------
- code : int
- Circle Primitive code. Must be 1
-
- exposure : string
- 'on' or 'off'
-
- diameter : float
- Circle diameter
-
- position : tuple (<float>, <float>)
- Position of the circle relative to the macro origin
-
- Returns
- -------
- CirclePrimitive : :class:`gerber.am_statements.AMCirclePrimitive`
- An initialized AMCirclePrimitive
-
- Raises
- ------
- ValueError, TypeError
- """
- @classmethod
- def from_gerber(cls, primitive):
- modifiers = primitive.strip(' *').split(',')
- code = int(modifiers[0])
- exposure = 'on' if float(modifiers[1]) == 1 else 'off'
- diameter = float(modifiers[2])
- position = (float(modifiers[3]), float(modifiers[4]))
- return cls(code, exposure, diameter, position)
-
- @classmethod
- def from_primitive(cls, primitive):
- return cls(1, 'on', primitive.diameter, primitive.position)
-
- def __init__(self, code, exposure, diameter, position):
- validate_coordinates(position)
- if code != 1:
- raise ValueError('CirclePrimitive code is 1')
- super(AMCirclePrimitive, self).__init__(code, exposure)
- self.diameter = diameter
- self.position = position
-
- def to_inch(self):
- self.diameter = inch(self.diameter)
- self.position = tuple([inch(x) for x in self.position])
-
- def to_metric(self):
- self.diameter = metric(self.diameter)
- self.position = tuple([metric(x) for x in self.position])
-
- def to_gerber(self, settings=None):
- data = dict(code=self.code,
- exposure='1' if self.exposure == 'on' else 0,
- diameter=self.diameter,
- x=self.position[0],
- y=self.position[1])
- return '{code},{exposure},{diameter},{x},{y}*'.format(**data)
-
- def to_primitive(self, units):
- return Circle((self.position), self.diameter, units=units, level_polarity=self._level_polarity)
-
-
-class AMVectorLinePrimitive(AMPrimitive):
- """ Aperture Macro Vector Line primitive. Code 2 or 20.
-
- A vector line is a rectangle defined by its line width, start, and end
- points. The line ends are rectangular.
-
- .. seealso::
- `The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
- **Section 4.12.3.3:** Vector Line, primitive code 2 or 20.
-
- Parameters
- ----------
- code : int
- Vector Line Primitive code. Must be either 2 or 20.
-
- exposure : string
- 'on' or 'off'
-
- width : float
- Line width
-
- start : tuple (<float>, <float>)
- coordinate of line start point
-
- end : tuple (<float>, <float>)
- coordinate of line end point
-
- rotation : float
- Line rotation about the origin.
-
- Returns
- -------
- LinePrimitive : :class:`gerber.am_statements.AMVectorLinePrimitive`
- An initialized AMVectorLinePrimitive
-
- Raises
- ------
- ValueError, TypeError
- """
-
- @classmethod
- def from_primitive(cls, primitive):
- return cls(2, 'on', primitive.aperture.width, primitive.start, primitive.end, 0)
-
- @classmethod
- def from_gerber(cls, primitive):
- modifiers = primitive.strip(' *').split(',')
- code = int(modifiers[0])
- exposure = 'on' if float(modifiers[1]) == 1 else 'off'
- width = float(modifiers[2])
- start = (float(modifiers[3]), float(modifiers[4]))
- end = (float(modifiers[5]), float(modifiers[6]))
- rotation = float(modifiers[7])
- return cls(code, exposure, width, start, end, rotation)
-
- def __init__(self, code, exposure, width, start, end, rotation):
- validate_coordinates(start)
- validate_coordinates(end)
- if code not in (2, 20):
- raise ValueError('VectorLinePrimitive codes are 2 or 20')
- super(AMVectorLinePrimitive, self).__init__(code, exposure)
- self.width = width
- self.start = start
- self.end = end
- self.rotation = rotation
-
- def to_inch(self):
- self.width = inch(self.width)
- self.start = tuple([inch(x) for x in self.start])
- self.end = tuple([inch(x) for x in self.end])
-
- def to_metric(self):
- self.width = metric(self.width)
- self.start = tuple([metric(x) for x in self.start])
- self.end = tuple([metric(x) for x in self.end])
-
- def to_gerber(self, settings=None):
- fmtstr = '{code},{exp},{width},{startx},{starty},{endx},{endy},{rotation}*'
- data = dict(code=self.code,
- exp=1 if self.exposure == 'on' else 0,
- width=self.width,
- startx=self.start[0],
- starty=self.start[1],
- endx=self.end[0],
- endy=self.end[1],
- rotation=self.rotation)
- return fmtstr.format(**data)
-
- def to_primitive(self, units):
- """
- Convert this to a primitive. We use the Outline to represent this (instead of Line)
- because the behaviour of the end caps is different for aperture macros compared to Lines
- when rotated.
- """
-
- # Use a line to generate our vertices easily
- line = Line(self.start, self.end, Rectangle(None, self.width, self.width))
- vertices = line.vertices
-
- aperture = Circle((0, 0), 0)
-
- lines = []
- prev_point = rotate_point(vertices[-1], self.rotation, (0, 0))
- for point in vertices:
- cur_point = rotate_point(point, self.rotation, (0, 0))
-
- lines.append(Line(prev_point, cur_point, aperture))
-
- return Outline(lines, units=units, level_polarity=self._level_polarity)
-
-
-class AMOutlinePrimitive(AMPrimitive):
- """ Aperture Macro Outline primitive. Code 4.
-
- An outline primitive is an area enclosed by an n-point polygon defined by
- its start point and n subsequent points. The outline must be closed, i.e.
- the last point must be equal to the start point. Self intersecting
- outlines are not allowed.
-
- .. seealso::
- `The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
- **Section 4.12.3.6:** Outline, primitive code 4.
-
- Parameters
- ----------
- code : int
- OutlinePrimitive code. Must be 6.
-
- exposure : string
- 'on' or 'off'
-
- start_point : tuple (<float>, <float>)
- coordinate of outline start point
-
- points : list of tuples (<float>, <float>)
- coordinates of subsequent points
-
- rotation : float
- outline rotation about the origin.
-
- Returns
- -------
- OutlinePrimitive : :class:`gerber.am_statements.AMOutlineinePrimitive`
- An initialized AMOutlinePrimitive
-
- Raises
- ------
- ValueError, TypeError
- """
-
- @classmethod
- def from_primitive(cls, primitive):
-
- start_point = (round(primitive.primitives[0].start[0], 6), round(primitive.primitives[0].start[1], 6))
- points = []
- for prim in primitive.primitives:
- points.append((round(prim.end[0], 6), round(prim.end[1], 6)))
-
- rotation = 0.0
-
- return cls(4, 'on', start_point, points, rotation)
-
- @classmethod
- def from_gerber(cls, primitive):
- modifiers = primitive.strip(' *').split(",")
-
- code = int(modifiers[0])
- exposure = "on" if float(modifiers[1]) == 1 else "off"
- n = int(float(modifiers[2]))
- start_point = (float(modifiers[3]), float(modifiers[4]))
- points = []
- for i in range(n):
- points.append((float(modifiers[5 + i * 2]),
- float(modifiers[5 + i * 2 + 1])))
- rotation = float(modifiers[-1])
- return cls(code, exposure, start_point, points, rotation)
-
- def __init__(self, code, exposure, start_point, points, rotation):
- """ Initialize AMOutlinePrimitive
- """
- validate_coordinates(start_point)
- for point in points:
- validate_coordinates(point)
- if code != 4:
- raise ValueError('OutlinePrimitive code is 4')
- super(AMOutlinePrimitive, self).__init__(code, exposure)
- self.start_point = start_point
- if points[-1] != start_point:
- raise ValueError('OutlinePrimitive must be closed')
- self.points = points
- self.rotation = rotation
-
- def to_inch(self):
- self.start_point = tuple([inch(x) for x in self.start_point])
- self.points = tuple([(inch(x), inch(y)) for x, y in self.points])
-
- def to_metric(self):
- self.start_point = tuple([metric(x) for x in self.start_point])
- self.points = tuple([(metric(x), metric(y)) for x, y in self.points])
-
- def to_gerber(self, settings=None):
- data = dict(
- code=self.code,
- exposure="1" if self.exposure == "on" else "0",
- n_points=len(self.points),
- start_point="%.6g,%.6g" % self.start_point,
- points=",\n".join(["%.6g,%.6g" % point for point in self.points]),
- rotation=str(self.rotation)
- )
- return "{code},{exposure},{n_points},{start_point},{points},{rotation}*".format(**data)
-
- def to_primitive(self, units):
- """
- Convert this to a drawable primitive. This uses the Outline instead of Line
- primitive to handle differences in end caps when rotated.
- """
-
- lines = []
- prev_point = rotate_point(self.start_point, self.rotation)
- for point in self.points:
- cur_point = rotate_point(point, self.rotation)
-
- lines.append(Line(prev_point, cur_point, Circle((0,0), 0)))
-
- prev_point = cur_point
-
- if lines[0].start != lines[-1].end:
- raise ValueError('Outline must be closed')
-
- return Outline(lines, units=units, level_polarity=self._level_polarity)
-
-
-class AMPolygonPrimitive(AMPrimitive):
- """ Aperture Macro Polygon primitive. Code 5.
-
- A polygon primitive is a regular polygon defined by the number of
- vertices, the center point, and the diameter of the circumscribed circle.
-
- .. seealso::
- `The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
- **Section 4.12.3.8:** Polygon, primitive code 5.
-
- Parameters
- ----------
- code : int
- PolygonPrimitive code. Must be 5.
-
- exposure : string
- 'on' or 'off'
-
- vertices : int, 3 <= vertices <= 12
- Number of vertices
-
- position : tuple (<float>, <float>)
- X and Y coordinates of polygon center
-
- diameter : float
- diameter of circumscribed circle.
-
- rotation : float
- polygon rotation about the origin.
-
- Returns
- -------
- PolygonPrimitive : :class:`gerber.am_statements.AMPolygonPrimitive`
- An initialized AMPolygonPrimitive
-
- Raises
- ------
- ValueError, TypeError
- """
-
- @classmethod
- def from_primitive(cls, primitive):
- return cls(5, 'on', primitive.sides, primitive.position, primitive.diameter, primitive.rotation)
-
- @classmethod
- def from_gerber(cls, primitive):
- modifiers = primitive.strip(' *').split(",")
- code = int(modifiers[0])
- exposure = "on" if float(modifiers[1]) == 1 else "off"
- vertices = int(float(modifiers[2]))
- position = (float(modifiers[3]), float(modifiers[4]))
- try:
- diameter = float(modifiers[5])
- except:
- diameter = 0
-
- rotation = float(modifiers[6])
- return cls(code, exposure, vertices, position, diameter, rotation)
-
- def __init__(self, code, exposure, vertices, position, diameter, rotation):
- """ Initialize AMPolygonPrimitive
- """
- if code != 5:
- raise ValueError('PolygonPrimitive code is 5')
- super(AMPolygonPrimitive, self).__init__(code, exposure)
- if vertices < 3 or vertices > 12:
- raise ValueError('Number of vertices must be between 3 and 12')
- self.vertices = vertices
- validate_coordinates(position)
- self.position = position
- self.diameter = diameter
- self.rotation = rotation
-
- def to_inch(self):
- self.position = tuple([inch(x) for x in self.position])
- self.diameter = inch(self.diameter)
-
- def to_metric(self):
- self.position = tuple([metric(x) for x in self.position])
- self.diameter = metric(self.diameter)
-
- def to_gerber(self, settings=None):
- data = dict(
- code=self.code,
- exposure="1" if self.exposure == "on" else "0",
- vertices=self.vertices,
- position="%.4g,%.4g" % self.position,
- diameter='%.4g' % self.diameter,
- rotation=str(self.rotation)
- )
- fmt = "{code},{exposure},{vertices},{position},{diameter},{rotation}*"
- return fmt.format(**data)
-
- def to_primitive(self, units):
- return Polygon(self.position, self.vertices, self.diameter / 2.0, 0, rotation=math.radians(self.rotation), units=units, level_polarity=self._level_polarity)
-
-
-class AMMoirePrimitive(AMPrimitive):
- """ Aperture Macro Moire primitive. Code 6.
-
- The moire primitive is a cross hair centered on concentric rings (annuli).
- Exposure is always on.
-
- .. seealso::
- `The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
- **Section 4.12.3.9:** Moire, primitive code 6.
-
- Parameters
- ----------
- code : int
- Moire Primitive code. Must be 6.
-
- position : tuple (<float>, <float>)
- X and Y coordinates of moire center
-
- diameter : float
- outer diameter of outer ring.
-
- ring_thickness : float
- thickness of concentric rings.
-
- gap : float
- gap between concentric rings.
-
- max_rings : float
- maximum number of rings
-
- crosshair_thickness : float
- thickness of crosshairs
-
- crosshair_length : float
- length of crosshairs
-
- rotation : float
- moire rotation about the origin.
-
- Returns
- -------
- MoirePrimitive : :class:`gerber.am_statements.AMMoirePrimitive`
- An initialized AMMoirePrimitive
-
- Raises
- ------
- ValueError, TypeError
- """
- @classmethod
- def from_gerber(cls, primitive):
- modifiers = primitive.strip(' *').split(",")
- code = int(modifiers[0])
- position = (float(modifiers[1]), float(modifiers[2]))
- diameter = float(modifiers[3])
- ring_thickness = float(modifiers[4])
- gap = float(modifiers[5])
- max_rings = int(float(modifiers[6]))
- crosshair_thickness = float(modifiers[7])
- crosshair_length = float(modifiers[8])
- rotation = float(modifiers[9])
- return cls(code, position, diameter, ring_thickness, gap, max_rings, crosshair_thickness, crosshair_length, rotation)
-
- def __init__(self, code, position, diameter, ring_thickness, gap, max_rings, crosshair_thickness, crosshair_length, rotation):
- """ Initialize AMoirePrimitive
- """
- if code != 6:
- raise ValueError('MoirePrimitive code is 6')
- super(AMMoirePrimitive, self).__init__(code, 'on')
- validate_coordinates(position)
- self.position = position
- self.diameter = diameter
- self.ring_thickness = ring_thickness
- self.gap = gap
- self.max_rings = max_rings
- self.crosshair_thickness = crosshair_thickness
- self.crosshair_length = crosshair_length
- self.rotation = rotation
-
- def to_inch(self):
- self.position = tuple([inch(x) for x in self.position])
- self.diameter = inch(self.diameter)
- self.ring_thickness = inch(self.ring_thickness)
- self.gap = inch(self.gap)
- self.crosshair_thickness = inch(self.crosshair_thickness)
- self.crosshair_length = inch(self.crosshair_length)
-
- def to_metric(self):
- self.position = tuple([metric(x) for x in self.position])
- self.diameter = metric(self.diameter)
- self.ring_thickness = metric(self.ring_thickness)
- self.gap = metric(self.gap)
- self.crosshair_thickness = metric(self.crosshair_thickness)
- self.crosshair_length = metric(self.crosshair_length)
-
-
- def to_gerber(self, settings=None):
- data = dict(
- code=self.code,
- position="%.4g,%.4g" % self.position,
- diameter=self.diameter,
- ring_thickness=self.ring_thickness,
- gap=self.gap,
- max_rings=self.max_rings,
- crosshair_thickness=self.crosshair_thickness,
- crosshair_length=self.crosshair_length,
- rotation=self.rotation
- )
- fmt = "{code},{position},{diameter},{ring_thickness},{gap},{max_rings},{crosshair_thickness},{crosshair_length},{rotation}*"
- return fmt.format(**data)
-
- def to_primitive(self, units):
- #raise NotImplementedError()
- return None
-
-
-class AMThermalPrimitive(AMPrimitive):
- """ Aperture Macro Thermal primitive. Code 7.
-
- The thermal primitive is a ring (annulus) interrupted by four gaps.
- Exposure is always on.
-
- .. seealso::
- `The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
- **Section 4.12.3.10:** Thermal, primitive code 7.
-
- Parameters
- ----------
- code : int
- Thermal Primitive code. Must be 7.
-
- position : tuple (<float>, <float>)
- X and Y coordinates of thermal center
-
- outer_diameter : float
- outer diameter of thermal.
-
- inner_diameter : float
- inner diameter of thermal.
-
- gap : float
- gap thickness
-
- rotation : float
- thermal rotation about the origin.
-
- Returns
- -------
- ThermalPrimitive : :class:`gerber.am_statements.AMThermalPrimitive`
- An initialized AMThermalPrimitive
-
- Raises
- ------
- ValueError, TypeError
- """
- @classmethod
- def from_gerber(cls, primitive):
- modifiers = primitive.strip(' *').split(",")
- code = int(modifiers[0])
- position = (float(modifiers[1]), float(modifiers[2]))
- outer_diameter = float(modifiers[3])
- inner_diameter = float(modifiers[4])
- gap = float(modifiers[5])
- rotation = float(modifiers[6])
- return cls(code, position, outer_diameter, inner_diameter, gap, rotation)
-
- def __init__(self, code, position, outer_diameter, inner_diameter, gap, rotation):
- if code != 7:
- raise ValueError('ThermalPrimitive code is 7')
- super(AMThermalPrimitive, self).__init__(code, 'on')
- validate_coordinates(position)
- self.position = position
- self.outer_diameter = outer_diameter
- self.inner_diameter = inner_diameter
- self.gap = gap
- self.rotation = rotation
-
- def to_inch(self):
- self.position = tuple([inch(x) for x in self.position])
- self.outer_diameter = inch(self.outer_diameter)
- self.inner_diameter = inch(self.inner_diameter)
- self.gap = inch(self.gap)
-
- def to_metric(self):
- self.position = tuple([metric(x) for x in self.position])
- self.outer_diameter = metric(self.outer_diameter)
- self.inner_diameter = metric(self.inner_diameter)
- self.gap = metric(self.gap)
-
- def to_gerber(self, settings=None):
- data = dict(
- code=self.code,
- position="%.4g,%.4g" % self.position,
- outer_diameter=self.outer_diameter,
- inner_diameter=self.inner_diameter,
- gap=self.gap,
- rotation=self.rotation
- )
- fmt = "{code},{position},{outer_diameter},{inner_diameter},{gap},{rotation}*"
- return fmt.format(**data)
-
- def _approximate_arc_cw(self, start_angle, end_angle, radius, center):
- """
- Get an arc as a series of points
-
- Parameters
- ----------
- start_angle : The start angle in radians
- end_angle : The end angle in radians
- radius`: Radius of the arc
- center : The center point of the arc (x, y) tuple
-
- Returns
- -------
- array of point tuples
- """
-
- # The total sweep
- sweep_angle = end_angle - start_angle
- num_steps = 10
-
- angle_step = sweep_angle / num_steps
-
- radius = radius
- center = center
-
- points = []
-
- for i in range(num_steps + 1):
- current_angle = start_angle + (angle_step * i)
-
- nextx = (center[0] + math.cos(current_angle) * radius)
- nexty = (center[1] + math.sin(current_angle) * radius)
-
- points.append((nextx, nexty))
-
- return points
-
- def to_primitive(self, units):
-
- # We start with calculating the top right section, then duplicate it
-
- inner_radius = self.inner_diameter / 2.0
- outer_radius = self.outer_diameter / 2.0
-
- # Calculate the start angle relative to the horizontal axis
- inner_offset_angle = asin(self.gap / 2.0 / inner_radius)
- outer_offset_angle = asin(self.gap / 2.0 / outer_radius)
-
- rotation_rad = math.radians(self.rotation)
- inner_start_angle = inner_offset_angle + rotation_rad
- inner_end_angle = math.pi / 2 - inner_offset_angle + rotation_rad
-
- outer_start_angle = outer_offset_angle + rotation_rad
- outer_end_angle = math.pi / 2 - outer_offset_angle + rotation_rad
-
- outlines = []
- aperture = Circle((0, 0), 0)
-
- points = (self._approximate_arc_cw(inner_start_angle, inner_end_angle, inner_radius, self.position)
- + list(reversed(self._approximate_arc_cw(outer_start_angle, outer_end_angle, outer_radius, self.position))))
- # Add in the last point since outlines should be closed
- points.append(points[0])
-
- # There are four outlines at rotated sections
- for rotation in [0, 90.0, 180.0, 270.0]:
-
- lines = []
- prev_point = rotate_point(points[0], rotation, self.position)
- for point in points[1:]:
- cur_point = rotate_point(point, rotation, self.position)
-
- lines.append(Line(prev_point, cur_point, aperture))
-
- prev_point = cur_point
-
- outlines.append(Outline(lines, units=units, level_polarity=self._level_polarity))
-
- return outlines
-
-
-class AMCenterLinePrimitive(AMPrimitive):
- """ Aperture Macro Center Line primitive. Code 21.
-
- The center line primitive is a rectangle defined by its width, height, and center point.
-
- .. seealso::
- `The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
- **Section 4.12.3.4:** Center Line, primitive code 21.
-
- Parameters
- ----------
- code : int
- Center Line Primitive code. Must be 21.
-
- exposure : str
- 'on' or 'off'
-
- width : float
- Width of rectangle
-
- height : float
- Height of rectangle
-
- center : tuple (<float>, <float>)
- X and Y coordinates of line center
-
- rotation : float
- rectangle rotation about its center.
-
- Returns
- -------
- CenterLinePrimitive : :class:`gerber.am_statements.AMCenterLinePrimitive`
- An initialized AMCenterLinePrimitive
-
- Raises
- ------
- ValueError, TypeError
- """
-
- @classmethod
- def from_primitive(cls, primitive):
- width = primitive.width
- height = primitive.height
- center = primitive.position
- rotation = math.degrees(primitive.rotation)
- return cls(21, 'on', width, height, center, rotation)
-
- @classmethod
- def from_gerber(cls, primitive):
- modifiers = primitive.strip(' *').split(",")
- code = int(modifiers[0])
- exposure = 'on' if float(modifiers[1]) == 1 else 'off'
- width = float(modifiers[2])
- height = float(modifiers[3])
- center = (float(modifiers[4]), float(modifiers[5]))
- rotation = float(modifiers[6])
- return cls(code, exposure, width, height, center, rotation)
-
- def __init__(self, code, exposure, width, height, center, rotation):
- if code != 21:
- raise ValueError('CenterLinePrimitive code is 21')
- super(AMCenterLinePrimitive, self).__init__(code, exposure)
- self.width = width
- self.height = height
- validate_coordinates(center)
- self.center = center
- self.rotation = rotation
-
- def to_inch(self):
- self.center = tuple([inch(x) for x in self.center])
- self.width = inch(self.width)
- self.height = inch(self.height)
-
- def to_metric(self):
- self.center = tuple([metric(x) for x in self.center])
- self.width = metric(self.width)
- self.height = metric(self.height)
-
- def to_gerber(self, settings=None):
- data = dict(
- code=self.code,
- exposure = '1' if self.exposure == 'on' else '0',
- width = self.width,
- height = self.height,
- center="%.4g,%.4g" % self.center,
- rotation=self.rotation
- )
- fmt = "{code},{exposure},{width},{height},{center},{rotation}*"
- return fmt.format(**data)
-
- def to_primitive(self, units):
-
- x = self.center[0]
- y = self.center[1]
- half_width = self.width / 2.0
- half_height = self.height / 2.0
-
- points = []
- points.append((x - half_width, y + half_height))
- points.append((x - half_width, y - half_height))
- points.append((x + half_width, y - half_height))
- points.append((x + half_width, y + half_height))
-
- aperture = Circle((0, 0), 0)
-
- lines = []
- prev_point = rotate_point(points[3], self.rotation, self.center)
- for point in points:
- cur_point = rotate_point(point, self.rotation, self.center)
-
- lines.append(Line(prev_point, cur_point, aperture))
-
- return Outline(lines, units=units, level_polarity=self._level_polarity)
-
-
-class AMLowerLeftLinePrimitive(AMPrimitive):
- """ Aperture Macro Lower Left Line primitive. Code 22.
-
- The lower left line primitive is a rectangle defined by its width, height, and the lower left point.
-
- .. seealso::
- `The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
- **Section 4.12.3.5:** Lower Left Line, primitive code 22.
-
- Parameters
- ----------
- code : int
- Center Line Primitive code. Must be 22.
-
- exposure : str
- 'on' or 'off'
-
- width : float
- Width of rectangle
-
- height : float
- Height of rectangle
-
- lower_left : tuple (<float>, <float>)
- X and Y coordinates of lower left corner
-
- rotation : float
- rectangle rotation about its origin.
-
- Returns
- -------
- LowerLeftLinePrimitive : :class:`gerber.am_statements.AMLowerLeftLinePrimitive`
- An initialized AMLowerLeftLinePrimitive
-
- Raises
- ------
- ValueError, TypeError
- """
- @classmethod
- def from_gerber(cls, primitive):
- modifiers = primitive.strip(' *').split(",")
- code = int(modifiers[0])
- exposure = 'on' if float(modifiers[1]) == 1 else 'off'
- width = float(modifiers[2])
- height = float(modifiers[3])
- lower_left = (float(modifiers[4]), float(modifiers[5]))
- rotation = float(modifiers[6])
- return cls(code, exposure, width, height, lower_left, rotation)
-
- def __init__(self, code, exposure, width, height, lower_left, rotation):
- if code != 22:
- raise ValueError('LowerLeftLinePrimitive code is 22')
- super (AMLowerLeftLinePrimitive, self).__init__(code, exposure)
- self.width = width
- self.height = height
- validate_coordinates(lower_left)
- self.lower_left = lower_left
- self.rotation = rotation
-
- def to_inch(self):
- self.lower_left = tuple([inch(x) for x in self.lower_left])
- self.width = inch(self.width)
- self.height = inch(self.height)
-
- def to_metric(self):
- self.lower_left = tuple([metric(x) for x in self.lower_left])
- self.width = metric(self.width)
- self.height = metric(self.height)
-
- def to_gerber(self, settings=None):
- data = dict(
- code=self.code,
- exposure = '1' if self.exposure == 'on' else '0',
- width = self.width,
- height = self.height,
- lower_left="%.4g,%.4g" % self.lower_left,
- rotation=self.rotation
- )
- fmt = "{code},{exposure},{width},{height},{lower_left},{rotation}*"
- return fmt.format(**data)
-
-
-class AMUnsupportPrimitive(AMPrimitive):
- @classmethod
- def from_gerber(cls, primitive):
- return cls(primitive)
-
- def __init__(self, primitive):
- super(AMUnsupportPrimitive, self).__init__(9999)
- self.primitive = primitive
-
- def to_inch(self):
- pass
-
- def to_metric(self):
- pass
-
- def to_gerber(self, settings=None):
- return self.primitive
diff --git a/gerber/cam.py b/gerber/cam.py
deleted file mode 100644
index 4f20283..0000000
--- a/gerber/cam.py
+++ /dev/null
@@ -1,286 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-CAM File
-============
-**AM file classes**
-
-This module provides common base classes for Excellon/Gerber CNC files
-"""
-
-
-class FileSettings(object):
- """ CAM File Settings
-
- Provides a common representation of gerber/excellon file settings
-
- Parameters
- ----------
- notation: string
- notation format. either 'absolute' or 'incremental'
-
- units : string
- Measurement units. 'inch' or 'metric'
-
- zero_suppression: string
- 'leading' to suppress leading zeros, 'trailing' to suppress trailing zeros.
- This is the convention used in Gerber files.
-
- format : tuple (int, int)
- Decimal format
-
- zeros : string
- 'leading' to include leading zeros, 'trailing to include trailing zeros.
- This is the convention used in Excellon files
-
- Notes
- -----
- Either `zeros` or `zero_suppression` should be specified, there is no need to
- specify both. `zero_suppression` will take on the opposite value of `zeros`
- and vice versa
- """
-
- def __init__(self, notation='absolute', units='inch',
- zero_suppression=None, format=(2, 5), zeros=None,
- angle_units='degrees'):
- if notation not in ['absolute', 'incremental']:
- raise ValueError('Notation must be either absolute or incremental')
- self.notation = notation
-
- if units not in ['inch', 'metric']:
- raise ValueError('Units must be either inch or metric')
- self.units = units
-
- if zero_suppression is None and zeros is None:
- self.zero_suppression = 'trailing'
-
- elif zero_suppression == zeros:
- raise ValueError('Zeros and Zero Suppression must be different. \
- Best practice is to specify only one.')
-
- elif zero_suppression is not None:
- if zero_suppression not in ['leading', 'trailing']:
- # This is a common problem in Eagle files, so just suppress it
- self.zero_suppression = 'leading'
- else:
- self.zero_suppression = zero_suppression
-
- elif zeros is not None:
- if zeros not in ['leading', 'trailing']:
- raise ValueError('Zeros must be either leading or trailling')
- self.zeros = zeros
-
- if len(format) != 2:
- raise ValueError('Format must be a tuple(n=2) of integers')
- self.format = format
-
- if angle_units not in ('degrees', 'radians'):
- raise ValueError('Angle units may be degrees or radians')
- self.angle_units = angle_units
-
- @property
- def zero_suppression(self):
- return self._zero_suppression
-
- @zero_suppression.setter
- def zero_suppression(self, value):
- self._zero_suppression = value
- self._zeros = 'leading' if value == 'trailing' else 'trailing'
-
- @property
- def zeros(self):
- return self._zeros
-
- @zeros.setter
- def zeros(self, value):
-
- self._zeros = value
- self._zero_suppression = 'leading' if value == 'trailing' else 'trailing'
-
- def __getitem__(self, key):
- if key == 'notation':
- return self.notation
- elif key == 'units':
- return self.units
- elif key == 'zero_suppression':
- return self.zero_suppression
- elif key == 'zeros':
- return self.zeros
- elif key == 'format':
- return self.format
- elif key == 'angle_units':
- return self.angle_units
- else:
- raise KeyError()
-
- def __setitem__(self, key, value):
- if key == 'notation':
- if value not in ['absolute', 'incremental']:
- raise ValueError('Notation must be either \
- absolute or incremental')
- self.notation = value
- elif key == 'units':
- if value not in ['inch', 'metric']:
- raise ValueError('Units must be either inch or metric')
- self.units = value
-
- elif key == 'zero_suppression':
- if value not in ['leading', 'trailing']:
- raise ValueError('Zero suppression must be either leading or \
- trailling')
- self.zero_suppression = value
-
- elif key == 'zeros':
- if value not in ['leading', 'trailing']:
- raise ValueError('Zeros must be either leading or trailling')
- self.zeros = value
-
- elif key == 'format':
- if len(value) != 2:
- raise ValueError('Format must be a tuple(n=2) of integers')
- self.format = value
-
- elif key == 'angle_units':
- if value not in ('degrees', 'radians'):
- raise ValueError('Angle units may be degrees or radians')
- self.angle_units = value
-
- else:
- raise KeyError('%s is not a valid key' % key)
-
- def __eq__(self, other):
- return (self.notation == other.notation and
- self.units == other.units and
- self.zero_suppression == other.zero_suppression and
- self.format == other.format and
- self.angle_units == other.angle_units)
-
- def __str__(self):
- return ('<Settings: %s %s %s %s %s>' %
- (self.units, self.notation, self.zero_suppression, self.format, self.angle_units))
-
-
-class CamFile(object):
- """ Base class for Gerber/Excellon files.
-
- Provides a common set of settings parameters.
-
- Parameters
- ----------
- settings : FileSettings
- The current file configuration.
-
- primitives : iterable
- List of primitives in the file.
-
- filename : string
- Name of the file that this CamFile represents.
-
- layer_name : string
- Name of the PCB layer that the file represents
-
- Attributes
- ----------
- settings : FileSettings
- File settings as a FileSettings object
-
- notation : string
- File notation setting. May be either 'absolute' or 'incremental'
-
- units : string
- File units setting. May be 'inch' or 'metric'
-
- zero_suppression : string
- File zero-suppression setting. May be either 'leading' or 'trailling'
-
- format : tuple (<int>, <int>)
- File decimal representation format as a tuple of (integer digits,
- decimal digits)
- """
-
- def __init__(self, statements=None, settings=None, primitives=None,
- filename=None, layer_name=None):
- if settings is not None:
- self.notation = settings['notation']
- self.units = settings['units']
- self.zero_suppression = settings['zero_suppression']
- self.zeros = settings['zeros']
- self.format = settings['format']
- else:
- self.notation = 'absolute'
- self.units = 'inch'
- self.zero_suppression = 'trailing'
- self.zeros = 'leading'
- self.format = (2, 5)
- self.statements = statements if statements is not None else []
- if primitives is not None:
- self.primitives = primitives
- self.filename = filename
- self.layer_name = layer_name
-
- @property
- def settings(self):
- """ File settings
-
- Returns
- -------
- settings : FileSettings (dict-like)
- A FileSettings object with the specified configuration.
- """
- return FileSettings(self.notation, self.units, self.zero_suppression,
- self.format)
-
- @property
- def bounds(self):
- """ File boundaries
- """
- pass
-
- @property
- def bounding_box(self):
- pass
-
- def to_inch(self):
- pass
-
- def to_metric(self):
- pass
-
- def render(self, ctx=None, invert=False, filename=None):
- """ Generate image of layer.
-
- Parameters
- ----------
- ctx : :class:`GerberContext`
- GerberContext subclass used for rendering the image
-
- filename : string <optional>
- If provided, save the rendered image to `filename`
- """
- if ctx is None:
- from .render import GerberCairoContext
- ctx = GerberCairoContext()
- ctx.set_bounds(self.bounding_box)
- ctx.paint_background()
- ctx.invert = invert
- ctx.new_render_layer()
- for p in self.primitives:
- ctx.render(p)
- ctx.flatten()
-
- if filename is not None:
- ctx.dump(filename)
diff --git a/gerber/common.py b/gerber/common.py
deleted file mode 100644
index f496809..0000000
--- a/gerber/common.py
+++ /dev/null
@@ -1,71 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from . import rs274x
-from . import excellon
-from . import ipc356
-from .exceptions import ParseError
-from .utils import detect_file_format
-
-
-def read(filename):
- """ Read a gerber or excellon file and return a representative object.
-
- Parameters
- ----------
- filename : string
- Filename of the file to read.
-
- Returns
- -------
- file : CncFile subclass
- CncFile object representing the file, either GerberFile, ExcellonFile,
- or IPCNetlist. Returns None if file is not of the proper type.
- """
- with open(filename, 'rU') as f:
- data = f.read()
- return loads(data, filename)
-
-
-def loads(data, filename=None):
- """ Read gerber or excellon file contents from a string and return a
- representative object.
-
- Parameters
- ----------
- data : string
- Source file contents as a string.
-
- filename : string, optional
- String containing the filename of the data source.
-
- Returns
- -------
- file : CncFile subclass
- CncFile object representing the data, either GerberFile, ExcellonFile,
- or IPCNetlist. Returns None if data is not of the proper type.
- """
-
- fmt = detect_file_format(data)
- if fmt == 'rs274x':
- return rs274x.loads(data, filename=filename)
- elif fmt == 'excellon':
- return excellon.loads(data, filename=filename)
- elif fmt == 'ipc_d_356':
- return ipc356.loads(data, filename=filename)
- else:
- raise ParseError('Unable to detect file format')
diff --git a/gerber/excellon.py b/gerber/excellon.py
deleted file mode 100755
index 5ab062a..0000000
--- a/gerber/excellon.py
+++ /dev/null
@@ -1,904 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Excellon File module
-====================
-**Excellon file classes**
-
-This module provides Excellon file classes and parsing utilities
-"""
-
-import math
-import operator
-
-from .cam import CamFile, FileSettings
-from .excellon_statements import *
-from .excellon_tool import ExcellonToolDefinitionParser
-from .primitives import Drill, Slot
-from .utils import inch, metric
-
-
-try:
- from cStringIO import StringIO
-except(ImportError):
- from io import StringIO
-
-
-
-def read(filename):
- """ Read data from filename and return an ExcellonFile
- Parameters
- ----------
- filename : string
- Filename of file to parse
-
- Returns
- -------
- file : :class:`gerber.excellon.ExcellonFile`
- An ExcellonFile created from the specified file.
-
- """
- # File object should use settings from source file by default.
- with open(filename, 'rU') as f:
- data = f.read()
- settings = FileSettings(**detect_excellon_format(data))
- return ExcellonParser(settings).parse(filename)
-
-def loads(data, filename=None, settings=None, tools=None):
- """ Read data from string and return an ExcellonFile
- Parameters
- ----------
- data : string
- string containing Excellon file contents
-
- filename : string, optional
- string containing the filename of the data source
-
- tools: dict (optional)
- externally defined tools
-
- Returns
- -------
- file : :class:`gerber.excellon.ExcellonFile`
- An ExcellonFile created from the specified file.
-
- """
- # File object should use settings from source file by default.
- if not settings:
- settings = FileSettings(**detect_excellon_format(data))
- return ExcellonParser(settings, tools).parse_raw(data, filename)
-
-
-class DrillHit(object):
- """Drill feature that is a single drill hole.
-
- Attributes
- ----------
- tool : ExcellonTool
- Tool to drill the hole. Defines the size of the hole that is generated.
- position : tuple(float, float)
- Center position of the drill.
-
- """
- def __init__(self, tool, position):
- self.tool = tool
- self.position = position
-
- def to_inch(self):
- if self.tool.settings.units == 'metric':
- self.tool.to_inch()
- self.position = tuple(map(inch, self.position))
-
- def to_metric(self):
- if self.tool.settings.units == 'inch':
- self.tool.to_metric()
- self.position = tuple(map(metric, self.position))
-
- @property
- def bounding_box(self):
- position = self.position
- radius = self.tool.diameter / 2.
-
- min_x = position[0] - radius
- max_x = position[0] + radius
- min_y = position[1] - radius
- max_y = position[1] + radius
- return ((min_x, max_x), (min_y, max_y))
-
- def offset(self, x_offset=0, y_offset=0):
- self.position = tuple(map(operator.add, self.position, (x_offset, y_offset)))
-
- def __str__(self):
- return 'Hit (%f, %f) {%s}' % (self.position[0], self.position[1], self.tool)
-
-class DrillSlot(object):
- """
- A slot is created between two points. The way the slot is created depends on the statement used to create it
- """
-
- TYPE_ROUT = 1
- TYPE_G85 = 2
-
- def __init__(self, tool, start, end, slot_type):
- self.tool = tool
- self.start = start
- self.end = end
- self.slot_type = slot_type
-
- def to_inch(self):
- if self.tool.settings.units == 'metric':
- self.tool.to_inch()
- self.start = tuple(map(inch, self.start))
- self.end = tuple(map(inch, self.end))
-
- def to_metric(self):
- if self.tool.settings.units == 'inch':
- self.tool.to_metric()
- self.start = tuple(map(metric, self.start))
- self.end = tuple(map(metric, self.end))
-
- @property
- def bounding_box(self):
- start = self.start
- end = self.end
- radius = self.tool.diameter / 2.
- min_x = min(start[0], end[0]) - radius
- max_x = max(start[0], end[0]) + radius
- min_y = min(start[1], end[1]) - radius
- max_y = max(start[1], end[1]) + radius
- return ((min_x, max_x), (min_y, max_y))
-
- def offset(self, x_offset=0, y_offset=0):
- self.start = tuple(map(operator.add, self.start, (x_offset, y_offset)))
- self.end = tuple(map(operator.add, self.end, (x_offset, y_offset)))
-
-
-class ExcellonFile(CamFile):
- """ A class representing a single excellon file
-
- The ExcellonFile class represents a single excellon file.
-
- http://www.excellon.com/manuals/program.htm
- (archived version at https://web.archive.org/web/20150920001043/http://www.excellon.com/manuals/program.htm)
-
- Parameters
- ----------
- tools : list
- list of gerber file statements
-
- hits : list of tuples
- list of drill hits as (<Tool>, (x, y))
-
- settings : dict
- Dictionary of gerber file settings
-
- filename : string
- Filename of the source gerber file
-
- Attributes
- ----------
- units : string
- either 'inch' or 'metric'.
-
- """
-
- def __init__(self, statements, tools, hits, settings, filename=None):
- super(ExcellonFile, self).__init__(statements=statements,
- settings=settings,
- filename=filename)
- self.tools = tools
- self.hits = hits
-
- @property
- def primitives(self):
- """
- Gets the primitives. Note that unlike Gerber, this generates new objects
- """
- primitives = []
- for hit in self.hits:
- if isinstance(hit, DrillHit):
- primitives.append(Drill(hit.position, hit.tool.diameter,
- units=self.settings.units))
- elif isinstance(hit, DrillSlot):
- primitives.append(Slot(hit.start, hit.end, hit.tool.diameter,
- units=self.settings.units))
- else:
- raise ValueError('Unknown hit type')
- return primitives
-
- @property
- def bounding_box(self):
- xmin = ymin = 100000000000
- xmax = ymax = -100000000000
- for hit in self.hits:
- bbox = hit.bounding_box
- xmin = min(bbox[0][0], xmin)
- xmax = max(bbox[0][1], xmax)
- ymin = min(bbox[1][0], ymin)
- ymax = max(bbox[1][1], ymax)
- return ((xmin, xmax), (ymin, ymax))
-
- def report(self, filename=None):
- """ Print or save drill report
- """
- if self.settings.units == 'inch':
- toolfmt = ' T{:0>2d} {:%d.%df} {: >3d} {:f}in.\n' % self.settings.format
- else:
- toolfmt = ' T{:0>2d} {:%d.%df} {: >3d} {:f}mm\n' % self.settings.format
- rprt = '=====================\nExcellon Drill Report\n=====================\n'
- if self.filename is not None:
- rprt += 'NC Drill File: %s\n\n' % self.filename
- rprt += 'Drill File Info:\n----------------\n'
- rprt += (' Data Mode %s\n' % 'Absolute'
- if self.settings.notation == 'absolute' else 'Incremental')
- rprt += (' Units %s\n' % 'Inches'
- if self.settings.units == 'inch' else 'Millimeters')
- rprt += '\nTool List:\n----------\n\n'
- rprt += ' Code Size Hits Path Length\n'
- rprt += ' --------------------------------------\n'
- for tool in iter(self.tools.values()):
- rprt += toolfmt.format(tool.number, tool.diameter,
- tool.hit_count, self.path_length(tool.number))
- if filename is not None:
- with open(filename, 'w') as f:
- f.write(rprt)
- return rprt
-
- def write(self, filename=None):
- filename = filename if filename is not None else self.filename
- with open(filename, 'w') as f:
-
- # Copy the header verbatim
- for statement in self.statements:
- if not isinstance(statement, ToolSelectionStmt):
- f.write(statement.to_excellon(self.settings) + '\n')
- else:
- break
-
- # Write out coordinates for drill hits by tool
- for tool in iter(self.tools.values()):
- f.write(ToolSelectionStmt(tool.number).to_excellon(self.settings) + '\n')
- for hit in self.hits:
- if hit.tool.number == tool.number:
- f.write(CoordinateStmt(
- *hit.position).to_excellon(self.settings) + '\n')
- f.write(EndOfProgramStmt().to_excellon() + '\n')
-
- def to_inch(self):
- """
- Convert units to inches
- """
- if self.units != 'inch':
- for statement in self.statements:
- statement.to_inch()
- for tool in iter(self.tools.values()):
- tool.to_inch()
- #for primitive in self.primitives:
- # primitive.to_inch()
- #for hit in self.hits:
- # hit.to_inch()
- self.units = 'inch'
-
- def to_metric(self):
- """ Convert units to metric
- """
- if self.units != 'metric':
- for statement in self.statements:
- statement.to_metric()
- for tool in iter(self.tools.values()):
- tool.to_metric()
- #for primitive in self.primitives:
- # print("Converting to metric: {}".format(primitive))
- # primitive.to_metric()
- # print(primitive)
- for hit in self.hits:
- hit.to_metric()
- self.units = 'metric'
-
- def offset(self, x_offset=0, y_offset=0):
- for statement in self.statements:
- statement.offset(x_offset, y_offset)
- for primitive in self.primitives:
- primitive.offset(x_offset, y_offset)
- for hit in self. hits:
- hit.offset(x_offset, y_offset)
-
- def path_length(self, tool_number=None):
- """ Return the path length for a given tool
- """
- lengths = {}
- positions = {}
- for hit in self.hits:
- tool = hit.tool
- num = tool.number
- positions[num] = ((0, 0) if positions.get(num) is None
- else positions[num])
- lengths[num] = 0.0 if lengths.get(num) is None else lengths[num]
- lengths[num] = lengths[
- num] + math.hypot(*tuple(map(operator.sub, positions[num], hit.position)))
- positions[num] = hit.position
-
- if tool_number is None:
- return lengths
- else:
- return lengths.get(tool_number)
-
- def hit_count(self, tool_number=None):
- counts = {}
- for tool in iter(self.tools.values()):
- counts[tool.number] = tool.hit_count
- if tool_number is None:
- return counts
- else:
- return counts.get(tool_number)
-
- def update_tool(self, tool_number, **kwargs):
- """ Change parameters of a tool
- """
- if kwargs.get('feed_rate') is not None:
- self.tools[tool_number].feed_rate = kwargs.get('feed_rate')
- if kwargs.get('retract_rate') is not None:
- self.tools[tool_number].retract_rate = kwargs.get('retract_rate')
- if kwargs.get('rpm') is not None:
- self.tools[tool_number].rpm = kwargs.get('rpm')
- if kwargs.get('diameter') is not None:
- self.tools[tool_number].diameter = kwargs.get('diameter')
- if kwargs.get('max_hit_count') is not None:
- self.tools[tool_number].max_hit_count = kwargs.get('max_hit_count')
- if kwargs.get('depth_offset') is not None:
- self.tools[tool_number].depth_offset = kwargs.get('depth_offset')
- # Update drill hits
- newtool = self.tools[tool_number]
- for hit in self.hits:
- if hit.tool.number == newtool.number:
- hit.tool = newtool
-
-
-class ExcellonParser(object):
- """ Excellon File Parser
-
- Parameters
- ----------
- settings : FileSettings or dict-like
- Excellon file settings to use when interpreting the excellon file.
- """
- def __init__(self, settings=None, ext_tools=None):
- self.notation = 'absolute'
- self.units = 'inch'
- self.zeros = 'leading'
- self.format = (2, 4)
- self.state = 'INIT'
- self.statements = []
- self.tools = {}
- self.ext_tools = ext_tools or {}
- self.comment_tools = {}
- self.hits = []
- self.active_tool = None
- self.pos = [0., 0.]
- self.drill_down = False
- self._previous_line = ''
- # Default for plated is None, which means we don't know
- self.plated = ExcellonTool.PLATED_UNKNOWN
- if settings is not None:
- self.units = settings.units
- self.zeros = settings.zeros
- self.notation = settings.notation
- self.format = settings.format
-
- @property
- def coordinates(self):
- return [(stmt.x, stmt.y) for stmt in self.statements if isinstance(stmt, CoordinateStmt)]
-
- @property
- def bounds(self):
- xmin = ymin = 100000000000
- xmax = ymax = -100000000000
- for x, y in self.coordinates:
- if x is not None:
- xmin = x if x < xmin else xmin
- xmax = x if x > xmax else xmax
- if y is not None:
- ymin = y if y < ymin else ymin
- ymax = y if y > ymax else ymax
- return ((xmin, xmax), (ymin, ymax))
-
- @property
- def hole_sizes(self):
- return [stmt.diameter for stmt in self.statements if isinstance(stmt, ExcellonTool)]
-
- @property
- def hole_count(self):
- return len(self.hits)
-
- def parse(self, filename):
- with open(filename, 'rU') as f:
- data = f.read()
- return self.parse_raw(data, filename)
-
- def parse_raw(self, data, filename=None):
- for line in StringIO(data):
- self._parse_line(line.strip())
- for stmt in self.statements:
- stmt.units = self.units
- return ExcellonFile(self.statements, self.tools, self.hits,
- self._settings(), filename)
-
- def _parse_line(self, line):
- # skip empty lines
- # Prepend previous line's data...
- line = '{}{}'.format(self._previous_line, line)
- self._previous_line = ''
-
- # Skip empty lines
- if not line.strip():
- return
-
- if line[0] == ';':
- comment_stmt = CommentStmt.from_excellon(line)
- self.statements.append(comment_stmt)
-
- # get format from altium comment
- if "FILE_FORMAT" in comment_stmt.comment:
- detected_format = tuple(
- [int(x) for x in comment_stmt.comment.split('=')[1].split(":")])
- if detected_format:
- self.format = detected_format
-
- if "TYPE=PLATED" in comment_stmt.comment:
- self.plated = ExcellonTool.PLATED_YES
-
- if "TYPE=NON_PLATED" in comment_stmt.comment:
- self.plated = ExcellonTool.PLATED_NO
-
- if "HEADER:" in comment_stmt.comment:
- self.state = "HEADER"
-
- if " Holesize " in comment_stmt.comment:
- self.state = "HEADER"
-
- # Parse this as a hole definition
- tools = ExcellonToolDefinitionParser(self._settings()).parse_raw(comment_stmt.comment)
- if len(tools) == 1:
- tool = tools[tools.keys()[0]]
- self._add_comment_tool(tool)
-
- elif line[:3] == 'M48':
- self.statements.append(HeaderBeginStmt())
- self.state = 'HEADER'
-
- elif line[0] == '%':
- self.statements.append(RewindStopStmt())
- if self.state == 'HEADER':
- self.state = 'DRILL'
- elif self.state == 'INIT':
- self.state = 'HEADER'
-
- elif line[:3] == 'M00' and self.state == 'DRILL':
- if self.active_tool:
- cur_tool_number = self.active_tool.number
- next_tool = self._get_tool(cur_tool_number + 1)
-
- self.statements.append(NextToolSelectionStmt(self.active_tool, next_tool))
- self.active_tool = next_tool
- else:
- raise Exception('Invalid state exception')
-
- elif line[:3] == 'M95':
- self.statements.append(HeaderEndStmt())
- if self.state == 'HEADER':
- self.state = 'DRILL'
-
- elif line[:3] == 'M15':
- self.statements.append(ZAxisRoutPositionStmt())
- self.drill_down = True
-
- elif line[:3] == 'M16':
- self.statements.append(RetractWithClampingStmt())
- self.drill_down = False
-
- elif line[:3] == 'M17':
- self.statements.append(RetractWithoutClampingStmt())
- self.drill_down = False
-
- elif line[:3] == 'M30':
- stmt = EndOfProgramStmt.from_excellon(line, self._settings())
- self.statements.append(stmt)
-
- elif line[:3] == 'G00':
- # Coordinates may be on the next line
- if line.strip() == 'G00':
- self._previous_line = line
- return
-
- self.statements.append(RouteModeStmt())
- self.state = 'ROUT'
-
- stmt = CoordinateStmt.from_excellon(line[3:], self._settings())
- stmt.mode = self.state
-
- x = stmt.x
- y = stmt.y
- self.statements.append(stmt)
- if self.notation == 'absolute':
- if x is not None:
- self.pos[0] = x
- if y is not None:
- self.pos[1] = y
- else:
- if x is not None:
- self.pos[0] += x
- if y is not None:
- self.pos[1] += y
-
- elif line[:3] == 'G01':
-
- # Coordinates might be on the next line...
- if line.strip() == 'G01':
- self._previous_line = line
- return
-
- self.statements.append(RouteModeStmt())
- self.state = 'LINEAR'
-
- stmt = CoordinateStmt.from_excellon(line[3:], self._settings())
- stmt.mode = self.state
-
- # The start position is where we were before the rout command
- start = (self.pos[0], self.pos[1])
-
- x = stmt.x
- y = stmt.y
- self.statements.append(stmt)
- if self.notation == 'absolute':
- if x is not None:
- self.pos[0] = x
- if y is not None:
- self.pos[1] = y
- else:
- if x is not None:
- self.pos[0] += x
- if y is not None:
- self.pos[1] += y
-
- # Our ending position
- end = (self.pos[0], self.pos[1])
-
- if self.drill_down:
- if not self.active_tool:
- self.active_tool = self._get_tool(1)
-
- self.hits.append(DrillSlot(self.active_tool, start, end, DrillSlot.TYPE_ROUT))
- self.active_tool._hit()
-
- elif line[:3] == 'G05':
- self.statements.append(DrillModeStmt())
- self.drill_down = False
- self.state = 'DRILL'
-
- elif 'INCH' in line or 'METRIC' in line:
- stmt = UnitStmt.from_excellon(line)
- self.units = stmt.units
- self.zeros = stmt.zeros
- if stmt.format:
- self.format = stmt.format
- self.statements.append(stmt)
-
- elif line[:3] == 'M71' or line[:3] == 'M72':
- stmt = MeasuringModeStmt.from_excellon(line)
- self.units = stmt.units
- self.statements.append(stmt)
-
- elif line[:3] == 'ICI':
- stmt = IncrementalModeStmt.from_excellon(line)
- self.notation = 'incremental' if stmt.mode == 'on' else 'absolute'
- self.statements.append(stmt)
-
- elif line[:3] == 'VER':
- stmt = VersionStmt.from_excellon(line)
- self.statements.append(stmt)
-
- elif line[:4] == 'FMAT':
- stmt = FormatStmt.from_excellon(line)
- self.statements.append(stmt)
- self.format = stmt.format_tuple
-
- elif line[:3] == 'G40':
- self.statements.append(CutterCompensationOffStmt())
-
- elif line[:3] == 'G41':
- self.statements.append(CutterCompensationLeftStmt())
-
- elif line[:3] == 'G42':
- self.statements.append(CutterCompensationRightStmt())
-
- elif line[:3] == 'G90':
- self.statements.append(AbsoluteModeStmt())
- self.notation = 'absolute'
-
- elif line[0] == 'F':
- infeed_rate_stmt = ZAxisInfeedRateStmt.from_excellon(line)
- self.statements.append(infeed_rate_stmt)
-
- elif line[0] == 'T' and self.state == 'HEADER':
- if not ',OFF' in line and not ',ON' in line:
- tool = ExcellonTool.from_excellon(line, self._settings(), None, self.plated)
- self._merge_properties(tool)
- self.tools[tool.number] = tool
- self.statements.append(tool)
- else:
- self.statements.append(UnknownStmt.from_excellon(line))
-
- elif line[0] == 'T' and self.state != 'HEADER':
- stmt = ToolSelectionStmt.from_excellon(line)
- self.statements.append(stmt)
-
- # T0 is used as END marker, just ignore
- if stmt.tool != 0:
- tool = self._get_tool(stmt.tool)
-
- if not tool:
- # FIXME: for weird files with no tools defined, original calc from gerb
- if self._settings().units == "inch":
- diameter = (16 + 8 * stmt.tool) / 1000.0
- else:
- diameter = metric((16 + 8 * stmt.tool) / 1000.0)
-
- tool = ExcellonTool(
- self._settings(), number=stmt.tool, diameter=diameter)
- self.tools[tool.number] = tool
-
- # FIXME: need to add this tool definition inside header to
- # make sure it is properly written
- for i, s in enumerate(self.statements):
- if isinstance(s, ToolSelectionStmt) or isinstance(s, ExcellonTool):
- self.statements.insert(i, tool)
- break
-
- self.active_tool = tool
-
- elif line[0] == 'R' and self.state != 'HEADER':
- stmt = RepeatHoleStmt.from_excellon(line, self._settings())
- self.statements.append(stmt)
- for i in range(stmt.count):
- self.pos[0] += stmt.xdelta if stmt.xdelta is not None else 0
- self.pos[1] += stmt.ydelta if stmt.ydelta is not None else 0
- self.hits.append(DrillHit(self.active_tool, tuple(self.pos)))
- self.active_tool._hit()
-
- elif line[0] in ['X', 'Y']:
- if 'G85' in line:
- stmt = SlotStmt.from_excellon(line, self._settings())
-
- # I don't know if this is actually correct, but it makes sense
- # that this is where the tool would end
- x = stmt.x_end
- y = stmt.y_end
-
- self.statements.append(stmt)
-
- if self.notation == 'absolute':
- if x is not None:
- self.pos[0] = x
- if y is not None:
- self.pos[1] = y
- else:
- if x is not None:
- self.pos[0] += x
- if y is not None:
- self.pos[1] += y
-
- if self.state == 'DRILL' or self.state == 'HEADER':
- if not self.active_tool:
- self.active_tool = self._get_tool(1)
-
- self.hits.append(DrillSlot(self.active_tool, (stmt.x_start, stmt.y_start), (stmt.x_end, stmt.y_end), DrillSlot.TYPE_G85))
- self.active_tool._hit()
- else:
- stmt = CoordinateStmt.from_excellon(line, self._settings())
-
- # We need this in case we are in rout mode
- start = (self.pos[0], self.pos[1])
-
- x = stmt.x
- y = stmt.y
- self.statements.append(stmt)
- if self.notation == 'absolute':
- if x is not None:
- self.pos[0] = x
- if y is not None:
- self.pos[1] = y
- else:
- if x is not None:
- self.pos[0] += x
- if y is not None:
- self.pos[1] += y
-
- if self.state == 'LINEAR' and self.drill_down:
- if not self.active_tool:
- self.active_tool = self._get_tool(1)
-
- self.hits.append(DrillSlot(self.active_tool, start, tuple(self.pos), DrillSlot.TYPE_ROUT))
-
- elif self.state == 'DRILL' or self.state == 'HEADER':
- # Yes, drills in the header doesn't follow the specification, but it there are many
- # files like this
- if not self.active_tool:
- self.active_tool = self._get_tool(1)
-
- self.hits.append(DrillHit(self.active_tool, tuple(self.pos)))
- self.active_tool._hit()
-
- else:
- self.statements.append(UnknownStmt.from_excellon(line))
-
- def _settings(self):
- return FileSettings(units=self.units, format=self.format,
- zeros=self.zeros, notation=self.notation)
-
- def _add_comment_tool(self, tool):
- """
- Add a tool that was defined in the comments to this file.
-
- If we have already found this tool, then we will merge this comment tool definition into
- the information for the tool
- """
-
- existing = self.tools.get(tool.number)
- if existing and existing.plated == None:
- existing.plated = tool.plated
-
- self.comment_tools[tool.number] = tool
-
- def _merge_properties(self, tool):
- """
- When we have externally defined tools, merge the properties of that tool into this one
-
- For now, this is only plated
- """
-
- if tool.plated == ExcellonTool.PLATED_UNKNOWN:
- ext_tool = self.ext_tools.get(tool.number)
-
- if ext_tool:
- tool.plated = ext_tool.plated
-
- def _get_tool(self, toolid):
-
- tool = self.tools.get(toolid)
- if not tool:
- tool = self.comment_tools.get(toolid)
- if tool:
- tool.settings = self._settings()
- self.tools[toolid] = tool
-
- if not tool:
- tool = self.ext_tools.get(toolid)
- if tool:
- tool.settings = self._settings()
- self.tools[toolid] = tool
-
- return tool
-
-def detect_excellon_format(data=None, filename=None):
- """ Detect excellon file decimal format and zero-suppression settings.
-
- Parameters
- ----------
- data : string
- String containing contents of Excellon file.
-
- Returns
- -------
- settings : dict
- Detected excellon file settings. Keys are
- - `format`: decimal format as tuple (<int part>, <decimal part>)
- - `zero_suppression`: zero suppression, 'leading' or 'trailing'
- """
- results = {}
- detected_zeros = None
- detected_format = None
- zeros_options = ('leading', 'trailing', )
- format_options = ((2, 4), (2, 5), (3, 3),)
-
- if data is None and filename is None:
- raise ValueError('Either data or filename arguments must be provided')
- if data is None:
- with open(filename, 'rU') as f:
- data = f.read()
-
- # Check for obvious clues:
- p = ExcellonParser()
- p.parse_raw(data)
-
- # Get zero_suppression from a unit statement
- zero_statements = [stmt.zeros for stmt in p.statements
- if isinstance(stmt, UnitStmt)]
-
- # get format from altium comment
- format_comment = [stmt.comment for stmt in p.statements
- if isinstance(stmt, CommentStmt)
- and 'FILE_FORMAT' in stmt.comment]
-
- detected_format = (tuple([int(val) for val in
- format_comment[0].split('=')[1].split(':')])
- if len(format_comment) == 1 else None)
- detected_zeros = zero_statements[0] if len(zero_statements) == 1 else None
-
- # Bail out here if possible
- if detected_format is not None and detected_zeros is not None:
- return {'format': detected_format, 'zeros': detected_zeros}
-
- # Only look at remaining options
- if detected_format is not None:
- format_options = (detected_format,)
- if detected_zeros is not None:
- zeros_options = (detected_zeros,)
-
- # Brute force all remaining options, and pick the best looking one...
- for zeros in zeros_options:
- for fmt in format_options:
- key = (fmt, zeros)
- settings = FileSettings(zeros=zeros, format=fmt)
- try:
- p = ExcellonParser(settings)
- ef = p.parse_raw(data)
- size = tuple([t[0] - t[1] for t in ef.bounding_box])
- hole_area = 0.0
- for hit in p.hits:
- tool = hit.tool
- hole_area += math.pow(math.pi * tool.diameter / 2., 2)
- results[key] = (size, p.hole_count, hole_area)
- except:
- pass
-
- # See if any of the dimensions are left with only a single option
- formats = set(key[0] for key in iter(results.keys()))
- zeros = set(key[1] for key in iter(results.keys()))
- if len(formats) == 1:
- detected_format = formats.pop()
- if len(zeros) == 1:
- detected_zeros = zeros.pop()
-
- # Bail out here if we got everything....
- if detected_format is not None and detected_zeros is not None:
- return {'format': detected_format, 'zeros': detected_zeros}
-
- # Otherwise score each option and pick the best candidate
- else:
- scores = {}
- for key in results.keys():
- size, count, diameter = results[key]
- scores[key] = _layer_size_score(size, count, diameter)
- minscore = min(scores.values())
- for key in iter(scores.keys()):
- if scores[key] == minscore:
- return {'format': key[0], 'zeros': key[1]}
-
-
-def _layer_size_score(size, hole_count, hole_area):
- """ Heuristic used for determining the correct file number interpretation.
- Lower is better.
- """
- board_area = size[0] * size[1]
- if board_area == 0:
- return 0
-
- hole_percentage = hole_area / board_area
- hole_score = (hole_percentage - 0.25) ** 2
- size_score = (board_area - 8) ** 2
- return hole_score * size_score
diff --git a/gerber/excellon_report/excellon_drr.py b/gerber/excellon_report/excellon_drr.py
deleted file mode 100644
index ab9e857..0000000
--- a/gerber/excellon_report/excellon_drr.py
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2015 Garret Fick <garret@ficksworkshop.com>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Excellon DRR File module
-====================
-**Excellon file classes**
-
-Extra parsers for allegro misc files that can be useful when the Excellon file doesn't contain parameter information
-"""
-
diff --git a/gerber/excellon_settings.py b/gerber/excellon_settings.py
deleted file mode 100644
index 4dbe0ca..0000000
--- a/gerber/excellon_settings.py
+++ /dev/null
@@ -1,105 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-from argparse import PARSER
-
-# Copyright 2015 Garret Fick <garret@ficksworkshop.com>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Excellon Settings Definition File module
-====================
-**Excellon file classes**
-
-This module provides Excellon file classes and parsing utilities
-"""
-
-import re
-try:
- from cStringIO import StringIO
-except(ImportError):
- from io import StringIO
-
-from .cam import FileSettings
-
-def loads(data):
- """ Read settings file information and return an FileSettings
- Parameters
- ----------
- data : string
- string containing Excellon settings file contents
-
- Returns
- -------
- file settings: FileSettings
-
- """
-
- return ExcellonSettingsParser().parse_raw(data)
-
-def map_coordinates(value):
- if value == 'ABSOLUTE':
- return 'absolute'
- return 'relative'
-
-def map_units(value):
- if value == 'ENGLISH':
- return 'inch'
- return 'metric'
-
-def map_boolean(value):
- return value == 'YES'
-
-SETTINGS_KEYS = {
- 'INTEGER-PLACES': (int, 'format-int'),
- 'DECIMAL-PLACES': (int, 'format-dec'),
- 'COORDINATES': (map_coordinates, 'notation'),
- 'OUTPUT-UNITS': (map_units, 'units'),
- }
-
-class ExcellonSettingsParser(object):
- """Excellon Settings PARSER
-
- Parameters
- ----------
- None
- """
-
- def __init__(self):
- self.values = {}
- self.settings = None
-
- def parse_raw(self, data):
- for line in StringIO(data):
- self._parse(line.strip())
-
- # Create the FileSettings object
- self.settings = FileSettings(
- notation=self.values['notation'],
- units=self.values['units'],
- format=(self.values['format-int'], self.values['format-dec'])
- )
-
- return self.settings
-
- def _parse(self, line):
-
- line_items = line.split()
- if len(line_items) == 2:
-
- item_type_info = SETTINGS_KEYS.get(line_items[0])
- if item_type_info:
- # Convert the value to the expected type
- item_value = item_type_info[0](line_items[1])
-
- self.values[item_type_info[1]] = item_value \ No newline at end of file
diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py
deleted file mode 100644
index 2c50ef9..0000000
--- a/gerber/excellon_statements.py
+++ /dev/null
@@ -1,979 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Excellon Statements
-====================
-**Excellon file statement classes**
-
-"""
-
-import re
-import uuid
-import itertools
-from .utils import (parse_gerber_value, write_gerber_value, decimal_string,
- inch, metric)
-
-
-__all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt',
- 'CommentStmt', 'HeaderBeginStmt', 'HeaderEndStmt',
- 'RewindStopStmt', 'EndOfProgramStmt', 'UnitStmt',
- 'IncrementalModeStmt', 'VersionStmt', 'FormatStmt', 'LinkToolStmt',
- 'MeasuringModeStmt', 'RouteModeStmt', 'LinearModeStmt', 'DrillModeStmt',
- 'AbsoluteModeStmt', 'RepeatHoleStmt', 'UnknownStmt',
- 'ExcellonStatement', 'ZAxisRoutPositionStmt',
- 'RetractWithClampingStmt', 'RetractWithoutClampingStmt',
- 'CutterCompensationOffStmt', 'CutterCompensationLeftStmt',
- 'CutterCompensationRightStmt', 'ZAxisInfeedRateStmt',
- 'NextToolSelectionStmt', 'SlotStmt']
-
-
-class ExcellonStatement(object):
- """ Excellon Statement abstract base class
- """
-
- @classmethod
- def from_excellon(cls, line):
- raise NotImplementedError('from_excellon must be implemented in a '
- 'subclass')
-
- def __init__(self, unit='inch', id=None):
- self.units = unit
- self.id = uuid.uuid4().int if id is None else id
-
- def to_excellon(self, settings=None):
- raise NotImplementedError('to_excellon must be implemented in a '
- 'subclass')
-
- def to_inch(self):
- self.units = 'inch'
-
- def to_metric(self):
- self.units = 'metric'
-
- def offset(self, x_offset=0, y_offset=0):
- pass
-
- def __eq__(self, other):
- return self.__dict__ == other.__dict__
-
-
-class ExcellonTool(ExcellonStatement):
- """ Excellon Tool class
-
- Parameters
- ----------
- settings : FileSettings (dict-like)
- File-wide settings.
-
- kwargs : dict-like
- Tool settings from the excellon statement. Valid keys are:
- - `diameter` : Tool diameter [expressed in file units]
- - `rpm` : Tool RPM
- - `feed_rate` : Z-axis tool feed rate
- - `retract_rate` : Z-axis tool retraction rate
- - `max_hit_count` : Number of hits allowed before a tool change
- - `depth_offset` : Offset of tool depth from tip of tool.
-
- Attributes
- ----------
- number : integer
- Tool number from the excellon file
-
- diameter : float
- Tool diameter in file units
-
- rpm : float
- Tool RPM
-
- feed_rate : float
- Tool Z-axis feed rate.
-
- retract_rate : float
- Tool Z-axis retract rate
-
- depth_offset : float
- Offset of depth measurement from tip of tool
-
- max_hit_count : integer
- Maximum number of tool hits allowed before a tool change
-
- hit_count : integer
- Number of tool hits in excellon file.
- """
-
- PLATED_UNKNOWN = None
- PLATED_YES = 'plated'
- PLATED_NO = 'nonplated'
- PLATED_OPTIONAL = 'optional'
-
- @classmethod
- def from_tool(cls, tool):
- args = {}
-
- args['depth_offset'] = tool.depth_offset
- args['diameter'] = tool.diameter
- args['feed_rate'] = tool.feed_rate
- args['max_hit_count'] = tool.max_hit_count
- args['number'] = tool.number
- args['plated'] = tool.plated
- args['retract_rate'] = tool.retract_rate
- args['rpm'] = tool.rpm
-
- return cls(None, **args)
-
- @classmethod
- def from_excellon(cls, line, settings, id=None, plated=None):
- """ Create a Tool from an excellon file tool definition line.
-
- Parameters
- ----------
- line : string
- Tool definition line from an excellon file.
-
- settings : FileSettings (dict-like)
- Excellon file-wide settings
-
- Returns
- -------
- tool : Tool
- An ExcellonTool representing the tool defined in `line`
- """
- commands = pairwise(re.split('([BCFHSTZ])', line)[1:])
- args = {}
- args['id'] = id
- nformat = settings.format
- zero_suppression = settings.zero_suppression
- for cmd, val in commands:
- if cmd == 'B':
- args['retract_rate'] = parse_gerber_value(val, nformat, zero_suppression)
- elif cmd == 'C':
- args['diameter'] = parse_gerber_value(val, nformat, zero_suppression)
- elif cmd == 'F':
- args['feed_rate'] = parse_gerber_value(val, nformat, zero_suppression)
- elif cmd == 'H':
- args['max_hit_count'] = parse_gerber_value(val, nformat, zero_suppression)
- elif cmd == 'S':
- args['rpm'] = 1000 * parse_gerber_value(val, nformat, zero_suppression)
- elif cmd == 'T':
- args['number'] = int(val)
- elif cmd == 'Z':
- args['depth_offset'] = parse_gerber_value(val, nformat, zero_suppression)
-
- if plated != ExcellonTool.PLATED_UNKNOWN:
- # Sometimees we can can parse the plating status
- args['plated'] = plated
- return cls(settings, **args)
-
- @classmethod
- def from_dict(cls, settings, tool_dict):
- """ Create an ExcellonTool from a dict.
-
- Parameters
- ----------
- settings : FileSettings (dict-like)
- Excellon File-wide settings
-
- tool_dict : dict
- Excellon tool parameters as a dict
-
- Returns
- -------
- tool : ExcellonTool
- An ExcellonTool initialized with the parameters in tool_dict.
- """
- return cls(settings, **tool_dict)
-
- def __init__(self, settings, **kwargs):
- if kwargs.get('id') is not None:
- super(ExcellonTool, self).__init__(id=kwargs.get('id'))
- self.settings = settings
- self.number = kwargs.get('number')
- self.feed_rate = kwargs.get('feed_rate')
- self.retract_rate = kwargs.get('retract_rate')
- self.rpm = kwargs.get('rpm')
- self.diameter = kwargs.get('diameter')
- self.max_hit_count = kwargs.get('max_hit_count')
- self.depth_offset = kwargs.get('depth_offset')
- self.plated = kwargs.get('plated')
-
- self.hit_count = 0
-
- def to_excellon(self, settings=None):
- if self.settings and not settings:
- settings = self.settings
- fmt = settings.format
- zs = settings.zero_suppression
- stmt = 'T%02d' % self.number
- if self.retract_rate is not None:
- stmt += 'B%s' % write_gerber_value(self.retract_rate, fmt, zs)
- if self.feed_rate is not None:
- stmt += 'F%s' % write_gerber_value(self.feed_rate, fmt, zs)
- if self.max_hit_count is not None:
- stmt += 'H%s' % write_gerber_value(self.max_hit_count, fmt, zs)
- if self.rpm is not None:
- if self.rpm < 100000.:
- stmt += 'S%s' % write_gerber_value(self.rpm / 1000., fmt, zs)
- else:
- stmt += 'S%g' % (self.rpm / 1000.)
- if self.diameter is not None:
- stmt += 'C%s' % decimal_string(self.diameter, fmt[1], True)
- if self.depth_offset is not None:
- stmt += 'Z%s' % write_gerber_value(self.depth_offset, fmt, zs)
- return stmt
-
- def to_inch(self):
- if self.settings.units != 'inch':
- self.settings.units = 'inch'
- if self.diameter is not None:
- self.diameter = inch(self.diameter)
-
- def to_metric(self):
- if self.settings.units != 'metric':
- self.settings.units = 'metric'
- if self.diameter is not None:
- self.diameter = metric(self.diameter)
-
- def _hit(self):
- self.hit_count += 1
-
- def equivalent(self, other):
- """
- Is the other tool equal to this, ignoring the tool number, and other file specified properties
- """
-
- if type(self) != type(other):
- return False
-
- return (self.diameter == other.diameter
- and self.feed_rate == other.feed_rate
- and self.retract_rate == other.retract_rate
- and self.rpm == other.rpm
- and self.depth_offset == other.depth_offset
- and self.max_hit_count == other.max_hit_count
- and self.plated == other.plated
- and self.settings.units == other.settings.units)
-
- def __repr__(self):
- unit = 'in.' if self.settings.units == 'inch' else 'mm'
- fmtstr = '<ExcellonTool %%02d: %%%d.%dg%%s dia.>' % self.settings.format
- return fmtstr % (self.number, self.diameter, unit)
-
-
-class ToolSelectionStmt(ExcellonStatement):
-
- @classmethod
- def from_excellon(cls, line, **kwargs):
- """ Create a ToolSelectionStmt from an excellon file line.
-
- Parameters
- ----------
- line : string
- Line from an Excellon file
-
- Returns
- -------
- tool_statement : ToolSelectionStmt
- ToolSelectionStmt representation of `line.`
- """
- line = line[1:]
- compensation_index = None
-
- # up to 3 characters for tool number (Frizting uses that)
- if len(line) <= 3:
- tool = int(line)
- else:
- tool = int(line[:2])
- compensation_index = int(line[2:])
-
- return cls(tool, compensation_index, **kwargs)
-
- def __init__(self, tool, compensation_index=None, **kwargs):
- super(ToolSelectionStmt, self).__init__(**kwargs)
- tool = int(tool)
- compensation_index = (int(compensation_index) if compensation_index
- is not None else None)
- self.tool = tool
- self.compensation_index = compensation_index
-
- def to_excellon(self, settings=None):
- stmt = 'T%02d' % self.tool
- if self.compensation_index is not None:
- stmt += '%02d' % self.compensation_index
- return stmt
-
-class NextToolSelectionStmt(ExcellonStatement):
-
- # TODO the statement exists outside of the context of the file,
- # so it is imposible to know that it is really the next tool
-
- def __init__(self, cur_tool, next_tool, **kwargs):
- """
- Select the next tool in the wheel.
- Parameters
- ----------
- cur_tool : the tool that is currently selected
- next_tool : the that that is now selected
- """
- super(NextToolSelectionStmt, self).__init__(**kwargs)
-
- self.cur_tool = cur_tool
- self.next_tool = next_tool
-
- def to_excellon(self, settings=None):
- stmt = 'M00'
- return stmt
-
-class ZAxisInfeedRateStmt(ExcellonStatement):
-
- @classmethod
- def from_excellon(cls, line, **kwargs):
- """ Create a ZAxisInfeedRate from an excellon file line.
-
- Parameters
- ----------
- line : string
- Line from an Excellon file
-
- Returns
- -------
- z_axis_infeed_rate : ToolSelectionStmt
- ToolSelectionStmt representation of `line.`
- """
- rate = int(line[1:])
-
- return cls(rate, **kwargs)
-
- def __init__(self, rate, **kwargs):
- super(ZAxisInfeedRateStmt, self).__init__(**kwargs)
- self.rate = rate
-
- def to_excellon(self, settings=None):
- return 'F%02d' % self.rate
-
-
-class CoordinateStmt(ExcellonStatement):
-
- @classmethod
- def from_point(cls, point, mode=None):
-
- stmt = cls(point[0], point[1])
- if mode:
- stmt.mode = mode
- return stmt
-
- @classmethod
- def from_excellon(cls, line, settings, **kwargs):
- x_coord = None
- y_coord = None
- if line[0] == 'X':
- splitline = line.strip('X').split('Y')
- x_coord = parse_gerber_value(splitline[0], settings.format,
- settings.zero_suppression)
- if len(splitline) == 2:
- y_coord = parse_gerber_value(splitline[1], settings.format,
- settings.zero_suppression)
- else:
- y_coord = parse_gerber_value(line.strip(' Y'), settings.format,
- settings.zero_suppression)
- c = cls(x_coord, y_coord, **kwargs)
- c.units = settings.units
- return c
-
- def __init__(self, x=None, y=None, **kwargs):
- super(CoordinateStmt, self).__init__(**kwargs)
- self.x = x
- self.y = y
- self.mode = None
-
- def to_excellon(self, settings):
- stmt = ''
- if self.mode == "ROUT":
- stmt += "G00"
- if self.mode == "LINEAR":
- stmt += "G01"
- if self.x is not None:
- stmt += 'X%s' % write_gerber_value(self.x, settings.format,
- settings.zero_suppression)
- if self.y is not None:
- stmt += 'Y%s' % write_gerber_value(self.y, settings.format,
- settings.zero_suppression)
- return stmt
-
- def to_inch(self):
- if self.units == 'metric':
- self.units = 'inch'
- if self.x is not None:
- self.x = inch(self.x)
- if self.y is not None:
- self.y = inch(self.y)
-
- def to_metric(self):
- if self.units == 'inch':
- self.units = 'metric'
- if self.x is not None:
- self.x = metric(self.x)
- if self.y is not None:
- self.y = metric(self.y)
-
- def offset(self, x_offset=0, y_offset=0):
- if self.x is not None:
- self.x += x_offset
- if self.y is not None:
- self.y += y_offset
-
- def __str__(self):
- coord_str = ''
- if self.x is not None:
- coord_str += 'X: %g ' % self.x
- if self.y is not None:
- coord_str += 'Y: %g ' % self.y
-
- return '<Coordinate Statement: %s>' % coord_str
-
-
-class RepeatHoleStmt(ExcellonStatement):
-
- @classmethod
- def from_excellon(cls, line, settings, **kwargs):
- match = re.compile(r'R(?P<rcount>[0-9]*)X?(?P<xdelta>[+\-]?\d*\.?\d*)?Y?'
- '(?P<ydelta>[+\-]?\d*\.?\d*)?').match(line)
- stmt = match.groupdict()
- count = int(stmt['rcount'])
- xdelta = (parse_gerber_value(stmt['xdelta'], settings.format,
- settings.zero_suppression)
- if stmt['xdelta'] is not '' else None)
- ydelta = (parse_gerber_value(stmt['ydelta'], settings.format,
- settings.zero_suppression)
- if stmt['ydelta'] is not '' else None)
- c = cls(count, xdelta, ydelta, **kwargs)
- c.units = settings.units
- return c
-
- def __init__(self, count, xdelta=0.0, ydelta=0.0, **kwargs):
- super(RepeatHoleStmt, self).__init__(**kwargs)
- self.count = count
- self.xdelta = xdelta
- self.ydelta = ydelta
-
- def to_excellon(self, settings):
- stmt = 'R%d' % self.count
- if self.xdelta is not None and self.xdelta != 0.0:
- stmt += 'X%s' % write_gerber_value(self.xdelta, settings.format,
- settings.zero_suppression)
- if self.ydelta is not None and self.ydelta != 0.0:
- stmt += 'Y%s' % write_gerber_value(self.ydelta, settings.format,
- settings.zero_suppression)
- return stmt
-
- def to_inch(self):
- if self.units == 'metric':
- self.units = 'inch'
- if self.xdelta is not None:
- self.xdelta = inch(self.xdelta)
- if self.ydelta is not None:
- self.ydelta = inch(self.ydelta)
-
- def to_metric(self):
- if self.units == 'inch':
- self.units = 'metric'
- if self.xdelta is not None:
- self.xdelta = metric(self.xdelta)
- if self.ydelta is not None:
- self.ydelta = metric(self.ydelta)
-
- def __str__(self):
- return '<Repeat Hole: %d times, offset X: %g Y: %g>' % (
- self.count,
- self.xdelta if self.xdelta is not None else 0,
- self.ydelta if self.ydelta is not None else 0)
-
-
-class CommentStmt(ExcellonStatement):
-
- @classmethod
- def from_excellon(cls, line, **kwargs):
- return cls(line.lstrip(';'))
-
- def __init__(self, comment, **kwargs):
- super(CommentStmt, self).__init__(**kwargs)
- self.comment = comment
-
- def to_excellon(self, settings=None):
- return ';%s' % self.comment
-
-
-class HeaderBeginStmt(ExcellonStatement):
-
- def __init__(self, **kwargs):
- super(HeaderBeginStmt, self).__init__(**kwargs)
-
- def to_excellon(self, settings=None):
- return 'M48'
-
-
-class HeaderEndStmt(ExcellonStatement):
-
- def __init__(self, **kwargs):
- super(HeaderEndStmt, self).__init__(**kwargs)
-
- def to_excellon(self, settings=None):
- return 'M95'
-
-
-class RewindStopStmt(ExcellonStatement):
-
- def __init__(self, **kwargs):
- super(RewindStopStmt, self).__init__(**kwargs)
-
- def to_excellon(self, settings=None):
- return '%'
-
-
-class ZAxisRoutPositionStmt(ExcellonStatement):
-
- def __init__(self, **kwargs):
- super(ZAxisRoutPositionStmt, self).__init__(**kwargs)
-
- def to_excellon(self, settings=None):
- return 'M15'
-
-
-class RetractWithClampingStmt(ExcellonStatement):
-
- def __init__(self, **kwargs):
- super(RetractWithClampingStmt, self).__init__(**kwargs)
-
- def to_excellon(self, settings=None):
- return 'M16'
-
-
-class RetractWithoutClampingStmt(ExcellonStatement):
-
- def __init__(self, **kwargs):
- super(RetractWithoutClampingStmt, self).__init__(**kwargs)
-
- def to_excellon(self, settings=None):
- return 'M17'
-
-
-class CutterCompensationOffStmt(ExcellonStatement):
-
- def __init__(self, **kwargs):
- super(CutterCompensationOffStmt, self).__init__(**kwargs)
-
- def to_excellon(self, settings=None):
- return 'G40'
-
-
-class CutterCompensationLeftStmt(ExcellonStatement):
-
- def __init__(self, **kwargs):
- super(CutterCompensationLeftStmt, self).__init__(**kwargs)
-
- def to_excellon(self, settings=None):
- return 'G41'
-
-
-class CutterCompensationRightStmt(ExcellonStatement):
-
- def __init__(self, **kwargs):
- super(CutterCompensationRightStmt, self).__init__(**kwargs)
-
- def to_excellon(self, settings=None):
- return 'G42'
-
-
-class EndOfProgramStmt(ExcellonStatement):
-
- @classmethod
- def from_excellon(cls, line, settings, **kwargs):
- match = re.compile(r'M30X?(?P<x>\d*\.?\d*)?Y?'
- '(?P<y>\d*\.?\d*)?').match(line)
- stmt = match.groupdict()
- x = (parse_gerber_value(stmt['x'], settings.format,
- settings.zero_suppression)
- if stmt['x'] is not '' else None)
- y = (parse_gerber_value(stmt['y'], settings.format,
- settings.zero_suppression)
- if stmt['y'] is not '' else None)
- c = cls(x, y, **kwargs)
- c.units = settings.units
- return c
-
- def __init__(self, x=None, y=None, **kwargs):
- super(EndOfProgramStmt, self).__init__(**kwargs)
- self.x = x
- self.y = y
-
- def to_excellon(self, settings=None):
- stmt = 'M30'
- if self.x is not None:
- stmt += 'X%s' % write_gerber_value(self.x)
- if self.y is not None:
- stmt += 'Y%s' % write_gerber_value(self.y)
- return stmt
-
- def to_inch(self):
- if self.units == 'metric':
- self.units = 'inch'
- if self.x is not None:
- self.x = inch(self.x)
- if self.y is not None:
- self.y = inch(self.y)
-
- def to_metric(self):
- if self.units == 'inch':
- self.units = 'metric'
- if self.x is not None:
- self.x = metric(self.x)
- if self.y is not None:
- self.y = metric(self.y)
-
- def offset(self, x_offset=0, y_offset=0):
- if self.x is not None:
- self.x += x_offset
- if self.y is not None:
- self.y += y_offset
-
-
-class UnitStmt(ExcellonStatement):
-
- @classmethod
- def from_settings(cls, settings):
- """Create the unit statement from the FileSettings"""
-
- return cls(settings.units, settings.zeros)
-
- @classmethod
- def from_excellon(cls, line, **kwargs):
- units = 'inch' if 'INCH' in line else 'metric'
- zeros = 'leading' if 'LZ' in line else 'trailing'
- if '0000.00' in line:
- format = (4, 2)
- elif '000.000' in line:
- format = (3, 3)
- elif '00.0000' in line:
- format = (2, 4)
- else:
- format = None
- return cls(units, zeros, format, **kwargs)
-
- def __init__(self, units='inch', zeros='leading', format=None, **kwargs):
- super(UnitStmt, self).__init__(**kwargs)
- self.units = units.lower()
- self.zeros = zeros
- self.format = format
-
- def to_excellon(self, settings=None):
- # TODO This won't export the invalid format statement if it exists
- stmt = '%s,%s' % ('INCH' if self.units == 'inch' else 'METRIC',
- 'LZ' if self.zeros == 'leading'
- else 'TZ')
- return stmt
-
- def to_inch(self):
- self.units = 'inch'
-
- def to_metric(self):
- self.units = 'metric'
-
-
-class IncrementalModeStmt(ExcellonStatement):
-
- @classmethod
- def from_excellon(cls, line, **kwargs):
- return cls('off', **kwargs) if 'OFF' in line else cls('on', **kwargs)
-
- def __init__(self, mode='off', **kwargs):
- super(IncrementalModeStmt, self).__init__(**kwargs)
- if mode.lower() not in ['on', 'off']:
- raise ValueError('Mode may be "on" or "off"')
- self.mode = mode
-
- def to_excellon(self, settings=None):
- return 'ICI,%s' % ('OFF' if self.mode == 'off' else 'ON')
-
-
-class VersionStmt(ExcellonStatement):
-
- @classmethod
- def from_excellon(cls, line, **kwargs):
- version = int(line.split(',')[1])
- return cls(version, **kwargs)
-
- def __init__(self, version=1, **kwargs):
- super(VersionStmt, self).__init__(**kwargs)
- version = int(version)
- if version not in [1, 2]:
- raise ValueError('Valid versions are 1 or 2')
- self.version = version
-
- def to_excellon(self, settings=None):
- return 'VER,%d' % self.version
-
-
-class FormatStmt(ExcellonStatement):
-
- @classmethod
- def from_excellon(cls, line, **kwargs):
- fmt = int(line.split(',')[1])
- return cls(fmt, **kwargs)
-
- def __init__(self, format=1, **kwargs):
- super(FormatStmt, self).__init__(**kwargs)
- format = int(format)
- if format not in [1, 2]:
- raise ValueError('Valid formats are 1 or 2')
- self.format = format
-
- def to_excellon(self, settings=None):
- return 'FMAT,%d' % self.format
-
- @property
- def format_tuple(self):
- return (self.format, 6 - self.format)
-
-
-class LinkToolStmt(ExcellonStatement):
-
- @classmethod
- def from_excellon(cls, line, **kwargs):
- linked = [int(tool) for tool in line.split('/')]
- return cls(linked, **kwargs)
-
- def __init__(self, linked_tools, **kwargs):
- super(LinkToolStmt, self).__init__(**kwargs)
- self.linked_tools = [int(x) for x in linked_tools]
-
- def to_excellon(self, settings=None):
- return '/'.join([str(x) for x in self.linked_tools])
-
-
-class MeasuringModeStmt(ExcellonStatement):
-
- @classmethod
- def from_excellon(cls, line, **kwargs):
- if not ('M71' in line or 'M72' in line):
- raise ValueError('Not a measuring mode statement')
- return cls('inch', **kwargs) if 'M72' in line else cls('metric', **kwargs)
-
- def __init__(self, units='inch', **kwargs):
- super(MeasuringModeStmt, self).__init__(**kwargs)
- units = units.lower()
- if units not in ['inch', 'metric']:
- raise ValueError('units must be "inch" or "metric"')
- self.units = units
-
- def to_excellon(self, settings=None):
- return 'M72' if self.units == 'inch' else 'M71'
-
- def to_inch(self):
- self.units = 'inch'
-
- def to_metric(self):
- self.units = 'metric'
-
-
-class RouteModeStmt(ExcellonStatement):
-
- def __init__(self, **kwargs):
- super(RouteModeStmt, self).__init__(**kwargs)
-
- def to_excellon(self, settings=None):
- return 'G00'
-
-
-class LinearModeStmt(ExcellonStatement):
-
- def __init__(self, **kwargs):
- super(LinearModeStmt, self).__init__(**kwargs)
-
- def to_excellon(self, settings=None):
- return 'G01'
-
-
-class DrillModeStmt(ExcellonStatement):
-
- def __init__(self, **kwargs):
- super(DrillModeStmt, self).__init__(**kwargs)
-
- def to_excellon(self, settings=None):
- return 'G05'
-
-
-class AbsoluteModeStmt(ExcellonStatement):
-
- def __init__(self, **kwargs):
- super(AbsoluteModeStmt, self).__init__(**kwargs)
-
- def to_excellon(self, settings=None):
- return 'G90'
-
-
-class UnknownStmt(ExcellonStatement):
-
- @classmethod
- def from_excellon(cls, line, **kwargs):
- return cls(line, **kwargs)
-
- def __init__(self, stmt, **kwargs):
- super(UnknownStmt, self).__init__(**kwargs)
- self.stmt = stmt
-
- def to_excellon(self, settings=None):
- return self.stmt
-
- def __str__(self):
- return "<Unknown Statement: %s>" % self.stmt
-
-
-class SlotStmt(ExcellonStatement):
- """
- G85 statement. Defines a slot created by multiple drills between two specified points.
-
- Format is two coordinates, split by G85in the middle, for example, XnY0nG85XnYn
- """
-
- @classmethod
- def from_points(cls, start, end):
-
- return cls(start[0], start[1], end[0], end[1])
-
- @classmethod
- def from_excellon(cls, line, settings, **kwargs):
- # Split the line based on the G85 separator
- sub_coords = line.split('G85')
- (x_start_coord, y_start_coord) = SlotStmt.parse_sub_coords(sub_coords[0], settings)
- (x_end_coord, y_end_coord) = SlotStmt.parse_sub_coords(sub_coords[1], settings)
-
- # Some files seem to specify only one of the coordinates
- if x_end_coord == None:
- x_end_coord = x_start_coord
- if y_end_coord == None:
- y_end_coord = y_start_coord
-
- c = cls(x_start_coord, y_start_coord, x_end_coord, y_end_coord, **kwargs)
- c.units = settings.units
- return c
-
- @staticmethod
- def parse_sub_coords(line, settings):
-
- x_coord = None
- y_coord = None
-
- if line[0] == 'X':
- splitline = line.strip('X').split('Y')
- x_coord = parse_gerber_value(splitline[0], settings.format,
- settings.zero_suppression)
- if len(splitline) == 2:
- y_coord = parse_gerber_value(splitline[1], settings.format,
- settings.zero_suppression)
- else:
- y_coord = parse_gerber_value(line.strip(' Y'), settings.format,
- settings.zero_suppression)
-
- return (x_coord, y_coord)
-
-
- def __init__(self, x_start=None, y_start=None, x_end=None, y_end=None, **kwargs):
- super(SlotStmt, self).__init__(**kwargs)
- self.x_start = x_start
- self.y_start = y_start
- self.x_end = x_end
- self.y_end = y_end
- self.mode = None
-
- def to_excellon(self, settings):
- stmt = ''
-
- if self.x_start is not None:
- stmt += 'X%s' % write_gerber_value(self.x_start, settings.format,
- settings.zero_suppression)
- if self.y_start is not None:
- stmt += 'Y%s' % write_gerber_value(self.y_start, settings.format,
- settings.zero_suppression)
-
- stmt += 'G85'
-
- if self.x_end is not None:
- stmt += 'X%s' % write_gerber_value(self.x_end, settings.format,
- settings.zero_suppression)
- if self.y_end is not None:
- stmt += 'Y%s' % write_gerber_value(self.y_end, settings.format,
- settings.zero_suppression)
-
- return stmt
-
- def to_inch(self):
- if self.units == 'metric':
- self.units = 'inch'
- if self.x_start is not None:
- self.x_start = inch(self.x_start)
- if self.y_start is not None:
- self.y_start = inch(self.y_start)
- if self.x_end is not None:
- self.x_end = inch(self.x_end)
- if self.y_end is not None:
- self.y_end = inch(self.y_end)
-
- def to_metric(self):
- if self.units == 'inch':
- self.units = 'metric'
- if self.x_start is not None:
- self.x_start = metric(self.x_start)
- if self.y_start is not None:
- self.y_start = metric(self.y_start)
- if self.x_end is not None:
- self.x_end = metric(self.x_end)
- if self.y_end is not None:
- self.y_end = metric(self.y_end)
-
- def offset(self, x_offset=0, y_offset=0):
- if self.x_start is not None:
- self.x_start += x_offset
- if self.y_start is not None:
- self.y_start += y_offset
- if self.x_end is not None:
- self.x_end += x_offset
- if self.y_end is not None:
- self.y_end += y_offset
-
- def __str__(self):
- start_str = ''
- if self.x_start is not None:
- start_str += 'X: %g ' % self.x_start
- if self.y_start is not None:
- start_str += 'Y: %g ' % self.y_start
-
- end_str = ''
- if self.x_end is not None:
- end_str += 'X: %g ' % self.x_end
- if self.y_end is not None:
- end_str += 'Y: %g ' % self.y_end
-
- return '<Slot Statement: %s to %s>' % (start_str, end_str)
-
-def pairwise(iterator):
- """ Iterate over list taking two elements at a time.
-
- e.g. [1, 2, 3, 4, 5, 6] ==> [(1, 2), (3, 4), (5, 6)]
- """
- a, b = itertools.tee(iterator)
- itr = zip(itertools.islice(a, 0, None, 2), itertools.islice(b, 1, None, 2))
- for elem in itr:
- yield elem
diff --git a/gerber/excellon_tool.py b/gerber/excellon_tool.py
deleted file mode 100644
index a9ac450..0000000
--- a/gerber/excellon_tool.py
+++ /dev/null
@@ -1,190 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2015 Garret Fick <garret@ficksworkshop.com>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Excellon Tool Definition File module
-====================
-**Excellon file classes**
-
-This module provides Excellon file classes and parsing utilities
-"""
-
-import re
-try:
- from cStringIO import StringIO
-except(ImportError):
- from io import StringIO
-
-from .excellon_statements import ExcellonTool
-
-def loads(data, settings=None):
- """ Read tool file information and return a map of tools
- Parameters
- ----------
- data : string
- string containing Excellon Tool Definition file contents
-
- Returns
- -------
- dict tool name: ExcellonTool
-
- """
- return ExcellonToolDefinitionParser(settings).parse_raw(data)
-
-class ExcellonToolDefinitionParser(object):
- """ Excellon File Parser
-
- Parameters
- ----------
- None
- """
-
- allegro_tool = re.compile(r'(?P<size>[0-9/.]+)\s+(?P<plated>P|N)\s+T(?P<toolid>[0-9]{2})\s+(?P<xtol>[0-9/.]+)\s+(?P<ytol>[0-9/.]+)')
- allegro_comment_mils = re.compile('Holesize (?P<toolid>[0-9]{1,2})\. = (?P<size>[0-9/.]+) Tolerance = \+(?P<xtol>[0-9/.]+)/-(?P<ytol>[0-9/.]+) (?P<plated>(PLATED)|(NON_PLATED)|(OPTIONAL)) MILS Quantity = [0-9]+')
- allegro2_comment_mils = re.compile('T(?P<toolid>[0-9]{1,2}) Holesize (?P<toolid2>[0-9]{1,2})\. = (?P<size>[0-9/.]+) Tolerance = \+(?P<xtol>[0-9/.]+)/-(?P<ytol>[0-9/.]+) (?P<plated>(PLATED)|(NON_PLATED)|(OPTIONAL)) MILS Quantity = [0-9]+')
- allegro_comment_mm = re.compile('Holesize (?P<toolid>[0-9]{1,2})\. = (?P<size>[0-9/.]+) Tolerance = \+(?P<xtol>[0-9/.]+)/-(?P<ytol>[0-9/.]+) (?P<plated>(PLATED)|(NON_PLATED)|(OPTIONAL)) MM Quantity = [0-9]+')
- allegro2_comment_mm = re.compile('T(?P<toolid>[0-9]{1,2}) Holesize (?P<toolid2>[0-9]{1,2})\. = (?P<size>[0-9/.]+) Tolerance = \+(?P<xtol>[0-9/.]+)/-(?P<ytol>[0-9/.]+) (?P<plated>(PLATED)|(NON_PLATED)|(OPTIONAL)) MM Quantity = [0-9]+')
-
- matchers = [
- (allegro_tool, 'mils'),
- (allegro_comment_mils, 'mils'),
- (allegro2_comment_mils, 'mils'),
- (allegro_comment_mm, 'mm'),
- (allegro2_comment_mm, 'mm'),
- ]
-
- def __init__(self, settings=None):
- self.tools = {}
- self.settings = settings
-
- def parse_raw(self, data):
- for line in StringIO(data):
- self._parse(line.strip())
-
- return self.tools
-
- def _parse(self, line):
-
- for matcher in ExcellonToolDefinitionParser.matchers:
- m = matcher[0].match(line)
- if m:
- unit = matcher[1]
-
- size = float(m.group('size'))
- platedstr = m.group('plated')
- toolid = int(m.group('toolid'))
- xtol = float(m.group('xtol'))
- ytol = float(m.group('ytol'))
-
- size = self._convert_length(size, unit)
- xtol = self._convert_length(xtol, unit)
- ytol = self._convert_length(ytol, unit)
-
- if platedstr == 'PLATED':
- plated = ExcellonTool.PLATED_YES
- elif platedstr == 'NON_PLATED':
- plated = ExcellonTool.PLATED_NO
- elif platedstr == 'OPTIONAL':
- plated = ExcellonTool.PLATED_OPTIONAL
- else:
- plated = ExcellonTool.PLATED_UNKNOWN
-
- tool = ExcellonTool(None, number=toolid, diameter=size,
- plated=plated)
-
- self.tools[tool.number] = tool
-
- break
-
- def _convert_length(self, value, unit):
-
- # Convert the value to mm
- if unit == 'mils':
- value /= 39.3700787402
-
- # Now convert to the settings unit
- if self.settings.units == 'inch':
- return value / 25.4
- else:
- # Already in mm
- return value
-
-def loads_rep(data, settings=None):
- """ Read tool report information generated by PADS and return a map of tools
- Parameters
- ----------
- data : string
- string containing Excellon Report file contents
-
- Returns
- -------
- dict tool name: ExcellonTool
-
- """
- return ExcellonReportParser(settings).parse_raw(data)
-
-class ExcellonReportParser(object):
-
- # We sometimes get files with different encoding, so we can't actually
- # match the text - the best we can do it detect the table header
- header = re.compile(r'====\s+====\s+====\s+====\s+=====\s+===')
-
- def __init__(self, settings=None):
- self.tools = {}
- self.settings = settings
-
- self.found_header = False
-
- def parse_raw(self, data):
- for line in StringIO(data):
- self._parse(line.strip())
-
- return self.tools
-
- def _parse(self, line):
-
- # skip empty lines and "comments"
- if not line.strip():
- return
-
- if not self.found_header:
- # Try to find the heaader, since we need that to be sure we
- # understand the contents correctly.
- if ExcellonReportParser.header.match(line):
- self.found_header = True
-
- elif line[0] != '=':
- # Already found the header, so we know to to map the contents
- parts = line.split()
- if len(parts) == 6:
- toolid = int(parts[0])
- size = float(parts[1])
- if parts[2] == 'x':
- plated = ExcellonTool.PLATED_YES
- elif parts[2] == '-':
- plated = ExcellonTool.PLATED_NO
- else:
- plated = ExcellonTool.PLATED_UNKNOWN
- feedrate = int(parts[3])
- speed = int(parts[4])
- qty = int(parts[5])
-
- tool = ExcellonTool(None, number=toolid, diameter=size,
- plated=plated, feed_rate=feedrate,
- rpm=speed)
-
- self.tools[tool.number] = tool
diff --git a/gerber/exceptions.py b/gerber/exceptions.py
deleted file mode 100644
index 65ae905..0000000
--- a/gerber/exceptions.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2015 Hamilton Kibbe <ham@hamiltonkib.be>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-class ParseError(Exception):
- pass
-
-
-class GerberParseError(ParseError):
- pass
-
-
-class ExcellonParseError(ParseError):
- pass
-
-
-class ExcellonFileError(IOError):
- pass
-
-
-class GerberFileError(IOError):
- pass
diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py
deleted file mode 100644
index 28f5e81..0000000
--- a/gerber/gerber_statements.py
+++ /dev/null
@@ -1,1189 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Gerber (RS-274X) Statements
-===========================
-**Gerber RS-274X file statement classes**
-
-"""
-from .utils import (parse_gerber_value, write_gerber_value, decimal_string,
- inch, metric)
-
-from .am_statements import *
-from .am_read import read_macro
-from .am_eval import eval_macro
-from .primitives import AMGroup
-
-
-class Statement(object):
- """ Gerber statement Base class
-
- The statement class provides a type attribute.
-
- Parameters
- ----------
- type : string
- String identifying the statement type.
-
- Attributes
- ----------
- type : string
- String identifying the statement type.
- """
-
- def __init__(self, stype, units='inch'):
- self.type = stype
- self.units = units
-
- def __str__(self):
- s = "<{0} ".format(self.__class__.__name__)
-
- for key, value in self.__dict__.items():
- s += "{0}={1} ".format(key, value)
-
- s = s.rstrip() + ">"
- return s
-
- def to_inch(self):
- self.units = 'inch'
-
- def to_metric(self):
- self.units = 'metric'
-
- def offset(self, x_offset=0, y_offset=0):
- pass
-
- def __eq__(self, other):
- return self.__dict__ == other.__dict__
-
-
-class ParamStmt(Statement):
- """ Gerber parameter statement Base class
-
- The parameter statement class provides a parameter type attribute.
-
- Parameters
- ----------
- param : string
- two-character code identifying the parameter statement type.
-
- Attributes
- ----------
- param : string
- Parameter type code
- """
-
- def __init__(self, param):
- Statement.__init__(self, "PARAM")
- self.param = param
-
-
-class FSParamStmt(ParamStmt):
- """ FS - Gerber Format Specification Statement
- """
-
- @classmethod
- def from_settings(cls, settings):
-
- return cls('FS', settings.zero_suppression, settings.notation, settings.format)
-
- @classmethod
- def from_dict(cls, stmt_dict):
- """
- """
- param = stmt_dict.get('param')
-
- if stmt_dict.get('zero') == 'L':
- zeros = 'leading'
- elif stmt_dict.get('zero') == 'T':
- zeros = 'trailing'
- else:
- zeros = 'none'
-
- notation = 'absolute' if stmt_dict.get('notation') == 'A' else 'incremental'
- fmt = tuple(map(int, stmt_dict.get('x')))
- return cls(param, zeros, notation, fmt)
-
- def __init__(self, param, zero_suppression='leading',
- notation='absolute', format=(2, 4)):
- """ Initialize FSParamStmt class
-
- .. note::
- The FS command specifies the format of the coordinate data. It
- must only be used once at the beginning of a file. It must be
- specified before the first use of coordinate data.
-
- Parameters
- ----------
- param : string
- Parameter.
-
- zero_suppression : string
- Zero-suppression mode. May be either 'leading', 'trailing' or 'none' (all zeros are present)
-
- notation : string
- Notation mode. May be either 'absolute' or 'incremental'
-
- format : tuple (int, int)
- Gerber precision format expressed as a tuple containing:
- (number of integer-part digits, number of decimal-part digits)
-
- Returns
- -------
- ParamStmt : FSParamStmt
- Initialized FSParamStmt class.
-
- """
- ParamStmt.__init__(self, param)
- self.zero_suppression = zero_suppression
- self.notation = notation
- self.format = format
-
- def to_gerber(self, settings=None):
- if settings:
- zero_suppression = 'L' if settings.zero_suppression == 'leading' else 'T'
- notation = 'A' if settings.notation == 'absolute' else 'I'
- fmt = ''.join(map(str, settings.format))
- else:
- zero_suppression = 'L' if self.zero_suppression == 'leading' else 'T'
- notation = 'A' if self.notation == 'absolute' else 'I'
- fmt = ''.join(map(str, self.format))
-
- return '%FS{0}{1}X{2}Y{3}*%'.format(zero_suppression, notation, fmt, fmt)
-
- def __str__(self):
- return ('<Format Spec: %d:%d %s zero suppression %s notation>' %
- (self.format[0], self.format[1], self.zero_suppression, self.notation))
-
-
-class MOParamStmt(ParamStmt):
- """ MO - Gerber Mode (measurement units) Statement.
- """
-
- @classmethod
- def from_units(cls, units):
- return cls(None, units)
-
- @classmethod
- def from_dict(cls, stmt_dict):
- param = stmt_dict.get('param')
- if stmt_dict.get('mo') is None:
- mo = None
- elif stmt_dict.get('mo').lower() not in ('in', 'mm'):
- raise ValueError('Mode may be mm or in')
- elif stmt_dict.get('mo').lower() == 'in':
- mo = 'inch'
- else:
- mo = 'metric'
- return cls(param, mo)
-
- def __init__(self, param, mo):
- """ Initialize MOParamStmt class
-
- Parameters
- ----------
- param : string
- Parameter.
-
- mo : string
- Measurement units. May be either 'inch' or 'metric'
-
- Returns
- -------
- ParamStmt : MOParamStmt
- Initialized MOParamStmt class.
-
- """
- ParamStmt.__init__(self, param)
- self.mode = mo
-
- def to_gerber(self, settings=None):
- mode = 'MM' if self.mode == 'metric' else 'IN'
- return '%MO{0}*%'.format(mode)
-
- def to_inch(self):
- self.mode = 'inch'
-
- def to_metric(self):
- self.mode = 'metric'
-
- def __str__(self):
- mode_str = 'millimeters' if self.mode == 'metric' else 'inches'
- return ('<Mode: %s>' % mode_str)
-
-
-class LPParamStmt(ParamStmt):
- """ LP - Gerber Level Polarity statement
- """
-
- @classmethod
- def from_dict(cls, stmt_dict):
- param = stmt_dict['param']
- lp = 'clear' if stmt_dict.get('lp') == 'C' else 'dark'
- return cls(param, lp)
-
- def __init__(self, param, lp):
- """ Initialize LPParamStmt class
-
- Parameters
- ----------
- param : string
- Parameter
-
- lp : string
- Level polarity. May be either 'clear' or 'dark'
-
- Returns
- -------
- ParamStmt : LPParamStmt
- Initialized LPParamStmt class.
-
- """
- ParamStmt.__init__(self, param)
- self.lp = lp
-
- def to_gerber(self, settings=None):
- lp = 'C' if self.lp == 'clear' else 'D'
- return '%LP{0}*%'.format(lp)
-
- def __str__(self):
- return '<Level Polarity: %s>' % self.lp
-
-
-class ADParamStmt(ParamStmt):
- """ AD - Gerber Aperture Definition Statement
- """
-
- @classmethod
- def rect(cls, dcode, width, height, hole_diameter=None, hole_width=None, hole_height=None):
- '''Create a rectangular aperture definition statement'''
- if hole_diameter is not None and hole_diameter > 0:
- return cls('AD', dcode, 'R', ([width, height, hole_diameter],))
- elif (hole_width is not None and hole_width > 0
- and hole_height is not None and hole_height > 0):
- return cls('AD', dcode, 'R', ([width, height, hole_width, hole_height],))
- return cls('AD', dcode, 'R', ([width, height],))
-
- @classmethod
- def circle(cls, dcode, diameter, hole_diameter=None, hole_width=None, hole_height=None):
- '''Create a circular aperture definition statement'''
- if hole_diameter is not None and hole_diameter > 0:
- return cls('AD', dcode, 'C', ([diameter, hole_diameter],))
- elif (hole_width is not None and hole_width > 0
- and hole_height is not None and hole_height > 0):
- return cls('AD', dcode, 'C', ([diameter, hole_width, hole_height],))
- return cls('AD', dcode, 'C', ([diameter],))
-
- @classmethod
- def obround(cls, dcode, width, height, hole_diameter=None, hole_width=None, hole_height=None):
- '''Create an obround aperture definition statement'''
- if hole_diameter is not None and hole_diameter > 0:
- return cls('AD', dcode, 'O', ([width, height, hole_diameter],))
- elif (hole_width is not None and hole_width > 0
- and hole_height is not None and hole_height > 0):
- return cls('AD', dcode, 'O', ([width, height, hole_width, hole_height],))
- return cls('AD', dcode, 'O', ([width, height],))
-
- @classmethod
- def polygon(cls, dcode, diameter, num_vertices, rotation, hole_diameter=None, hole_width=None, hole_height=None):
- '''Create a polygon aperture definition statement'''
- if hole_diameter is not None and hole_diameter > 0:
- return cls('AD', dcode, 'P', ([diameter, num_vertices, rotation, hole_diameter],))
- elif (hole_width is not None and hole_width > 0
- and hole_height is not None and hole_height > 0):
- return cls('AD', dcode, 'P', ([diameter, num_vertices, rotation, hole_width, hole_height],))
- return cls('AD', dcode, 'P', ([diameter, num_vertices, rotation],))
-
-
- @classmethod
- def macro(cls, dcode, name):
- return cls('AD', dcode, name, '')
-
- @classmethod
- def from_dict(cls, stmt_dict):
- param = stmt_dict.get('param')
- d = int(stmt_dict.get('d'))
- shape = stmt_dict.get('shape')
- modifiers = stmt_dict.get('modifiers')
- return cls(param, d, shape, modifiers)
-
- def __init__(self, param, d, shape, modifiers):
- """ Initialize ADParamStmt class
-
- Parameters
- ----------
- param : string
- Parameter code
-
- d : int
- Aperture D-code
-
- shape : string
- aperture name
-
- modifiers : list of lists of floats
- Shape modifiers
-
- Returns
- -------
- ParamStmt : ADParamStmt
- Initialized ADParamStmt class.
-
- """
- ParamStmt.__init__(self, param)
- self.d = d
- self.shape = shape
- if isinstance(modifiers, tuple):
- self.modifiers = modifiers
- elif modifiers:
- self.modifiers = [tuple([float(x) for x in m.split("X") if len(x)])
- for m in modifiers.split(",") if len(m)]
- else:
- self.modifiers = [tuple()]
-
- def to_inch(self):
- if self.units == 'metric':
- self.units = 'inch'
- self.modifiers = [tuple([inch(x) for x in modifier])
- for modifier in self.modifiers]
-
- def to_metric(self):
- if self.units == 'inch':
- self.units = 'metric'
- self.modifiers = [tuple([metric(x) for x in modifier])
- for modifier in self.modifiers]
-
- def to_gerber(self, settings=None):
- if any(self.modifiers):
- return '%ADD{0}{1},{2}*%'.format(self.d, self.shape, ','.join(['X'.join(["%.4g" % x for x in modifier]) for modifier in self.modifiers]))
- else:
- return '%ADD{0}{1}*%'.format(self.d, self.shape)
-
- def __str__(self):
- if self.shape == 'C':
- shape = 'circle'
- elif self.shape == 'R':
- shape = 'rectangle'
- elif self.shape == 'O':
- shape = 'obround'
- else:
- shape = self.shape
-
- return '<Aperture Definition: %d: %s>' % (self.d, shape)
-
-
-class AMParamStmt(ParamStmt):
- """ AM - Aperture Macro Statement
- """
-
- @classmethod
- def from_dict(cls, stmt_dict):
- return cls(**stmt_dict)
-
- def __init__(self, param, name, macro):
- """ Initialize AMParamStmt class
-
- Parameters
- ----------
- param : string
- Parameter code
-
- name : string
- Aperture macro name
-
- macro : string
- Aperture macro string
-
- Returns
- -------
- ParamStmt : AMParamStmt
- Initialized AMParamStmt class.
-
- """
- ParamStmt.__init__(self, param)
- self.name = name
- self.macro = macro
-
- self.instructions = self.read(macro)
- self.primitives = []
-
- def read(self, macro):
- return read_macro(macro)
-
- def build(self, modifiers=[[]]):
- self.primitives = []
-
- for primitive in eval_macro(self.instructions, modifiers[0]):
- if primitive[0] == '0':
- self.primitives.append(AMCommentPrimitive.from_gerber(primitive))
- elif primitive[0] == '1':
- self.primitives.append(AMCirclePrimitive.from_gerber(primitive))
- elif primitive[0:2] in ('2,', '20'):
- self.primitives.append(AMVectorLinePrimitive.from_gerber(primitive))
- elif primitive[0:2] == '21':
- self.primitives.append(AMCenterLinePrimitive.from_gerber(primitive))
- elif primitive[0:2] == '22':
- self.primitives.append(AMLowerLeftLinePrimitive.from_gerber(primitive))
- elif primitive[0] == '4':
- self.primitives.append(AMOutlinePrimitive.from_gerber(primitive))
- elif primitive[0] == '5':
- self.primitives.append(AMPolygonPrimitive.from_gerber(primitive))
- elif primitive[0] == '6':
- self.primitives.append(AMMoirePrimitive.from_gerber(primitive))
- elif primitive[0] == '7':
- self.primitives.append(
- AMThermalPrimitive.from_gerber(primitive))
- else:
- self.primitives.append(
- AMUnsupportPrimitive.from_gerber(primitive))
-
- return AMGroup(self.primitives, stmt=self, units=self.units)
-
- def to_inch(self):
- if self.units == 'metric':
- self.units = 'inch'
- for primitive in self.primitives:
- primitive.to_inch()
-
- def to_metric(self):
- if self.units == 'inch':
- self.units = 'metric'
- for primitive in self.primitives:
- primitive.to_metric()
-
- def to_gerber(self, settings=None):
- return '%AM{0}*{1}%'.format(self.name, "".join([primitive.to_gerber() for primitive in self.primitives]))
-
- def __str__(self):
- return '<Aperture Macro %s: %s>' % (self.name, self.macro)
-
-
-class ASParamStmt(ParamStmt):
- """ AS - Axis Select. (Deprecated)
- """
- @classmethod
- def from_dict(cls, stmt_dict):
- param = stmt_dict.get('param')
- mode = stmt_dict.get('mode')
- return cls(param, mode)
-
- def __init__(self, param, mode):
- """ Initialize ASParamStmt class
-
- Parameters
- ----------
- param : string
- Parameter string.
-
- mode : string
- Axis select. May be either 'AXBY' or 'AYBX'
-
- Returns
- -------
- ParamStmt : ASParamStmt
- Initialized ASParamStmt class.
-
- """
- ParamStmt.__init__(self, param)
- self.mode = mode
-
- def to_gerber(self, settings=None):
- return '%AS{0}*%'.format(self.mode)
-
- def __str__(self):
- return ('<Axis Select: %s>' % self.mode)
-
-
-class INParamStmt(ParamStmt):
- """ IN - Image Name Statement (Deprecated)
- """
- @classmethod
- def from_dict(cls, stmt_dict):
- return cls(**stmt_dict)
-
- def __init__(self, param, name):
- """ Initialize INParamStmt class
-
- Parameters
- ----------
- param : string
- Parameter code
-
- name : string
- Image name
-
- Returns
- -------
- ParamStmt : INParamStmt
- Initialized INParamStmt class.
-
- """
- ParamStmt.__init__(self, param)
- self.name = name
-
- def to_gerber(self, settings=None):
- return '%IN{0}*%'.format(self.name)
-
- def __str__(self):
- return '<Image Name: %s>' % self.name
-
-
-class IPParamStmt(ParamStmt):
- """ IP - Gerber Image Polarity Statement. (Deprecated)
- """
- @classmethod
- def from_dict(cls, stmt_dict):
- param = stmt_dict.get('param')
- ip = 'positive' if stmt_dict.get('ip') == 'POS' else 'negative'
- return cls(param, ip)
-
- def __init__(self, param, ip):
- """ Initialize IPParamStmt class
-
- Parameters
- ----------
- param : string
- Parameter string.
-
- ip : string
- Image polarity. May be either'positive' or 'negative'
-
- Returns
- -------
- ParamStmt : IPParamStmt
- Initialized IPParamStmt class.
-
- """
- ParamStmt.__init__(self, param)
- self.ip = ip
-
- def to_gerber(self, settings=None):
- ip = 'POS' if self.ip == 'positive' else 'NEG'
- return '%IP{0}*%'.format(ip)
-
- def __str__(self):
- return ('<Image Polarity: %s>' % self.ip)
-
-
-class IRParamStmt(ParamStmt):
- """ IR - Image Rotation Param (Deprecated)
- """
- @classmethod
- def from_dict(cls, stmt_dict):
- angle = int(stmt_dict['angle'])
- return cls(stmt_dict['param'], angle)
-
- def __init__(self, param, angle):
- """ Initialize IRParamStmt class
-
- Parameters
- ----------
- param : string
- Parameter code
-
- angle : int
- Image angle
-
- Returns
- -------
- ParamStmt : IRParamStmt
- Initialized IRParamStmt class.
-
- """
- ParamStmt.__init__(self, param)
- self.angle = angle
-
- def to_gerber(self, settings=None):
- return '%IR{0}*%'.format(self.angle)
-
- def __str__(self):
- return '<Image Angle: %s>' % self.angle
-
-
-class MIParamStmt(ParamStmt):
- """ MI - Image Mirror Param (Deprecated)
- """
- @classmethod
- def from_dict(cls, stmt_dict):
- param = stmt_dict.get('param')
- a = int(stmt_dict.get('a', 0))
- b = int(stmt_dict.get('b', 0))
- return cls(param, a, b)
-
- def __init__(self, param, a, b):
- """ Initialize MIParamStmt class
-
- Parameters
- ----------
- param : string
- Parameter code
-
- a : int
- Mirror for A output devices axis (0=disabled, 1=mirrored)
-
- b : int
- Mirror for B output devices axis (0=disabled, 1=mirrored)
-
- Returns
- -------
- ParamStmt : MIParamStmt
- Initialized MIParamStmt class.
-
- """
- ParamStmt.__init__(self, param)
- self.a = a
- self.b = b
-
- def to_gerber(self, settings=None):
- ret = "%MI"
- if self.a is not None:
- ret += "A{0}".format(self.a)
- if self.b is not None:
- ret += "B{0}".format(self.b)
- ret += "*%"
- return ret
-
- def __str__(self):
- return '<Image Mirror: A=%d B=%d>' % (self.a, self.b)
-
-
-class OFParamStmt(ParamStmt):
- """ OF - Gerber Offset statement (Deprecated)
- """
-
- @classmethod
- def from_dict(cls, stmt_dict):
- param = stmt_dict.get('param')
- a = float(stmt_dict.get('a', 0))
- b = float(stmt_dict.get('b', 0))
- return cls(param, a, b)
-
- def __init__(self, param, a, b):
- """ Initialize OFParamStmt class
-
- Parameters
- ----------
- param : string
- Parameter
-
- a : float
- Offset along the output device A axis
-
- b : float
- Offset along the output device B axis
-
- Returns
- -------
- ParamStmt : OFParamStmt
- Initialized OFParamStmt class.
-
- """
- ParamStmt.__init__(self, param)
- self.a = a
- self.b = b
-
- def to_gerber(self, settings=None):
- ret = '%OF'
- if self.a is not None:
- ret += 'A' + decimal_string(self.a, precision=5)
- if self.b is not None:
- ret += 'B' + decimal_string(self.b, precision=5)
- return ret + '*%'
-
- def to_inch(self):
- if self.units == 'metric':
- self.units = 'inch'
- if self.a is not None:
- self.a = inch(self.a)
- if self.b is not None:
- self.b = inch(self.b)
-
- def to_metric(self):
- if self.units == 'inch':
- self.units = 'metric'
- if self.a is not None:
- self.a = metric(self.a)
- if self.b is not None:
- self.b = metric(self.b)
-
- def offset(self, x_offset=0, y_offset=0):
- if self.a is not None:
- self.a += x_offset
- if self.b is not None:
- self.b += y_offset
-
- def __str__(self):
- offset_str = ''
- if self.a is not None:
- offset_str += ('X: %f ' % self.a)
- if self.b is not None:
- offset_str += ('Y: %f ' % self.b)
- return ('<Offset: %s>' % offset_str)
-
-
-class SFParamStmt(ParamStmt):
- """ SF - Scale Factor Param (Deprecated)
- """
-
- @classmethod
- def from_dict(cls, stmt_dict):
- param = stmt_dict.get('param')
- a = float(stmt_dict.get('a', 1))
- b = float(stmt_dict.get('b', 1))
- return cls(param, a, b)
-
- def __init__(self, param, a, b):
- """ Initialize OFParamStmt class
-
- Parameters
- ----------
- param : string
- Parameter
-
- a : float
- Scale factor for the output device A axis
-
- b : float
- Scale factor for the output device B axis
-
- Returns
- -------
- ParamStmt : SFParamStmt
- Initialized SFParamStmt class.
-
- """
- ParamStmt.__init__(self, param)
- self.a = a
- self.b = b
-
- def to_gerber(self, settings=None):
- ret = '%SF'
- if self.a is not None:
- ret += 'A' + decimal_string(self.a, precision=5)
- if self.b is not None:
- ret += 'B' + decimal_string(self.b, precision=5)
- return ret + '*%'
-
- def to_inch(self):
- if self.units == 'metric':
- self.units = 'inch'
- if self.a is not None:
- self.a = inch(self.a)
- if self.b is not None:
- self.b = inch(self.b)
-
- def to_metric(self):
- if self.units == 'inch':
- self.units = 'metric'
- if self.a is not None:
- self.a = metric(self.a)
- if self.b is not None:
- self.b = metric(self.b)
-
- def offset(self, x_offset=0, y_offset=0):
- if self.a is not None:
- self.a += x_offset
- if self.b is not None:
- self.b += y_offset
-
- def __str__(self):
- scale_factor = ''
- if self.a is not None:
- scale_factor += ('X: %g ' % self.a)
- if self.b is not None:
- scale_factor += ('Y: %g' % self.b)
- return ('<Scale Factor: %s>' % scale_factor)
-
-
-class LNParamStmt(ParamStmt):
- """ LN - Level Name Statement (Deprecated)
- """
- @classmethod
- def from_dict(cls, stmt_dict):
- return cls(**stmt_dict)
-
- def __init__(self, param, name):
- """ Initialize LNParamStmt class
-
- Parameters
- ----------
- param : string
- Parameter code
-
- name : string
- Level name
-
- Returns
- -------
- ParamStmt : LNParamStmt
- Initialized LNParamStmt class.
-
- """
- ParamStmt.__init__(self, param)
- self.name = name
-
- def to_gerber(self, settings=None):
- return '%LN{0}*%'.format(self.name)
-
- def __str__(self):
- return '<Level Name: %s>' % self.name
-
-
-class DeprecatedStmt(Statement):
- """ Unimportant deprecated statement, will be parsed but not emitted.
- """
- @classmethod
- def from_gerber(cls, line):
- return cls(line)
-
- def __init__(self, line):
- """ Initialize DeprecatedStmt class
-
- Parameters
- ----------
- line : string
- Deprecated statement text
-
- Returns
- -------
- DeprecatedStmt
- Initialized DeprecatedStmt class.
-
- """
- Statement.__init__(self, "DEPRECATED")
- self.line = line
-
- def to_gerber(self, settings=None):
- return self.line
-
- def __str__(self):
- return '<Deprecated Statement: \'%s\'>' % self.line
-
-
-class CoordStmt(Statement):
- """ Coordinate Data Block
- """
-
- OP_DRAW = 'D01'
- OP_MOVE = 'D02'
- OP_FLASH = 'D03'
-
- FUNC_LINEAR = 'G01'
- FUNC_ARC_CW = 'G02'
- FUNC_ARC_CCW = 'G03'
-
- @classmethod
- def from_dict(cls, stmt_dict, settings):
- function = stmt_dict['function']
- x = stmt_dict.get('x')
- y = stmt_dict.get('y')
- i = stmt_dict.get('i')
- j = stmt_dict.get('j')
- op = stmt_dict.get('op')
-
- if x is not None:
- x = parse_gerber_value(stmt_dict.get('x'), settings.format,
- settings.zero_suppression)
- if y is not None:
- y = parse_gerber_value(stmt_dict.get('y'), settings.format,
- settings.zero_suppression)
- if i is not None:
- i = parse_gerber_value(stmt_dict.get('i'), settings.format,
- settings.zero_suppression)
- if j is not None:
- j = parse_gerber_value(stmt_dict.get('j'), settings.format,
- settings.zero_suppression)
- return cls(function, x, y, i, j, op, settings)
-
- @classmethod
- def move(cls, func, point):
- if point:
- return cls(func, point[0], point[1], None, None, CoordStmt.OP_MOVE, None)
- # No point specified, so just write the function. This is normally for ending a region (D02*)
- return cls(func, None, None, None, None, CoordStmt.OP_MOVE, None)
-
- @classmethod
- def line(cls, func, point):
- return cls(func, point[0], point[1], None, None, CoordStmt.OP_DRAW, None)
-
- @classmethod
- def mode(cls, func):
- return cls(func, None, None, None, None, None, None)
-
- @classmethod
- def arc(cls, func, point, center):
- return cls(func, point[0], point[1], center[0], center[1], CoordStmt.OP_DRAW, None)
-
- @classmethod
- def flash(cls, point):
- if point:
- return cls(None, point[0], point[1], None, None, CoordStmt.OP_FLASH, None)
- else:
- return cls(None, None, None, None, None, CoordStmt.OP_FLASH, None)
-
- def __init__(self, function, x, y, i, j, op, settings):
- """ Initialize CoordStmt class
-
- Parameters
- ----------
- function : string
- function
-
- x : float
- X coordinate
-
- y : float
- Y coordinate
-
- i : float
- Coordinate offset in the X direction
-
- j : float
- Coordinate offset in the Y direction
-
- op : string
- Operation code
-
- settings : dict {'zero_suppression', 'format'}
- Gerber file coordinate format
-
- Returns
- -------
- Statement : CoordStmt
- Initialized CoordStmt class.
-
- """
- Statement.__init__(self, "COORD")
- self.function = function
- self.x = x
- self.y = y
- self.i = i
- self.j = j
- self.op = op
-
- def to_gerber(self, settings=None):
- ret = ''
- if self.function:
- ret += self.function
- if self.x is not None:
- ret += 'X{0}'.format(write_gerber_value(self.x, settings.format,
- settings.zero_suppression))
- if self.y is not None:
- ret += 'Y{0}'.format(write_gerber_value(self.y, settings.format,
- settings.zero_suppression))
- if self.i is not None:
- ret += 'I{0}'.format(write_gerber_value(self.i, settings.format,
- settings.zero_suppression))
- if self.j is not None:
- ret += 'J{0}'.format(write_gerber_value(self.j, settings.format,
- settings.zero_suppression))
- if self.op:
- ret += self.op
- return ret + '*'
-
- def to_inch(self):
- if self.units == 'metric':
- self.units = 'inch'
- if self.x is not None:
- self.x = inch(self.x)
- if self.y is not None:
- self.y = inch(self.y)
- if self.i is not None:
- self.i = inch(self.i)
- if self.j is not None:
- self.j = inch(self.j)
- if self.function == "G71":
- self.function = "G70"
-
- def to_metric(self):
- if self.units == 'inch':
- self.units = 'metric'
- if self.x is not None:
- self.x = metric(self.x)
- if self.y is not None:
- self.y = metric(self.y)
- if self.i is not None:
- self.i = metric(self.i)
- if self.j is not None:
- self.j = metric(self.j)
- if self.function == "G70":
- self.function = "G71"
-
- def offset(self, x_offset=0, y_offset=0):
- if self.x is not None:
- self.x += x_offset
- if self.y is not None:
- self.y += y_offset
- if self.i is not None:
- self.i += x_offset
- if self.j is not None:
- self.j += y_offset
-
- def __str__(self):
- coord_str = ''
- if self.function:
- coord_str += 'Fn: %s ' % self.function
- if self.x is not None:
- coord_str += 'X: %g ' % self.x
- if self.y is not None:
- coord_str += 'Y: %g ' % self.y
- if self.i is not None:
- coord_str += 'I: %g ' % self.i
- if self.j is not None:
- coord_str += 'J: %g ' % self.j
- if self.op:
- if self.op == 'D01':
- op = 'Lights On'
- elif self.op == 'D02':
- op = 'Lights Off'
- elif self.op == 'D03':
- op = 'Flash'
- else:
- op = self.op
- coord_str += 'Op: %s' % op
-
- return '<Coordinate Statement: %s>' % coord_str
-
- @property
- def only_function(self):
- """
- Returns if the statement only set the function.
- """
-
- # TODO I would like to refactor this so that the function is handled separately and then
- # TODO this isn't required
- return self.function != None and self.op == None and self.x == None and self.y == None and self.i == None and self.j == None
-
-
-class ApertureStmt(Statement):
- """ Aperture Statement
- """
-
- def __init__(self, d, deprecated=None):
- Statement.__init__(self, "APERTURE")
- self.d = int(d)
- self.deprecated = True if deprecated is not None and deprecated is not False else False
-
- def to_gerber(self, settings=None):
- if self.deprecated:
- return 'G54D{0}*'.format(self.d)
- else:
- return 'D{0}*'.format(self.d)
-
- def __str__(self):
- return '<Aperture: %d>' % self.d
-
-
-class CommentStmt(Statement):
- """ Comment Statment
- """
-
- def __init__(self, comment):
- Statement.__init__(self, "COMMENT")
- self.comment = comment if comment is not None else ""
-
- def to_gerber(self, settings=None):
- return 'G04{0}*'.format(self.comment)
-
- def __str__(self):
- return '<Comment: %s>' % self.comment
-
-
-class EofStmt(Statement):
- """ EOF Statement
- """
-
- def __init__(self):
- Statement.__init__(self, "EOF")
-
- def to_gerber(self, settings=None):
- return 'M02*'
-
- def __str__(self):
- return '<EOF Statement>'
-
-
-class QuadrantModeStmt(Statement):
-
- @classmethod
- def single(cls):
- return cls('single-quadrant')
-
- @classmethod
- def multi(cls):
- return cls('multi-quadrant')
-
- @classmethod
- def from_gerber(cls, line):
- if 'G74' not in line and 'G75' not in line:
- raise ValueError('%s is not a valid quadrant mode statement'
- % line)
- return (cls('single-quadrant') if line[:3] == 'G74'
- else cls('multi-quadrant'))
-
- def __init__(self, mode):
- super(QuadrantModeStmt, self).__init__('QuadrantMode')
- mode = mode.lower()
- if mode not in ['single-quadrant', 'multi-quadrant']:
- raise ValueError('Quadrant mode must be "single-quadrant" \
- or "multi-quadrant"')
- self.mode = mode
-
- def to_gerber(self, settings=None):
- return 'G74*' if self.mode == 'single-quadrant' else 'G75*'
-
-
-class RegionModeStmt(Statement):
-
- @classmethod
- def from_gerber(cls, line):
- if 'G36' not in line and 'G37' not in line:
- raise ValueError('%s is not a valid region mode statement' % line)
- return (cls('on') if line[:3] == 'G36' else cls('off'))
-
- @classmethod
- def on(cls):
- return cls('on')
-
- @classmethod
- def off(cls):
- return cls('off')
-
- def __init__(self, mode):
- super(RegionModeStmt, self).__init__('RegionMode')
- mode = mode.lower()
- if mode not in ['on', 'off']:
- raise ValueError('Valid modes are "on" or "off"')
- self.mode = mode
-
- def to_gerber(self, settings=None):
- return 'G36*' if self.mode == 'on' else 'G37*'
-
-
-class UnknownStmt(Statement):
- """ Unknown Statement
- """
-
- def __init__(self, line):
- Statement.__init__(self, "UNKNOWN")
- self.line = line
-
- def to_gerber(self, settings=None):
- return self.line
-
- def __str__(self):
- return '<Unknown Statement: \'%s\'>' % self.line
diff --git a/gerber/ipc356.py b/gerber/ipc356.py
deleted file mode 100644
index 9337a99..0000000
--- a/gerber/ipc356.py
+++ /dev/null
@@ -1,485 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-# Modified from parser.py by Paulo Henrique Silva <ph.silva@gmail.com>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import math
-import re
-from .cam import CamFile, FileSettings
-from .primitives import TestRecord
-
-# Net Name Variables
-_NNAME = re.compile(r'^NNAME\d+$')
-
-# Board Edge Coordinates
-_COORD = re.compile(r'X?(?P<x>[\d\s]*)?Y?(?P<y>[\d\s]*)?')
-
-_SM_FIELD = {
- '0': 'none',
- '1': 'primary side',
- '2': 'secondary side',
- '3': 'both'}
-
-
-def read(filename):
- """ Read data from filename and return an IPCNetlist
- Parameters
- ----------
- filename : string
- Filename of file to parse
-
- Returns
- -------
- file : :class:`gerber.ipc356.IPCNetlist`
- An IPCNetlist object created from the specified file.
-
- """
- # File object should use settings from source file by default.
- return IPCNetlist.from_file(filename)
-
-
-def loads(data, filename=None):
- """ Generate an IPCNetlist object from IPC-D-356 data in memory
-
- Parameters
- ----------
- data : string
- string containing netlist file contents
-
- filename : string, optional
- string containing the filename of the data source
-
- Returns
- -------
- file : :class:`gerber.ipc356.IPCNetlist`
- An IPCNetlist created from the specified file.
- """
- return IPCNetlistParser().parse_raw(data, filename)
-
-
-class IPCNetlist(CamFile):
-
- @classmethod
- def from_file(cls, filename):
- parser = IPCNetlistParser()
- return parser.parse(filename)
-
- def __init__(self, statements, settings, primitives=None, filename=None):
- self.statements = statements
- self.units = settings.units
- self.angle_units = settings.angle_units
- self.primitives = [TestRecord((rec.x_coord, rec.y_coord), rec.net_name,
- rec.access) for rec in self.test_records]
- self.filename = filename
-
- @property
- def settings(self):
- return FileSettings(units=self.units, angle_units=self.angle_units)
-
- @property
- def comments(self):
- return [record for record in self.statements
- if isinstance(record, IPC356_Comment)]
-
- @property
- def parameters(self):
- return [record for record in self.statements
- if isinstance(record, IPC356_Parameter)]
-
- @property
- def test_records(self):
- return [record for record in self.statements
- if isinstance(record, IPC356_TestRecord)]
-
- @property
- def nets(self):
- nets = []
- for net in list(set([rec.net_name for rec in self.test_records
- if rec.net_name is not None])):
- adjacent_nets = set()
- for record in self.adjacency_records:
- if record.net == net:
- adjacent_nets = adjacent_nets.update(record.adjacent_nets)
- elif net in record.adjacent_nets:
- adjacent_nets.add(record.net)
- nets.append(IPC356_Net(net, adjacent_nets))
- return nets
-
- @property
- def components(self):
- return list(set([rec.id for rec in self.test_records
- if rec.id is not None and rec.id != 'VIA']))
-
- @property
- def vias(self):
- return [rec.id for rec in self.test_records if rec.id == 'VIA']
-
- @property
- def outlines(self):
- return [stmt for stmt in self.statements
- if isinstance(stmt, IPC356_Outline)]
-
- @property
- def adjacency_records(self):
- return [record for record in self.statements
- if isinstance(record, IPC356_Adjacency)]
-
- def render(self, ctx, layer='both', filename=None):
- for p in self.primitives:
- if layer == 'both' and p.layer in ('top', 'bottom', 'both'):
- ctx.render(p)
- elif layer == 'top' and p.layer in ('top', 'both'):
- ctx.render(p)
- elif layer == 'bottom' and p.layer in ('bottom', 'both'):
- ctx.render(p)
- if filename is not None:
- ctx.dump(filename)
-
-
-class IPCNetlistParser(object):
- # TODO: Allow multi-line statements (e.g. Altium board edge)
-
- def __init__(self):
- self.units = 'inch'
- self.angle_units = 'degrees'
- self.statements = []
- self.nnames = {}
-
- @property
- def settings(self):
- return FileSettings(units=self.units, angle_units=self.angle_units)
-
- def parse(self, filename):
- with open(filename, 'rU') as f:
- data = f.read()
- return self.parse_raw(data, filename)
-
- def parse_raw(self, data, filename=None):
- oldline = ''
- for line in data.splitlines():
- # Check for existing multiline data...
- if oldline != '':
- if len(line) and line[0] == '0':
- oldline = oldline.rstrip('\r\n') + line[3:].rstrip()
- else:
- self._parse_line(oldline)
- oldline = line
- else:
- oldline = line
- self._parse_line(oldline)
-
- return IPCNetlist(self.statements, self.settings, filename=filename)
-
- def _parse_line(self, line):
- if not len(line):
- return
- if line[0] == 'C':
- # Comment
- self.statements.append(IPC356_Comment.from_line(line))
-
- elif line[0] == 'P':
- # Parameter
- p = IPC356_Parameter.from_line(line)
- if p.parameter == 'UNITS':
- if p.value in ('CUST', 'CUST 0'):
- self.units = 'inch'
- self.angle_units = 'degrees'
- elif p.value == 'CUST 1':
- self.units = 'metric'
- self.angle_units = 'degrees'
- elif p.value == 'CUST 2':
- self.units = 'inch'
- self.angle_units = 'radians'
- self.statements.append(p)
- if _NNAME.match(p.parameter):
- # Add to list of net name variables
- self.nnames[p.parameter] = p.value
-
- elif line[0] == '9':
- self.statements.append(IPC356_EndOfFile())
-
- elif line[0:3] in ('317', '327', '367'):
- # Test Record
- record = IPC356_TestRecord.from_line(line, self.settings)
-
- # Substitute net name variables
- net = record.net_name
- if (_NNAME.match(net) and net in self.nnames.keys()):
- record.net_name = self.nnames[record.net_name]
- self.statements.append(record)
-
- elif line[0:3] == '378':
- # Conductor
- self.statements.append(
- IPC356_Conductor.from_line(
- line, self.settings))
-
- elif line[0:3] == '379':
- # Net Adjacency
- self.statements.append(IPC356_Adjacency.from_line(line))
-
- elif line[0:3] == '389':
- # Outline
- self.statements.append(
- IPC356_Outline.from_line(
- line, self.settings))
-
-
-class IPC356_Comment(object):
-
- @classmethod
- def from_line(cls, line):
- if line[0] != 'C':
- raise ValueError('Not a valid comment statment')
- comment = line[2:].strip()
- return cls(comment)
-
- def __init__(self, comment):
- self.comment = comment
-
- def __repr__(self):
- return '<IPC-D-356 Comment: %s>' % self.comment
-
-
-class IPC356_Parameter(object):
-
- @classmethod
- def from_line(cls, line):
- if line[0] != 'P':
- raise ValueError('Not a valid parameter statment')
- splitline = line[2:].split()
- parameter = splitline[0].strip()
- value = ' '.join(splitline[1:]).strip()
- return cls(parameter, value)
-
- def __init__(self, parameter, value):
- self.parameter = parameter
- self.value = value
-
- def __repr__(self):
- return '<IPC-D-356 Parameter: %s=%s>' % (self.parameter, self.value)
-
-
-class IPC356_TestRecord(object):
-
- @classmethod
- def from_line(cls, line, settings):
- offset = 0
- units = settings.units
- angle = settings.angle_units
- feature_types = {'1': 'through-hole', '2': 'smt',
- '3': 'tooling-feature', '4': 'tooling-hole',
- '6': 'non-plated-tooling-hole'}
- access = ['both', 'top', 'layer2', 'layer3', 'layer4', 'layer5',
- 'layer6', 'layer7', 'bottom']
- record = {}
- line = line.strip()
- if line[0] != '3':
- raise ValueError('Not a valid test record statment')
- record['feature_type'] = feature_types[line[1]]
-
- end = len(line) - 1 if len(line) < 18 else 17
- record['net_name'] = line[3:end].strip()
-
- if len(line) >= 27 and line[26] != '-':
- offset = line[26:].find('-')
- offset = 0 if offset == -1 else offset
- end = len(line) - 1 if len(line) < (27 + offset) else (26 + offset)
- record['id'] = line[20:end].strip()
-
- end = len(line) - 1 if len(line) < (32 + offset) else (31 + offset)
- record['pin'] = (line[27 + offset:end].strip() if line[27 + offset:end].strip() != ''
- else None)
-
- record['location'] = 'middle' if line[31 + offset] == 'M' else 'end'
- if line[32 + offset] == 'D':
- end = len(line) - 1 if len(line) < (38 + offset) else (37 + offset)
- dia = int(line[33 + offset:end].strip())
- record['hole_diameter'] = (dia * 0.0001 if units == 'inch'
- else dia * 0.001)
- if len(line) >= (38 + offset):
- record['plated'] = (line[37 + offset] == 'P')
-
- if len(line) >= (40 + offset):
- end = len(line) - 1 if len(line) < (42 + offset) else (41 + offset)
- record['access'] = access[int(line[39 + offset:end])]
-
- if len(line) >= (43 + offset):
- end = len(line) - 1 if len(line) < (50 + offset) else (49 + offset)
- coord = int(line[42 + offset:end].strip())
- record['x_coord'] = (coord * 0.0001 if units == 'inch'
- else coord * 0.001)
-
- if len(line) >= (51 + offset):
- end = len(line) - 1 if len(line) < (58 + offset) else (57 + offset)
- coord = int(line[50 + offset:end].strip())
- record['y_coord'] = (coord * 0.0001 if units == 'inch'
- else coord * 0.001)
-
- if len(line) >= (59 + offset):
- end = len(line) - 1 if len(line) < (63 + offset) else (62 + offset)
- dim = line[58 + offset:end].strip()
- if dim != '':
- record['rect_x'] = (int(dim) * 0.0001 if units == 'inch'
- else int(dim) * 0.001)
-
- if len(line) >= (64 + offset):
- end = len(line) - 1 if len(line) < (68 + offset) else (67 + offset)
- dim = line[63 + offset:end].strip()
- if dim != '':
- record['rect_y'] = (int(dim) * 0.0001 if units == 'inch'
- else int(dim) * 0.001)
-
- if len(line) >= (69 + offset):
- end = len(line) - 1 if len(line) < (72 + offset) else (71 + offset)
- rot = line[68 + offset:end].strip()
- if rot != '':
- record['rect_rotation'] = (int(rot) if angle == 'degrees'
- else math.degrees(rot))
-
- if len(line) >= (74 + offset):
- end = 74 + offset
- sm_info = line[73 + offset:end].strip()
- record['soldermask_info'] = _SM_FIELD.get(sm_info)
-
- if len(line) >= (76 + offset):
- end = len(line) - 1 if len(line) < (80 + offset) else 79 + offset
- record['optional_info'] = line[75 + offset:end]
-
- return cls(**record)
-
- def __init__(self, **kwargs):
- for key in kwargs:
- setattr(self, key, kwargs[key])
-
- def __repr__(self):
- return '<IPC-D-356 %s Test Record: %s>' % (self.net_name,
- self.feature_type)
-
-
-class IPC356_Outline(object):
-
- @classmethod
- def from_line(cls, line, settings):
- type = line[3:17].strip()
- scale = 0.0001 if settings.units == 'inch' else 0.001
- points = []
- x = 0
- y = 0
- coord_strings = line.strip().split()[1:]
- for coord in coord_strings:
- coord_dict = _COORD.match(coord).groupdict()
- x = int(coord_dict['x']) if coord_dict['x'] is not '' else x
- y = int(coord_dict['y']) if coord_dict['y'] is not '' else y
- points.append((x * scale, y * scale))
- return cls(type, points)
-
- def __init__(self, type, points):
- self.type = type
- self.points = points
-
- def __repr__(self):
- return '<IPC-D-356 %s Outline Definition>' % self.type
-
-
-class IPC356_Conductor(object):
-
- @classmethod
- def from_line(cls, line, settings):
- if line[0:3] != '378':
- raise ValueError('Not a valid IPC-D-356 Conductor statement')
-
- scale = 0.0001 if settings.units == 'inch' else 0.001
- net_name = line[3:17].strip()
- layer = int(line[19:21])
-
- # Parse out aperture definiting
- raw_aperture = line[22:].split()[0]
- aperture_dict = _COORD.match(raw_aperture).groupdict()
- x = 0
- y = 0
- x = int(aperture_dict['x']) * \
- scale if aperture_dict['x'] is not '' else None
- y = int(aperture_dict['y']) * \
- scale if aperture_dict['y'] is not '' else None
- aperture = (x, y)
-
- # Parse out conductor shapes
- shapes = []
- coord_list = ' '.join(line[22:].split()[1:])
- raw_shapes = coord_list.split('*')
- for rshape in raw_shapes:
- x = 0
- y = 0
- shape = []
- coords = rshape.split()
- for coord in coords:
- coord_dict = _COORD.match(coord).groupdict()
- x = int(coord_dict['x']) if coord_dict['x'] is not '' else x
- y = int(coord_dict['y']) if coord_dict['y'] is not '' else y
- shape.append((x * scale, y * scale))
- shapes.append(tuple(shape))
- return cls(net_name, layer, aperture, tuple(shapes))
-
- def __init__(self, net_name, layer, aperture, shapes):
- self.net_name = net_name
- self.layer = layer
- self.aperture = aperture
- self.shapes = shapes
-
- def __repr__(self):
- return '<IPC-D-356 %s Conductor Record>' % self.net_name
-
-
-class IPC356_Adjacency(object):
-
- @classmethod
- def from_line(cls, line):
- if line[0:3] != '379':
- raise ValueError('Not a valid IPC-D-356 Conductor statement')
- nets = line[3:].strip().split()
-
- return cls(nets[0], nets[1:])
-
- def __init__(self, net, adjacent_nets):
- self.net = net
- self.adjacent_nets = adjacent_nets
-
- def __repr__(self):
- return '<IPC-D-356 %s Adjacency Record>' % self.net
-
-
-class IPC356_EndOfFile(object):
-
- def __init__(self):
- pass
-
- def to_netlist(self):
- return '999'
-
- def __repr__(self):
- return '<IPC-D-356 EOF>'
-
-
-class IPC356_Net(object):
-
- def __init__(self, name, adjacent_nets):
- self.name = name
- self.adjacent_nets = set(
- adjacent_nets) if adjacent_nets is not None else set()
-
- def __repr__(self):
- return '<IPC-D-356 Net %s>' % self.name
diff --git a/gerber/layers.py b/gerber/layers.py
deleted file mode 100644
index 69e1c0d..0000000
--- a/gerber/layers.py
+++ /dev/null
@@ -1,295 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import re
-from collections import namedtuple
-
-from . import common
-from .excellon import ExcellonFile
-from .ipc356 import IPCNetlist
-
-
-Hint = namedtuple('Hint', 'layer ext name regex content')
-
-hints = [
- Hint(layer='top',
- ext=['gtl', 'cmp', 'top', ],
- name=['art01', 'top', 'GTL', 'layer1', 'soldcom', 'comp', 'F.Cu', ],
- regex='',
- content=[]
- ),
- Hint(layer='bottom',
- ext=['gbl', 'sld', 'bot', 'sol', 'bottom', ],
- name=['art02', 'bottom', 'bot', 'GBL', 'layer2', 'soldsold', 'B.Cu', ],
- regex='',
- content=[]
- ),
- Hint(layer='internal',
- ext=['in', 'gt1', 'gt2', 'gt3', 'gt4', 'gt5', 'gt6',
- 'g1', 'g2', 'g3', 'g4', 'g5', 'g6', ],
- name=['art', 'internal', 'pgp', 'pwr', 'gnd', 'ground',
- 'gp1', 'gp2', 'gp3', 'gp4', 'gt5', 'gp6',
- 'In1.Cu', 'In2.Cu', 'In3.Cu', 'In4.Cu',
- 'group3', 'group4', 'group5', 'group6', 'group7', 'group8', ],
- regex='',
- content=[]
- ),
- Hint(layer='topsilk',
- ext=['gto', 'sst', 'plc', 'ts', 'skt', 'topsilk', ],
- name=['sst01', 'topsilk', 'silk', 'slk', 'sst', 'F.SilkS'],
- regex='',
- content=[]
- ),
- Hint(layer='bottomsilk',
- ext=['gbo', 'ssb', 'pls', 'bs', 'skb', 'bottomsilk', ],
- name=['bsilk', 'ssb', 'botsilk', 'bottomsilk', 'B.SilkS'],
- regex='',
- content=[]
- ),
- Hint(layer='topmask',
- ext=['gts', 'stc', 'tmk', 'smt', 'tr', 'topmask', ],
- name=['sm01', 'cmask', 'tmask', 'mask1', 'maskcom', 'topmask',
- 'mst', 'F.Mask', ],
- regex='',
- content=[]
- ),
- Hint(layer='bottommask',
- ext=['gbs', 'sts', 'bmk', 'smb', 'br', 'bottommask', ],
- name=['sm', 'bmask', 'mask2', 'masksold', 'botmask', 'bottommask',
- 'msb', 'B.Mask', ],
- regex='',
- content=[]
- ),
- Hint(layer='toppaste',
- ext=['gtp', 'tm', 'toppaste', ],
- name=['sp01', 'toppaste', 'pst', 'F.Paste'],
- regex='',
- content=[]
- ),
- Hint(layer='bottompaste',
- ext=['gbp', 'bm', 'bottompaste', ],
- name=['sp02', 'botpaste', 'bottompaste', 'psb', 'B.Paste', ],
- regex='',
- content=[]
- ),
- Hint(layer='outline',
- ext=['gko', 'outline', ],
- name=['BDR', 'border', 'out', 'outline', 'Edge.Cuts', ],
- regex='',
- content=[]
- ),
- Hint(layer='ipc_netlist',
- ext=['ipc'],
- name=[],
- regex='',
- content=[]
- ),
- Hint(layer='drawing',
- ext=['fab'],
- name=['assembly drawing', 'assembly', 'fabrication',
- 'fab drawing', 'fab'],
- regex='',
- content=[]
- ),
-]
-
-
-def layer_signatures(layer_class):
- for hint in hints:
- if hint.layer == layer_class:
- return hint.ext + hint.name
- return []
-
-
-def load_layer(filename):
- return PCBLayer.from_cam(common.read(filename))
-
-
-def load_layer_data(data, filename=None):
- return PCBLayer.from_cam(common.loads(data, filename))
-
-
-def guess_layer_class(filename):
- try:
- layer = guess_layer_class_by_content(filename)
- if layer:
- return layer
- except:
- pass
-
- try:
- directory, filename = os.path.split(filename)
- name, ext = os.path.splitext(filename.lower())
- for hint in hints:
- if hint.regex:
- if re.findall(hint.regex, filename, re.IGNORECASE):
- return hint.layer
-
- patterns = [r'^(\w*[.-])*{}([.-]\w*)?$'.format(x) for x in hint.name]
- if ext[1:] in hint.ext or any(re.findall(p, name, re.IGNORECASE) for p in patterns):
- return hint.layer
- except:
- pass
- return 'unknown'
-
-
-def guess_layer_class_by_content(filename):
- try:
- file = open(filename, 'r')
- for line in file:
- for hint in hints:
- if len(hint.content) > 0:
- patterns = [r'^(.*){}(.*)$'.format(x) for x in hint.content]
- if any(re.findall(p, line, re.IGNORECASE) for p in patterns):
- return hint.layer
- except:
- pass
-
- return False
-
-
-def sort_layers(layers, from_top=True):
- layer_order = ['outline', 'toppaste', 'topsilk', 'topmask', 'top',
- 'internal', 'bottom', 'bottommask', 'bottomsilk',
- 'bottompaste']
- append_after = ['drill', 'drawing']
-
- output = []
- drill_layers = [layer for layer in layers if layer.layer_class == 'drill']
- internal_layers = list(sorted([layer for layer in layers
- if layer.layer_class == 'internal']))
-
- for layer_class in layer_order:
- if layer_class == 'internal':
- output += internal_layers
- elif layer_class == 'drill':
- output += drill_layers
- else:
- for layer in layers:
- if layer.layer_class == layer_class:
- output.append(layer)
- if not from_top:
- output = list(reversed(output))
-
- for layer_class in append_after:
- for layer in layers:
- if layer.layer_class == layer_class:
- output.append(layer)
- return output
-
-
-class PCBLayer(object):
- """ Base class for PCB Layers
-
- Parameters
- ----------
- source : CAMFile
- CAMFile representing the layer
-
-
- Attributes
- ----------
- filename : string
- Source Filename
-
- """
- @classmethod
- def from_cam(cls, camfile):
- filename = camfile.filename
- layer_class = guess_layer_class(filename)
- if isinstance(camfile, ExcellonFile) or (layer_class == 'drill'):
- return DrillLayer.from_cam(camfile)
- elif layer_class == 'internal':
- return InternalLayer.from_cam(camfile)
- if isinstance(camfile, IPCNetlist):
- layer_class = 'ipc_netlist'
- return cls(filename, layer_class, camfile)
-
- def __init__(self, filename=None, layer_class=None, cam_source=None, **kwargs):
- super(PCBLayer, self).__init__(**kwargs)
- self.filename = filename
- self.layer_class = layer_class
- self.cam_source = cam_source
- self.surface = None
- self.primitives = cam_source.primitives if cam_source is not None else []
-
- @property
- def bounds(self):
- if self.cam_source is not None:
- return self.cam_source.bounds
- else:
- return None
-
- def __repr__(self):
- return '<PCBLayer: {}>'.format(self.layer_class)
-
-
-class DrillLayer(PCBLayer):
- @classmethod
- def from_cam(cls, camfile):
- return cls(camfile.filename, camfile)
-
- def __init__(self, filename=None, cam_source=None, layers=None, **kwargs):
- super(DrillLayer, self).__init__(filename, 'drill', cam_source, **kwargs)
- self.layers = layers if layers is not None else ['top', 'bottom']
-
-
-class InternalLayer(PCBLayer):
-
- @classmethod
- def from_cam(cls, camfile):
- filename = camfile.filename
- try:
- order = int(re.search(r'\d+', filename).group())
- except AttributeError:
- order = 0
- return cls(filename, camfile, order)
-
- def __init__(self, filename=None, cam_source=None, order=0, **kwargs):
- super(InternalLayer, self).__init__(filename, 'internal', cam_source, **kwargs)
- self.order = order
-
- def __eq__(self, other):
- if not hasattr(other, 'order'):
- raise TypeError()
- return (self.order == other.order)
-
- def __ne__(self, other):
- if not hasattr(other, 'order'):
- raise TypeError()
- return (self.order != other.order)
-
- def __gt__(self, other):
- if not hasattr(other, 'order'):
- raise TypeError()
- return (self.order > other.order)
-
- def __lt__(self, other):
- if not hasattr(other, 'order'):
- raise TypeError()
- return (self.order < other.order)
-
- def __ge__(self, other):
- if not hasattr(other, 'order'):
- raise TypeError()
- return (self.order >= other.order)
-
- def __le__(self, other):
- if not hasattr(other, 'order'):
- raise TypeError()
- return (self.order <= other.order)
diff --git a/gerber/ncparam/allegro.py b/gerber/ncparam/allegro.py
deleted file mode 100644
index a67bcf1..0000000
--- a/gerber/ncparam/allegro.py
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2015 Garret Fick <garret@ficksworkshop.com>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Allegro File module
-====================
-**Excellon file classes**
-
-Extra parsers for allegro misc files that can be useful when the Excellon file doesn't contain parameter information
-"""
-
diff --git a/gerber/operations.py b/gerber/operations.py
deleted file mode 100644
index d06876e..0000000
--- a/gerber/operations.py
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2015 Hamilton Kibbe <ham@hamiltonkib.be>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-CAM File Operations
-===================
-**Transformations and other operations performed on Gerber and Excellon files**
-
-"""
-import copy
-
-
-def to_inch(cam_file):
- """ Convert Gerber or Excellon file units to imperial
-
- Parameters
- ----------
- cam_file : :class:`gerber.cam.CamFile` subclass
- Gerber or Excellon file to convert
-
- Returns
- -------
- cam_file : :class:`gerber.cam.CamFile` subclass
- A deep copy of the source file with units converted to imperial.
- """
- cam_file = copy.deepcopy(cam_file)
- cam_file.to_inch()
- return cam_file
-
-
-def to_metric(cam_file):
- """ Convert Gerber or Excellon file units to metric
-
- Parameters
- ----------
- cam_file : :class:`gerber.cam.CamFile` subclass
- Gerber or Excellon file to convert
-
- Returns
- -------
- cam_file : :class:`gerber.cam.CamFile` subclass
- A deep copy of the source file with units converted to metric.
- """
- cam_file = copy.deepcopy(cam_file)
- cam_file.to_metric()
- return cam_file
-
-
-def offset(cam_file, x_offset, y_offset):
- """ Offset a Cam file by a specified amount in the X and Y directions.
-
- Parameters
- ----------
- cam_file : :class:`gerber.cam.CamFile` subclass
- Gerber or Excellon file to offset
-
- x_offset : float
- Amount to offset the file in the X direction
-
- y_offset : float
- Amount to offset the file in the Y direction
-
- Returns
- -------
- cam_file : :class:`gerber.cam.CamFile` subclass
- An offset deep copy of the source file.
- """
- cam_file = copy.deepcopy(cam_file)
- cam_file.offset(x_offset, y_offset)
- return cam_file
-
-
-def scale(cam_file, x_scale, y_scale):
- """ Scale a Cam file by a specified amount in the X and Y directions.
-
- Parameters
- ----------
- cam_file : :class:`gerber.cam.CamFile` subclass
- Gerber or Excellon file to scale
-
- x_scale : float
- X-axis scale factor
-
- y_scale : float
- Y-axis scale factor
-
- Returns
- -------
- cam_file : :class:`gerber.cam.CamFile` subclass
- An scaled deep copy of the source file.
- """
- # TODO
- pass
-
-
-def rotate(cam_file, angle):
- """ Rotate a Cam file a specified amount about the origin.
-
- Parameters
- ----------
- cam_file : :class:`gerber.cam.CamFile` subclass
- Gerber or Excellon file to rotate
-
- angle : float
- Angle to rotate the file in degrees.
-
- Returns
- -------
- cam_file : :class:`gerber.cam.CamFile` subclass
- An rotated deep copy of the source file.
- """
- # TODO
- pass
diff --git a/gerber/pcb.py b/gerber/pcb.py
deleted file mode 100644
index 1d22e74..0000000
--- a/gerber/pcb.py
+++ /dev/null
@@ -1,124 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2015 Hamilton Kibbe <ham@hamiltonkib.be>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import os
-from .exceptions import ParseError
-from .layers import PCBLayer, sort_layers, layer_signatures
-from .common import read as gerber_read
-from .utils import listdir
-
-
-class PCB(object):
-
- @classmethod
- def from_directory(cls, directory, board_name=None, verbose=False):
- layers = []
- names = set()
-
- # Validate
- directory = os.path.abspath(directory)
- if not os.path.isdir(directory):
- raise TypeError('{} is not a directory.'.format(directory))
-
- # Load gerber files
- for filename in listdir(directory, True, True):
- try:
- camfile = gerber_read(os.path.join(directory, filename))
- layer = PCBLayer.from_cam(camfile)
- layers.append(layer)
- name = os.path.splitext(filename)[0]
- if len(os.path.splitext(filename)) > 1:
- _name, ext = os.path.splitext(name)
- if ext[1:] in layer_signatures(layer.layer_class):
- name = _name
- if layer.layer_class == 'drill' and 'drill' in ext:
- name = _name
- names.add(name)
- if verbose:
- print('[PCB]: Added {} layer <{}>'.format(layer.layer_class,
- filename))
- except ParseError:
- if verbose:
- print('[PCB]: Skipping file {}'.format(filename))
- except IOError:
- if verbose:
- print('[PCB]: Skipping file {}'.format(filename))
-
- # Try to guess board name
- if board_name is None:
- if len(names) == 1:
- board_name = names.pop()
- else:
- board_name = os.path.basename(directory)
- # Return PCB
- return cls(layers, board_name)
-
- def __init__(self, layers, name=None):
- self.layers = sort_layers(layers)
- self.name = name
-
- def __len__(self):
- return len(self.layers)
-
- @property
- def top_layers(self):
- board_layers = [l for l in reversed(self.layers) if l.layer_class in
- ('topsilk', 'topmask', 'top')]
- drill_layers = [l for l in self.drill_layers if 'top' in l.layers]
- # Drill layer goes under soldermask for proper rendering of tented vias
- return [board_layers[0]] + drill_layers + board_layers[1:]
-
- @property
- def bottom_layers(self):
- board_layers = [l for l in self.layers if l.layer_class in
- ('bottomsilk', 'bottommask', 'bottom')]
- drill_layers = [l for l in self.drill_layers if 'bottom' in l.layers]
- # Drill layer goes under soldermask for proper rendering of tented vias
- return [board_layers[0]] + drill_layers + board_layers[1:]
-
- @property
- def drill_layers(self):
- return [l for l in self.layers if l.layer_class == 'drill']
-
- @property
- def copper_layers(self):
- return list(reversed([layer for layer in self.layers if
- layer.layer_class in
- ('top', 'bottom', 'internal')]))
-
- @property
- def outline_layer(self):
- for layer in self.layers:
- if layer.layer_class == 'outline':
- return layer
-
- @property
- def layer_count(self):
- """ Number of *COPPER* layers
- """
- return len([l for l in self.layers if l.layer_class in
- ('top', 'bottom', 'internal')])
-
- @property
- def board_bounds(self):
- for layer in self.layers:
- if layer.layer_class == 'outline':
- return layer.bounds
- for layer in self.layers:
- if layer.layer_class == 'top':
- return layer.bounds
diff --git a/gerber/primitives.py b/gerber/primitives.py
deleted file mode 100644
index 757f117..0000000
--- a/gerber/primitives.py
+++ /dev/null
@@ -1,1697 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2016 Hamilton Kibbe <ham@hamiltonkib.be>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import math
-from operator import add
-from itertools import combinations
-from .utils import validate_coordinates, inch, metric, convex_hull
-from .utils import rotate_point, nearly_equal
-
-
-
-
-class Primitive(object):
- """ Base class for all Cam file primitives
-
- Parameters
- ---------
- level_polarity : string
- Polarity of the parameter. May be 'dark' or 'clear'. Dark indicates
- a "positive" primitive, i.e. indicating where coppper should remain,
- and clear indicates a negative primitive, such as where copper should
- be removed. clear primitives are often used to create cutouts in region
- pours.
-
- rotation : float
- Rotation of a primitive about its origin in degrees. Positive rotation
- is counter-clockwise as viewed from the board top.
-
- units : string
- Units in which primitive was defined. 'inch' or 'metric'
-
- net_name : string
- Name of the electrical net the primitive belongs to
- """
-
- def __init__(self, level_polarity='dark', rotation=0, units=None, net_name=None):
- self.level_polarity = level_polarity
- self.net_name = net_name
- self._to_convert = list()
- self._memoized = list()
- self._units = units
- self._rotation = rotation
- self._cos_theta = math.cos(math.radians(rotation))
- self._sin_theta = math.sin(math.radians(rotation))
- self._bounding_box = None
- self._vertices = None
- self._segments = None
-
- @property
- def flashed(self):
- '''Is this a flashed primitive'''
- raise NotImplementedError('Is flashed must be '
- 'implemented in subclass')
-
- def __eq__(self, other):
- return self.__dict__ == other.__dict__
-
- @property
- def units(self):
- return self._units
-
- @units.setter
- def units(self, value):
- self._changed()
- self._units = value
-
- @property
- def rotation(self):
- return self._rotation
-
- @rotation.setter
- def rotation(self, value):
- self._changed()
- self._rotation = value
- self._cos_theta = math.cos(math.radians(value))
- self._sin_theta = math.sin(math.radians(value))
-
- @property
- def vertices(self):
- return None
-
- @property
- def segments(self):
- if self._segments is None:
- if self.vertices is not None and len(self.vertices):
- self._segments = [segment for segment in
- combinations(self.vertices, 2)]
- return self._segments
-
- @property
- def bounding_box(self):
- """ Calculate axis-aligned bounding box
-
- will be helpful for sweep & prune during DRC clearance checks.
-
- Return ((min x, max x), (min y, max y))
- """
- raise NotImplementedError('Bounding box calculation must be '
- 'implemented in subclass')
-
- @property
- def bounding_box_no_aperture(self):
- """ Calculate bouxing box without considering the aperture
-
- for most objects, this is the same as the bounding_box, but is different for
- Lines and Arcs (which are not flashed)
-
- Return ((min x, max x), (min y, max y))
- """
- return self.bounding_box
-
- def to_inch(self):
- """ Convert primitive units to inches.
- """
- if self.units == 'metric':
- self.units = 'inch'
- for attr, value in [(attr, getattr(self, attr))
- for attr in self._to_convert]:
- if hasattr(value, 'to_inch'):
- value.to_inch()
- else:
- try:
- if len(value) > 1:
- if hasattr(value[0], 'to_inch'):
- for v in value:
- v.to_inch()
- elif isinstance(value[0], tuple):
- setattr(self, attr,
- [tuple(map(inch, point))
- for point in value])
- else:
- setattr(self, attr, tuple(map(inch, value)))
- except:
- if value is not None:
- setattr(self, attr, inch(value))
-
- def to_metric(self):
- """ Convert primitive units to metric.
- """
- if self.units == 'inch':
- self.units = 'metric'
- for attr, value in [(attr, getattr(self, attr))
- for attr in self._to_convert]:
- if hasattr(value, 'to_metric'):
- value.to_metric()
- else:
- try:
- if len(value) > 1:
- if hasattr(value[0], 'to_metric'):
- for v in value:
- v.to_metric()
- elif isinstance(value[0], tuple):
- setattr(self, attr,
- [tuple(map(metric, point))
- for point in value])
- else:
- setattr(self, attr, tuple(map(metric, value)))
- except:
- if value is not None:
- setattr(self, attr, metric(value))
-
- def offset(self, x_offset=0, y_offset=0):
- """ Move the primitive by the specified x and y offset amount.
-
- values are specified in the primitive's native units
- """
- if hasattr(self, 'position'):
- self._changed()
- self.position = tuple([coord + offset for coord, offset
- in zip(self.position,
- (x_offset, y_offset))])
-
- def to_statement(self):
- pass
-
- def _changed(self):
- """ Clear memoized properties.
-
- Forces a recalculation next time any memoized propery is queried.
- This must be called from a subclass every time a parameter that affects
- a memoized property is changed. The easiest way to do this is to call
- _changed() from property.setter methods.
- """
- self._bounding_box = None
- self._vertices = None
- self._segments = None
- for attr in self._memoized:
- setattr(self, attr, None)
-
-class Line(Primitive):
- """
- """
-
- def __init__(self, start, end, aperture, level_polarity=None, **kwargs):
- super(Line, self).__init__(**kwargs)
- self.level_polarity = level_polarity
- self._start = start
- self._end = end
- self.aperture = aperture
- self._to_convert = ['start', 'end', 'aperture']
-
- @property
- def flashed(self):
- return False
-
- @property
- def start(self):
- return self._start
-
- @start.setter
- def start(self, value):
- self._changed()
- self._start = value
-
- @property
- def end(self):
- return self._end
-
- @end.setter
- def end(self, value):
- self._changed()
- self._end = value
-
- @property
- def angle(self):
- delta_x, delta_y = tuple(
- [end - start for end, start in zip(self.end, self.start)])
- angle = math.atan2(delta_y, delta_x)
- return angle
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- if isinstance(self.aperture, Circle):
- width_2 = self.aperture.radius
- height_2 = width_2
- else:
- width_2 = self.aperture.width / 2.
- height_2 = self.aperture.height / 2.
- min_x = min(self.start[0], self.end[0]) - width_2
- max_x = max(self.start[0], self.end[0]) + width_2
- min_y = min(self.start[1], self.end[1]) - height_2
- max_y = max(self.start[1], self.end[1]) + height_2
- self._bounding_box = ((min_x, max_x), (min_y, max_y))
- return self._bounding_box
-
- @property
- def bounding_box_no_aperture(self):
- '''Gets the bounding box without the aperture'''
- min_x = min(self.start[0], self.end[0])
- max_x = max(self.start[0], self.end[0])
- min_y = min(self.start[1], self.end[1])
- max_y = max(self.start[1], self.end[1])
- return ((min_x, max_x), (min_y, max_y))
-
- @property
- def vertices(self):
- if self._vertices is None:
- start = self.start
- end = self.end
- if isinstance(self.aperture, Rectangle):
- width = self.aperture.width
- height = self.aperture.height
-
- # Find all the corners of the start and end position
- start_ll = (start[0] - (width / 2.), start[1] - (height / 2.))
- start_lr = (start[0] + (width / 2.), start[1] - (height / 2.))
- start_ul = (start[0] - (width / 2.), start[1] + (height / 2.))
- start_ur = (start[0] + (width / 2.), start[1] + (height / 2.))
- end_ll = (end[0] - (width / 2.), end[1] - (height / 2.))
- end_lr = (end[0] + (width / 2.), end[1] - (height / 2.))
- end_ul = (end[0] - (width / 2.), end[1] + (height / 2.))
- end_ur = (end[0] + (width / 2.), end[1] + (height / 2.))
-
- # The line is defined by the convex hull of the points
- self._vertices = convex_hull((start_ll, start_lr, start_ul, start_ur, end_ll, end_lr, end_ul, end_ur))
- elif isinstance(self.aperture, Polygon):
- points = [map(add, point, vertex)
- for vertex in self.aperture.vertices
- for point in (start, end)]
- self._vertices = convex_hull(points)
- return self._vertices
-
- def offset(self, x_offset=0, y_offset=0):
- self._changed()
- self.start = tuple([coord + offset for coord, offset
- in zip(self.start, (x_offset, y_offset))])
- self.end = tuple([coord + offset for coord, offset
- in zip(self.end, (x_offset, y_offset))])
-
- def equivalent(self, other, offset):
-
- if not isinstance(other, Line):
- return False
-
- equiv_start = tuple(map(add, other.start, offset))
- equiv_end = tuple(map(add, other.end, offset))
-
-
- return nearly_equal(self.start, equiv_start) and nearly_equal(self.end, equiv_end)
-
- def __str__(self):
- return "<Line {} to {}>".format(self.start, self.end)
-
- def __repr__(self):
- return str(self)
-
-class Arc(Primitive):
- """
- """
-
- def __init__(self, start, end, center, direction, aperture, quadrant_mode,
- level_polarity=None, **kwargs):
- super(Arc, self).__init__(**kwargs)
- self.level_polarity = level_polarity
- self._start = start
- self._end = end
- self._center = center
- self.direction = direction
- self.aperture = aperture
- self._quadrant_mode = quadrant_mode
- self._to_convert = ['start', 'end', 'center', 'aperture']
-
- @property
- def flashed(self):
- return False
-
- @property
- def start(self):
- return self._start
-
- @start.setter
- def start(self, value):
- self._changed()
- self._start = value
-
- @property
- def end(self):
- return self._end
-
- @end.setter
- def end(self, value):
- self._changed()
- self._end = value
-
- @property
- def center(self):
- return self._center
-
- @center.setter
- def center(self, value):
- self._changed()
- self._center = value
-
- @property
- def quadrant_mode(self):
- return self._quadrant_mode
-
- @quadrant_mode.setter
- def quadrant_mode(self, quadrant_mode):
- self._changed()
- self._quadrant_mode = quadrant_mode
-
- @property
- def radius(self):
- dy, dx = tuple([start - center for start, center
- in zip(self.start, self.center)])
- return math.sqrt(dy ** 2 + dx ** 2)
-
- @property
- def start_angle(self):
- dx, dy = tuple([start - center for start, center
- in zip(self.start, self.center)])
- return math.atan2(dy, dx)
-
- @property
- def end_angle(self):
- dx, dy = tuple([end - center for end, center
- in zip(self.end, self.center)])
- return math.atan2(dy, dx)
-
- @property
- def sweep_angle(self):
- two_pi = 2 * math.pi
- theta0 = (self.start_angle + two_pi) % two_pi
- theta1 = (self.end_angle + two_pi) % two_pi
- if self.direction == 'counterclockwise':
- return abs(theta1 - theta0)
- else:
- theta0 += two_pi
- return abs(theta0 - theta1) % two_pi
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- two_pi = 2 * math.pi
- theta0 = (self.start_angle + two_pi) % two_pi
- theta1 = (self.end_angle + two_pi) % two_pi
- points = [self.start, self.end]
- if self.quadrant_mode == 'multi-quadrant':
- if self.direction == 'counterclockwise':
- # Passes through 0 degrees
- if theta0 >= theta1:
- points.append((self.center[0] + self.radius, self.center[1]))
- # Passes through 90 degrees
- if (((theta0 <= math.pi / 2.) and ((theta1 >= math.pi / 2.) or (theta1 <= theta0)))
- or ((theta1 > math.pi / 2.) and (theta1 <= theta0))):
- points.append((self.center[0], self.center[1] + self.radius))
- # Passes through 180 degrees
- if ((theta0 <= math.pi and (theta1 >= math.pi or theta1 <= theta0))
- or ((theta1 > math.pi) and (theta1 <= theta0))):
- points.append((self.center[0] - self.radius, self.center[1]))
- # Passes through 270 degrees
- if (theta0 <= math.pi * 1.5 and (theta1 >= math.pi * 1.5 or theta1 <= theta0)
- or ((theta1 > math.pi * 1.5) and (theta1 <= theta0))):
- points.append((self.center[0], self.center[1] - self.radius))
- else:
- # Passes through 0 degrees
- if theta1 >= theta0:
- points.append((self.center[0] + self.radius, self.center[1]))
- # Passes through 90 degrees
- if (((theta1 <= math.pi / 2.) and (theta0 >= math.pi / 2. or theta0 <= theta1))
- or ((theta0 > math.pi / 2.) and (theta0 <= theta1))):
- points.append((self.center[0], self.center[1] + self.radius))
- # Passes through 180 degrees
- if (((theta1 <= math.pi) and (theta0 >= math.pi or theta0 <= theta1))
- or ((theta0 > math.pi) and (theta0 <= theta1))):
- points.append((self.center[0] - self.radius, self.center[1]))
- # Passes through 270 degrees
- if (((theta1 <= math.pi * 1.5) and (theta0 >= math.pi * 1.5 or theta0 <= theta1))
- or ((theta0 > math.pi * 1.5) and (theta0 <= theta1))):
- points.append((self.center[0], self.center[1] - self.radius))
- x, y = zip(*points)
- if hasattr(self.aperture, 'radius'):
- min_x = min(x) - self.aperture.radius
- max_x = max(x) + self.aperture.radius
- min_y = min(y) - self.aperture.radius
- max_y = max(y) + self.aperture.radius
- else:
- min_x = min(x) - self.aperture.width
- max_x = max(x) + self.aperture.width
- min_y = min(y) - self.aperture.height
- max_y = max(y) + self.aperture.height
-
- self._bounding_box = ((min_x, max_x), (min_y, max_y))
- return self._bounding_box
-
- @property
- def bounding_box_no_aperture(self):
- '''Gets the bounding box without considering the aperture'''
- two_pi = 2 * math.pi
- theta0 = (self.start_angle + two_pi) % two_pi
- theta1 = (self.end_angle + two_pi) % two_pi
- points = [self.start, self.end]
- if self.quadrant_mode == 'multi-quadrant':
- if self.direction == 'counterclockwise':
- # Passes through 0 degrees
- if theta0 >= theta1:
- points.append((self.center[0] + self.radius, self.center[1]))
- # Passes through 90 degrees
- if (((theta0 <= math.pi / 2.) and (
- (theta1 >= math.pi / 2.) or (theta1 <= theta0)))
- or ((theta1 > math.pi / 2.) and (theta1 <= theta0))):
- points.append((self.center[0], self.center[1] + self.radius))
- # Passes through 180 degrees
- if ((theta0 <= math.pi and (theta1 >= math.pi or theta1 <= theta0))
- or ((theta1 > math.pi) and (theta1 <= theta0))):
- points.append((self.center[0] - self.radius, self.center[1]))
- # Passes through 270 degrees
- if (theta0 <= math.pi * 1.5 and (
- theta1 >= math.pi * 1.5 or theta1 <= theta0)
- or ((theta1 > math.pi * 1.5) and (theta1 <= theta0))):
- points.append((self.center[0], self.center[1] - self.radius))
- else:
- # Passes through 0 degrees
- if theta1 >= theta0:
- points.append((self.center[0] + self.radius, self.center[1]))
- # Passes through 90 degrees
- if (((theta1 <= math.pi / 2.) and (
- theta0 >= math.pi / 2. or theta0 <= theta1))
- or ((theta0 > math.pi / 2.) and (theta0 <= theta1))):
- points.append((self.center[0], self.center[1] + self.radius))
- # Passes through 180 degrees
- if (((theta1 <= math.pi) and (theta0 >= math.pi or theta0 <= theta1))
- or ((theta0 > math.pi) and (theta0 <= theta1))):
- points.append((self.center[0] - self.radius, self.center[1]))
- # Passes through 270 degrees
- if (((theta1 <= math.pi * 1.5) and (
- theta0 >= math.pi * 1.5 or theta0 <= theta1))
- or ((theta0 > math.pi * 1.5) and (theta0 <= theta1))):
- points.append((self.center[0], self.center[1] - self.radius))
- x, y = zip(*points)
-
- min_x = min(x)
- max_x = max(x)
- min_y = min(y)
- max_y = max(y)
- return ((min_x, max_x), (min_y, max_y))
-
- def offset(self, x_offset=0, y_offset=0):
- self._changed()
- self.start = tuple(map(add, self.start, (x_offset, y_offset)))
- self.end = tuple(map(add, self.end, (x_offset, y_offset)))
- self.center = tuple(map(add, self.center, (x_offset, y_offset)))
-
-
-class Circle(Primitive):
- """
- """
-
- def __init__(self, position, diameter, hole_diameter=None,
- hole_width=0, hole_height=0, **kwargs):
- super(Circle, self).__init__(**kwargs)
- validate_coordinates(position)
- self._position = position
- self._diameter = diameter
- self.hole_diameter = hole_diameter
- self.hole_width = hole_width
- self.hole_height = hole_height
- self._to_convert = ['position', 'diameter', 'hole_diameter', 'hole_width', 'hole_height']
-
- @property
- def flashed(self):
- return True
-
- @property
- def position(self):
- return self._position
-
- @position.setter
- def position(self, value):
- self._changed()
- self._position = value
-
- @property
- def diameter(self):
- return self._diameter
-
- @diameter.setter
- def diameter(self, value):
- self._changed()
- self._diameter = value
-
- @property
- def radius(self):
- return self.diameter / 2.
-
- @property
- def hole_radius(self):
- if self.hole_diameter != None:
- return self.hole_diameter / 2.
- return None
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- min_x = self.position[0] - self.radius
- max_x = self.position[0] + self.radius
- min_y = self.position[1] - self.radius
- max_y = self.position[1] + self.radius
- self._bounding_box = ((min_x, max_x), (min_y, max_y))
- return self._bounding_box
-
- def offset(self, x_offset=0, y_offset=0):
- self.position = tuple(map(add, self.position, (x_offset, y_offset)))
-
- def equivalent(self, other, offset):
- '''Is this the same as the other circle, ignoring the offiset?'''
-
- if not isinstance(other, Circle):
- return False
-
- if self.diameter != other.diameter or self.hole_diameter != other.hole_diameter:
- return False
-
- equiv_position = tuple(map(add, other.position, offset))
-
- return nearly_equal(self.position, equiv_position)
-
-
-class Ellipse(Primitive):
- """
- """
- def __init__(self, position, width, height, **kwargs):
- super(Ellipse, self).__init__(**kwargs)
- validate_coordinates(position)
- self._position = position
- self._width = width
- self._height = height
- self._to_convert = ['position', 'width', 'height']
-
- @property
- def flashed(self):
- return True
-
- @property
- def position(self):
- return self._position
-
- @position.setter
- def position(self, value):
- self._changed()
- self._position = value
-
- @property
- def width(self):
- return self._width
-
- @width.setter
- def width(self, value):
- self._changed()
- self._width = value
-
- @property
- def height(self):
- return self._height
-
- @height.setter
- def height(self, value):
- self._changed()
- self._height = value
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- min_x = self.position[0] - (self.axis_aligned_width / 2.0)
- max_x = self.position[0] + (self.axis_aligned_width / 2.0)
- min_y = self.position[1] - (self.axis_aligned_height / 2.0)
- max_y = self.position[1] + (self.axis_aligned_height / 2.0)
- self._bounding_box = ((min_x, max_x), (min_y, max_y))
- return self._bounding_box
-
- @property
- def axis_aligned_width(self):
- ux = (self.width / 2.) * math.cos(math.radians(self.rotation))
- vx = (self.height / 2.) * \
- math.cos(math.radians(self.rotation) + (math.pi / 2.))
- return 2 * math.sqrt((ux * ux) + (vx * vx))
-
- @property
- def axis_aligned_height(self):
- uy = (self.width / 2.) * math.sin(math.radians(self.rotation))
- vy = (self.height / 2.) * \
- math.sin(math.radians(self.rotation) + (math.pi / 2.))
- return 2 * math.sqrt((uy * uy) + (vy * vy))
-
-
-class Rectangle(Primitive):
- """
- When rotated, the rotation is about the center point.
-
- Only aperture macro generated Rectangle objects can be rotated. If you aren't in a AMGroup,
- then you don't need to worry about rotation
- """
-
- def __init__(self, position, width, height, hole_diameter=0,
- hole_width=0, hole_height=0, **kwargs):
- super(Rectangle, self).__init__(**kwargs)
- validate_coordinates(position)
- self._position = position
- self._width = width
- self._height = height
- self.hole_diameter = hole_diameter
- self.hole_width = hole_width
- self.hole_height = hole_height
- self._to_convert = ['position', 'width', 'height', 'hole_diameter',
- 'hole_width', 'hole_height']
- # TODO These are probably wrong when rotated
- self._lower_left = None
- self._upper_right = None
-
- @property
- def flashed(self):
- return True
-
- @property
- def position(self):
- return self._position
-
- @position.setter
- def position(self, value):
- self._changed()
- self._position = value
-
- @property
- def width(self):
- return self._width
-
- @width.setter
- def width(self, value):
- self._changed()
- self._width = value
-
- @property
- def height(self):
- return self._height
-
- @height.setter
- def height(self, value):
- self._changed()
- self._height = value
-
- @property
- def hole_radius(self):
- """The radius of the hole. If there is no hole, returns None"""
- if self.hole_diameter != None:
- return self.hole_diameter / 2.
- return None
-
- @property
- def upper_right(self):
- return (self.position[0] + (self.axis_aligned_width / 2.),
- self.position[1] + (self.axis_aligned_height / 2.))
-
- @property
- def lower_left(self):
- return (self.position[0] - (self.axis_aligned_width / 2.),
- self.position[1] - (self.axis_aligned_height / 2.))
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- ll = (self.position[0] - (self.axis_aligned_width / 2.),
- self.position[1] - (self.axis_aligned_height / 2.))
- ur = (self.position[0] + (self.axis_aligned_width / 2.),
- self.position[1] + (self.axis_aligned_height / 2.))
- self._bounding_box = ((ll[0], ur[0]), (ll[1], ur[1]))
- return self._bounding_box
-
- @property
- def vertices(self):
- if self._vertices is None:
- delta_w = self.width / 2.
- delta_h = self.height / 2.
- ll = ((self.position[0] - delta_w), (self.position[1] - delta_h))
- ul = ((self.position[0] - delta_w), (self.position[1] + delta_h))
- ur = ((self.position[0] + delta_w), (self.position[1] + delta_h))
- lr = ((self.position[0] + delta_w), (self.position[1] - delta_h))
- self._vertices = [((x * self._cos_theta - y * self._sin_theta),
- (x * self._sin_theta + y * self._cos_theta))
- for x, y in [ll, ul, ur, lr]]
- return self._vertices
-
- @property
- def axis_aligned_width(self):
- return (self._cos_theta * self.width + self._sin_theta * self.height)
-
- @property
- def axis_aligned_height(self):
- return (self._cos_theta * self.height + self._sin_theta * self.width)
-
- def equivalent(self, other, offset):
- """Is this the same as the other rect, ignoring the offset?"""
-
- if not isinstance(other, Rectangle):
- return False
-
- if self.width != other.width or self.height != other.height or self.rotation != other.rotation or self.hole_diameter != other.hole_diameter:
- return False
-
- equiv_position = tuple(map(add, other.position, offset))
-
- return nearly_equal(self.position, equiv_position)
-
- def __str__(self):
- return "<Rectangle W {} H {} R {}>".format(self.width, self.height, self.rotation * 180/math.pi)
-
- def __repr__(self):
- return self.__str__()
-
-
-class Diamond(Primitive):
- """
- """
-
- def __init__(self, position, width, height, **kwargs):
- super(Diamond, self).__init__(**kwargs)
- validate_coordinates(position)
- self._position = position
- self._width = width
- self._height = height
- self._to_convert = ['position', 'width', 'height']
-
- @property
- def flashed(self):
- return True
-
- @property
- def position(self):
- return self._position
-
- @position.setter
- def position(self, value):
- self._changed()
- self._position = value
-
- @property
- def width(self):
- return self._width
-
- @width.setter
- def width(self, value):
- self._changed()
- self._width = value
-
- @property
- def height(self):
- return self._height
-
- @height.setter
- def height(self, value):
- self._changed()
- self._height = value
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- ll = (self.position[0] - (self.axis_aligned_width / 2.),
- self.position[1] - (self.axis_aligned_height / 2.))
- ur = (self.position[0] + (self.axis_aligned_width / 2.),
- self.position[1] + (self.axis_aligned_height / 2.))
- self._bounding_box = ((ll[0], ur[0]), (ll[1], ur[1]))
- return self._bounding_box
-
- @property
- def vertices(self):
- if self._vertices is None:
- delta_w = self.width / 2.
- delta_h = self.height / 2.
- top = (self.position[0], (self.position[1] + delta_h))
- right = ((self.position[0] + delta_w), self.position[1])
- bottom = (self.position[0], (self.position[1] - delta_h))
- left = ((self.position[0] - delta_w), self.position[1])
- self._vertices = [(((x * self._cos_theta) - (y * self._sin_theta)),
- ((x * self._sin_theta) + (y * self._cos_theta)))
- for x, y in [top, right, bottom, left]]
- return self._vertices
-
- @property
- def axis_aligned_width(self):
- return (self._cos_theta * self.width + self._sin_theta * self.height)
-
- @property
- def axis_aligned_height(self):
- return (self._cos_theta * self.height + self._sin_theta * self.width)
-
-
-class ChamferRectangle(Primitive):
- """
- """
- def __init__(self, position, width, height, chamfer, corners=None, **kwargs):
- super(ChamferRectangle, self).__init__(**kwargs)
- validate_coordinates(position)
- self._position = position
- self._width = width
- self._height = height
- self._chamfer = chamfer
- self._corners = corners if corners is not None else [True] * 4
- self._to_convert = ['position', 'width', 'height', 'chamfer']
-
- @property
- def flashed(self):
- return True
-
- @property
- def position(self):
- return self._position
-
- @position.setter
- def position(self, value):
- self._changed()
- self._position = value
-
- @property
- def width(self):
- return self._width
-
- @width.setter
- def width(self, value):
- self._changed()
- self._width = value
-
- @property
- def height(self):
- return self._height
-
- @height.setter
- def height(self, value):
- self._changed()
- self._height = value
-
- @property
- def chamfer(self):
- return self._chamfer
-
- @chamfer.setter
- def chamfer(self, value):
- self._changed()
- self._chamfer = value
-
- @property
- def corners(self):
- return self._corners
-
- @corners.setter
- def corners(self, value):
- self._changed()
- self._corners = value
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- ll = (self.position[0] - (self.axis_aligned_width / 2.),
- self.position[1] - (self.axis_aligned_height / 2.))
- ur = (self.position[0] + (self.axis_aligned_width / 2.),
- self.position[1] + (self.axis_aligned_height / 2.))
- self._bounding_box = ((ll[0], ur[0]), (ll[1], ur[1]))
- return self._bounding_box
-
- @property
- def vertices(self):
- if self._vertices is None:
- vertices = []
- delta_w = self.width / 2.
- delta_h = self.height / 2.
- # order is UR, UL, LL, LR
- rect_corners = [
- ((self.position[0] + delta_w), (self.position[1] + delta_h)),
- ((self.position[0] - delta_w), (self.position[1] + delta_h)),
- ((self.position[0] - delta_w), (self.position[1] - delta_h)),
- ((self.position[0] + delta_w), (self.position[1] - delta_h))
- ]
- for idx, params in enumerate(zip(rect_corners, self.corners)):
- corner, chamfered = params
- x, y = corner
- if chamfered:
- if idx == 0:
- vertices.append((x - self.chamfer, y))
- vertices.append((x, y - self.chamfer))
- elif idx == 1:
- vertices.append((x + self.chamfer, y))
- vertices.append((x, y - self.chamfer))
- elif idx == 2:
- vertices.append((x + self.chamfer, y))
- vertices.append((x, y + self.chamfer))
- elif idx == 3:
- vertices.append((x - self.chamfer, y))
- vertices.append((x, y + self.chamfer))
- else:
- vertices.append(corner)
- self._vertices = [((x * self._cos_theta - y * self._sin_theta),
- (x * self._sin_theta + y * self._cos_theta))
- for x, y in vertices]
- return self._vertices
-
- @property
- def axis_aligned_width(self):
- return (self._cos_theta * self.width +
- self._sin_theta * self.height)
-
- @property
- def axis_aligned_height(self):
- return (self._cos_theta * self.height +
- self._sin_theta * self.width)
-
-
-class RoundRectangle(Primitive):
- """
- """
-
- def __init__(self, position, width, height, radius, corners, **kwargs):
- super(RoundRectangle, self).__init__(**kwargs)
- validate_coordinates(position)
- self._position = position
- self._width = width
- self._height = height
- self._radius = radius
- self._corners = corners
- self._to_convert = ['position', 'width', 'height', 'radius']
-
- @property
- def flashed(self):
- return True
-
- @property
- def position(self):
- return self._position
-
- @position.setter
- def position(self, value):
- self._changed()
- self._position = value
-
- @property
- def width(self):
- return self._width
-
- @width.setter
- def width(self, value):
- self._changed()
- self._width = value
-
- @property
- def height(self):
- return self._height
-
- @height.setter
- def height(self, value):
- self._changed()
- self._height = value
-
- @property
- def radius(self):
- return self._radius
-
- @radius.setter
- def radius(self, value):
- self._changed()
- self._radius = value
-
- @property
- def corners(self):
- return self._corners
-
- @corners.setter
- def corners(self, value):
- self._changed()
- self._corners = value
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- ll = (self.position[0] - (self.axis_aligned_width / 2.),
- self.position[1] - (self.axis_aligned_height / 2.))
- ur = (self.position[0] + (self.axis_aligned_width / 2.),
- self.position[1] + (self.axis_aligned_height / 2.))
- self._bounding_box = ((ll[0], ur[0]), (ll[1], ur[1]))
- return self._bounding_box
-
- @property
- def axis_aligned_width(self):
- return (self._cos_theta * self.width +
- self._sin_theta * self.height)
-
- @property
- def axis_aligned_height(self):
- return (self._cos_theta * self.height +
- self._sin_theta * self.width)
-
-
-class Obround(Primitive):
- """
- """
-
- def __init__(self, position, width, height, hole_diameter=0,
- hole_width=0,hole_height=0, **kwargs):
- super(Obround, self).__init__(**kwargs)
- validate_coordinates(position)
- self._position = position
- self._width = width
- self._height = height
- self.hole_diameter = hole_diameter
- self.hole_width = hole_width
- self.hole_height = hole_height
- self._to_convert = ['position', 'width', 'height', 'hole_diameter',
- 'hole_width', 'hole_height' ]
-
- @property
- def flashed(self):
- return True
-
- @property
- def position(self):
- return self._position
-
- @position.setter
- def position(self, value):
- self._changed()
- self._position = value
-
- @property
- def width(self):
- return self._width
-
- @width.setter
- def width(self, value):
- self._changed()
- self._width = value
-
- @property
- def height(self):
- return self._height
-
- @height.setter
- def height(self, value):
- self._changed()
- self._height = value
-
- @property
- def hole_radius(self):
- """The radius of the hole. If there is no hole, returns None"""
- if self.hole_diameter != None:
- return self.hole_diameter / 2.
-
- return None
-
- @property
- def orientation(self):
- return 'vertical' if self.height > self.width else 'horizontal'
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- ll = (self.position[0] - (self.axis_aligned_width / 2.),
- self.position[1] - (self.axis_aligned_height / 2.))
- ur = (self.position[0] + (self.axis_aligned_width / 2.),
- self.position[1] + (self.axis_aligned_height / 2.))
- self._bounding_box = ((ll[0], ur[0]), (ll[1], ur[1]))
- return self._bounding_box
-
- @property
- def subshapes(self):
- if self.orientation == 'vertical':
- circle1 = Circle((self.position[0], self.position[1] +
- (self.height - self.width) / 2.), self.width)
- circle2 = Circle((self.position[0], self.position[1] -
- (self.height - self.width) / 2.), self.width)
- rect = Rectangle(self.position, self.width,
- (self.height - self.width))
- else:
- circle1 = Circle((self.position[0]
- - (self.height - self.width) / 2.,
- self.position[1]), self.height)
- circle2 = Circle((self.position[0]
- + (self.height - self.width) / 2.,
- self.position[1]), self.height)
- rect = Rectangle(self.position, (self.width - self.height),
- self.height)
- return {'circle1': circle1, 'circle2': circle2, 'rectangle': rect}
-
- @property
- def axis_aligned_width(self):
- return (self._cos_theta * self.width +
- self._sin_theta * self.height)
-
- @property
- def axis_aligned_height(self):
- return (self._cos_theta * self.height +
- self._sin_theta * self.width)
-
-
-class Polygon(Primitive):
- """
- Polygon flash defined by a set number of sides.
- """
- def __init__(self, position, sides, radius, hole_diameter=0,
- hole_width=0, hole_height=0, **kwargs):
- super(Polygon, self).__init__(**kwargs)
- validate_coordinates(position)
- self._position = position
- self.sides = sides
- self._radius = radius
- self.hole_diameter = hole_diameter
- self.hole_width = hole_width
- self.hole_height = hole_height
- self._to_convert = ['position', 'radius', 'hole_diameter',
- 'hole_width', 'hole_height']
-
- @property
- def flashed(self):
- return True
-
- @property
- def diameter(self):
- return self.radius * 2
-
- @property
- def hole_radius(self):
- if self.hole_diameter != None:
- return self.hole_diameter / 2.
- return None
-
- @property
- def position(self):
- return self._position
-
- @position.setter
- def position(self, value):
- self._changed()
- self._position = value
-
- @property
- def radius(self):
- return self._radius
-
- @radius.setter
- def radius(self, value):
- self._changed()
- self._radius = value
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- min_x = self.position[0] - self.radius
- max_x = self.position[0] + self.radius
- min_y = self.position[1] - self.radius
- max_y = self.position[1] + self.radius
- self._bounding_box = ((min_x, max_x), (min_y, max_y))
- return self._bounding_box
-
- def offset(self, x_offset=0, y_offset=0):
- self.position = tuple(map(add, self.position, (x_offset, y_offset)))
-
- @property
- def vertices(self):
-
- offset = self.rotation
- delta_angle = 360.0 / self.sides
-
- points = []
- for i in range(self.sides):
- points.append(
- rotate_point((self.position[0] + self.radius, self.position[1]), offset + delta_angle * i, self.position))
- return points
-
-
- def equivalent(self, other, offset):
- """
- Is this the outline the same as the other, ignoring the position offset?
- """
-
- # Quick check if it even makes sense to compare them
- if type(self) != type(other) or self.sides != other.sides or self.radius != other.radius:
- return False
-
- equiv_pos = tuple(map(add, other.position, offset))
-
- return nearly_equal(self.position, equiv_pos)
-
-
-class AMGroup(Primitive):
- """
- """
- def __init__(self, amprimitives, stmt = None, **kwargs):
- """
-
- stmt : The original statment that generated this, since it is really hard to re-generate from primitives
- """
- super(AMGroup, self).__init__(**kwargs)
-
- self.primitives = []
- for amprim in amprimitives:
- prim = amprim.to_primitive(self.units)
- if isinstance(prim, list):
- for p in prim:
- self.primitives.append(p)
- elif prim:
- self.primitives.append(prim)
- self._position = None
- self._to_convert = ['_position', 'primitives']
- self.stmt = stmt
-
- def to_inch(self):
- if self.units == 'metric':
- super(AMGroup, self).to_inch()
-
- # If we also have a stmt, convert that too
- if self.stmt:
- self.stmt.to_inch()
-
-
- def to_metric(self):
- if self.units == 'inch':
- super(AMGroup, self).to_metric()
-
- # If we also have a stmt, convert that too
- if self.stmt:
- self.stmt.to_metric()
-
- @property
- def flashed(self):
- return True
-
- @property
- def bounding_box(self):
- # TODO Make this cached like other items
- xlims, ylims = zip(*[p.bounding_box for p in self.primitives])
- minx, maxx = zip(*xlims)
- miny, maxy = zip(*ylims)
- min_x = min(minx)
- max_x = max(maxx)
- min_y = min(miny)
- max_y = max(maxy)
- return ((min_x, max_x), (min_y, max_y))
-
- @property
- def position(self):
- return self._position
-
- def offset(self, x_offset=0, y_offset=0):
- self._position = tuple(map(add, self._position, (x_offset, y_offset)))
-
- for primitive in self.primitives:
- primitive.offset(x_offset, y_offset)
-
- @position.setter
- def position(self, new_pos):
- '''
- Sets the position of the AMGroup.
- This offset all of the objects by the specified distance.
- '''
-
- if self._position:
- dx = new_pos[0] - self._position[0]
- dy = new_pos[1] - self._position[1]
- else:
- dx = new_pos[0]
- dy = new_pos[1]
-
- for primitive in self.primitives:
- primitive.offset(dx, dy)
-
- self._position = new_pos
-
- def equivalent(self, other, offset):
- '''
- Is this the macro group the same as the other, ignoring the position offset?
- '''
-
- if len(self.primitives) != len(other.primitives):
- return False
-
- # We know they have the same number of primitives, so now check them all
- for i in range(0, len(self.primitives)):
- if not self.primitives[i].equivalent(other.primitives[i], offset):
- return False
-
- # If we didn't find any differences, then they are the same
- return True
-
-class Outline(Primitive):
- """
- Outlines only exist as the rendering for a apeture macro outline.
- They don't exist outside of AMGroup objects
- """
-
- def __init__(self, primitives, **kwargs):
- super(Outline, self).__init__(**kwargs)
- self.primitives = primitives
- self._to_convert = ['primitives']
-
- if self.primitives[0].start != self.primitives[-1].end:
- raise ValueError('Outline must be closed')
-
- @property
- def flashed(self):
- return True
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- xlims, ylims = zip(*[p.bounding_box for p in self.primitives])
- minx, maxx = zip(*xlims)
- miny, maxy = zip(*ylims)
- min_x = min(minx)
- max_x = max(maxx)
- min_y = min(miny)
- max_y = max(maxy)
- self._bounding_box = ((min_x, max_x), (min_y, max_y))
- return self._bounding_box
-
- def offset(self, x_offset=0, y_offset=0):
- self._changed()
- for p in self.primitives:
- p.offset(x_offset, y_offset)
-
- @property
- def vertices(self):
- if self._vertices is None:
- theta = math.radians(360/self.sides)
- vertices = [(self.position[0] + (math.cos(theta * side) * self.radius),
- self.position[1] + (math.sin(theta * side) * self.radius))
- for side in range(self.sides)]
- self._vertices = [(((x * self._cos_theta) - (y * self._sin_theta)),
- ((x * self._sin_theta) + (y * self._cos_theta)))
- for x, y in vertices]
- return self._vertices
-
- @property
- def width(self):
- bounding_box = self.bounding_box()
- return bounding_box[0][1] - bounding_box[0][0]
-
- def equivalent(self, other, offset):
- '''
- Is this the outline the same as the other, ignoring the position offset?
- '''
-
- # Quick check if it even makes sense to compare them
- if type(self) != type(other) or len(self.primitives) != len(other.primitives):
- return False
-
- for i in range(0, len(self.primitives)):
- if not self.primitives[i].equivalent(other.primitives[i], offset):
- return False
-
- return True
-
-class Region(Primitive):
- """
- """
-
- def __init__(self, primitives, **kwargs):
- super(Region, self).__init__(**kwargs)
- self.primitives = primitives
- self._to_convert = ['primitives']
-
- @property
- def flashed(self):
- return False
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- xlims, ylims = zip(*[p.bounding_box_no_aperture for p in self.primitives])
- minx, maxx = zip(*xlims)
- miny, maxy = zip(*ylims)
- min_x = min(minx)
- max_x = max(maxx)
- min_y = min(miny)
- max_y = max(maxy)
- self._bounding_box = ((min_x, max_x), (min_y, max_y))
- return self._bounding_box
-
- def offset(self, x_offset=0, y_offset=0):
- self._changed()
- for p in self.primitives:
- p.offset(x_offset, y_offset)
-
-
-class RoundButterfly(Primitive):
- """ A circle with two diagonally-opposite quadrants removed
- """
-
- def __init__(self, position, diameter, **kwargs):
- super(RoundButterfly, self).__init__(**kwargs)
- validate_coordinates(position)
- self.position = position
- self.diameter = diameter
- self._to_convert = ['position', 'diameter']
-
- # TODO This does not reset bounding box correctly
-
- @property
- def flashed(self):
- return True
-
- @property
- def radius(self):
- return self.diameter / 2.
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- min_x = self.position[0] - self.radius
- max_x = self.position[0] + self.radius
- min_y = self.position[1] - self.radius
- max_y = self.position[1] + self.radius
- self._bounding_box = ((min_x, max_x), (min_y, max_y))
- return self._bounding_box
-
-
-class SquareButterfly(Primitive):
- """ A square with two diagonally-opposite quadrants removed
- """
-
- def __init__(self, position, side, **kwargs):
- super(SquareButterfly, self).__init__(**kwargs)
- validate_coordinates(position)
- self.position = position
- self.side = side
- self._to_convert = ['position', 'side']
-
- # TODO This does not reset bounding box correctly
-
- @property
- def flashed(self):
- return True
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- min_x = self.position[0] - (self.side / 2.)
- max_x = self.position[0] + (self.side / 2.)
- min_y = self.position[1] - (self.side / 2.)
- max_y = self.position[1] + (self.side / 2.)
- self._bounding_box = ((min_x, max_x), (min_y, max_y))
- return self._bounding_box
-
-
-class Donut(Primitive):
- """ A Shape with an identical concentric shape removed from its center
- """
-
- def __init__(self, position, shape, inner_diameter,
- outer_diameter, **kwargs):
- super(Donut, self).__init__(**kwargs)
- validate_coordinates(position)
- self.position = position
- if shape not in ('round', 'square', 'hexagon', 'octagon'):
- raise ValueError(
- 'Valid shapes are round, square, hexagon or octagon')
- self.shape = shape
- if inner_diameter >= outer_diameter:
- raise ValueError(
- 'Outer diameter must be larger than inner diameter.')
- self.inner_diameter = inner_diameter
- self.outer_diameter = outer_diameter
- if self.shape in ('round', 'square', 'octagon'):
- self.width = outer_diameter
- self.height = outer_diameter
- else:
- # Hexagon
- self.width = 0.5 * math.sqrt(3.) * outer_diameter
- self.height = outer_diameter
-
- self._to_convert = ['position', 'width',
- 'height', 'inner_diameter', 'outer_diameter']
-
- # TODO This does not reset bounding box correctly
-
- @property
- def flashed(self):
- return True
-
- @property
- def lower_left(self):
- return (self.position[0] - (self.width / 2.),
- self.position[1] - (self.height / 2.))
-
- @property
- def upper_right(self):
- return (self.position[0] + (self.width / 2.),
- self.position[1] + (self.height / 2.))
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- ll = (self.position[0] - (self.width / 2.),
- self.position[1] - (self.height / 2.))
- ur = (self.position[0] + (self.width / 2.),
- self.position[1] + (self.height / 2.))
- self._bounding_box = ((ll[0], ur[0]), (ll[1], ur[1]))
- return self._bounding_box
-
-
-class SquareRoundDonut(Primitive):
- """ A Square with a circular cutout in the center
- """
-
- def __init__(self, position, inner_diameter, outer_diameter, **kwargs):
- super(SquareRoundDonut, self).__init__(**kwargs)
- validate_coordinates(position)
- self.position = position
- if inner_diameter >= outer_diameter:
- raise ValueError(
- 'Outer diameter must be larger than inner diameter.')
- self.inner_diameter = inner_diameter
- self.outer_diameter = outer_diameter
- self._to_convert = ['position', 'inner_diameter', 'outer_diameter']
-
- @property
- def flashed(self):
- return True
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- ll = tuple([c - self.outer_diameter / 2. for c in self.position])
- ur = tuple([c + self.outer_diameter / 2. for c in self.position])
- self._bounding_box = ((ll[0], ur[0]), (ll[1], ur[1]))
- return self._bounding_box
-
-
-class Drill(Primitive):
- """ A drill hole
- """
- def __init__(self, position, diameter, **kwargs):
- super(Drill, self).__init__('dark', **kwargs)
- validate_coordinates(position)
- self._position = position
- self._diameter = diameter
- self._to_convert = ['position', 'diameter']
-
- @property
- def flashed(self):
- return False
-
- @property
- def position(self):
- return self._position
-
- @position.setter
- def position(self, value):
- self._changed()
- self._position = value
-
- @property
- def diameter(self):
- return self._diameter
-
- @diameter.setter
- def diameter(self, value):
- self._changed()
- self._diameter = value
-
- @property
- def radius(self):
- return self.diameter / 2.
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- min_x = self.position[0] - self.radius
- max_x = self.position[0] + self.radius
- min_y = self.position[1] - self.radius
- max_y = self.position[1] + self.radius
- self._bounding_box = ((min_x, max_x), (min_y, max_y))
- return self._bounding_box
-
- def offset(self, x_offset=0, y_offset=0):
- self._changed()
- self.position = tuple(map(add, self.position, (x_offset, y_offset)))
-
- def __str__(self):
- return '<Drill %f %s (%f, %f)>' % (self.diameter, self.units, self.position[0], self.position[1])
-
-
-class Slot(Primitive):
- """ A drilled slot
- """
- def __init__(self, start, end, diameter, **kwargs):
- super(Slot, self).__init__('dark', **kwargs)
- validate_coordinates(start)
- validate_coordinates(end)
- self.start = start
- self.end = end
- self.diameter = diameter
- self._to_convert = ['start', 'end', 'diameter']
-
-
- @property
- def flashed(self):
- return False
-
- @property
- def bounding_box(self):
- if self._bounding_box is None:
- radius = self.diameter / 2.
- min_x = min(self.start[0], self.end[0]) - radius
- max_x = max(self.start[0], self.end[0]) + radius
- min_y = min(self.start[1], self.end[1]) - radius
- max_y = max(self.start[1], self.end[1]) + radius
- self._bounding_box = ((min_x, max_x), (min_y, max_y))
- return self._bounding_box
-
- def offset(self, x_offset=0, y_offset=0):
- self.start = tuple(map(add, self.start, (x_offset, y_offset)))
- self.end = tuple(map(add, self.end, (x_offset, y_offset)))
-
-
-class TestRecord(Primitive):
- """ Netlist Test record
- """
-
- def __init__(self, position, net_name, layer, **kwargs):
- super(TestRecord, self).__init__(**kwargs)
- validate_coordinates(position)
- self.position = position
- self.net_name = net_name
- self.layer = layer
- self._to_convert = ['position']
diff --git a/gerber/render/__init__.py b/gerber/render/__init__.py
deleted file mode 100644
index c7dbdd5..0000000
--- a/gerber/render/__init__.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-gerber.render
-============
-**Gerber Renderers**
-
-This module provides contexts for rendering images of gerber layers. Currently
-SVG is the only supported format.
-"""
-
-from .render import RenderSettings
-from .cairo_backend import GerberCairoContext
-
-available_renderers = {
- 'cairo': GerberCairoContext,
-}
diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py
deleted file mode 100644
index e1d1408..0000000
--- a/gerber/render/cairo_backend.py
+++ /dev/null
@@ -1,616 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-try:
- import cairo
-except ImportError:
- import cairocffi as cairo
-
-from operator import mul
-import tempfile
-import copy
-import os
-
-from .render import GerberContext, RenderSettings
-from .theme import THEMES
-from ..primitives import *
-from ..utils import rotate_point
-
-from io import BytesIO
-
-
-class GerberCairoContext(GerberContext):
-
- def __init__(self, scale=300):
- super(GerberCairoContext, self).__init__()
- self.scale = (scale, scale)
- self.surface = None
- self.surface_buffer = None
- self.ctx = None
- self.active_layer = None
- self.active_matrix = None
- self.output_ctx = None
- self.has_bg = False
- self.origin_in_inch = None
- self.size_in_inch = None
- self._xform_matrix = None
- self._render_count = 0
-
- @property
- def origin_in_pixels(self):
- return (self.scale_point(self.origin_in_inch)
- if self.origin_in_inch is not None else (0.0, 0.0))
-
- @property
- def size_in_pixels(self):
- return (self.scale_point(self.size_in_inch)
- if self.size_in_inch is not None else (0.0, 0.0))
-
- def set_bounds(self, bounds, new_surface=False):
- origin_in_inch = (bounds[0][0], bounds[1][0])
- size_in_inch = (abs(bounds[0][1] - bounds[0][0]),
- abs(bounds[1][1] - bounds[1][0]))
- size_in_pixels = self.scale_point(size_in_inch)
- self.origin_in_inch = origin_in_inch if self.origin_in_inch is None else self.origin_in_inch
- self.size_in_inch = size_in_inch if self.size_in_inch is None else self.size_in_inch
- self._xform_matrix = cairo.Matrix(xx=1.0, yy=-1.0,
- x0=-self.origin_in_pixels[0],
- y0=self.size_in_pixels[1])
- if (self.surface is None) or new_surface:
- self.surface_buffer = tempfile.NamedTemporaryFile()
- self.surface = cairo.SVGSurface(self.surface_buffer, size_in_pixels[0], size_in_pixels[1])
- self.output_ctx = cairo.Context(self.surface)
-
- def render_layer(self, layer, filename=None, settings=None, bgsettings=None,
- verbose=False, bounds=None):
- if settings is None:
- settings = THEMES['default'].get(layer.layer_class, RenderSettings())
- if bgsettings is None:
- bgsettings = THEMES['default'].get('background', RenderSettings())
-
- if self._render_count == 0:
- if verbose:
- print('[Render]: Rendering Background.')
- self.clear()
- if bounds is not None:
- self.set_bounds(bounds)
- else:
- self.set_bounds(layer.bounds)
- self.paint_background(bgsettings)
- if verbose:
- print('[Render]: Rendering {} Layer.'.format(layer.layer_class))
- self._render_count += 1
- self._render_layer(layer, settings)
- if filename is not None:
- self.dump(filename, verbose)
-
- def render_layers(self, layers, filename, theme=THEMES['default'],
- verbose=False, max_width=800, max_height=600):
- """ Render a set of layers
- """
- # Calculate scale parameter
- x_range = [10000, -10000]
- y_range = [10000, -10000]
- for layer in layers:
- bounds = layer.bounds
- if bounds is not None:
- layer_x, layer_y = bounds
- x_range[0] = min(x_range[0], layer_x[0])
- x_range[1] = max(x_range[1], layer_x[1])
- y_range[0] = min(y_range[0], layer_y[0])
- y_range[1] = max(y_range[1], layer_y[1])
- width = x_range[1] - x_range[0]
- height = y_range[1] - y_range[0]
-
- scale = math.floor(min(float(max_width)/width, float(max_height)/height))
- self.scale = (scale, scale)
-
- self.clear()
-
- # Render layers
- bgsettings = theme['background']
- for layer in layers:
- settings = theme.get(layer.layer_class, RenderSettings())
- self.render_layer(layer, settings=settings, bgsettings=bgsettings,
- verbose=verbose)
- self.dump(filename, verbose)
-
- def dump(self, filename=None, verbose=False):
- """ Save image as `filename`
- """
- try:
- is_svg = os.path.splitext(filename.lower())[1] == '.svg'
- except:
- is_svg = False
- if verbose:
- print('[Render]: Writing image to {}'.format(filename))
- if is_svg:
- self.surface.finish()
- self.surface_buffer.flush()
- with open(filename, "wb") as f:
- self.surface_buffer.seek(0)
- f.write(self.surface_buffer.read())
- f.flush()
- else:
- return self.surface.write_to_png(filename)
-
- def dump_str(self):
- """ Return a byte-string containing the rendered image.
- """
- fobj = BytesIO()
- self.surface.write_to_png(fobj)
- return fobj.getvalue()
-
- def dump_svg_str(self):
- """ Return a string containg the rendered SVG.
- """
- self.surface.finish()
- self.surface_buffer.flush()
- return self.surface_buffer.read()
-
- def clear(self):
- self.surface = None
- self.output_ctx = None
- self.has_bg = False
- self.origin_in_inch = None
- self.size_in_inch = None
- self._xform_matrix = None
- self._render_count = 0
- self.surface_buffer = None
-
- def _new_mask(self):
- class Mask:
- def __enter__(msk):
- size_in_pixels = self.size_in_pixels
- msk.surface = cairo.SVGSurface(None, size_in_pixels[0],
- size_in_pixels[1])
- msk.ctx = cairo.Context(msk.surface)
- msk.ctx.translate(-self.origin_in_pixels[0], -self.origin_in_pixels[1])
- return msk
-
-
- def __exit__(msk, exc_type, exc_val, traceback):
- if hasattr(msk.surface, 'finish'):
- msk.surface.finish()
-
- return Mask()
-
- def _render_layer(self, layer, settings):
- self.invert = settings.invert
- # Get a new clean layer to render on
- self.new_render_layer(mirror=settings.mirror)
- for prim in layer.primitives:
- self.render(prim)
- # Add layer to image
- self.flatten(settings.color, settings.alpha)
-
- def _render_line(self, line, color):
- start = self.scale_point(line.start)
- end = self.scale_point(line.end)
- self.ctx.set_operator(cairo.OPERATOR_OVER
- if (not self.invert)
- and line.level_polarity == 'dark'
- else cairo.OPERATOR_CLEAR)
-
- with self._clip_primitive(line):
- with self._new_mask() as mask:
- if isinstance(line.aperture, Circle):
- width = line.aperture.diameter
- mask.ctx.set_line_width(width * self.scale[0])
- mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND)
- mask.ctx.move_to(*start)
- mask.ctx.line_to(*end)
- mask.ctx.stroke()
-
- elif hasattr(line, 'vertices') and line.vertices is not None:
- points = [self.scale_point(x) for x in line.vertices]
- mask.ctx.set_line_width(0)
- mask.ctx.move_to(*points[-1])
- for point in points:
- mask.ctx.line_to(*point)
- mask.ctx.fill()
- self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0])
-
- def _render_arc(self, arc, color):
- center = self.scale_point(arc.center)
- start = self.scale_point(arc.start)
- end = self.scale_point(arc.end)
- radius = self.scale[0] * arc.radius
- two_pi = 2 * math.pi
- angle1 = (arc.start_angle + two_pi) % two_pi
- angle2 = (arc.end_angle + two_pi) % two_pi
- if angle1 == angle2 and arc.quadrant_mode != 'single-quadrant':
- # Make the angles slightly different otherwise Cario will draw nothing
- angle2 -= 0.000000001
- if isinstance(arc.aperture, Circle):
- width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001
- else:
- width = max(arc.aperture.width, arc.aperture.height, 0.001)
-
- self.ctx.set_operator(cairo.OPERATOR_OVER
- if (not self.invert)
- and arc.level_polarity == 'dark'
- else cairo.OPERATOR_CLEAR)
- with self._clip_primitive(arc):
- with self._new_mask() as mask:
- mask.ctx.set_line_width(width * self.scale[0])
- mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND if isinstance(arc.aperture, Circle) else cairo.LINE_CAP_SQUARE)
- mask.ctx.move_to(*start) # You actually have to do this...
- if arc.direction == 'counterclockwise':
- mask.ctx.arc(center[0], center[1], radius, angle1, angle2)
- else:
- mask.ctx.arc_negative(center[0], center[1], radius,
- angle1, angle2)
- mask.ctx.move_to(*end) # ...lame
- mask.ctx.stroke()
-
- #if isinstance(arc.aperture, Rectangle):
- # print("Flash Rectangle Ends")
- # print(arc.aperture.rotation * 180/math.pi)
- # rect = arc.aperture
- # width = self.scale[0] * rect.width
- # height = self.scale[1] * rect.height
- # for point, angle in zip((start, end), (angle1, angle2)):
- # print("{} w {} h{}".format(point, rect.width, rect.height))
- # mask.ctx.rectangle(point[0] - width/2.0,
- # point[1] - height/2.0, width, height)
- # mask.ctx.fill()
-
- self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0])
-
- def _render_region(self, region, color):
- self.ctx.set_operator(cairo.OPERATOR_OVER
- if (not self.invert) and region.level_polarity == 'dark'
- else cairo.OPERATOR_CLEAR)
- with self._clip_primitive(region):
- with self._new_mask() as mask:
- mask.ctx.set_line_width(0)
- mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND)
- mask.ctx.move_to(*self.scale_point(region.primitives[0].start))
- for prim in region.primitives:
- if isinstance(prim, Line):
- mask.ctx.line_to(*self.scale_point(prim.end))
- else:
- center = self.scale_point(prim.center)
- radius = self.scale[0] * prim.radius
- angle1 = prim.start_angle
- angle2 = prim.end_angle
- if prim.direction == 'counterclockwise':
- mask.ctx.arc(center[0], center[1], radius,
- angle1, angle2)
- else:
- mask.ctx.arc_negative(center[0], center[1], radius,
- angle1, angle2)
- mask.ctx.fill()
- self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0])
-
- def _render_circle(self, circle, color):
- center = self.scale_point(circle.position)
- self.ctx.set_operator(cairo.OPERATOR_OVER
- if (not self.invert)
- and circle.level_polarity == 'dark'
- else cairo.OPERATOR_CLEAR)
- with self._clip_primitive(circle):
- with self._new_mask() as mask:
- mask.ctx.set_line_width(0)
- mask.ctx.arc(center[0], center[1], (circle.radius * self.scale[0]), 0, (2 * math.pi))
- mask.ctx.fill()
-
- if hasattr(circle, 'hole_diameter') and circle.hole_diameter is not None and circle.hole_diameter > 0:
- mask.ctx.set_operator(cairo.OPERATOR_CLEAR)
- mask.ctx.arc(center[0], center[1], circle.hole_radius * self.scale[0], 0, 2 * math.pi)
- mask.ctx.fill()
-
- if (hasattr(circle, 'hole_width') and hasattr(circle, 'hole_height')
- and circle.hole_width is not None and circle.hole_height is not None
- and circle.hole_width > 0 and circle.hole_height > 0):
- mask.ctx.set_operator(cairo.OPERATOR_CLEAR
- if circle.level_polarity == 'dark'
- and (not self.invert)
- else cairo.OPERATOR_OVER)
- width, height = self.scale_point((circle.hole_width, circle.hole_height))
- lower_left = rotate_point(
- (center[0] - width / 2.0, center[1] - height / 2.0),
- circle.rotation, center)
- lower_right = rotate_point((center[0] + width / 2.0, center[1] - height / 2.0),
- circle.rotation, center)
- upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0),
- circle.rotation, center)
- upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0),
- circle.rotation, center)
- points = (lower_left, lower_right, upper_right, upper_left)
- mask.ctx.move_to(*points[-1])
- for point in points:
- mask.ctx.line_to(*point)
- mask.ctx.fill()
- self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0])
-
- def _render_rectangle(self, rectangle, color):
- lower_left = self.scale_point(rectangle.lower_left)
- width, height = tuple([abs(coord) for coord in
- self.scale_point((rectangle.width,
- rectangle.height))])
- self.ctx.set_operator(cairo.OPERATOR_OVER
- if (not self.invert)
- and rectangle.level_polarity == 'dark'
- else cairo.OPERATOR_CLEAR)
- with self._clip_primitive(rectangle):
- with self._new_mask() as mask:
- mask.ctx.set_line_width(0)
- mask.ctx.rectangle(lower_left[0], lower_left[1], width, height)
- mask.ctx.fill()
-
- center = self.scale_point(rectangle.position)
- if rectangle.hole_diameter > 0:
- # Render the center clear
- mask.ctx.set_operator(cairo.OPERATOR_CLEAR
- if rectangle.level_polarity == 'dark'
- and (not self.invert)
- else cairo.OPERATOR_OVER)
-
- mask.ctx.arc(center[0], center[1], rectangle.hole_radius * self.scale[0], 0, 2 * math.pi)
- mask.ctx.fill()
-
- if rectangle.hole_width > 0 and rectangle.hole_height > 0:
- mask.ctx.set_operator(cairo.OPERATOR_CLEAR
- if rectangle.level_polarity == 'dark'
- and (not self.invert)
- else cairo.OPERATOR_OVER)
- width, height = self.scale_point((rectangle.hole_width, rectangle.hole_height))
- lower_left = rotate_point((center[0] - width/2.0, center[1] - height/2.0), rectangle.rotation, center)
- lower_right = rotate_point((center[0] + width/2.0, center[1] - height/2.0), rectangle.rotation, center)
- upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0), rectangle.rotation, center)
- upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0), rectangle.rotation, center)
- points = (lower_left, lower_right, upper_right, upper_left)
- mask.ctx.move_to(*points[-1])
- for point in points:
- mask.ctx.line_to(*point)
- mask.ctx.fill()
- self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0])
-
- def _render_obround(self, obround, color):
- self.ctx.set_operator(cairo.OPERATOR_OVER
- if (not self.invert)
- and obround.level_polarity == 'dark'
- else cairo.OPERATOR_CLEAR)
- with self._clip_primitive(obround):
- with self._new_mask() as mask:
- mask.ctx.set_line_width(0)
-
- # Render circles
- for circle in (obround.subshapes['circle1'], obround.subshapes['circle2']):
- center = self.scale_point(circle.position)
- mask.ctx.arc(center[0], center[1], (circle.radius * self.scale[0]), 0, (2 * math.pi))
- mask.ctx.fill()
-
- # Render Rectangle
- rectangle = obround.subshapes['rectangle']
- lower_left = self.scale_point(rectangle.lower_left)
- width, height = tuple([abs(coord) for coord in
- self.scale_point((rectangle.width,
- rectangle.height))])
- mask.ctx.rectangle(lower_left[0], lower_left[1], width, height)
- mask.ctx.fill()
-
- center = self.scale_point(obround.position)
- if obround.hole_diameter > 0:
- # Render the center clear
- mask.ctx.set_operator(cairo.OPERATOR_CLEAR)
- mask.ctx.arc(center[0], center[1], obround.hole_radius * self.scale[0], 0, 2 * math.pi)
- mask.ctx.fill()
-
- if obround.hole_width > 0 and obround.hole_height > 0:
- mask.ctx.set_operator(cairo.OPERATOR_CLEAR
- if rectangle.level_polarity == 'dark'
- and (not self.invert)
- else cairo.OPERATOR_OVER)
- width, height =self.scale_point((obround.hole_width, obround.hole_height))
- lower_left = rotate_point((center[0] - width / 2.0, center[1] - height / 2.0),
- obround.rotation, center)
- lower_right = rotate_point((center[0] + width / 2.0, center[1] - height / 2.0),
- obround.rotation, center)
- upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0),
- obround.rotation, center)
- upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0),
- obround.rotation, center)
- points = (lower_left, lower_right, upper_right, upper_left)
- mask.ctx.move_to(*points[-1])
- for point in points:
- mask.ctx.line_to(*point)
- mask.ctx.fill()
-
- self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0])
-
- def _render_polygon(self, polygon, color):
- self.ctx.set_operator(cairo.OPERATOR_OVER
- if (not self.invert)
- and polygon.level_polarity == 'dark'
- else cairo.OPERATOR_CLEAR)
- with self._clip_primitive(polygon):
- with self._new_mask() as mask:
-
- vertices = polygon.vertices
- mask.ctx.set_line_width(0)
- mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND)
- # Start from before the end so it is easy to iterate and make sure
- # it is closed
- mask.ctx.move_to(*self.scale_point(vertices[-1]))
- for v in vertices:
- mask.ctx.line_to(*self.scale_point(v))
- mask.ctx.fill()
-
- center = self.scale_point(polygon.position)
- if polygon.hole_radius > 0:
- # Render the center clear
- mask.ctx.set_operator(cairo.OPERATOR_CLEAR
- if polygon.level_polarity == 'dark'
- and (not self.invert)
- else cairo.OPERATOR_OVER)
- mask.ctx.set_line_width(0)
- mask.ctx.arc(center[0],
- center[1],
- polygon.hole_radius * self.scale[0], 0, 2 * math.pi)
- mask.ctx.fill()
-
- if polygon.hole_width > 0 and polygon.hole_height > 0:
- mask.ctx.set_operator(cairo.OPERATOR_CLEAR
- if polygon.level_polarity == 'dark'
- and (not self.invert)
- else cairo.OPERATOR_OVER)
- width, height = self.scale_point((polygon.hole_width, polygon.hole_height))
- lower_left = rotate_point((center[0] - width / 2.0, center[1] - height / 2.0),
- polygon.rotation, center)
- lower_right = rotate_point((center[0] + width / 2.0, center[1] - height / 2.0),
- polygon.rotation, center)
- upper_left = rotate_point((center[0] - width / 2.0, center[1] + height / 2.0),
- polygon.rotation, center)
- upper_right = rotate_point((center[0] + width / 2.0, center[1] + height / 2.0),
- polygon.rotation, center)
- points = (lower_left, lower_right, upper_right, upper_left)
- mask.ctx.move_to(*points[-1])
- for point in points:
- mask.ctx.line_to(*point)
- mask.ctx.fill()
-
- self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0])
-
- def _render_drill(self, circle, color=None):
- color = color if color is not None else self.drill_color
- self._render_circle(circle, color)
-
- def _render_slot(self, slot, color):
- start = map(mul, slot.start, self.scale)
- end = map(mul, slot.end, self.scale)
-
- width = slot.diameter
-
- self.ctx.set_operator(cairo.OPERATOR_OVER
- if slot.level_polarity == 'dark' and
- (not self.invert) else cairo.OPERATOR_CLEAR)
- with self._clip_primitive(slot):
- with self._new_mask() as mask:
- mask.ctx.set_line_width(width * self.scale[0])
- mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND)
- mask.ctx.move_to(*start)
- mask.ctx.line_to(*end)
- mask.ctx.stroke()
- self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0])
-
- def _render_amgroup(self, amgroup, color):
- for primitive in amgroup.primitives:
- self.render(primitive)
-
- def _render_test_record(self, primitive, color):
- position = [pos + origin for pos, origin in
- zip(primitive.position, self.origin_in_inch)]
- self.ctx.select_font_face(
- 'monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
- self.ctx.set_font_size(13)
- self._render_circle(Circle(position, 0.015), color)
- self.ctx.set_operator(cairo.OPERATOR_OVER
- if primitive.level_polarity == 'dark' and
- (not self.invert) else cairo.OPERATOR_CLEAR)
- self.ctx.move_to(*[self.scale[0] * (coord + 0.015) for coord in position])
- self.ctx.scale(1, -1)
- self.ctx.show_text(primitive.net_name)
- self.ctx.scale(1, -1)
-
- def new_render_layer(self, color=None, mirror=False):
- size_in_pixels = self.scale_point(self.size_in_inch)
- matrix = copy.copy(self._xform_matrix)
- layer = cairo.SVGSurface(None, size_in_pixels[0], size_in_pixels[1])
- ctx = cairo.Context(layer)
-
- if self.invert:
- ctx.set_source_rgba(0.0, 0.0, 0.0, 1.0)
- ctx.set_operator(cairo.OPERATOR_OVER)
- ctx.paint()
- if mirror:
- matrix.xx = -1.0
- matrix.x0 = self.origin_in_pixels[0] + self.size_in_pixels[0]
- self.ctx = ctx
- self.ctx.set_matrix(matrix)
- self.active_layer = layer
- self.active_matrix = matrix
-
- def flatten(self, color=None, alpha=None):
- color = color if color is not None else self.color
- alpha = alpha if alpha is not None else self.alpha
- self.output_ctx.set_source_rgba(color[0], color[1], color[2], alpha)
- self.output_ctx.mask_surface(self.active_layer)
- self.ctx = None
- self.active_layer = None
- self.active_matrix = None
-
- def paint_background(self, settings=None):
- color = settings.color if settings is not None else self.background_color
- alpha = settings.alpha if settings is not None else 1.0
- if not self.has_bg:
- self.has_bg = True
- self.output_ctx.set_source_rgba(color[0], color[1], color[2], alpha)
- self.output_ctx.paint()
-
- def _clip_primitive(self, primitive):
- """ Clip rendering context to pixel-aligned bounding box
-
- Calculates pixel- and axis- aligned bounding box, and clips current
- context to that region. Improves rendering speed significantly. This
- returns a context manager, use as follows:
-
- with self._clip_primitive(some_primitive):
- do_rendering_stuff()
- do_more_rendering stuff(with, arguments)
-
- The context manager will reset the context's clipping region when it
- goes out of scope.
-
- """
- class Clip:
- def __init__(clp, primitive):
- x_range, y_range = primitive.bounding_box
- xmin, xmax = x_range
- ymin, ymax = y_range
-
- # Round bounds to the nearest pixel outside of the primitive
- clp.xmin = math.floor(self.scale[0] * xmin)
- clp.xmax = math.ceil(self.scale[0] * xmax)
-
- # We need to offset Y to take care of the difference in y-pos
- # caused by flipping the axis.
- clp.ymin = math.floor(
- (self.scale[1] * ymin) - math.ceil(self.origin_in_pixels[1]))
- clp.ymax = math.floor(
- (self.scale[1] * ymax) - math.floor(self.origin_in_pixels[1]))
-
- # Calculate width and height, rounded to the nearest pixel
- clp.width = abs(clp.xmax - clp.xmin)
- clp.height = abs(clp.ymax - clp.ymin)
-
- def __enter__(clp):
- # Clip current context to primitive's bounding box
- self.ctx.rectangle(clp.xmin, clp.ymin, clp.width, clp.height)
- self.ctx.clip()
-
- def __exit__(clp, exc_type, exc_val, traceback):
- # Reset context clip region
- self.ctx.reset_clip()
-
- return Clip(primitive)
-
- def scale_point(self, point):
- return tuple([coord * scale for coord, scale in zip(point, self.scale)])
diff --git a/gerber/render/excellon_backend.py b/gerber/render/excellon_backend.py
deleted file mode 100644
index 765d68c..0000000
--- a/gerber/render/excellon_backend.py
+++ /dev/null
@@ -1,188 +0,0 @@
-
-from .render import GerberContext
-from ..excellon import DrillSlot
-from ..excellon_statements import *
-
-class ExcellonContext(GerberContext):
-
- MODE_DRILL = 1
- MODE_SLOT =2
-
- def __init__(self, settings):
- GerberContext.__init__(self)
-
- # Statements that we write
- self.comments = []
- self.header = []
- self.tool_def = []
- self.body_start = [RewindStopStmt()]
- self.body = []
- self.start = [HeaderBeginStmt()]
-
- # Current tool and position
- self.handled_tools = set()
- self.cur_tool = None
- self.drill_mode = ExcellonContext.MODE_DRILL
- self.drill_down = False
- self._pos = (None, None)
-
- self.settings = settings
-
- self._start_header()
- self._start_comments()
-
- def _start_header(self):
- """Create the header from the settings"""
-
- self.header.append(UnitStmt.from_settings(self.settings))
-
- if self.settings.notation == 'incremental':
- raise NotImplementedError('Incremental mode is not implemented')
- else:
- self.body.append(AbsoluteModeStmt())
-
- def _start_comments(self):
-
- # Write the digits used - this isn't valid Excellon statement, so we write as a comment
- self.comments.append(CommentStmt('FILE_FORMAT=%d:%d' % (self.settings.format[0], self.settings.format[1])))
-
- def _get_end(self):
- """How we end depends on our mode"""
-
- end = []
-
- if self.drill_down:
- end.append(RetractWithClampingStmt())
- end.append(RetractWithoutClampingStmt())
-
- end.append(EndOfProgramStmt())
-
- return end
-
- @property
- def statements(self):
- return self.start + self.comments + self.header + self.body_start + self.body + self._get_end()
-
- def set_bounds(self, bounds, *args, **kwargs):
- pass
-
- def paint_background(self):
- pass
-
- def _render_line(self, line, color):
- raise ValueError('Invalid Excellon object')
- def _render_arc(self, arc, color):
- raise ValueError('Invalid Excellon object')
-
- def _render_region(self, region, color):
- raise ValueError('Invalid Excellon object')
-
- def _render_level_polarity(self, region):
- raise ValueError('Invalid Excellon object')
-
- def _render_circle(self, circle, color):
- raise ValueError('Invalid Excellon object')
-
- def _render_rectangle(self, rectangle, color):
- raise ValueError('Invalid Excellon object')
-
- def _render_obround(self, obround, color):
- raise ValueError('Invalid Excellon object')
-
- def _render_polygon(self, polygon, color):
- raise ValueError('Invalid Excellon object')
-
- def _simplify_point(self, point):
- return (point[0] if point[0] != self._pos[0] else None, point[1] if point[1] != self._pos[1] else None)
-
- def _render_drill(self, drill, color):
-
- if self.drill_mode != ExcellonContext.MODE_DRILL:
- self._start_drill_mode()
-
- tool = drill.hit.tool
- if not tool in self.handled_tools:
- self.handled_tools.add(tool)
- self.header.append(ExcellonTool.from_tool(tool))
-
- if tool != self.cur_tool:
- self.body.append(ToolSelectionStmt(tool.number))
- self.cur_tool = tool
-
- point = self._simplify_point(drill.position)
- self._pos = drill.position
- self.body.append(CoordinateStmt.from_point(point))
-
- def _start_drill_mode(self):
- """
- If we are not in drill mode, then end the ROUT so we can do basic drilling
- """
-
- if self.drill_mode == ExcellonContext.MODE_SLOT:
-
- # Make sure we are retracted before changing modes
- last_cmd = self.body[-1]
- if self.drill_down:
- self.body.append(RetractWithClampingStmt())
- self.body.append(RetractWithoutClampingStmt())
- self.drill_down = False
-
- # Switch to drill mode
- self.body.append(DrillModeStmt())
- self.drill_mode = ExcellonContext.MODE_DRILL
-
- else:
- raise ValueError('Should be in slot mode')
-
- def _render_slot(self, slot, color):
-
- # Set the tool first, before we might go into drill mode
- tool = slot.hit.tool
- if not tool in self.handled_tools:
- self.handled_tools.add(tool)
- self.header.append(ExcellonTool.from_tool(tool))
-
- if tool != self.cur_tool:
- self.body.append(ToolSelectionStmt(tool.number))
- self.cur_tool = tool
-
- # Two types of drilling - normal drill and slots
- if slot.hit.slot_type == DrillSlot.TYPE_ROUT:
-
- # For ROUT, setting the mode is part of the actual command.
-
- # Are we in the right position?
- if slot.start != self._pos:
- if self.drill_down:
- # We need to move into the right position, so retract
- self.body.append(RetractWithClampingStmt())
- self.drill_down = False
-
- # Move to the right spot
- point = self._simplify_point(slot.start)
- self._pos = slot.start
- self.body.append(CoordinateStmt.from_point(point, mode="ROUT"))
-
- # Now we are in the right spot, so drill down
- if not self.drill_down:
- self.body.append(ZAxisRoutPositionStmt())
- self.drill_down = True
-
- # Do a linear move from our current position to the end position
- point = self._simplify_point(slot.end)
- self._pos = slot.end
- self.body.append(CoordinateStmt.from_point(point, mode="LINEAR"))
-
- self.drill_mode = ExcellonContext.MODE_SLOT
-
- else:
- # This is a G85 slot, so do this in normally drilling mode
- if self.drill_mode != ExcellonContext.MODE_DRILL:
- self._start_drill_mode()
-
- # Slots don't use simplified points
- self._pos = slot.end
- self.body.append(SlotStmt.from_points(slot.start, slot.end))
-
- def _render_inverted_layer(self):
- pass
diff --git a/gerber/render/render.py b/gerber/render/render.py
deleted file mode 100644
index 580a7ea..0000000
--- a/gerber/render/render.py
+++ /dev/null
@@ -1,246 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-# Modified from code by Paulo Henrique Silva <ph.silva@gmail.com>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Rendering
-============
-**Gerber (RS-274X) and Excellon file rendering**
-
-Render Gerber and Excellon files to a variety of formats. The render module
-currently supports SVG rendering using the `svgwrite` library.
-"""
-
-
-from ..primitives import *
-from ..gerber_statements import (CommentStmt, UnknownStmt, EofStmt, ParamStmt,
- CoordStmt, ApertureStmt, RegionModeStmt,
- QuadrantModeStmt,)
-
-
-class GerberContext(object):
- """ Gerber rendering context base class
-
- Provides basic functionality and API for rendering gerber files. Medium-
- specific renderers should subclass GerberContext and implement the drawing
- functions. Colors are stored internally as 32-bit RGB and may need to be
- converted to a native format in the rendering subclass.
-
- Attributes
- ----------
- units : string
- Measurement units. 'inch' or 'metric'
-
- color : tuple (<float>, <float>, <float>)
- Color used for rendering as a tuple of normalized (red, green, blue)
- values.
-
- drill_color : tuple (<float>, <float>, <float>)
- Color used for rendering drill hits. Format is the same as for `color`.
-
- background_color : tuple (<float>, <float>, <float>)
- Color of the background. Used when exposing areas in 'clear' level
- polarity mode. Format is the same as for `color`.
-
- alpha : float
- Rendering opacity. Between 0.0 (transparent) and 1.0 (opaque.)
- """
-
- def __init__(self, units='inch'):
- self._units = units
- self._color = (0.7215, 0.451, 0.200)
- self._background_color = (0.0, 0.0, 0.0)
- self._drill_color = (0.0, 0.0, 0.0)
- self._alpha = 1.0
- self._invert = False
- self.ctx = None
-
- @property
- def units(self):
- return self._units
-
- @units.setter
- def units(self, units):
- if units not in ('inch', 'metric'):
- raise ValueError('Units may be "inch" or "metric"')
- self._units = units
-
- @property
- def color(self):
- return self._color
-
- @color.setter
- def color(self, color):
- if len(color) != 3:
- raise TypeError('Color must be a tuple of R, G, and B values')
- for c in color:
- if c < 0 or c > 1:
- raise ValueError('Channel values must be between 0.0 and 1.0')
- self._color = color
-
- @property
- def drill_color(self):
- return self._drill_color
-
- @drill_color.setter
- def drill_color(self, color):
- if len(color) != 3:
- raise TypeError('Drill color must be a tuple of R, G, and B values')
- for c in color:
- if c < 0 or c > 1:
- raise ValueError('Channel values must be between 0.0 and 1.0')
- self._drill_color = color
-
- @property
- def background_color(self):
- return self._background_color
-
- @background_color.setter
- def background_color(self, color):
- if len(color) != 3:
- raise TypeError('Background color must be a tuple of R, G, and B values')
- for c in color:
- if c < 0 or c > 1:
- raise ValueError('Channel values must be between 0.0 and 1.0')
- self._background_color = color
-
- @property
- def alpha(self):
- return self._alpha
-
- @alpha.setter
- def alpha(self, alpha):
- if alpha < 0 or alpha > 1:
- raise ValueError('Alpha must be between 0.0 and 1.0')
- self._alpha = alpha
-
- @property
- def invert(self):
- return self._invert
-
- @invert.setter
- def invert(self, invert):
- self._invert = invert
-
- def render(self, primitive):
- if not primitive:
- return
-
- self.pre_render_primitive(primitive)
-
- color = self.color
- if isinstance(primitive, Line):
- self._render_line(primitive, color)
- elif isinstance(primitive, Arc):
- self._render_arc(primitive, color)
- elif isinstance(primitive, Region):
- self._render_region(primitive, color)
- elif isinstance(primitive, Circle):
- self._render_circle(primitive, color)
- elif isinstance(primitive, Rectangle):
- self._render_rectangle(primitive, color)
- elif isinstance(primitive, Obround):
- self._render_obround(primitive, color)
- elif isinstance(primitive, Polygon):
- self._render_polygon(primitive, color)
- elif isinstance(primitive, Drill):
- self._render_drill(primitive, self.color)
- elif isinstance(primitive, Slot):
- self._render_slot(primitive, self.color)
- elif isinstance(primitive, AMGroup):
- self._render_amgroup(primitive, color)
- elif isinstance(primitive, Outline):
- self._render_region(primitive, color)
- elif isinstance(primitive, TestRecord):
- self._render_test_record(primitive, color)
-
- self.post_render_primitive(primitive)
-
- def set_bounds(self, bounds, *args, **kwargs):
- """Called by the renderer to set the extents of the file to render.
-
- Parameters
- ----------
- bounds: Tuple[Tuple[float, float], Tuple[float, float]]
- ( (x_min, x_max), (y_min, y_max)
- """
- pass
-
- def paint_background(self):
- pass
-
- def new_render_layer(self):
- pass
-
- def flatten(self):
- pass
-
- def pre_render_primitive(self, primitive):
- """
- Called before rendering a primitive. Use the callback to perform some action before rendering
- a primitive, for example adding a comment.
- """
- return
-
- def post_render_primitive(self, primitive):
- """
- Called after rendering a primitive. Use the callback to perform some action after rendering
- a primitive
- """
- return
-
-
- def _render_line(self, primitive, color):
- pass
-
- def _render_arc(self, primitive, color):
- pass
-
- def _render_region(self, primitive, color):
- pass
-
- def _render_circle(self, primitive, color):
- pass
-
- def _render_rectangle(self, primitive, color):
- pass
-
- def _render_obround(self, primitive, color):
- pass
-
- def _render_polygon(self, primitive, color):
- pass
-
- def _render_drill(self, primitive, color):
- pass
-
- def _render_slot(self, primitive, color):
- pass
-
- def _render_amgroup(self, primitive, color):
- pass
-
- def _render_test_record(self, primitive, color):
- pass
-
-
-class RenderSettings(object):
- def __init__(self, color=(0.0, 0.0, 0.0), alpha=1.0, invert=False,
- mirror=False):
- self.color = color
- self.alpha = alpha
- self.invert = invert
- self.mirror = mirror
diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py
deleted file mode 100644
index c7af2ea..0000000
--- a/gerber/render/rs274x_backend.py
+++ /dev/null
@@ -1,510 +0,0 @@
-"""Renders an in-memory Gerber file to statements which can be written to a string
-"""
-from copy import deepcopy
-
-try:
- from cStringIO import StringIO
-except(ImportError):
- from io import StringIO
-
-from .render import GerberContext
-from ..am_statements import *
-from ..gerber_statements import *
-from ..primitives import AMGroup, Arc, Circle, Line, Obround, Outline, Polygon, Rectangle
-
-
-class AMGroupContext(object):
- '''A special renderer to generate aperature macros from an AMGroup'''
-
- def __init__(self):
- self.statements = []
-
- def render(self, amgroup, name):
-
- if amgroup.stmt:
- # We know the statement it was generated from, so use that to create the AMParamStmt
- # It will give a much better result
-
- stmt = deepcopy(amgroup.stmt)
- stmt.name = name
-
- return stmt
-
- else:
- # Clone ourselves, then offset by the psotion so that
- # our render doesn't have to consider offset. Just makes things simpler
- nooffset_group = deepcopy(amgroup)
- nooffset_group.position = (0, 0)
-
- # Now draw the shapes
- for primitive in nooffset_group.primitives:
- if isinstance(primitive, Outline):
- self._render_outline(primitive)
- elif isinstance(primitive, Circle):
- self._render_circle(primitive)
- elif isinstance(primitive, Rectangle):
- self._render_rectangle(primitive)
- elif isinstance(primitive, Line):
- self._render_line(primitive)
- elif isinstance(primitive, Polygon):
- self._render_polygon(primitive)
- else:
- raise ValueError('amgroup')
-
- statement = AMParamStmt('AM', name, self._statements_to_string())
- return statement
-
- def _statements_to_string(self):
- macro = ''
-
- for statement in self.statements:
- macro += statement.to_gerber()
-
- return macro
-
- def _render_circle(self, circle):
- self.statements.append(AMCirclePrimitive.from_primitive(circle))
-
- def _render_rectangle(self, rectangle):
- self.statements.append(AMCenterLinePrimitive.from_primitive(rectangle))
-
- def _render_line(self, line):
- self.statements.append(AMVectorLinePrimitive.from_primitive(line))
-
- def _render_outline(self, outline):
- self.statements.append(AMOutlinePrimitive.from_primitive(outline))
-
- def _render_polygon(self, polygon):
- self.statements.append(AMPolygonPrimitive.from_primitive(polygon))
-
- def _render_thermal(self, thermal):
- pass
-
-
-class Rs274xContext(GerberContext):
-
- def __init__(self, settings):
- GerberContext.__init__(self)
- self.comments = []
- self.header = []
- self.body = []
- self.end = [EofStmt()]
-
- # Current values so we know if we have to execute
- # moves, levey changes before anything else
- self._level_polarity = None
- self._pos = (None, None)
- self._func = None
- self._quadrant_mode = None
- self._dcode = None
-
- # Primarily for testing and comarison to files, should we write
- # flashes as a single statement or a move plus flash? Set to true
- # to do in a single statement. Normally this can be false
- self.condensed_flash = True
-
- # When closing a region, force a D02 staement to close a region.
- # This is normally not necessary because regions are closed with a G37
- # staement, but this will add an extra statement for doubly close
- # the region
- self.explicit_region_move_end = False
-
- self._next_dcode = 10
- self._rects = {}
- self._circles = {}
- self._obrounds = {}
- self._polygons = {}
- self._macros = {}
-
- self._i_none = 0
- self._j_none = 0
-
- self.settings = settings
-
- self._start_header(settings)
-
- def _start_header(self, settings):
- self.header.append(FSParamStmt.from_settings(settings))
- self.header.append(MOParamStmt.from_units(settings.units))
-
- def _simplify_point(self, point):
- return (point[0] if point[0] != self._pos[0] else None, point[1] if point[1] != self._pos[1] else None)
-
- def _simplify_offset(self, point, offset):
-
- if point[0] != offset[0]:
- xoffset = point[0] - offset[0]
- else:
- xoffset = self._i_none
-
- if point[1] != offset[1]:
- yoffset = point[1] - offset[1]
- else:
- yoffset = self._j_none
-
- return (xoffset, yoffset)
-
- @property
- def statements(self):
- return self.comments + self.header + self.body + self.end
-
- def set_bounds(self, bounds, *args, **kwargs):
- pass
-
- def paint_background(self):
- pass
-
- def _select_aperture(self, aperture):
-
- # Select the right aperture if not already selected
- if aperture:
- if isinstance(aperture, Circle):
- aper = self._get_circle(aperture.diameter, aperture.hole_diameter, aperture.hole_width, aperture.hole_height)
- elif isinstance(aperture, Rectangle):
- aper = self._get_rectangle(aperture.width, aperture.height)
- elif isinstance(aperture, Obround):
- aper = self._get_obround(aperture.width, aperture.height)
- elif isinstance(aperture, AMGroup):
- aper = self._get_amacro(aperture)
- else:
- raise NotImplementedError('Line with invalid aperture type')
-
- if aper.d != self._dcode:
- self.body.append(ApertureStmt(aper.d))
- self._dcode = aper.d
-
- def pre_render_primitive(self, primitive):
-
- if hasattr(primitive, 'comment'):
- self.body.append(CommentStmt(primitive.comment))
-
- def _render_line(self, line, color, default_polarity='dark'):
-
- self._select_aperture(line.aperture)
-
- self._render_level_polarity(line, default_polarity)
-
- # Get the right function
- if self._func != CoordStmt.FUNC_LINEAR:
- func = CoordStmt.FUNC_LINEAR
- else:
- func = None
- self._func = CoordStmt.FUNC_LINEAR
-
- if self._pos != line.start:
- self.body.append(CoordStmt.move(func, self._simplify_point(line.start)))
- self._pos = line.start
- # We already set the function, so the next command doesn't require that
- func = None
-
- point = self._simplify_point(line.end)
-
- # In some files, we see a lot of duplicated ponts, so omit those
- if point[0] != None or point[1] != None:
- self.body.append(CoordStmt.line(func, self._simplify_point(line.end)))
- self._pos = line.end
- elif func:
- self.body.append(CoordStmt.mode(func))
-
- def _render_arc(self, arc, color, default_polarity='dark'):
-
- # Optionally set the quadrant mode if it has changed:
- if arc.quadrant_mode != self._quadrant_mode:
-
- if arc.quadrant_mode != 'multi-quadrant':
- self.body.append(QuadrantModeStmt.single())
- else:
- self.body.append(QuadrantModeStmt.multi())
-
- self._quadrant_mode = arc.quadrant_mode
-
- # Select the right aperture if not already selected
- self._select_aperture(arc.aperture)
-
- self._render_level_polarity(arc, default_polarity)
-
- # Find the right movement mode. Always set to be sure it is really right
- dir = arc.direction
- if dir == 'clockwise':
- func = CoordStmt.FUNC_ARC_CW
- self._func = CoordStmt.FUNC_ARC_CW
- elif dir == 'counterclockwise':
- func = CoordStmt.FUNC_ARC_CCW
- self._func = CoordStmt.FUNC_ARC_CCW
- else:
- raise ValueError('Invalid circular interpolation mode')
-
- if self._pos != arc.start:
- # TODO I'm not sure if this is right
- self.body.append(CoordStmt.move(CoordStmt.FUNC_LINEAR, self._simplify_point(arc.start)))
- self._pos = arc.start
-
- center = self._simplify_offset(arc.center, arc.start)
- end = self._simplify_point(arc.end)
- self.body.append(CoordStmt.arc(func, end, center))
- self._pos = arc.end
-
- def _render_region(self, region, color):
-
- self._render_level_polarity(region)
-
- self.body.append(RegionModeStmt.on())
-
- for p in region.primitives:
-
- # Make programmatically generated primitives within a region with
- # unset level polarity inherit the region's level polarity
- if isinstance(p, Line):
- self._render_line(p, color, default_polarity=region.level_polarity)
- else:
- self._render_arc(p, color, default_polarity=region.level_polarity)
-
- if self.explicit_region_move_end:
- self.body.append(CoordStmt.move(None, None))
-
- self.body.append(RegionModeStmt.off())
-
- def _render_level_polarity(self, obj, default='dark'):
- obj_polarity = obj.level_polarity if obj.level_polarity is not None else default
- if obj_polarity != self._level_polarity:
- self._level_polarity = obj_polarity
- self.body.append(LPParamStmt('LP', obj_polarity))
-
- def _render_flash(self, primitive, aperture):
-
- self._render_level_polarity(primitive)
-
- if aperture.d != self._dcode:
- self.body.append(ApertureStmt(aperture.d))
- self._dcode = aperture.d
-
- if self.condensed_flash:
- self.body.append(CoordStmt.flash(self._simplify_point(primitive.position)))
- else:
- self.body.append(CoordStmt.move(None, self._simplify_point(primitive.position)))
- self.body.append(CoordStmt.flash(None))
-
- self._pos = primitive.position
-
- def _get_circle(self, diameter, hole_diameter=None, hole_width=None,
- hole_height=None, dcode = None):
- '''Define a circlar aperture'''
-
- key = (diameter, hole_diameter, hole_width, hole_height)
- aper = self._circles.get(key, None)
-
- if not aper:
- if not dcode:
- dcode = self._next_dcode
- self._next_dcode += 1
- else:
- self._next_dcode = max(dcode + 1, self._next_dcode)
-
- aper = ADParamStmt.circle(dcode, diameter, hole_diameter, hole_width, hole_height)
- self._circles[(diameter, hole_diameter, hole_width, hole_height)] = aper
- self.header.append(aper)
-
- return aper
-
- def _render_circle(self, circle, color):
-
- aper = self._get_circle(circle.diameter, circle.hole_diameter, circle.hole_width, circle.hole_height)
- self._render_flash(circle, aper)
-
- def _get_rectangle(self, width, height, hole_diameter=None, hole_width=None,
- hole_height=None, dcode = None):
- '''Get a rectanglar aperture. If it isn't defined, create it'''
-
- key = (width, height, hole_diameter, hole_width, hole_height)
- aper = self._rects.get(key, None)
-
- if not aper:
- if not dcode:
- dcode = self._next_dcode
- self._next_dcode += 1
- else:
- self._next_dcode = max(dcode + 1, self._next_dcode)
-
- aper = ADParamStmt.rect(dcode, width, height, hole_diameter, hole_width, hole_height)
- self._rects[(width, height, hole_diameter, hole_width, hole_height)] = aper
- self.header.append(aper)
-
- return aper
-
- def _render_rectangle(self, rectangle, color):
-
- aper = self._get_rectangle(rectangle.width, rectangle.height,
- rectangle.hole_diameter,
- rectangle.hole_width, rectangle.hole_height)
- self._render_flash(rectangle, aper)
-
- def _get_obround(self, width, height, hole_diameter=None, hole_width=None,
- hole_height=None, dcode = None):
-
- key = (width, height, hole_diameter, hole_width, hole_height)
- aper = self._obrounds.get(key, None)
-
- if not aper:
- if not dcode:
- dcode = self._next_dcode
- self._next_dcode += 1
- else:
- self._next_dcode = max(dcode + 1, self._next_dcode)
-
- aper = ADParamStmt.obround(dcode, width, height, hole_diameter, hole_width, hole_height)
- self._obrounds[key] = aper
- self.header.append(aper)
-
- return aper
-
- def _render_obround(self, obround, color):
-
- aper = self._get_obround(obround.width, obround.height,
- obround.hole_diameter, obround.hole_width,
- obround.hole_height)
- self._render_flash(obround, aper)
-
- def _render_polygon(self, polygon, color):
-
- aper = self._get_polygon(polygon.radius, polygon.sides,
- polygon.rotation, polygon.hole_diameter,
- polygon.hole_width, polygon.hole_height)
- self._render_flash(polygon, aper)
-
- def _get_polygon(self, radius, num_vertices, rotation, hole_diameter=None,
- hole_width=None, hole_height=None, dcode = None):
-
- key = (radius, num_vertices, rotation, hole_diameter, hole_width, hole_height)
- aper = self._polygons.get(key, None)
-
- if not aper:
- if not dcode:
- dcode = self._next_dcode
- self._next_dcode += 1
- else:
- self._next_dcode = max(dcode + 1, self._next_dcode)
-
- aper = ADParamStmt.polygon(dcode, radius * 2, num_vertices,
- rotation, hole_diameter, hole_width,
- hole_height)
- self._polygons[key] = aper
- self.header.append(aper)
-
- return aper
-
- def _render_drill(self, drill, color):
- raise ValueError('Drills are not valid in RS274X files')
-
- def _hash_amacro(self, amgroup):
- '''Calculate a very quick hash code for deciding if we should even check AM groups for comparision'''
-
- # We always start with an X because this forms part of the name
- # Basically, in some cases, the name might start with a C, R, etc. That can appear
- # to conflict with normal aperture definitions. Technically, it shouldn't because normal
- # aperture definitions should have a comma, but in some cases the commit is omitted
- hash = 'X'
- for primitive in amgroup.primitives:
-
- hash += primitive.__class__.__name__[0]
-
- bbox = primitive.bounding_box
- hash += str((bbox[0][1] - bbox[0][0]) * 100000)[0:2]
- hash += str((bbox[1][1] - bbox[1][0]) * 100000)[0:2]
-
- if hasattr(primitive, 'primitives'):
- hash += str(len(primitive.primitives))
-
- if isinstance(primitive, Rectangle):
- hash += str(primitive.width * 1000000)[0:2]
- hash += str(primitive.height * 1000000)[0:2]
- elif isinstance(primitive, Circle):
- hash += str(primitive.diameter * 1000000)[0:2]
-
- if len(hash) > 20:
- # The hash might actually get quite complex, so stop before
- # it gets too long
- break
-
- return hash
-
- def _get_amacro(self, amgroup, dcode = None):
- # Macros are a little special since we don't have a good way to compare them quickly
- # but in most cases, this should work
-
- hash = self._hash_amacro(amgroup)
- macro = None
- macroinfo = self._macros.get(hash, None)
-
- if macroinfo:
-
- # We have a definition, but check that the groups actually are the same
- for macro in macroinfo:
-
- # Macros should have positions, right? But if the macro is selected for non-flashes
- # then it won't have a position. This is of course a bad gerber, but they do exist
- if amgroup.position:
- position = amgroup.position
- else:
- position = (0, 0)
-
- offset = (position[0] - macro[1].position[0], position[1] - macro[1].position[1])
- if amgroup.equivalent(macro[1], offset):
- break
- macro = None
-
- # Did we find one in the group0
- if not macro:
- # This is a new macro, so define it
- if not dcode:
- dcode = self._next_dcode
- self._next_dcode += 1
- else:
- self._next_dcode = max(dcode + 1, self._next_dcode)
-
- # Create the statements
- # TODO
- amrenderer = AMGroupContext()
- statement = amrenderer.render(amgroup, hash)
-
- self.header.append(statement)
-
- aperdef = ADParamStmt.macro(dcode, hash)
- self.header.append(aperdef)
-
- # Store the dcode and the original so we can check if it really is the same
- # If it didn't have a postition, set it to 0, 0
- if amgroup.position == None:
- amgroup.position = (0, 0)
- macro = (aperdef, amgroup)
-
- if macroinfo:
- macroinfo.append(macro)
- else:
- self._macros[hash] = [macro]
-
- return macro[0]
-
- def _render_amgroup(self, amgroup, color):
-
- aper = self._get_amacro(amgroup)
- self._render_flash(amgroup, aper)
-
- def _render_inverted_layer(self):
- pass
-
- def new_render_layer(self):
- # TODO Might need to implement this
- pass
-
- def flatten(self):
- # TODO Might need to implement this
- pass
-
- def dump(self):
- """Write the rendered file to a StringIO steam"""
- statements = map(lambda stmt: stmt.to_gerber(self.settings), self.statements)
- stream = StringIO()
- for statement in statements:
- stream.write(statement + '\n')
-
- return stream
diff --git a/gerber/render/theme.py b/gerber/render/theme.py
deleted file mode 100644
index 2f558a1..0000000
--- a/gerber/render/theme.py
+++ /dev/null
@@ -1,112 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2013-2014 Paulo Henrique Silva <ph.silva@gmail.com>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-from .render import RenderSettings
-
-COLORS = {
- 'black': (0.0, 0.0, 0.0),
- 'white': (1.0, 1.0, 1.0),
- 'red': (1.0, 0.0, 0.0),
- 'green': (0.0, 1.0, 0.0),
- 'yellow': (1.0, 1.0, 0),
- 'blue': (0.0, 0.0, 1.0),
- 'fr-4': (0.290, 0.345, 0.0),
- 'green soldermask': (0.0, 0.412, 0.278),
- 'blue soldermask': (0.059, 0.478, 0.651),
- 'red soldermask': (0.968, 0.169, 0.165),
- 'black soldermask': (0.298, 0.275, 0.282),
- 'purple soldermask': (0.2, 0.0, 0.334),
- 'enig copper': (0.694, 0.533, 0.514),
- 'hasl copper': (0.871, 0.851, 0.839)
-}
-
-
-SPECTRUM = [
- (0.804, 0.216, 0),
- (0.78, 0.776, 0.251),
- (0.545, 0.451, 0.333),
- (0.545, 0.137, 0.137),
- (0.329, 0.545, 0.329),
- (0.133, 0.545, 0.133),
- (0, 0.525, 0.545),
- (0.227, 0.373, 0.804),
-]
-
-
-class Theme(object):
-
- def __init__(self, name=None, **kwargs):
- self.name = 'Default' if name is None else name
- self.background = kwargs.get('background', RenderSettings(COLORS['fr-4']))
- self.topsilk = kwargs.get('topsilk', RenderSettings(COLORS['white']))
- self.bottomsilk = kwargs.get('bottomsilk', RenderSettings(COLORS['white'], mirror=True))
- self.topmask = kwargs.get('topmask', RenderSettings(COLORS['green soldermask'], alpha=0.85, invert=True))
- self.bottommask = kwargs.get('bottommask', RenderSettings(COLORS['green soldermask'], alpha=0.85, invert=True, mirror=True))
- self.top = kwargs.get('top', RenderSettings(COLORS['hasl copper']))
- self.bottom = kwargs.get('bottom', RenderSettings(COLORS['hasl copper'], mirror=True))
- self.drill = kwargs.get('drill', RenderSettings(COLORS['black']))
- self.ipc_netlist = kwargs.get('ipc_netlist', RenderSettings(COLORS['red']))
- self._internal = kwargs.get('internal', [RenderSettings(x) for x in SPECTRUM])
- self._internal_gen = None
-
- def __getitem__(self, key):
- return getattr(self, key)
-
- @property
- def internal(self):
- if not self._internal_gen:
- self._internal_gen = self._internal_gen_func()
- return next(self._internal_gen)
-
- def _internal_gen_func(self):
- for setting in self._internal:
- yield setting
-
- def get(self, key, noneval=None):
- val = getattr(self, key, None)
- return val if val is not None else noneval
-
-
-THEMES = {
- 'default': Theme(),
- 'OSH Park': Theme(name='OSH Park',
- background=RenderSettings(COLORS['purple soldermask']),
- top=RenderSettings(COLORS['enig copper']),
- bottom=RenderSettings(COLORS['enig copper'], mirror=True),
- topmask=RenderSettings(COLORS['purple soldermask'], alpha=0.85, invert=True),
- bottommask=RenderSettings(COLORS['purple soldermask'], alpha=0.85, invert=True, mirror=True),
- topsilk=RenderSettings(COLORS['white'], alpha=0.8),
- bottomsilk=RenderSettings(COLORS['white'], alpha=0.8, mirror=True)),
-
- 'Blue': Theme(name='Blue',
- topmask=RenderSettings(COLORS['blue soldermask'], alpha=0.8, invert=True),
- bottommask=RenderSettings(COLORS['blue soldermask'], alpha=0.8, invert=True)),
-
- 'Transparent Copper': Theme(name='Transparent',
- background=RenderSettings((0.9, 0.9, 0.9)),
- top=RenderSettings(COLORS['red'], alpha=0.5),
- bottom=RenderSettings(COLORS['blue'], alpha=0.5),
- drill=RenderSettings((0.3, 0.3, 0.3))),
-
- 'Transparent Multilayer': Theme(name='Transparent Multilayer',
- background=RenderSettings((0, 0, 0)),
- top=RenderSettings(SPECTRUM[0], alpha=0.8),
- bottom=RenderSettings(SPECTRUM[-1], alpha=0.8),
- drill=RenderSettings((0.3, 0.3, 0.3)),
- internal=[RenderSettings(x, alpha=0.5) for x in SPECTRUM[1:-1]]),
-}
diff --git a/gerber/rs274x.py b/gerber/rs274x.py
deleted file mode 100644
index afdf45f..0000000
--- a/gerber/rs274x.py
+++ /dev/null
@@ -1,800 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-# Modified from parser.py by Paulo Henrique Silva <ph.silva@gmail.com>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-""" This module provides an RS-274-X class and parser.
-"""
-
-import copy
-import json
-import os
-import re
-import sys
-
-try:
- from cStringIO import StringIO
-except(ImportError):
- from io import StringIO
-
-from .gerber_statements import *
-from .primitives import *
-from .cam import CamFile, FileSettings
-from .utils import sq_distance
-
-
-def read(filename):
- """ Read data from filename and return a GerberFile
-
- Parameters
- ----------
- filename : string
- Filename of file to parse
-
- Returns
- -------
- file : :class:`gerber.rs274x.GerberFile`
- A GerberFile created from the specified file.
- """
- return GerberParser().parse(filename)
-
-
-def loads(data, filename=None):
- """ Generate a GerberFile object from rs274x data in memory
-
- Parameters
- ----------
- data : string
- string containing gerber file contents
-
- filename : string, optional
- string containing the filename of the data source
-
- Returns
- -------
- file : :class:`gerber.rs274x.GerberFile`
- A GerberFile created from the specified file.
- """
- return GerberParser().parse_raw(data, filename)
-
-
-class GerberFile(CamFile):
- """ A class representing a single gerber file
-
- The GerberFile class represents a single gerber file.
-
- Parameters
- ----------
- statements : list
- list of gerber file statements
-
- settings : dict
- Dictionary of gerber file settings
-
- filename : string
- Filename of the source gerber file
-
- Attributes
- ----------
- comments: list of strings
- List of comments contained in the gerber file.
-
- size : tuple, (<float>, <float>)
- Size in [self.units] of the layer described by the gerber file.
-
- bounds: tuple, ((<float>, <float>), (<float>, <float>))
- boundaries of the layer described by the gerber file.
- `bounds` is stored as ((min x, max x), (min y, max y))
-
- """
-
- def __init__(self, statements, settings, primitives, apertures, filename=None):
- super(GerberFile, self).__init__(statements, settings, primitives, filename)
-
- self.apertures = apertures
-
- @property
- def comments(self):
- return [comment.comment for comment in self.statements
- if isinstance(comment, CommentStmt)]
-
- @property
- def size(self):
- xbounds, ybounds = self.bounds
- return (xbounds[1] - xbounds[0], ybounds[1] - ybounds[0])
-
- @property
- def bounds(self):
- min_x = min_y = 1000000
- max_x = max_y = -1000000
-
- for stmt in [stmt for stmt in self.statements if isinstance(stmt, CoordStmt)]:
- if stmt.x is not None:
- min_x = min(stmt.x, min_x)
- max_x = max(stmt.x, max_x)
-
- if stmt.y is not None:
- min_y = min(stmt.y, min_y)
- max_y = max(stmt.y, max_y)
-
- return ((min_x, max_x), (min_y, max_y))
-
- @property
- def bounding_box(self):
- min_x = min_y = 1000000
- max_x = max_y = -1000000
-
- for prim in self.primitives:
- bounds = prim.bounding_box
- min_x = min(bounds[0][0], min_x)
- max_x = max(bounds[0][1], max_x)
-
- min_y = min(bounds[1][0], min_y)
- max_y = max(bounds[1][1], max_y)
-
- return ((min_x, max_x), (min_y, max_y))
-
- def write(self, filename, settings=None):
- """ Write data out to a gerber file.
- """
- with open(filename, 'w') as f:
- for statement in self.statements:
- f.write(statement.to_gerber(settings or self.settings))
- f.write("\n")
-
- def to_inch(self):
- if self.units != 'inch':
- self.units = 'inch'
- for statement in self.statements:
- statement.to_inch()
- for primitive in self.primitives:
- primitive.to_inch()
-
- def to_metric(self):
- if self.units != 'metric':
- self.units = 'metric'
- for statement in self.statements:
- statement.to_metric()
- for primitive in self.primitives:
- primitive.to_metric()
-
- def offset(self, x_offset=0, y_offset=0):
- for statement in self.statements:
- statement.offset(x_offset, y_offset)
- for primitive in self.primitives:
- primitive.offset(x_offset, y_offset)
-
-
-class GerberParser(object):
- """ GerberParser
- """
- NUMBER = r"[\+-]?\d+"
- DECIMAL = r"[\+-]?\d+([.]?\d+)?"
- STRING = r"[a-zA-Z0-9_+\-/!?<>”’(){}.\|&@# :]+"
- NAME = r"[a-zA-Z_$\.][a-zA-Z_$\.0-9+\-]+"
-
- FS = r"(?P<param>FS)(?P<zero>(L|T|D))?(?P<notation>(A|I))[NG0-9]*X(?P<x>[0-7][0-7])Y(?P<y>[0-7][0-7])[DM0-9]*"
- MO = r"(?P<param>MO)(?P<mo>(MM|IN))"
- LP = r"(?P<param>LP)(?P<lp>(D|C))"
- AD_CIRCLE = r"(?P<param>AD)D(?P<d>\d+)(?P<shape>C)[,]?(?P<modifiers>[^,%]*)"
- AD_RECT = r"(?P<param>AD)D(?P<d>\d+)(?P<shape>R)[,](?P<modifiers>[^,%]*)"
- AD_OBROUND = r"(?P<param>AD)D(?P<d>\d+)(?P<shape>O)[,](?P<modifiers>[^,%]*)"
- AD_POLY = r"(?P<param>AD)D(?P<d>\d+)(?P<shape>P)[,](?P<modifiers>[^,%]*)"
- AD_MACRO = r"(?P<param>AD)D(?P<d>\d+)(?P<shape>{name})[,]?(?P<modifiers>[^,%]*)".format(name=NAME)
- AM = r"(?P<param>AM)(?P<name>{name})\*(?P<macro>[^%]*)".format(name=NAME)
- # Include File
- IF = r"(?P<param>IF)(?P<filename>.*)"
-
-
- # begin deprecated
- AS = r"(?P<param>AS)(?P<mode>(AXBY)|(AYBX))"
- IN = r"(?P<param>IN)(?P<name>.*)"
- IP = r"(?P<param>IP)(?P<ip>(POS|NEG))"
- IR = r"(?P<param>IR)(?P<angle>{number})".format(number=NUMBER)
- MI = r"(?P<param>MI)(A(?P<a>0|1))?(B(?P<b>0|1))?"
- OF = r"(?P<param>OF)(A(?P<a>{decimal}))?(B(?P<b>{decimal}))?".format(decimal=DECIMAL)
- SF = r"(?P<param>SF)(?P<discarded>.*)"
- LN = r"(?P<param>LN)(?P<name>.*)"
- DEPRECATED_UNIT = re.compile(r'(?P<mode>G7[01])\*')
- DEPRECATED_FORMAT = re.compile(r'(?P<format>G9[01])\*')
- # end deprecated
-
- PARAMS = (FS, MO, LP, AD_CIRCLE, AD_RECT, AD_OBROUND, AD_POLY,
- AD_MACRO, AM, AS, IF, IN, IP, IR, MI, OF, SF, LN)
-
- PARAM_STMT = [re.compile(r"%?{0}\*%?".format(p)) for p in PARAMS]
-
- COORD_FUNCTION = r"G0?[123]"
- COORD_OP = r"D0?[123]"
-
- COORD_STMT = re.compile((
- r"(?P<function>{function})?"
- r"(X(?P<x>{number}))?(Y(?P<y>{number}))?"
- r"(I(?P<i>{number}))?(J(?P<j>{number}))?"
- r"(?P<op>{op})?\*".format(number=NUMBER, function=COORD_FUNCTION, op=COORD_OP)))
-
- APERTURE_STMT = re.compile(r"(?P<deprecated>(G54)|(G55))?D(?P<d>\d+)\*")
-
- COMMENT_STMT = re.compile(r"G0?4(?P<comment>[^*]*)(\*)?")
-
- EOF_STMT = re.compile(r"(?P<eof>M[0]?[012])\*")
-
- REGION_MODE_STMT = re.compile(r'(?P<mode>G3[67])\*')
- QUAD_MODE_STMT = re.compile(r'(?P<mode>G7[45])\*')
-
- # Keep include loop from crashing us
- INCLUDE_FILE_RECURSION_LIMIT = 10
-
- def __init__(self):
- self.filename = None
- self.settings = FileSettings()
- self.statements = []
- self.primitives = []
- self.apertures = {}
- self.macros = {}
- self.current_region = None
- self.x = 0
- self.y = 0
- self.op = "D02"
- self.aperture = 0
- self.interpolation = 'linear'
- self.direction = 'clockwise'
- self.image_polarity = 'positive'
- self.level_polarity = 'dark'
- self.region_mode = 'off'
- self.quadrant_mode = 'multi-quadrant'
- self.step_and_repeat = (1, 1, 0, 0)
- self._recursion_depth = 0
-
- def parse(self, filename):
- self.filename = filename
- with open(filename, "rU") as fp:
- data = fp.read()
- return self.parse_raw(data, filename)
-
- def parse_raw(self, data, filename=None):
- self.filename = filename
- for stmt in self._parse(self._split_commands(data)):
- self.evaluate(stmt)
- self.statements.append(stmt)
-
- # Initialize statement units
- for stmt in self.statements:
- stmt.units = self.settings.units
-
- return GerberFile(self.statements, self.settings, self.primitives, self.apertures.values(), filename)
-
- def _split_commands(self, data):
- """
- Split the data into commands. Commands end with * (and also newline to help with some badly formatted files)
- """
-
- length = len(data)
- start = 0
- in_header = True
-
- for cur in range(0, length):
-
- val = data[cur]
-
- if val == '%' and start == cur:
- in_header = True
- continue
-
- if val == '\r' or val == '\n':
- if start != cur:
- yield data[start:cur]
- start = cur + 1
-
- elif not in_header and val == '*':
- yield data[start:cur + 1]
- start = cur + 1
-
- elif in_header and val == '%':
- yield data[start:cur + 1]
- start = cur + 1
- in_header = False
-
- def dump_json(self):
- stmts = {"statements": [stmt.__dict__ for stmt in self.statements]}
- return json.dumps(stmts)
-
- def dump_str(self):
- string = ""
- for stmt in self.statements:
- string += str(stmt) + "\n"
- return string
-
- def _parse(self, data):
- oldline = ''
-
- for line in data:
- line = oldline + line.strip()
-
- # skip empty lines
- if not len(line):
- continue
-
- # deal with multi-line parameters
- if line.startswith("%") and not line.endswith("%") and not "%" in line[1:]:
- oldline = line
- continue
-
- did_something = True # make sure we do at least one loop
- while did_something and len(line) > 0:
- did_something = False
-
- # consume empty data blocks
- if line[0] == '*':
- line = line[1:]
- did_something = True
- continue
-
- # coord
- (coord, r) = _match_one(self.COORD_STMT, line)
- if coord:
- yield CoordStmt.from_dict(coord, self.settings)
- line = r
- did_something = True
- continue
-
- # aperture selection
- (aperture, r) = _match_one(self.APERTURE_STMT, line)
- if aperture:
- yield ApertureStmt(**aperture)
- did_something = True
- line = r
- continue
-
- # parameter
- (param, r) = _match_one_from_many(self.PARAM_STMT, line)
-
- if param:
- if param["param"] == "FS":
- stmt = FSParamStmt.from_dict(param)
- self.settings.zero_suppression = stmt.zero_suppression
- self.settings.format = stmt.format
- self.settings.notation = stmt.notation
- yield stmt
- elif param["param"] == "MO":
- stmt = MOParamStmt.from_dict(param)
- self.settings.units = stmt.mode
- yield stmt
- elif param["param"] == "LP":
- yield LPParamStmt.from_dict(param)
- elif param["param"] == "AD":
- yield ADParamStmt.from_dict(param)
- elif param["param"] == "AM":
- stmt = AMParamStmt.from_dict(param)
- stmt.units = self.settings.units
- yield stmt
- elif param["param"] == "OF":
- yield OFParamStmt.from_dict(param)
- elif param["param"] == "IF":
- # Don't crash on include loop
- if self._recursion_depth < self.INCLUDE_FILE_RECURSION_LIMIT:
- self._recursion_depth += 1
- with open(os.path.join(os.path.dirname(self.filename), param["filename"]), 'r') as f:
- inc_data = f.read()
- for stmt in self._parse(self._split_commands(inc_data)):
- yield stmt
- self._recursion_depth -= 1
- else:
- raise IOError("Include file nesting depth limit exceeded.")
- elif param["param"] == "IN":
- yield INParamStmt.from_dict(param)
- elif param["param"] == "LN":
- yield LNParamStmt.from_dict(param)
- # deprecated commands AS, IN, IP, IR, MI, OF, SF, LN
- elif param["param"] == "AS":
- yield ASParamStmt.from_dict(param)
- elif param["param"] == "IN":
- yield INParamStmt.from_dict(param)
- elif param["param"] == "IP":
- yield IPParamStmt.from_dict(param)
- elif param["param"] == "IR":
- yield IRParamStmt.from_dict(param)
- elif param["param"] == "MI":
- yield MIParamStmt.from_dict(param)
- elif param["param"] == "OF":
- yield OFParamStmt.from_dict(param)
- elif param["param"] == "SF":
- yield SFParamStmt.from_dict(param)
- elif param["param"] == "LN":
- yield LNParamStmt.from_dict(param)
- else:
- yield UnknownStmt(line)
-
- did_something = True
- line = r
- continue
-
- # Region Mode
- (mode, r) = _match_one(self.REGION_MODE_STMT, line)
- if mode:
- yield RegionModeStmt.from_gerber(line)
- line = r
- did_something = True
- continue
-
- # Quadrant Mode
- (mode, r) = _match_one(self.QUAD_MODE_STMT, line)
- if mode:
- yield QuadrantModeStmt.from_gerber(line)
- line = r
- did_something = True
- continue
-
- # comment
- (comment, r) = _match_one(self.COMMENT_STMT, line)
- if comment:
- yield CommentStmt(comment["comment"])
- did_something = True
- line = r
- continue
-
- # deprecated codes
- (deprecated_unit, r) = _match_one(self.DEPRECATED_UNIT, line)
- if deprecated_unit:
- stmt = MOParamStmt(param="MO", mo="inch" if "G70" in
- deprecated_unit["mode"] else "metric")
- self.settings.units = stmt.mode
- yield stmt
- line = r
- did_something = True
- continue
-
- (deprecated_format, r) = _match_one(self.DEPRECATED_FORMAT, line)
- if deprecated_format:
- yield DeprecatedStmt.from_gerber(line)
- line = r
- did_something = True
- continue
-
- # eof
- (eof, r) = _match_one(self.EOF_STMT, line)
- if eof:
- yield EofStmt()
- did_something = True
- line = r
- continue
-
- if line.find('*') > 0:
- yield UnknownStmt(line)
- did_something = True
- line = ""
- continue
-
- oldline = line
-
- def evaluate(self, stmt):
- """ Evaluate Gerber statement and update image accordingly.
-
- This method is called once for each statement in the file as it
- is parsed.
-
- Parameters
- ----------
- statement : Statement
- Gerber/Excellon statement to evaluate.
-
- """
- if isinstance(stmt, CoordStmt):
- self._evaluate_coord(stmt)
-
- elif isinstance(stmt, ParamStmt):
- self._evaluate_param(stmt)
-
- elif isinstance(stmt, ApertureStmt):
- self._evaluate_aperture(stmt)
-
- elif isinstance(stmt, (RegionModeStmt, QuadrantModeStmt)):
- self._evaluate_mode(stmt)
-
- elif isinstance(stmt, (CommentStmt, UnknownStmt, DeprecatedStmt, EofStmt)):
- return
-
- else:
- raise Exception("Invalid statement to evaluate")
-
- def _define_aperture(self, d, shape, modifiers):
- aperture = None
- if shape == 'C':
- diameter = modifiers[0][0]
-
- hole_diameter = 0
- rectangular_hole = (0, 0)
- if len(modifiers[0]) == 2:
- hole_diameter = modifiers[0][1]
- elif len(modifiers[0]) == 3:
- rectangular_hole = modifiers[0][1:3]
-
- aperture = Circle(position=None, diameter=diameter,
- hole_diameter=hole_diameter,
- hole_width=rectangular_hole[0],
- hole_height=rectangular_hole[1],
- units=self.settings.units)
-
- elif shape == 'R':
- width = modifiers[0][0]
- height = modifiers[0][1]
-
- hole_diameter = 0
- rectangular_hole = (0, 0)
- if len(modifiers[0]) == 3:
- hole_diameter = modifiers[0][2]
- elif len(modifiers[0]) == 4:
- rectangular_hole = modifiers[0][2:4]
-
- aperture = Rectangle(position=None, width=width, height=height,
- hole_diameter=hole_diameter,
- hole_width=rectangular_hole[0],
- hole_height=rectangular_hole[1],
- units=self.settings.units)
- elif shape == 'O':
- width = modifiers[0][0]
- height = modifiers[0][1]
-
- hole_diameter = 0
- rectangular_hole = (0, 0)
- if len(modifiers[0]) == 3:
- hole_diameter = modifiers[0][2]
- elif len(modifiers[0]) == 4:
- rectangular_hole = modifiers[0][2:4]
-
- aperture = Obround(position=None, width=width, height=height,
- hole_diameter=hole_diameter,
- hole_width=rectangular_hole[0],
- hole_height=rectangular_hole[1],
- units=self.settings.units)
- elif shape == 'P':
- outer_diameter = modifiers[0][0]
- number_vertices = int(modifiers[0][1])
- if len(modifiers[0]) > 2:
- rotation = modifiers[0][2]
- else:
- rotation = 0
-
- hole_diameter = 0
- rectangular_hole = (0, 0)
- if len(modifiers[0]) == 4:
- hole_diameter = modifiers[0][3]
- elif len(modifiers[0]) >= 5:
- rectangular_hole = modifiers[0][3:5]
-
- aperture = Polygon(position=None, sides=number_vertices,
- radius=outer_diameter/2.0,
- hole_diameter=hole_diameter,
- hole_width=rectangular_hole[0],
- hole_height=rectangular_hole[1],
- rotation=rotation)
- else:
- aperture = self.macros[shape].build(modifiers)
-
- aperture.units = self.settings.units
- self.apertures[d] = aperture
-
- def _evaluate_mode(self, stmt):
- if stmt.type == 'RegionMode':
- if self.region_mode == 'on' and stmt.mode == 'off':
- # Sometimes we have regions that have no points. Skip those
- if self.current_region:
- self.primitives.append(Region(self.current_region,
- level_polarity=self.level_polarity, units=self.settings.units))
-
- self.current_region = None
- self.region_mode = stmt.mode
- elif stmt.type == 'QuadrantMode':
- self.quadrant_mode = stmt.mode
-
- def _evaluate_param(self, stmt):
- if stmt.param == "FS":
- self.settings.zero_suppression = stmt.zero_suppression
- self.settings.format = stmt.format
- self.settings.notation = stmt.notation
- elif stmt.param == "MO":
- self.settings.units = stmt.mode
- elif stmt.param == "IP":
- self.image_polarity = stmt.ip
- elif stmt.param == "LP":
- self.level_polarity = stmt.lp
- elif stmt.param == "AM":
- self.macros[stmt.name] = stmt
- elif stmt.param == "AD":
- self._define_aperture(stmt.d, stmt.shape, stmt.modifiers)
-
- def _evaluate_coord(self, stmt):
- x = self.x if stmt.x is None else stmt.x
- y = self.y if stmt.y is None else stmt.y
-
- if stmt.function in ("G01", "G1"):
- self.interpolation = 'linear'
- elif stmt.function in ('G02', 'G2', 'G03', 'G3'):
- self.interpolation = 'arc'
- self.direction = ('clockwise' if stmt.function in
- ('G02', 'G2') else 'counterclockwise')
-
- if stmt.only_function:
- # Sometimes we get a coordinate statement
- # that only sets the function. If so, don't
- # try futher otherwise that might draw/flash something
- return
-
- if stmt.op:
- self.op = stmt.op
- else:
- # no implicit op allowed, force here if coord block doesn't have it
- stmt.op = self.op
-
- if self.op == "D01" or self.op == "D1":
- start = (self.x, self.y)
- end = (x, y)
-
- if self.interpolation == 'linear':
- if self.region_mode == 'off':
- self.primitives.append(Line(start, end,
- self.apertures[self.aperture],
- level_polarity=self.level_polarity,
- units=self.settings.units))
- else:
- # from gerber spec revision J3, Section 4.5, page 55:
- # The segments are not graphics objects in themselves; segments are part of region which is the graphics object. The segments have no thickness.
- # The current aperture is associated with the region.
- # This has no graphical effect, but allows all its attributes to
- # be applied to the region.
-
- if self.current_region is None:
- self.current_region = [Line(start, end,
- self.apertures.get(self.aperture,
- Circle((0, 0), 0)),
- level_polarity=self.level_polarity,
- units=self.settings.units), ]
- else:
- self.current_region.append(Line(start, end,
- self.apertures.get(self.aperture,
- Circle((0, 0), 0)),
- level_polarity=self.level_polarity,
- units=self.settings.units))
- else:
- i = 0 if stmt.i is None else stmt.i
- j = 0 if stmt.j is None else stmt.j
- center = self._find_center(start, end, (i, j))
- if self.region_mode == 'off':
- self.primitives.append(Arc(start, end, center, self.direction,
- self.apertures[self.aperture],
- quadrant_mode=self.quadrant_mode,
- level_polarity=self.level_polarity,
- units=self.settings.units))
- else:
- if self.current_region is None:
- self.current_region = [Arc(start, end, center, self.direction,
- self.apertures.get(self.aperture, Circle((0,0), 0)),
- quadrant_mode=self.quadrant_mode,
- level_polarity=self.level_polarity,
- units=self.settings.units),]
- else:
- self.current_region.append(Arc(start, end, center, self.direction,
- self.apertures.get(self.aperture, Circle((0,0), 0)),
- quadrant_mode=self.quadrant_mode,
- level_polarity=self.level_polarity,
- units=self.settings.units))
- # Gerbv seems to reset interpolation mode in regions..
- # TODO: Make sure this is right.
- self.interpolation = 'linear'
-
- elif self.op == "D02" or self.op == "D2":
-
- if self.region_mode == "on":
- # D02 in the middle of a region finishes that region and starts a new one
- if self.current_region and len(self.current_region) > 1:
- self.primitives.append(Region(self.current_region,
- level_polarity=self.level_polarity,
- units=self.settings.units))
- self.current_region = None
-
- elif self.op == "D03" or self.op == "D3":
- primitive = copy.deepcopy(self.apertures[self.aperture])
-
- if primitive is not None:
-
- if not isinstance(primitive, AMParamStmt):
- primitive.position = (x, y)
- primitive.level_polarity = self.level_polarity
- primitive.units = self.settings.units
- self.primitives.append(primitive)
- else:
- # Aperture Macro
- for am_prim in primitive.primitives:
- renderable = am_prim.to_primitive((x, y),
- self.level_polarity,
- self.settings.units)
- if renderable is not None:
- self.primitives.append(renderable)
- self.x, self.y = x, y
-
- def _find_center(self, start, end, offsets):
- """
- In single quadrant mode, the offsets are always positive, which means
- there are 4 possible centers. The correct center is the only one that
- results in an arc with sweep angle of less than or equal to 90 degrees
- in the specified direction
- """
- two_pi = 2 * math.pi
- if self.quadrant_mode == 'single-quadrant':
- # The Gerber spec says single quadrant only has one possible center,
- # and you can detect it based on the angle. But for real files, this
- # seems to work better - there is usually only one option that makes
- # sense for the center (since the distance should be the same
- # from start and end). We select the center with the least error in
- # radius from all the options with a valid sweep angle.
-
- sqdist_diff_min = sys.maxsize
- center = None
- for factors in [(1, 1), (1, -1), (-1, 1), (-1, -1)]:
-
- test_center = (start[0] + offsets[0] * factors[0],
- start[1] + offsets[1] * factors[1])
-
- # Find angle from center to start and end points
- start_angle = math.atan2(*reversed([_start - _center for _start, _center in zip(start, test_center)]))
- end_angle = math.atan2(*reversed([_end - _center for _end, _center in zip(end, test_center)]))
-
- # Clamp angles to 0, 2pi
- theta0 = (start_angle + two_pi) % two_pi
- theta1 = (end_angle + two_pi) % two_pi
-
- # Determine sweep angle in the current arc direction
- if self.direction == 'counterclockwise':
- sweep_angle = abs(theta1 - theta0)
- else:
- theta0 += two_pi
- sweep_angle = abs(theta0 - theta1) % two_pi
-
- # Calculate the radius error
- sqdist_start = sq_distance(start, test_center)
- sqdist_end = sq_distance(end, test_center)
- sqdist_diff = abs(sqdist_start - sqdist_end)
-
- # Take the option with the lowest radius error from the set of
- # options with a valid sweep angle
- # In some rare cases, the sweep angle is numerically (10**-14) above pi/2
- # So it is safer to compare the angles with some tolerance
- is_lowest_radius_error = sqdist_diff < sqdist_diff_min
- is_valid_sweep_angle = sweep_angle >= 0 and sweep_angle <= math.pi / 2.0 + 1e-6
- if is_lowest_radius_error and is_valid_sweep_angle:
- center = test_center
- sqdist_diff_min = sqdist_diff
- return center
- else:
- return (start[0] + offsets[0], start[1] + offsets[1])
-
- def _evaluate_aperture(self, stmt):
- self.aperture = stmt.d
-
-def _match_one(expr, data):
- match = expr.match(data)
- if match is None:
- return ({}, None)
- else:
- return (match.groupdict(), data[match.end(0):])
-
-
-def _match_one_from_many(exprs, data):
- for expr in exprs:
- match = expr.match(data)
- if match:
- return (match.groupdict(), data[match.end(0):])
-
- return ({}, None)
diff --git a/gerber/tests/__init__.py b/gerber/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/gerber/tests/__init__.py
+++ /dev/null
diff --git a/gerber/tests/golden/example_am_exposure_modifier.png b/gerber/tests/golden/example_am_exposure_modifier.png
deleted file mode 100644
index dac951f..0000000
--- a/gerber/tests/golden/example_am_exposure_modifier.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_coincident_hole.png b/gerber/tests/golden/example_coincident_hole.png
deleted file mode 100644
index 9855b11..0000000
--- a/gerber/tests/golden/example_coincident_hole.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_cutin_multiple.png b/gerber/tests/golden/example_cutin_multiple.png
deleted file mode 100644
index ebc1191..0000000
--- a/gerber/tests/golden/example_cutin_multiple.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_flash_circle.png b/gerber/tests/golden/example_flash_circle.png
deleted file mode 100644
index 0c407f6..0000000
--- a/gerber/tests/golden/example_flash_circle.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_flash_obround.png b/gerber/tests/golden/example_flash_obround.png
deleted file mode 100644
index 2fd4dc3..0000000
--- a/gerber/tests/golden/example_flash_obround.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_flash_polygon.png b/gerber/tests/golden/example_flash_polygon.png
deleted file mode 100644
index 89a964b..0000000
--- a/gerber/tests/golden/example_flash_polygon.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_flash_rectangle.png b/gerber/tests/golden/example_flash_rectangle.png
deleted file mode 100644
index 797e0c3..0000000
--- a/gerber/tests/golden/example_flash_rectangle.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_fully_coincident.png b/gerber/tests/golden/example_fully_coincident.png
deleted file mode 100644
index 4e522ff..0000000
--- a/gerber/tests/golden/example_fully_coincident.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_holes_dont_clear.png b/gerber/tests/golden/example_holes_dont_clear.png
deleted file mode 100644
index 7efb67b..0000000
--- a/gerber/tests/golden/example_holes_dont_clear.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_not_overlapping_contour.png b/gerber/tests/golden/example_not_overlapping_contour.png
deleted file mode 100644
index 4e522ff..0000000
--- a/gerber/tests/golden/example_not_overlapping_contour.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_not_overlapping_touching.png b/gerber/tests/golden/example_not_overlapping_touching.png
deleted file mode 100644
index d485495..0000000
--- a/gerber/tests/golden/example_not_overlapping_touching.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_overlapping_contour.png b/gerber/tests/golden/example_overlapping_contour.png
deleted file mode 100644
index 7504311..0000000
--- a/gerber/tests/golden/example_overlapping_contour.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_overlapping_touching.png b/gerber/tests/golden/example_overlapping_touching.png
deleted file mode 100644
index 7504311..0000000
--- a/gerber/tests/golden/example_overlapping_touching.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_simple_contour.png b/gerber/tests/golden/example_simple_contour.png
deleted file mode 100644
index 564ae14..0000000
--- a/gerber/tests/golden/example_simple_contour.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_single_contour.png b/gerber/tests/golden/example_single_contour.png
deleted file mode 100644
index 3341638..0000000
--- a/gerber/tests/golden/example_single_contour.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_single_contour_3.png b/gerber/tests/golden/example_single_contour_3.png
deleted file mode 100644
index 1eecfee..0000000
--- a/gerber/tests/golden/example_single_contour_3.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_single_quadrant.gbr b/gerber/tests/golden/example_single_quadrant.gbr
deleted file mode 100644
index b0a3166..0000000
--- a/gerber/tests/golden/example_single_quadrant.gbr
+++ /dev/null
@@ -1,16 +0,0 @@
-%FSLAX23Y23*%
-%MOIN*%
-%ADD10C,0.01*%
-G74*
-D10*
-%LPD*%
-G01X1100Y600D02*
-G03X700Y1000I-400J0D01*
-G03X300Y600I0J-400D01*
-G03X700Y200I400J0D01*
-G03X1100Y600I0J400D01*
-G01X300D02*
-X1100D01*
-X700Y200D02*
-Y1000D01*
-M02*
diff --git a/gerber/tests/golden/example_single_quadrant.png b/gerber/tests/golden/example_single_quadrant.png
deleted file mode 100644
index 89b763f..0000000
--- a/gerber/tests/golden/example_single_quadrant.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/golden/example_two_square_boxes.gbr b/gerber/tests/golden/example_two_square_boxes.gbr
deleted file mode 100644
index b5c60d1..0000000
--- a/gerber/tests/golden/example_two_square_boxes.gbr
+++ /dev/null
@@ -1,16 +0,0 @@
-%FSLAX25Y25*%
-%MOMM*%
-%ADD10C,0.01*%
-D10*
-%LPD*%
-G01X0Y0D02*
-X500000D01*
-Y500000D01*
-X0D01*
-Y0D01*
-X600000D02*
-X1100000D01*
-Y500000D01*
-X600000D01*
-Y0D01*
-M02*
diff --git a/gerber/tests/golden/example_two_square_boxes.png b/gerber/tests/golden/example_two_square_boxes.png
deleted file mode 100644
index 98d0518..0000000
--- a/gerber/tests/golden/example_two_square_boxes.png
+++ /dev/null
Binary files differ
diff --git a/gerber/tests/resources/board_outline.GKO b/gerber/tests/resources/board_outline.GKO
deleted file mode 100644
index 40b8c7d..0000000
--- a/gerber/tests/resources/board_outline.GKO
+++ /dev/null
@@ -1,503 +0,0 @@
-G75*
-%MOIN*%
-%OFA0B0*%
-%FSLAX24Y24*%
-%IPPOS*%
-%LPD*%
-%AMOC8*
-5,1,8,0,0,1.08239X$1,22.5*
-%
-%ADD10C,0.0000*%
-%ADD11C,0.0004*%
-D10*
-X000300Y003064D02*
-X000300Y018064D01*
-X022800Y018064D01*
-X022800Y003064D01*
-X000300Y003064D01*
-X001720Y005114D02*
-X001722Y005164D01*
-X001728Y005214D01*
-X001738Y005263D01*
-X001752Y005311D01*
-X001769Y005358D01*
-X001790Y005403D01*
-X001815Y005447D01*
-X001843Y005488D01*
-X001875Y005527D01*
-X001909Y005564D01*
-X001946Y005598D01*
-X001986Y005628D01*
-X002028Y005655D01*
-X002072Y005679D01*
-X002118Y005700D01*
-X002165Y005716D01*
-X002213Y005729D01*
-X002263Y005738D01*
-X002312Y005743D01*
-X002363Y005744D01*
-X002413Y005741D01*
-X002462Y005734D01*
-X002511Y005723D01*
-X002559Y005708D01*
-X002605Y005690D01*
-X002650Y005668D01*
-X002693Y005642D01*
-X002734Y005613D01*
-X002773Y005581D01*
-X002809Y005546D01*
-X002841Y005508D01*
-X002871Y005468D01*
-X002898Y005425D01*
-X002921Y005381D01*
-X002940Y005335D01*
-X002956Y005287D01*
-X002968Y005238D01*
-X002976Y005189D01*
-X002980Y005139D01*
-X002980Y005089D01*
-X002976Y005039D01*
-X002968Y004990D01*
-X002956Y004941D01*
-X002940Y004893D01*
-X002921Y004847D01*
-X002898Y004803D01*
-X002871Y004760D01*
-X002841Y004720D01*
-X002809Y004682D01*
-X002773Y004647D01*
-X002734Y004615D01*
-X002693Y004586D01*
-X002650Y004560D01*
-X002605Y004538D01*
-X002559Y004520D01*
-X002511Y004505D01*
-X002462Y004494D01*
-X002413Y004487D01*
-X002363Y004484D01*
-X002312Y004485D01*
-X002263Y004490D01*
-X002213Y004499D01*
-X002165Y004512D01*
-X002118Y004528D01*
-X002072Y004549D01*
-X002028Y004573D01*
-X001986Y004600D01*
-X001946Y004630D01*
-X001909Y004664D01*
-X001875Y004701D01*
-X001843Y004740D01*
-X001815Y004781D01*
-X001790Y004825D01*
-X001769Y004870D01*
-X001752Y004917D01*
-X001738Y004965D01*
-X001728Y005014D01*
-X001722Y005064D01*
-X001720Y005114D01*
-X001670Y016064D02*
-X001672Y016114D01*
-X001678Y016164D01*
-X001688Y016213D01*
-X001702Y016261D01*
-X001719Y016308D01*
-X001740Y016353D01*
-X001765Y016397D01*
-X001793Y016438D01*
-X001825Y016477D01*
-X001859Y016514D01*
-X001896Y016548D01*
-X001936Y016578D01*
-X001978Y016605D01*
-X002022Y016629D01*
-X002068Y016650D01*
-X002115Y016666D01*
-X002163Y016679D01*
-X002213Y016688D01*
-X002262Y016693D01*
-X002313Y016694D01*
-X002363Y016691D01*
-X002412Y016684D01*
-X002461Y016673D01*
-X002509Y016658D01*
-X002555Y016640D01*
-X002600Y016618D01*
-X002643Y016592D01*
-X002684Y016563D01*
-X002723Y016531D01*
-X002759Y016496D01*
-X002791Y016458D01*
-X002821Y016418D01*
-X002848Y016375D01*
-X002871Y016331D01*
-X002890Y016285D01*
-X002906Y016237D01*
-X002918Y016188D01*
-X002926Y016139D01*
-X002930Y016089D01*
-X002930Y016039D01*
-X002926Y015989D01*
-X002918Y015940D01*
-X002906Y015891D01*
-X002890Y015843D01*
-X002871Y015797D01*
-X002848Y015753D01*
-X002821Y015710D01*
-X002791Y015670D01*
-X002759Y015632D01*
-X002723Y015597D01*
-X002684Y015565D01*
-X002643Y015536D01*
-X002600Y015510D01*
-X002555Y015488D01*
-X002509Y015470D01*
-X002461Y015455D01*
-X002412Y015444D01*
-X002363Y015437D01*
-X002313Y015434D01*
-X002262Y015435D01*
-X002213Y015440D01*
-X002163Y015449D01*
-X002115Y015462D01*
-X002068Y015478D01*
-X002022Y015499D01*
-X001978Y015523D01*
-X001936Y015550D01*
-X001896Y015580D01*
-X001859Y015614D01*
-X001825Y015651D01*
-X001793Y015690D01*
-X001765Y015731D01*
-X001740Y015775D01*
-X001719Y015820D01*
-X001702Y015867D01*
-X001688Y015915D01*
-X001678Y015964D01*
-X001672Y016014D01*
-X001670Y016064D01*
-X020060Y012714D02*
-X020062Y012764D01*
-X020068Y012814D01*
-X020078Y012863D01*
-X020091Y012912D01*
-X020109Y012959D01*
-X020130Y013005D01*
-X020154Y013048D01*
-X020182Y013090D01*
-X020213Y013130D01*
-X020247Y013167D01*
-X020284Y013201D01*
-X020324Y013232D01*
-X020366Y013260D01*
-X020409Y013284D01*
-X020455Y013305D01*
-X020502Y013323D01*
-X020551Y013336D01*
-X020600Y013346D01*
-X020650Y013352D01*
-X020700Y013354D01*
-X020750Y013352D01*
-X020800Y013346D01*
-X020849Y013336D01*
-X020898Y013323D01*
-X020945Y013305D01*
-X020991Y013284D01*
-X021034Y013260D01*
-X021076Y013232D01*
-X021116Y013201D01*
-X021153Y013167D01*
-X021187Y013130D01*
-X021218Y013090D01*
-X021246Y013048D01*
-X021270Y013005D01*
-X021291Y012959D01*
-X021309Y012912D01*
-X021322Y012863D01*
-X021332Y012814D01*
-X021338Y012764D01*
-X021340Y012714D01*
-X021338Y012664D01*
-X021332Y012614D01*
-X021322Y012565D01*
-X021309Y012516D01*
-X021291Y012469D01*
-X021270Y012423D01*
-X021246Y012380D01*
-X021218Y012338D01*
-X021187Y012298D01*
-X021153Y012261D01*
-X021116Y012227D01*
-X021076Y012196D01*
-X021034Y012168D01*
-X020991Y012144D01*
-X020945Y012123D01*
-X020898Y012105D01*
-X020849Y012092D01*
-X020800Y012082D01*
-X020750Y012076D01*
-X020700Y012074D01*
-X020650Y012076D01*
-X020600Y012082D01*
-X020551Y012092D01*
-X020502Y012105D01*
-X020455Y012123D01*
-X020409Y012144D01*
-X020366Y012168D01*
-X020324Y012196D01*
-X020284Y012227D01*
-X020247Y012261D01*
-X020213Y012298D01*
-X020182Y012338D01*
-X020154Y012380D01*
-X020130Y012423D01*
-X020109Y012469D01*
-X020091Y012516D01*
-X020078Y012565D01*
-X020068Y012614D01*
-X020062Y012664D01*
-X020060Y012714D01*
-X020170Y016064D02*
-X020172Y016114D01*
-X020178Y016164D01*
-X020188Y016213D01*
-X020202Y016261D01*
-X020219Y016308D01*
-X020240Y016353D01*
-X020265Y016397D01*
-X020293Y016438D01*
-X020325Y016477D01*
-X020359Y016514D01*
-X020396Y016548D01*
-X020436Y016578D01*
-X020478Y016605D01*
-X020522Y016629D01*
-X020568Y016650D01*
-X020615Y016666D01*
-X020663Y016679D01*
-X020713Y016688D01*
-X020762Y016693D01*
-X020813Y016694D01*
-X020863Y016691D01*
-X020912Y016684D01*
-X020961Y016673D01*
-X021009Y016658D01*
-X021055Y016640D01*
-X021100Y016618D01*
-X021143Y016592D01*
-X021184Y016563D01*
-X021223Y016531D01*
-X021259Y016496D01*
-X021291Y016458D01*
-X021321Y016418D01*
-X021348Y016375D01*
-X021371Y016331D01*
-X021390Y016285D01*
-X021406Y016237D01*
-X021418Y016188D01*
-X021426Y016139D01*
-X021430Y016089D01*
-X021430Y016039D01*
-X021426Y015989D01*
-X021418Y015940D01*
-X021406Y015891D01*
-X021390Y015843D01*
-X021371Y015797D01*
-X021348Y015753D01*
-X021321Y015710D01*
-X021291Y015670D01*
-X021259Y015632D01*
-X021223Y015597D01*
-X021184Y015565D01*
-X021143Y015536D01*
-X021100Y015510D01*
-X021055Y015488D01*
-X021009Y015470D01*
-X020961Y015455D01*
-X020912Y015444D01*
-X020863Y015437D01*
-X020813Y015434D01*
-X020762Y015435D01*
-X020713Y015440D01*
-X020663Y015449D01*
-X020615Y015462D01*
-X020568Y015478D01*
-X020522Y015499D01*
-X020478Y015523D01*
-X020436Y015550D01*
-X020396Y015580D01*
-X020359Y015614D01*
-X020325Y015651D01*
-X020293Y015690D01*
-X020265Y015731D01*
-X020240Y015775D01*
-X020219Y015820D01*
-X020202Y015867D01*
-X020188Y015915D01*
-X020178Y015964D01*
-X020172Y016014D01*
-X020170Y016064D01*
-X020060Y008714D02*
-X020062Y008764D01*
-X020068Y008814D01*
-X020078Y008863D01*
-X020091Y008912D01*
-X020109Y008959D01*
-X020130Y009005D01*
-X020154Y009048D01*
-X020182Y009090D01*
-X020213Y009130D01*
-X020247Y009167D01*
-X020284Y009201D01*
-X020324Y009232D01*
-X020366Y009260D01*
-X020409Y009284D01*
-X020455Y009305D01*
-X020502Y009323D01*
-X020551Y009336D01*
-X020600Y009346D01*
-X020650Y009352D01*
-X020700Y009354D01*
-X020750Y009352D01*
-X020800Y009346D01*
-X020849Y009336D01*
-X020898Y009323D01*
-X020945Y009305D01*
-X020991Y009284D01*
-X021034Y009260D01*
-X021076Y009232D01*
-X021116Y009201D01*
-X021153Y009167D01*
-X021187Y009130D01*
-X021218Y009090D01*
-X021246Y009048D01*
-X021270Y009005D01*
-X021291Y008959D01*
-X021309Y008912D01*
-X021322Y008863D01*
-X021332Y008814D01*
-X021338Y008764D01*
-X021340Y008714D01*
-X021338Y008664D01*
-X021332Y008614D01*
-X021322Y008565D01*
-X021309Y008516D01*
-X021291Y008469D01*
-X021270Y008423D01*
-X021246Y008380D01*
-X021218Y008338D01*
-X021187Y008298D01*
-X021153Y008261D01*
-X021116Y008227D01*
-X021076Y008196D01*
-X021034Y008168D01*
-X020991Y008144D01*
-X020945Y008123D01*
-X020898Y008105D01*
-X020849Y008092D01*
-X020800Y008082D01*
-X020750Y008076D01*
-X020700Y008074D01*
-X020650Y008076D01*
-X020600Y008082D01*
-X020551Y008092D01*
-X020502Y008105D01*
-X020455Y008123D01*
-X020409Y008144D01*
-X020366Y008168D01*
-X020324Y008196D01*
-X020284Y008227D01*
-X020247Y008261D01*
-X020213Y008298D01*
-X020182Y008338D01*
-X020154Y008380D01*
-X020130Y008423D01*
-X020109Y008469D01*
-X020091Y008516D01*
-X020078Y008565D01*
-X020068Y008614D01*
-X020062Y008664D01*
-X020060Y008714D01*
-X020170Y005064D02*
-X020172Y005114D01*
-X020178Y005164D01*
-X020188Y005213D01*
-X020202Y005261D01*
-X020219Y005308D01*
-X020240Y005353D01*
-X020265Y005397D01*
-X020293Y005438D01*
-X020325Y005477D01*
-X020359Y005514D01*
-X020396Y005548D01*
-X020436Y005578D01*
-X020478Y005605D01*
-X020522Y005629D01*
-X020568Y005650D01*
-X020615Y005666D01*
-X020663Y005679D01*
-X020713Y005688D01*
-X020762Y005693D01*
-X020813Y005694D01*
-X020863Y005691D01*
-X020912Y005684D01*
-X020961Y005673D01*
-X021009Y005658D01*
-X021055Y005640D01*
-X021100Y005618D01*
-X021143Y005592D01*
-X021184Y005563D01*
-X021223Y005531D01*
-X021259Y005496D01*
-X021291Y005458D01*
-X021321Y005418D01*
-X021348Y005375D01*
-X021371Y005331D01*
-X021390Y005285D01*
-X021406Y005237D01*
-X021418Y005188D01*
-X021426Y005139D01*
-X021430Y005089D01*
-X021430Y005039D01*
-X021426Y004989D01*
-X021418Y004940D01*
-X021406Y004891D01*
-X021390Y004843D01*
-X021371Y004797D01*
-X021348Y004753D01*
-X021321Y004710D01*
-X021291Y004670D01*
-X021259Y004632D01*
-X021223Y004597D01*
-X021184Y004565D01*
-X021143Y004536D01*
-X021100Y004510D01*
-X021055Y004488D01*
-X021009Y004470D01*
-X020961Y004455D01*
-X020912Y004444D01*
-X020863Y004437D01*
-X020813Y004434D01*
-X020762Y004435D01*
-X020713Y004440D01*
-X020663Y004449D01*
-X020615Y004462D01*
-X020568Y004478D01*
-X020522Y004499D01*
-X020478Y004523D01*
-X020436Y004550D01*
-X020396Y004580D01*
-X020359Y004614D01*
-X020325Y004651D01*
-X020293Y004690D01*
-X020265Y004731D01*
-X020240Y004775D01*
-X020219Y004820D01*
-X020202Y004867D01*
-X020188Y004915D01*
-X020178Y004964D01*
-X020172Y005014D01*
-X020170Y005064D01*
-D11*
-X022869Y007639D02*
-X022869Y013789D01*
-M02*
diff --git a/gerber/tests/resources/bottom_copper.GBL b/gerber/tests/resources/bottom_copper.GBL
deleted file mode 100644
index 0d98da3..0000000
--- a/gerber/tests/resources/bottom_copper.GBL
+++ /dev/null
@@ -1,1811 +0,0 @@
-G75*
-%MOIN*%
-%OFA0B0*%
-%FSLAX24Y24*%
-%IPPOS*%
-%LPD*%
-%AMOC8*
-5,1,8,0,0,1.08239X$1,22.5*
-%
-%ADD10C,0.0000*%
-%ADD11C,0.0110*%
-%ADD12C,0.0004*%
-%ADD13C,0.0554*%
-%ADD14C,0.0600*%
-%ADD15C,0.0160*%
-%ADD16C,0.0396*%
-%ADD17C,0.0240*%
-D10*
-X000300Y003064D02*
-X000300Y018064D01*
-X022800Y018064D01*
-X022800Y003064D01*
-X000300Y003064D01*
-X001720Y005114D02*
-X001722Y005164D01*
-X001728Y005214D01*
-X001738Y005263D01*
-X001752Y005311D01*
-X001769Y005358D01*
-X001790Y005403D01*
-X001815Y005447D01*
-X001843Y005488D01*
-X001875Y005527D01*
-X001909Y005564D01*
-X001946Y005598D01*
-X001986Y005628D01*
-X002028Y005655D01*
-X002072Y005679D01*
-X002118Y005700D01*
-X002165Y005716D01*
-X002213Y005729D01*
-X002263Y005738D01*
-X002312Y005743D01*
-X002363Y005744D01*
-X002413Y005741D01*
-X002462Y005734D01*
-X002511Y005723D01*
-X002559Y005708D01*
-X002605Y005690D01*
-X002650Y005668D01*
-X002693Y005642D01*
-X002734Y005613D01*
-X002773Y005581D01*
-X002809Y005546D01*
-X002841Y005508D01*
-X002871Y005468D01*
-X002898Y005425D01*
-X002921Y005381D01*
-X002940Y005335D01*
-X002956Y005287D01*
-X002968Y005238D01*
-X002976Y005189D01*
-X002980Y005139D01*
-X002980Y005089D01*
-X002976Y005039D01*
-X002968Y004990D01*
-X002956Y004941D01*
-X002940Y004893D01*
-X002921Y004847D01*
-X002898Y004803D01*
-X002871Y004760D01*
-X002841Y004720D01*
-X002809Y004682D01*
-X002773Y004647D01*
-X002734Y004615D01*
-X002693Y004586D01*
-X002650Y004560D01*
-X002605Y004538D01*
-X002559Y004520D01*
-X002511Y004505D01*
-X002462Y004494D01*
-X002413Y004487D01*
-X002363Y004484D01*
-X002312Y004485D01*
-X002263Y004490D01*
-X002213Y004499D01*
-X002165Y004512D01*
-X002118Y004528D01*
-X002072Y004549D01*
-X002028Y004573D01*
-X001986Y004600D01*
-X001946Y004630D01*
-X001909Y004664D01*
-X001875Y004701D01*
-X001843Y004740D01*
-X001815Y004781D01*
-X001790Y004825D01*
-X001769Y004870D01*
-X001752Y004917D01*
-X001738Y004965D01*
-X001728Y005014D01*
-X001722Y005064D01*
-X001720Y005114D01*
-X001670Y016064D02*
-X001672Y016114D01*
-X001678Y016164D01*
-X001688Y016213D01*
-X001702Y016261D01*
-X001719Y016308D01*
-X001740Y016353D01*
-X001765Y016397D01*
-X001793Y016438D01*
-X001825Y016477D01*
-X001859Y016514D01*
-X001896Y016548D01*
-X001936Y016578D01*
-X001978Y016605D01*
-X002022Y016629D01*
-X002068Y016650D01*
-X002115Y016666D01*
-X002163Y016679D01*
-X002213Y016688D01*
-X002262Y016693D01*
-X002313Y016694D01*
-X002363Y016691D01*
-X002412Y016684D01*
-X002461Y016673D01*
-X002509Y016658D01*
-X002555Y016640D01*
-X002600Y016618D01*
-X002643Y016592D01*
-X002684Y016563D01*
-X002723Y016531D01*
-X002759Y016496D01*
-X002791Y016458D01*
-X002821Y016418D01*
-X002848Y016375D01*
-X002871Y016331D01*
-X002890Y016285D01*
-X002906Y016237D01*
-X002918Y016188D01*
-X002926Y016139D01*
-X002930Y016089D01*
-X002930Y016039D01*
-X002926Y015989D01*
-X002918Y015940D01*
-X002906Y015891D01*
-X002890Y015843D01*
-X002871Y015797D01*
-X002848Y015753D01*
-X002821Y015710D01*
-X002791Y015670D01*
-X002759Y015632D01*
-X002723Y015597D01*
-X002684Y015565D01*
-X002643Y015536D01*
-X002600Y015510D01*
-X002555Y015488D01*
-X002509Y015470D01*
-X002461Y015455D01*
-X002412Y015444D01*
-X002363Y015437D01*
-X002313Y015434D01*
-X002262Y015435D01*
-X002213Y015440D01*
-X002163Y015449D01*
-X002115Y015462D01*
-X002068Y015478D01*
-X002022Y015499D01*
-X001978Y015523D01*
-X001936Y015550D01*
-X001896Y015580D01*
-X001859Y015614D01*
-X001825Y015651D01*
-X001793Y015690D01*
-X001765Y015731D01*
-X001740Y015775D01*
-X001719Y015820D01*
-X001702Y015867D01*
-X001688Y015915D01*
-X001678Y015964D01*
-X001672Y016014D01*
-X001670Y016064D01*
-X020060Y012714D02*
-X020062Y012764D01*
-X020068Y012814D01*
-X020078Y012863D01*
-X020091Y012912D01*
-X020109Y012959D01*
-X020130Y013005D01*
-X020154Y013048D01*
-X020182Y013090D01*
-X020213Y013130D01*
-X020247Y013167D01*
-X020284Y013201D01*
-X020324Y013232D01*
-X020366Y013260D01*
-X020409Y013284D01*
-X020455Y013305D01*
-X020502Y013323D01*
-X020551Y013336D01*
-X020600Y013346D01*
-X020650Y013352D01*
-X020700Y013354D01*
-X020750Y013352D01*
-X020800Y013346D01*
-X020849Y013336D01*
-X020898Y013323D01*
-X020945Y013305D01*
-X020991Y013284D01*
-X021034Y013260D01*
-X021076Y013232D01*
-X021116Y013201D01*
-X021153Y013167D01*
-X021187Y013130D01*
-X021218Y013090D01*
-X021246Y013048D01*
-X021270Y013005D01*
-X021291Y012959D01*
-X021309Y012912D01*
-X021322Y012863D01*
-X021332Y012814D01*
-X021338Y012764D01*
-X021340Y012714D01*
-X021338Y012664D01*
-X021332Y012614D01*
-X021322Y012565D01*
-X021309Y012516D01*
-X021291Y012469D01*
-X021270Y012423D01*
-X021246Y012380D01*
-X021218Y012338D01*
-X021187Y012298D01*
-X021153Y012261D01*
-X021116Y012227D01*
-X021076Y012196D01*
-X021034Y012168D01*
-X020991Y012144D01*
-X020945Y012123D01*
-X020898Y012105D01*
-X020849Y012092D01*
-X020800Y012082D01*
-X020750Y012076D01*
-X020700Y012074D01*
-X020650Y012076D01*
-X020600Y012082D01*
-X020551Y012092D01*
-X020502Y012105D01*
-X020455Y012123D01*
-X020409Y012144D01*
-X020366Y012168D01*
-X020324Y012196D01*
-X020284Y012227D01*
-X020247Y012261D01*
-X020213Y012298D01*
-X020182Y012338D01*
-X020154Y012380D01*
-X020130Y012423D01*
-X020109Y012469D01*
-X020091Y012516D01*
-X020078Y012565D01*
-X020068Y012614D01*
-X020062Y012664D01*
-X020060Y012714D01*
-X020170Y016064D02*
-X020172Y016114D01*
-X020178Y016164D01*
-X020188Y016213D01*
-X020202Y016261D01*
-X020219Y016308D01*
-X020240Y016353D01*
-X020265Y016397D01*
-X020293Y016438D01*
-X020325Y016477D01*
-X020359Y016514D01*
-X020396Y016548D01*
-X020436Y016578D01*
-X020478Y016605D01*
-X020522Y016629D01*
-X020568Y016650D01*
-X020615Y016666D01*
-X020663Y016679D01*
-X020713Y016688D01*
-X020762Y016693D01*
-X020813Y016694D01*
-X020863Y016691D01*
-X020912Y016684D01*
-X020961Y016673D01*
-X021009Y016658D01*
-X021055Y016640D01*
-X021100Y016618D01*
-X021143Y016592D01*
-X021184Y016563D01*
-X021223Y016531D01*
-X021259Y016496D01*
-X021291Y016458D01*
-X021321Y016418D01*
-X021348Y016375D01*
-X021371Y016331D01*
-X021390Y016285D01*
-X021406Y016237D01*
-X021418Y016188D01*
-X021426Y016139D01*
-X021430Y016089D01*
-X021430Y016039D01*
-X021426Y015989D01*
-X021418Y015940D01*
-X021406Y015891D01*
-X021390Y015843D01*
-X021371Y015797D01*
-X021348Y015753D01*
-X021321Y015710D01*
-X021291Y015670D01*
-X021259Y015632D01*
-X021223Y015597D01*
-X021184Y015565D01*
-X021143Y015536D01*
-X021100Y015510D01*
-X021055Y015488D01*
-X021009Y015470D01*
-X020961Y015455D01*
-X020912Y015444D01*
-X020863Y015437D01*
-X020813Y015434D01*
-X020762Y015435D01*
-X020713Y015440D01*
-X020663Y015449D01*
-X020615Y015462D01*
-X020568Y015478D01*
-X020522Y015499D01*
-X020478Y015523D01*
-X020436Y015550D01*
-X020396Y015580D01*
-X020359Y015614D01*
-X020325Y015651D01*
-X020293Y015690D01*
-X020265Y015731D01*
-X020240Y015775D01*
-X020219Y015820D01*
-X020202Y015867D01*
-X020188Y015915D01*
-X020178Y015964D01*
-X020172Y016014D01*
-X020170Y016064D01*
-X020060Y008714D02*
-X020062Y008764D01*
-X020068Y008814D01*
-X020078Y008863D01*
-X020091Y008912D01*
-X020109Y008959D01*
-X020130Y009005D01*
-X020154Y009048D01*
-X020182Y009090D01*
-X020213Y009130D01*
-X020247Y009167D01*
-X020284Y009201D01*
-X020324Y009232D01*
-X020366Y009260D01*
-X020409Y009284D01*
-X020455Y009305D01*
-X020502Y009323D01*
-X020551Y009336D01*
-X020600Y009346D01*
-X020650Y009352D01*
-X020700Y009354D01*
-X020750Y009352D01*
-X020800Y009346D01*
-X020849Y009336D01*
-X020898Y009323D01*
-X020945Y009305D01*
-X020991Y009284D01*
-X021034Y009260D01*
-X021076Y009232D01*
-X021116Y009201D01*
-X021153Y009167D01*
-X021187Y009130D01*
-X021218Y009090D01*
-X021246Y009048D01*
-X021270Y009005D01*
-X021291Y008959D01*
-X021309Y008912D01*
-X021322Y008863D01*
-X021332Y008814D01*
-X021338Y008764D01*
-X021340Y008714D01*
-X021338Y008664D01*
-X021332Y008614D01*
-X021322Y008565D01*
-X021309Y008516D01*
-X021291Y008469D01*
-X021270Y008423D01*
-X021246Y008380D01*
-X021218Y008338D01*
-X021187Y008298D01*
-X021153Y008261D01*
-X021116Y008227D01*
-X021076Y008196D01*
-X021034Y008168D01*
-X020991Y008144D01*
-X020945Y008123D01*
-X020898Y008105D01*
-X020849Y008092D01*
-X020800Y008082D01*
-X020750Y008076D01*
-X020700Y008074D01*
-X020650Y008076D01*
-X020600Y008082D01*
-X020551Y008092D01*
-X020502Y008105D01*
-X020455Y008123D01*
-X020409Y008144D01*
-X020366Y008168D01*
-X020324Y008196D01*
-X020284Y008227D01*
-X020247Y008261D01*
-X020213Y008298D01*
-X020182Y008338D01*
-X020154Y008380D01*
-X020130Y008423D01*
-X020109Y008469D01*
-X020091Y008516D01*
-X020078Y008565D01*
-X020068Y008614D01*
-X020062Y008664D01*
-X020060Y008714D01*
-X020170Y005064D02*
-X020172Y005114D01*
-X020178Y005164D01*
-X020188Y005213D01*
-X020202Y005261D01*
-X020219Y005308D01*
-X020240Y005353D01*
-X020265Y005397D01*
-X020293Y005438D01*
-X020325Y005477D01*
-X020359Y005514D01*
-X020396Y005548D01*
-X020436Y005578D01*
-X020478Y005605D01*
-X020522Y005629D01*
-X020568Y005650D01*
-X020615Y005666D01*
-X020663Y005679D01*
-X020713Y005688D01*
-X020762Y005693D01*
-X020813Y005694D01*
-X020863Y005691D01*
-X020912Y005684D01*
-X020961Y005673D01*
-X021009Y005658D01*
-X021055Y005640D01*
-X021100Y005618D01*
-X021143Y005592D01*
-X021184Y005563D01*
-X021223Y005531D01*
-X021259Y005496D01*
-X021291Y005458D01*
-X021321Y005418D01*
-X021348Y005375D01*
-X021371Y005331D01*
-X021390Y005285D01*
-X021406Y005237D01*
-X021418Y005188D01*
-X021426Y005139D01*
-X021430Y005089D01*
-X021430Y005039D01*
-X021426Y004989D01*
-X021418Y004940D01*
-X021406Y004891D01*
-X021390Y004843D01*
-X021371Y004797D01*
-X021348Y004753D01*
-X021321Y004710D01*
-X021291Y004670D01*
-X021259Y004632D01*
-X021223Y004597D01*
-X021184Y004565D01*
-X021143Y004536D01*
-X021100Y004510D01*
-X021055Y004488D01*
-X021009Y004470D01*
-X020961Y004455D01*
-X020912Y004444D01*
-X020863Y004437D01*
-X020813Y004434D01*
-X020762Y004435D01*
-X020713Y004440D01*
-X020663Y004449D01*
-X020615Y004462D01*
-X020568Y004478D01*
-X020522Y004499D01*
-X020478Y004523D01*
-X020436Y004550D01*
-X020396Y004580D01*
-X020359Y004614D01*
-X020325Y004651D01*
-X020293Y004690D01*
-X020265Y004731D01*
-X020240Y004775D01*
-X020219Y004820D01*
-X020202Y004867D01*
-X020188Y004915D01*
-X020178Y004964D01*
-X020172Y005014D01*
-X020170Y005064D01*
-D11*
-X019495Y004010D02*
-X019298Y003813D01*
-X019101Y004010D01*
-X019101Y003419D01*
-X018850Y003419D02*
-X018654Y003419D01*
-X018752Y003419D02*
-X018752Y004010D01*
-X018850Y004010D02*
-X018654Y004010D01*
-X018421Y004010D02*
-X018125Y004010D01*
-X018027Y003911D01*
-X018027Y003518D01*
-X018125Y003419D01*
-X018421Y003419D01*
-X018421Y004010D01*
-X017776Y004010D02*
-X017579Y004010D01*
-X017678Y004010D02*
-X017678Y003419D01*
-X017776Y003419D02*
-X017579Y003419D01*
-X016702Y003715D02*
-X016308Y003715D01*
-X015413Y004010D02*
-X015413Y003419D01*
-X015118Y003419D01*
-X015019Y003518D01*
-X015019Y003911D01*
-X015118Y004010D01*
-X015413Y004010D01*
-X014768Y004010D02*
-X014768Y003419D01*
-X014375Y003419D02*
-X014375Y004010D01*
-X014571Y003813D01*
-X014768Y004010D01*
-X014124Y004010D02*
-X013730Y003419D01*
-X014124Y003419D02*
-X013730Y004010D01*
-X012835Y004010D02*
-X012835Y003419D01*
-X012539Y003419D01*
-X012441Y003518D01*
-X012441Y003616D01*
-X012539Y003715D01*
-X012835Y003715D01*
-X012835Y004010D02*
-X012539Y004010D01*
-X012441Y003911D01*
-X012441Y003813D01*
-X012539Y003715D01*
-X012190Y003813D02*
-X012190Y003419D01*
-X012190Y003616D02*
-X011993Y003813D01*
-X011895Y003813D01*
-X011653Y003813D02*
-X011555Y003813D01*
-X011555Y003419D01*
-X011653Y003419D02*
-X011456Y003419D01*
-X011223Y003518D02*
-X011223Y003715D01*
-X011125Y003813D01*
-X010830Y003813D01*
-X010830Y004010D02*
-X010830Y003419D01*
-X011125Y003419D01*
-X011223Y003518D01*
-X011555Y004010D02*
-X011555Y004108D01*
-X010579Y003715D02*
-X010579Y003518D01*
-X010480Y003419D01*
-X010185Y003419D01*
-X010185Y003321D02*
-X010185Y003813D01*
-X010480Y003813D01*
-X010579Y003715D01*
-X010185Y003321D02*
-X010283Y003222D01*
-X010382Y003222D01*
-X009934Y003518D02*
-X009934Y003715D01*
-X009836Y003813D01*
-X009639Y003813D01*
-X009541Y003715D01*
-X009541Y003616D01*
-X009934Y003616D01*
-X009934Y003518D02*
-X009836Y003419D01*
-X009639Y003419D01*
-X019495Y003419D02*
-X019495Y004010D01*
-D12*
-X022869Y007639D02*
-X022869Y013789D01*
-D13*
-X018200Y011964D03*
-X017200Y011464D03*
-X017200Y010464D03*
-X018200Y009964D03*
-X018200Y010964D03*
-X017200Y009464D03*
-D14*
-X017350Y016514D02*
-X017350Y017114D01*
-X018350Y017114D02*
-X018350Y016514D01*
-X007350Y016664D02*
-X007350Y017264D01*
-X006350Y017264D02*
-X006350Y016664D01*
-X005350Y016664D02*
-X005350Y017264D01*
-X001800Y012564D02*
-X001200Y012564D01*
-X001200Y011564D02*
-X001800Y011564D01*
-X001800Y010564D02*
-X001200Y010564D01*
-X001200Y009564D02*
-X001800Y009564D01*
-X001800Y008564D02*
-X001200Y008564D01*
-D15*
-X001031Y008136D02*
-X000780Y008136D01*
-X000780Y007978D02*
-X019853Y007978D01*
-X019804Y008027D02*
-X020012Y007818D01*
-X020268Y007671D01*
-X020553Y007594D01*
-X020847Y007594D01*
-X021132Y007671D01*
-X021388Y007818D01*
-X021596Y008027D01*
-X021744Y008282D01*
-X021820Y008567D01*
-X021820Y008862D01*
-X021744Y009147D01*
-X021596Y009402D01*
-X021388Y009611D01*
-X021132Y009758D01*
-X020847Y009834D01*
-X020553Y009834D01*
-X020268Y009758D01*
-X020012Y009611D01*
-X019804Y009402D01*
-X019656Y009147D01*
-X019580Y008862D01*
-X019580Y008567D01*
-X019656Y008282D01*
-X019804Y008027D01*
-X019740Y008136D02*
-X001969Y008136D01*
-X001891Y008104D02*
-X002061Y008174D01*
-X002190Y008304D01*
-X002260Y008473D01*
-X002260Y008656D01*
-X002190Y008825D01*
-X002061Y008954D01*
-X001891Y009024D01*
-X001108Y009024D01*
-X000939Y008954D01*
-X000810Y008825D01*
-X000780Y008752D01*
-X000780Y009376D01*
-X000810Y009304D01*
-X000939Y009174D01*
-X001108Y009104D01*
-X001891Y009104D01*
-X002061Y009174D01*
-X002190Y009304D01*
-X002260Y009473D01*
-X002260Y009656D01*
-X002190Y009825D01*
-X002061Y009954D01*
-X001891Y010024D01*
-X001108Y010024D01*
-X000939Y009954D01*
-X000810Y009825D01*
-X000780Y009752D01*
-X000780Y010376D01*
-X000810Y010304D01*
-X000939Y010174D01*
-X001108Y010104D01*
-X001891Y010104D01*
-X002061Y010174D01*
-X002190Y010304D01*
-X002260Y010473D01*
-X002260Y010656D01*
-X002190Y010825D01*
-X002061Y010954D01*
-X001891Y011024D01*
-X001108Y011024D01*
-X000939Y010954D01*
-X000810Y010825D01*
-X000780Y010752D01*
-X000780Y011376D01*
-X000810Y011304D01*
-X000939Y011174D01*
-X001108Y011104D01*
-X001891Y011104D01*
-X002061Y011174D01*
-X002190Y011304D01*
-X002260Y011473D01*
-X002260Y011656D01*
-X002190Y011825D01*
-X002061Y011954D01*
-X001891Y012024D01*
-X001108Y012024D01*
-X000939Y011954D01*
-X000810Y011825D01*
-X000780Y011752D01*
-X000780Y012376D01*
-X000810Y012304D01*
-X000939Y012174D01*
-X001108Y012104D01*
-X001891Y012104D01*
-X002061Y012174D01*
-X002190Y012304D01*
-X002260Y012473D01*
-X002260Y012656D01*
-X002190Y012825D01*
-X002061Y012954D01*
-X001891Y013024D01*
-X001108Y013024D01*
-X000939Y012954D01*
-X000810Y012825D01*
-X000780Y012752D01*
-X000780Y015356D01*
-X000786Y015335D01*
-X001068Y014922D01*
-X001068Y014922D01*
-X001068Y014922D01*
-X001460Y014609D01*
-X001926Y014426D01*
-X002426Y014389D01*
-X002914Y014500D01*
-X003347Y014751D01*
-X003347Y014751D01*
-X003688Y015118D01*
-X003905Y015569D01*
-X003980Y016064D01*
-X003905Y016560D01*
-X003688Y017011D01*
-X003347Y017378D01*
-X002990Y017584D01*
-X005019Y017584D01*
-X004960Y017525D01*
-X004890Y017356D01*
-X004890Y016573D01*
-X004960Y016404D01*
-X005089Y016274D01*
-X005258Y016204D01*
-X005441Y016204D01*
-X005611Y016274D01*
-X005740Y016404D01*
-X005810Y016573D01*
-X005810Y017356D01*
-X005740Y017525D01*
-X005681Y017584D01*
-X006019Y017584D01*
-X005960Y017525D01*
-X005890Y017356D01*
-X005890Y016573D01*
-X005960Y016404D01*
-X006089Y016274D01*
-X006258Y016204D01*
-X006441Y016204D01*
-X006611Y016274D01*
-X006740Y016404D01*
-X006810Y016573D01*
-X006810Y017356D01*
-X006740Y017525D01*
-X006681Y017584D01*
-X006991Y017584D01*
-X006984Y017577D01*
-X006939Y017516D01*
-X006905Y017449D01*
-X006882Y017377D01*
-X006870Y017302D01*
-X006870Y016984D01*
-X007330Y016984D01*
-X007330Y016944D01*
-X007370Y016944D01*
-X007370Y016184D01*
-X007388Y016184D01*
-X007462Y016196D01*
-X007534Y016219D01*
-X007602Y016254D01*
-X007663Y016298D01*
-X007716Y016352D01*
-X007761Y016413D01*
-X007795Y016480D01*
-X007818Y016552D01*
-X007830Y016627D01*
-X007830Y016944D01*
-X007370Y016944D01*
-X007370Y016984D01*
-X007830Y016984D01*
-X007830Y017302D01*
-X007818Y017377D01*
-X007795Y017449D01*
-X007761Y017516D01*
-X007716Y017577D01*
-X007709Y017584D01*
-X018249Y017584D01*
-X018238Y017583D01*
-X018166Y017559D01*
-X018098Y017525D01*
-X018037Y017480D01*
-X017984Y017427D01*
-X017939Y017366D01*
-X017905Y017299D01*
-X017882Y017227D01*
-X017870Y017152D01*
-X017870Y016834D01*
-X018330Y016834D01*
-X018330Y016794D01*
-X018370Y016794D01*
-X018370Y016034D01*
-X018388Y016034D01*
-X018462Y016046D01*
-X018534Y016069D01*
-X018602Y016104D01*
-X018663Y016148D01*
-X018716Y016202D01*
-X018761Y016263D01*
-X018795Y016330D01*
-X018818Y016402D01*
-X018830Y016477D01*
-X018830Y016794D01*
-X018370Y016794D01*
-X018370Y016834D01*
-X018830Y016834D01*
-X018830Y017152D01*
-X018818Y017227D01*
-X018795Y017299D01*
-X018761Y017366D01*
-X018716Y017427D01*
-X018663Y017480D01*
-X018602Y017525D01*
-X018534Y017559D01*
-X018462Y017583D01*
-X018451Y017584D01*
-X020126Y017584D01*
-X019960Y017519D01*
-X019568Y017207D01*
-X019286Y016793D01*
-X019139Y016315D01*
-X019139Y015814D01*
-X019286Y015335D01*
-X019568Y014922D01*
-X019568Y014922D01*
-X019568Y014922D01*
-X019960Y014609D01*
-X020426Y014426D01*
-X020926Y014389D01*
-X021414Y014500D01*
-X021847Y014751D01*
-X021847Y014751D01*
-X022188Y015118D01*
-X022320Y015392D01*
-X022320Y005737D01*
-X022188Y006011D01*
-X021847Y006378D01*
-X021414Y006628D01*
-X021414Y006628D01*
-X020926Y006740D01*
-X020926Y006740D01*
-X020426Y006702D01*
-X019960Y006519D01*
-X019568Y006207D01*
-X019286Y005793D01*
-X019139Y005315D01*
-X019139Y004814D01*
-X019231Y004514D01*
-X009450Y004514D01*
-X009450Y003928D01*
-X009326Y003804D01*
-X009326Y003544D01*
-X002937Y003544D01*
-X002964Y003550D01*
-X003397Y003801D01*
-X003397Y003801D01*
-X003738Y004168D01*
-X003955Y004619D01*
-X004030Y005114D01*
-X003955Y005610D01*
-X003738Y006061D01*
-X003397Y006428D01*
-X002964Y006678D01*
-X002964Y006678D01*
-X002476Y006790D01*
-X002476Y006790D01*
-X001976Y006752D01*
-X001510Y006569D01*
-X001118Y006257D01*
-X000836Y005843D01*
-X000780Y005660D01*
-X000780Y008376D01*
-X000810Y008304D01*
-X000939Y008174D01*
-X001108Y008104D01*
-X001891Y008104D01*
-X002181Y008295D02*
-X019653Y008295D01*
-X019610Y008453D02*
-X013735Y008453D01*
-X013753Y008461D02*
-X013854Y008561D01*
-X013908Y008693D01*
-X013908Y008836D01*
-X013854Y008967D01*
-X013753Y009068D01*
-X013621Y009122D01*
-X013588Y009122D01*
-X011930Y010780D01*
-X011930Y012938D01*
-X011954Y012961D01*
-X012008Y013093D01*
-X012008Y013236D01*
-X011954Y013367D01*
-X019783Y013367D01*
-X019804Y013402D02*
-X019656Y013147D01*
-X019580Y012862D01*
-X019580Y012567D01*
-X019656Y012282D01*
-X019804Y012027D01*
-X020012Y011818D01*
-X020268Y011671D01*
-X020553Y011594D01*
-X020847Y011594D01*
-X021132Y011671D01*
-X021388Y011818D01*
-X021596Y012027D01*
-X021744Y012282D01*
-X021820Y012567D01*
-X021820Y012862D01*
-X021744Y013147D01*
-X021596Y013402D01*
-X021388Y013611D01*
-X021132Y013758D01*
-X020847Y013834D01*
-X020553Y013834D01*
-X020268Y013758D01*
-X020012Y013611D01*
-X019804Y013402D01*
-X019927Y013525D02*
-X000780Y013525D01*
-X000780Y013367D02*
-X011346Y013367D01*
-X011292Y013236D01*
-X011292Y013093D01*
-X011346Y012961D01*
-X011370Y012938D01*
-X011370Y010609D01*
-X011413Y010506D01*
-X013192Y008726D01*
-X013192Y008693D01*
-X013246Y008561D01*
-X013347Y008461D01*
-X013479Y008406D01*
-X013621Y008406D01*
-X013753Y008461D01*
-X013874Y008612D02*
-X019580Y008612D01*
-X019580Y008770D02*
-X013908Y008770D01*
-X013869Y008929D02*
-X019598Y008929D01*
-X019640Y009087D02*
-X017432Y009087D01*
-X017448Y009094D02*
-X017571Y009217D01*
-X017637Y009377D01*
-X017637Y009551D01*
-X017571Y009712D01*
-X017558Y009724D01*
-X017826Y009724D01*
-X017829Y009717D01*
-X017952Y009594D01*
-X018113Y009527D01*
-X018287Y009527D01*
-X018448Y009594D01*
-X018571Y009717D01*
-X018637Y009877D01*
-X018637Y010051D01*
-X018571Y010212D01*
-X018448Y010335D01*
-X018287Y010401D01*
-X018113Y010401D01*
-X017952Y010335D01*
-X017829Y010212D01*
-X017826Y010204D01*
-X017576Y010204D01*
-X017591Y010225D01*
-X017624Y010289D01*
-X017646Y010357D01*
-X017657Y010428D01*
-X017657Y010456D01*
-X017209Y010456D01*
-X017209Y010473D01*
-X017657Y010473D01*
-X017657Y010500D01*
-X017646Y010571D01*
-X017624Y010640D01*
-X017591Y010704D01*
-X017549Y010762D01*
-X017498Y010813D01*
-X017440Y010855D01*
-X017375Y010888D01*
-X017307Y010910D01*
-X017236Y010921D01*
-X017209Y010921D01*
-X017209Y010473D01*
-X017191Y010473D01*
-X017191Y010456D01*
-X016743Y010456D01*
-X016743Y010428D01*
-X016754Y010357D01*
-X016776Y010289D01*
-X016809Y010225D01*
-X016824Y010204D01*
-X016066Y010204D01*
-X016053Y010218D01*
-X015921Y010272D01*
-X015779Y010272D01*
-X015647Y010218D01*
-X015546Y010117D01*
-X015492Y009986D01*
-X015492Y009843D01*
-X015546Y009711D01*
-X015647Y009611D01*
-X015779Y009556D01*
-X015921Y009556D01*
-X016053Y009611D01*
-X016154Y009711D01*
-X016159Y009724D01*
-X016842Y009724D01*
-X016829Y009712D01*
-X016763Y009551D01*
-X016763Y009377D01*
-X016829Y009217D01*
-X016952Y009094D01*
-X017113Y009027D01*
-X017287Y009027D01*
-X017448Y009094D01*
-X017583Y009246D02*
-X019714Y009246D01*
-X019806Y009404D02*
-X017637Y009404D01*
-X017632Y009563D02*
-X018027Y009563D01*
-X017827Y009721D02*
-X017561Y009721D01*
-X017645Y010355D02*
-X018002Y010355D01*
-X018113Y010527D02*
-X018287Y010527D01*
-X018448Y010594D01*
-X018571Y010717D01*
-X018637Y010877D01*
-X018637Y011051D01*
-X018571Y011212D01*
-X018448Y011335D01*
-X018287Y011401D01*
-X018113Y011401D01*
-X017952Y011335D01*
-X017829Y011212D01*
-X017763Y011051D01*
-X017763Y010877D01*
-X017829Y010717D01*
-X017952Y010594D01*
-X018113Y010527D01*
-X017874Y010672D02*
-X017607Y010672D01*
-X017655Y010514D02*
-X022320Y010514D01*
-X022320Y010672D02*
-X018526Y010672D01*
-X018618Y010831D02*
-X022320Y010831D01*
-X022320Y010989D02*
-X018637Y010989D01*
-X018597Y011148D02*
-X022320Y011148D01*
-X022320Y011306D02*
-X018476Y011306D01*
-X018448Y011594D02*
-X018287Y011527D01*
-X018113Y011527D01*
-X017952Y011594D01*
-X017829Y011717D01*
-X017763Y011877D01*
-X017763Y012051D01*
-X017829Y012212D01*
-X017952Y012335D01*
-X018113Y012401D01*
-X018287Y012401D01*
-X018448Y012335D01*
-X018571Y012212D01*
-X018637Y012051D01*
-X018637Y011877D01*
-X018571Y011717D01*
-X018448Y011594D01*
-X018477Y011623D02*
-X020444Y011623D01*
-X020075Y011782D02*
-X018598Y011782D01*
-X018637Y011940D02*
-X019890Y011940D01*
-X019762Y012099D02*
-X018617Y012099D01*
-X018525Y012257D02*
-X019671Y012257D01*
-X019620Y012416D02*
-X011930Y012416D01*
-X011930Y012574D02*
-X019580Y012574D01*
-X019580Y012733D02*
-X011930Y012733D01*
-X011930Y012891D02*
-X019588Y012891D01*
-X019630Y013050D02*
-X011990Y013050D01*
-X012008Y013208D02*
-X019692Y013208D01*
-X020139Y013684D02*
-X000780Y013684D01*
-X000780Y013842D02*
-X022320Y013842D01*
-X022320Y013684D02*
-X021261Y013684D01*
-X021473Y013525D02*
-X022320Y013525D01*
-X022320Y013367D02*
-X021617Y013367D01*
-X021708Y013208D02*
-X022320Y013208D01*
-X022320Y013050D02*
-X021770Y013050D01*
-X021812Y012891D02*
-X022320Y012891D01*
-X022320Y012733D02*
-X021820Y012733D01*
-X021820Y012574D02*
-X022320Y012574D01*
-X022320Y012416D02*
-X021780Y012416D01*
-X021729Y012257D02*
-X022320Y012257D01*
-X022320Y012099D02*
-X021638Y012099D01*
-X021510Y011940D02*
-X022320Y011940D01*
-X022320Y011782D02*
-X021325Y011782D01*
-X020956Y011623D02*
-X022320Y011623D01*
-X022320Y011465D02*
-X017637Y011465D01*
-X017637Y011551D02*
-X017637Y011377D01*
-X017571Y011217D01*
-X017448Y011094D01*
-X017287Y011027D01*
-X017113Y011027D01*
-X016952Y011094D01*
-X016829Y011217D01*
-X016763Y011377D01*
-X016763Y011551D01*
-X016829Y011712D01*
-X016952Y011835D01*
-X017113Y011901D01*
-X017287Y011901D01*
-X017448Y011835D01*
-X017571Y011712D01*
-X017637Y011551D01*
-X017607Y011623D02*
-X017923Y011623D01*
-X017802Y011782D02*
-X017501Y011782D01*
-X017763Y011940D02*
-X011930Y011940D01*
-X011930Y011782D02*
-X016899Y011782D01*
-X016793Y011623D02*
-X011930Y011623D01*
-X011930Y011465D02*
-X016763Y011465D01*
-X016792Y011306D02*
-X011930Y011306D01*
-X011930Y011148D02*
-X016898Y011148D01*
-X017025Y010888D02*
-X016960Y010855D01*
-X016902Y010813D01*
-X016851Y010762D01*
-X016809Y010704D01*
-X016776Y010640D01*
-X016754Y010571D01*
-X016743Y010500D01*
-X016743Y010473D01*
-X017191Y010473D01*
-X017191Y010921D01*
-X017164Y010921D01*
-X017093Y010910D01*
-X017025Y010888D01*
-X016927Y010831D02*
-X011930Y010831D01*
-X011930Y010989D02*
-X017763Y010989D01*
-X017782Y010831D02*
-X017473Y010831D01*
-X017502Y011148D02*
-X017803Y011148D01*
-X017924Y011306D02*
-X017608Y011306D01*
-X017209Y010831D02*
-X017191Y010831D01*
-X017191Y010672D02*
-X017209Y010672D01*
-X017209Y010514D02*
-X017191Y010514D01*
-X016793Y010672D02*
-X012038Y010672D01*
-X012196Y010514D02*
-X016745Y010514D01*
-X016755Y010355D02*
-X012355Y010355D01*
-X012513Y010197D02*
-X015626Y010197D01*
-X015514Y010038D02*
-X012672Y010038D01*
-X012830Y009880D02*
-X015492Y009880D01*
-X015542Y009721D02*
-X012989Y009721D01*
-X013147Y009563D02*
-X015763Y009563D01*
-X015937Y009563D02*
-X016768Y009563D01*
-X016763Y009404D02*
-X013306Y009404D01*
-X013464Y009246D02*
-X016817Y009246D01*
-X016968Y009087D02*
-X013706Y009087D01*
-X013148Y008770D02*
-X002213Y008770D01*
-X002260Y008612D02*
-X013226Y008612D01*
-X013365Y008453D02*
-X002252Y008453D01*
-X002086Y008929D02*
-X012990Y008929D01*
-X012831Y009087D02*
-X000780Y009087D01*
-X000780Y008929D02*
-X000914Y008929D01*
-X000787Y008770D02*
-X000780Y008770D01*
-X000780Y008295D02*
-X000819Y008295D01*
-X000780Y007819D02*
-X020011Y007819D01*
-X020304Y007661D02*
-X000780Y007661D01*
-X000780Y007502D02*
-X022320Y007502D01*
-X022320Y007344D02*
-X000780Y007344D01*
-X000780Y007185D02*
-X022320Y007185D01*
-X022320Y007027D02*
-X000780Y007027D01*
-X000780Y006868D02*
-X022320Y006868D01*
-X022320Y006710D02*
-X021056Y006710D01*
-X021547Y006551D02*
-X022320Y006551D01*
-X022320Y006393D02*
-X021821Y006393D01*
-X021847Y006378D02*
-X021847Y006378D01*
-X021981Y006234D02*
-X022320Y006234D01*
-X022320Y006076D02*
-X022128Y006076D01*
-X022188Y006011D02*
-X022188Y006011D01*
-X022233Y005917D02*
-X022320Y005917D01*
-X022309Y005759D02*
-X022320Y005759D01*
-X020528Y006710D02*
-X002825Y006710D01*
-X003184Y006551D02*
-X020042Y006551D01*
-X019960Y006519D02*
-X019960Y006519D01*
-X019801Y006393D02*
-X003430Y006393D01*
-X003397Y006428D02*
-X003397Y006428D01*
-X003577Y006234D02*
-X019603Y006234D01*
-X019568Y006207D02*
-X019568Y006207D01*
-X019479Y006076D02*
-X003724Y006076D01*
-X003738Y006061D02*
-X003738Y006061D01*
-X003807Y005917D02*
-X019371Y005917D01*
-X019286Y005793D02*
-X019286Y005793D01*
-X019276Y005759D02*
-X003883Y005759D01*
-X003955Y005610D02*
-X003955Y005610D01*
-X003957Y005600D02*
-X019227Y005600D01*
-X019178Y005442D02*
-X003981Y005442D01*
-X004005Y005283D02*
-X019139Y005283D01*
-X019139Y005125D02*
-X004028Y005125D01*
-X004008Y004966D02*
-X019139Y004966D01*
-X019141Y004808D02*
-X003984Y004808D01*
-X003960Y004649D02*
-X019190Y004649D01*
-X020426Y006702D02*
-X020426Y006702D01*
-X021096Y007661D02*
-X022320Y007661D01*
-X022320Y007819D02*
-X021389Y007819D01*
-X021547Y007978D02*
-X022320Y007978D01*
-X022320Y008136D02*
-X021660Y008136D01*
-X021747Y008295D02*
-X022320Y008295D01*
-X022320Y008453D02*
-X021790Y008453D01*
-X021820Y008612D02*
-X022320Y008612D01*
-X022320Y008770D02*
-X021820Y008770D01*
-X021802Y008929D02*
-X022320Y008929D01*
-X022320Y009087D02*
-X021760Y009087D01*
-X021686Y009246D02*
-X022320Y009246D01*
-X022320Y009404D02*
-X021594Y009404D01*
-X021435Y009563D02*
-X022320Y009563D01*
-X022320Y009721D02*
-X021196Y009721D01*
-X020204Y009721D02*
-X018573Y009721D01*
-X018637Y009880D02*
-X022320Y009880D01*
-X022320Y010038D02*
-X018637Y010038D01*
-X018577Y010197D02*
-X022320Y010197D01*
-X022320Y010355D02*
-X018398Y010355D01*
-X018200Y009964D02*
-X015900Y009964D01*
-X015850Y009914D01*
-X016158Y009721D02*
-X016839Y009721D01*
-X018373Y009563D02*
-X019965Y009563D01*
-X017783Y012099D02*
-X011930Y012099D01*
-X011930Y012257D02*
-X017875Y012257D01*
-X020426Y014426D02*
-X020426Y014426D01*
-X020299Y014476D02*
-X002808Y014476D01*
-X002914Y014500D02*
-X002914Y014500D01*
-X003147Y014635D02*
-X019928Y014635D01*
-X019960Y014609D02*
-X019960Y014609D01*
-X019729Y014793D02*
-X003387Y014793D01*
-X003534Y014952D02*
-X019548Y014952D01*
-X019440Y015110D02*
-X003681Y015110D01*
-X003688Y015118D02*
-X003688Y015118D01*
-X003761Y015269D02*
-X019332Y015269D01*
-X019286Y015335D02*
-X019286Y015335D01*
-X019258Y015427D02*
-X003837Y015427D01*
-X003905Y015569D02*
-X003905Y015569D01*
-X003908Y015586D02*
-X019209Y015586D01*
-X019160Y015744D02*
-X003932Y015744D01*
-X003956Y015903D02*
-X019139Y015903D01*
-X019139Y016061D02*
-X018509Y016061D01*
-X018370Y016061D02*
-X018330Y016061D01*
-X018330Y016034D02*
-X018330Y016794D01*
-X017870Y016794D01*
-X017870Y016477D01*
-X017882Y016402D01*
-X017905Y016330D01*
-X017939Y016263D01*
-X017984Y016202D01*
-X018037Y016148D01*
-X018098Y016104D01*
-X018166Y016069D01*
-X018238Y016046D01*
-X018312Y016034D01*
-X018330Y016034D01*
-X018191Y016061D02*
-X017458Y016061D01*
-X017441Y016054D02*
-X017611Y016124D01*
-X017740Y016254D01*
-X017810Y016423D01*
-X017810Y017206D01*
-X017740Y017375D01*
-X017611Y017504D01*
-X017441Y017574D01*
-X017258Y017574D01*
-X017089Y017504D01*
-X016960Y017375D01*
-X016890Y017206D01*
-X016890Y016423D01*
-X016960Y016254D01*
-X017089Y016124D01*
-X017258Y016054D01*
-X017441Y016054D01*
-X017242Y016061D02*
-X003980Y016061D01*
-X003980Y016064D02*
-X003980Y016064D01*
-X003957Y016220D02*
-X005221Y016220D01*
-X005479Y016220D02*
-X006221Y016220D01*
-X006479Y016220D02*
-X007165Y016220D01*
-X007166Y016219D02*
-X007238Y016196D01*
-X007312Y016184D01*
-X007330Y016184D01*
-X007330Y016944D01*
-X006870Y016944D01*
-X006870Y016627D01*
-X006882Y016552D01*
-X006905Y016480D01*
-X006939Y016413D01*
-X006984Y016352D01*
-X007037Y016298D01*
-X007098Y016254D01*
-X007166Y016219D01*
-X007330Y016220D02*
-X007370Y016220D01*
-X007370Y016378D02*
-X007330Y016378D01*
-X007330Y016537D02*
-X007370Y016537D01*
-X007370Y016695D02*
-X007330Y016695D01*
-X007330Y016854D02*
-X007370Y016854D01*
-X007830Y016854D02*
-X016890Y016854D01*
-X016890Y017012D02*
-X007830Y017012D01*
-X007830Y017171D02*
-X016890Y017171D01*
-X016941Y017329D02*
-X007826Y017329D01*
-X007775Y017488D02*
-X017073Y017488D01*
-X017627Y017488D02*
-X018047Y017488D01*
-X017921Y017329D02*
-X017759Y017329D01*
-X017810Y017171D02*
-X017873Y017171D01*
-X017870Y017012D02*
-X017810Y017012D01*
-X017810Y016854D02*
-X017870Y016854D01*
-X017870Y016695D02*
-X017810Y016695D01*
-X017810Y016537D02*
-X017870Y016537D01*
-X017889Y016378D02*
-X017792Y016378D01*
-X017706Y016220D02*
-X017971Y016220D01*
-X018330Y016220D02*
-X018370Y016220D01*
-X018370Y016378D02*
-X018330Y016378D01*
-X018330Y016537D02*
-X018370Y016537D01*
-X018370Y016695D02*
-X018330Y016695D01*
-X018830Y016695D02*
-X019256Y016695D01*
-X019286Y016793D02*
-X019286Y016793D01*
-X019328Y016854D02*
-X018830Y016854D01*
-X018830Y017012D02*
-X019436Y017012D01*
-X019544Y017171D02*
-X018827Y017171D01*
-X018779Y017329D02*
-X019722Y017329D01*
-X019568Y017207D02*
-X019568Y017207D01*
-X019921Y017488D02*
-X018653Y017488D01*
-X018830Y016537D02*
-X019207Y016537D01*
-X019158Y016378D02*
-X018811Y016378D01*
-X018729Y016220D02*
-X019139Y016220D01*
-X019960Y017519D02*
-X019960Y017519D01*
-X022261Y015269D02*
-X022320Y015269D01*
-X022320Y015110D02*
-X022181Y015110D01*
-X022188Y015118D02*
-X022188Y015118D01*
-X022320Y014952D02*
-X022034Y014952D01*
-X021887Y014793D02*
-X022320Y014793D01*
-X022320Y014635D02*
-X021647Y014635D01*
-X021414Y014500D02*
-X021414Y014500D01*
-X021308Y014476D02*
-X022320Y014476D01*
-X022320Y014318D02*
-X000780Y014318D01*
-X000780Y014476D02*
-X001799Y014476D01*
-X001926Y014426D02*
-X001926Y014426D01*
-X001460Y014609D02*
-X001460Y014609D01*
-X001428Y014635D02*
-X000780Y014635D01*
-X000780Y014793D02*
-X001229Y014793D01*
-X001048Y014952D02*
-X000780Y014952D01*
-X000780Y015110D02*
-X000940Y015110D01*
-X000832Y015269D02*
-X000780Y015269D01*
-X000786Y015335D02*
-X000786Y015335D01*
-X000780Y014159D02*
-X022320Y014159D01*
-X022320Y014001D02*
-X000780Y014001D01*
-X000780Y013208D02*
-X011292Y013208D01*
-X011310Y013050D02*
-X000780Y013050D01*
-X000780Y012891D02*
-X000876Y012891D01*
-X000856Y012257D02*
-X000780Y012257D01*
-X000780Y012099D02*
-X011370Y012099D01*
-X011370Y012257D02*
-X002144Y012257D01*
-X002236Y012416D02*
-X011370Y012416D01*
-X011370Y012574D02*
-X002260Y012574D01*
-X002228Y012733D02*
-X011370Y012733D01*
-X011370Y012891D02*
-X002124Y012891D01*
-X002075Y011940D02*
-X011370Y011940D01*
-X011370Y011782D02*
-X002208Y011782D01*
-X002260Y011623D02*
-X011370Y011623D01*
-X011370Y011465D02*
-X002257Y011465D01*
-X002191Y011306D02*
-X011370Y011306D01*
-X011370Y011148D02*
-X001997Y011148D01*
-X001976Y010989D02*
-X011370Y010989D01*
-X011370Y010831D02*
-X002184Y010831D01*
-X002253Y010672D02*
-X011370Y010672D01*
-X011409Y010514D02*
-X002260Y010514D01*
-X002211Y010355D02*
-X011563Y010355D01*
-X011722Y010197D02*
-X002083Y010197D01*
-X002135Y009880D02*
-X012039Y009880D01*
-X012197Y009721D02*
-X002233Y009721D01*
-X002260Y009563D02*
-X012356Y009563D01*
-X012514Y009404D02*
-X002232Y009404D01*
-X002132Y009246D02*
-X012673Y009246D01*
-X011880Y010038D02*
-X000780Y010038D01*
-X000780Y009880D02*
-X000865Y009880D01*
-X000917Y010197D02*
-X000780Y010197D01*
-X000780Y010355D02*
-X000789Y010355D01*
-X000780Y010831D02*
-X000816Y010831D01*
-X000780Y010989D02*
-X001024Y010989D01*
-X001003Y011148D02*
-X000780Y011148D01*
-X000780Y011306D02*
-X000809Y011306D01*
-X000780Y011782D02*
-X000792Y011782D01*
-X000780Y011940D02*
-X000925Y011940D01*
-X002426Y014389D02*
-X002426Y014389D01*
-X003933Y016378D02*
-X004985Y016378D01*
-X004905Y016537D02*
-X003909Y016537D01*
-X003840Y016695D02*
-X004890Y016695D01*
-X004890Y016854D02*
-X003764Y016854D01*
-X003688Y017011D02*
-X003688Y017011D01*
-X003687Y017012D02*
-X004890Y017012D01*
-X004890Y017171D02*
-X003539Y017171D01*
-X003392Y017329D02*
-X004890Y017329D01*
-X004945Y017488D02*
-X003157Y017488D01*
-X003347Y017378D02*
-X003347Y017378D01*
-X005715Y016378D02*
-X005985Y016378D01*
-X005905Y016537D02*
-X005795Y016537D01*
-X005810Y016695D02*
-X005890Y016695D01*
-X005890Y016854D02*
-X005810Y016854D01*
-X005810Y017012D02*
-X005890Y017012D01*
-X005890Y017171D02*
-X005810Y017171D01*
-X005810Y017329D02*
-X005890Y017329D01*
-X005945Y017488D02*
-X005755Y017488D01*
-X006755Y017488D02*
-X006925Y017488D01*
-X006874Y017329D02*
-X006810Y017329D01*
-X006810Y017171D02*
-X006870Y017171D01*
-X006870Y017012D02*
-X006810Y017012D01*
-X006810Y016854D02*
-X006870Y016854D01*
-X006870Y016695D02*
-X006810Y016695D01*
-X006795Y016537D02*
-X006887Y016537D01*
-X006964Y016378D02*
-X006715Y016378D01*
-X007535Y016220D02*
-X016994Y016220D01*
-X016908Y016378D02*
-X007736Y016378D01*
-X007813Y016537D02*
-X016890Y016537D01*
-X016890Y016695D02*
-X007830Y016695D01*
-X011346Y013367D02*
-X011447Y013468D01*
-X011579Y013522D01*
-X011721Y013522D01*
-X011853Y013468D01*
-X011954Y013367D01*
-X020926Y014389D02*
-X020926Y014389D01*
-X009450Y004491D02*
-X003894Y004491D01*
-X003955Y004619D02*
-X003955Y004619D01*
-X003817Y004332D02*
-X009450Y004332D01*
-X009450Y004174D02*
-X003741Y004174D01*
-X003738Y004168D02*
-X003738Y004168D01*
-X003596Y004015D02*
-X009450Y004015D01*
-X009379Y003857D02*
-X003449Y003857D01*
-X003220Y003698D02*
-X009326Y003698D01*
-X002964Y003550D02*
-X002964Y003550D01*
-X000810Y005759D02*
-X000780Y005759D01*
-X000836Y005843D02*
-X000836Y005843D01*
-X000887Y005917D02*
-X000780Y005917D01*
-X000780Y006076D02*
-X000995Y006076D01*
-X001103Y006234D02*
-X000780Y006234D01*
-X000780Y006393D02*
-X001289Y006393D01*
-X001118Y006257D02*
-X001118Y006257D01*
-X000780Y006551D02*
-X001488Y006551D01*
-X001510Y006569D02*
-X001510Y006569D01*
-X001868Y006710D02*
-X000780Y006710D01*
-X001976Y006752D02*
-X001976Y006752D01*
-X000868Y009246D02*
-X000780Y009246D01*
-D16*
-X004150Y011564D03*
-X006500Y013714D03*
-X010000Y015114D03*
-X011650Y013164D03*
-X013300Y011464D03*
-X013350Y010114D03*
-X013550Y008764D03*
-X013500Y006864D03*
-X012100Y005314D03*
-X009250Y004064D03*
-X015200Y004514D03*
-X015650Y006264D03*
-X015850Y009914D03*
-X014250Y014964D03*
-D17*
-X011650Y013164D02*
-X011650Y010664D01*
-X013550Y008764D01*
-M02*
diff --git a/gerber/tests/resources/bottom_mask.GBS b/gerber/tests/resources/bottom_mask.GBS
deleted file mode 100644
index b06654f..0000000
--- a/gerber/tests/resources/bottom_mask.GBS
+++ /dev/null
@@ -1,66 +0,0 @@
-G75*
-%MOIN*%
-%OFA0B0*%
-%FSLAX24Y24*%
-%IPPOS*%
-%LPD*%
-%AMOC8*
-5,1,8,0,0,1.08239X$1,22.5*
-%
-%ADD10C,0.0634*%
-%ADD11C,0.1360*%
-%ADD12C,0.0680*%
-%ADD13C,0.1340*%
-%ADD14C,0.0476*%
-D10*
-X017200Y009464D03*
-X018200Y009964D03*
-X018200Y010964D03*
-X017200Y010464D03*
-X017200Y011464D03*
-X018200Y011964D03*
-D11*
-X020700Y012714D03*
-X020700Y008714D03*
-D12*
-X018350Y016514D02*
-X018350Y017114D01*
-X017350Y017114D02*
-X017350Y016514D01*
-X007350Y016664D02*
-X007350Y017264D01*
-X006350Y017264D02*
-X006350Y016664D01*
-X005350Y016664D02*
-X005350Y017264D01*
-X001800Y012564D02*
-X001200Y012564D01*
-X001200Y011564D02*
-X001800Y011564D01*
-X001800Y010564D02*
-X001200Y010564D01*
-X001200Y009564D02*
-X001800Y009564D01*
-X001800Y008564D02*
-X001200Y008564D01*
-D13*
-X002350Y005114D03*
-X002300Y016064D03*
-X020800Y016064D03*
-X020800Y005064D03*
-D14*
-X015650Y006264D03*
-X013500Y006864D03*
-X012100Y005314D03*
-X009250Y004064D03*
-X015200Y004514D03*
-X013550Y008764D03*
-X013350Y010114D03*
-X013300Y011464D03*
-X011650Y013164D03*
-X010000Y015114D03*
-X006500Y013714D03*
-X004150Y011564D03*
-X014250Y014964D03*
-X015850Y009914D03*
-M02*
diff --git a/gerber/tests/resources/bottom_silk.GBO b/gerber/tests/resources/bottom_silk.GBO
deleted file mode 100644
index 0e19197..0000000
--- a/gerber/tests/resources/bottom_silk.GBO
+++ /dev/null
@@ -1,6007 +0,0 @@
-G75*
-%MOIN*%
-%OFA0B0*%
-%FSLAX24Y24*%
-%IPPOS*%
-%LPD*%
-%AMOC8*
-5,1,8,0,0,1.08239X$1,22.5*
-%
-%ADD10C,0.0000*%
-%ADD11R,0.0470X0.0010*%
-%ADD12R,0.0560X0.0010*%
-%ADD13R,0.0570X0.0010*%
-%ADD14R,0.0580X0.0010*%
-%ADD15R,0.0300X0.0010*%
-%ADD16R,0.0450X0.0010*%
-%ADD17R,0.0670X0.0010*%
-%ADD18R,0.0510X0.0010*%
-%ADD19R,0.0760X0.0010*%
-%ADD20R,0.0520X0.0010*%
-%ADD21R,0.1900X0.0010*%
-%ADD22R,0.0820X0.0010*%
-%ADD23R,0.0880X0.0010*%
-%ADD24R,0.0530X0.0010*%
-%ADD25R,0.0940X0.0010*%
-%ADD26R,0.1000X0.0010*%
-%ADD27R,0.0540X0.0010*%
-%ADD28R,0.1050X0.0010*%
-%ADD29R,0.0550X0.0010*%
-%ADD30R,0.1100X0.0010*%
-%ADD31R,0.1140X0.0010*%
-%ADD32R,0.1180X0.0010*%
-%ADD33R,0.1220X0.0010*%
-%ADD34R,0.1260X0.0010*%
-%ADD35R,0.1300X0.0010*%
-%ADD36R,0.1320X0.0010*%
-%ADD37R,0.0590X0.0010*%
-%ADD38R,0.1360X0.0010*%
-%ADD39R,0.0600X0.0010*%
-%ADD40R,0.1400X0.0010*%
-%ADD41R,0.1420X0.0010*%
-%ADD42R,0.0610X0.0010*%
-%ADD43R,0.1460X0.0010*%
-%ADD44R,0.1480X0.0010*%
-%ADD45R,0.0620X0.0010*%
-%ADD46R,0.1500X0.0010*%
-%ADD47R,0.0630X0.0010*%
-%ADD48R,0.1540X0.0010*%
-%ADD49R,0.1560X0.0010*%
-%ADD50R,0.0640X0.0010*%
-%ADD51R,0.1580X0.0010*%
-%ADD52R,0.0650X0.0010*%
-%ADD53R,0.1600X0.0010*%
-%ADD54R,0.1640X0.0010*%
-%ADD55R,0.0660X0.0010*%
-%ADD56R,0.1660X0.0010*%
-%ADD57R,0.1680X0.0010*%
-%ADD58R,0.1700X0.0010*%
-%ADD59R,0.0680X0.0010*%
-%ADD60R,0.1720X0.0010*%
-%ADD61R,0.1740X0.0010*%
-%ADD62R,0.0690X0.0010*%
-%ADD63R,0.1760X0.0010*%
-%ADD64R,0.1780X0.0010*%
-%ADD65R,0.0700X0.0010*%
-%ADD66R,0.1800X0.0010*%
-%ADD67R,0.0710X0.0010*%
-%ADD68R,0.1820X0.0010*%
-%ADD69R,0.0720X0.0010*%
-%ADD70R,0.1840X0.0010*%
-%ADD71R,0.0730X0.0010*%
-%ADD72R,0.1860X0.0010*%
-%ADD73R,0.1880X0.0010*%
-%ADD74R,0.0740X0.0010*%
-%ADD75R,0.1920X0.0010*%
-%ADD76R,0.0750X0.0010*%
-%ADD77R,0.1940X0.0010*%
-%ADD78R,0.0860X0.0010*%
-%ADD79R,0.0850X0.0010*%
-%ADD80R,0.0810X0.0010*%
-%ADD81R,0.0770X0.0010*%
-%ADD82R,0.0790X0.0010*%
-%ADD83R,0.0780X0.0010*%
-%ADD84R,0.0800X0.0010*%
-%ADD85R,0.0830X0.0010*%
-%ADD86R,0.0840X0.0010*%
-%ADD87R,0.0870X0.0010*%
-%ADD88R,0.0890X0.0010*%
-%ADD89R,0.0900X0.0010*%
-%ADD90R,0.0910X0.0010*%
-%ADD91R,0.0920X0.0010*%
-%ADD92R,0.0930X0.0010*%
-%ADD93R,0.0950X0.0010*%
-%ADD94R,0.0960X0.0010*%
-%ADD95R,0.0970X0.0010*%
-%ADD96R,0.0980X0.0010*%
-%ADD97R,0.0990X0.0010*%
-%ADD98R,0.1010X0.0010*%
-%ADD99R,0.1020X0.0010*%
-%ADD100R,0.1030X0.0010*%
-%ADD101R,0.1040X0.0010*%
-%ADD102R,0.0480X0.0010*%
-%ADD103R,0.1990X0.0010*%
-%ADD104R,0.1850X0.0010*%
-%ADD105R,0.1620X0.0010*%
-%ADD106R,0.1570X0.0010*%
-%ADD107R,0.1550X0.0010*%
-%ADD108R,0.1520X0.0010*%
-%ADD109R,0.1490X0.0010*%
-%ADD110R,0.1470X0.0010*%
-%ADD111R,0.1430X0.0010*%
-%ADD112R,0.1410X0.0010*%
-%ADD113R,0.1380X0.0010*%
-%ADD114R,0.1350X0.0010*%
-%ADD115R,0.1310X0.0010*%
-%ADD116R,0.1280X0.0010*%
-%ADD117R,0.1250X0.0010*%
-%ADD118R,0.1210X0.0010*%
-%ADD119R,0.1170X0.0010*%
-%ADD120R,0.1120X0.0010*%
-%ADD121R,0.1080X0.0010*%
-%ADD122R,0.0500X0.0010*%
-%ADD123R,0.0370X0.0010*%
-%ADD124R,0.0070X0.0010*%
-%ADD125R,0.2950X0.0010*%
-%ADD126R,0.0490X0.0010*%
-%ADD127R,0.1290X0.0010*%
-%ADD128R,0.1610X0.0010*%
-%ADD129R,0.1690X0.0010*%
-%ADD130R,0.1710X0.0010*%
-%ADD131R,0.1730X0.0010*%
-%ADD132R,0.1750X0.0010*%
-%ADD133R,0.1810X0.0010*%
-%ADD134R,0.1830X0.0010*%
-%ADD135R,0.1870X0.0010*%
-%ADD136R,0.1890X0.0010*%
-%ADD137R,0.1910X0.0010*%
-%ADD138R,0.1930X0.0010*%
-%ADD139R,0.1950X0.0010*%
-%ADD140R,0.1960X0.0010*%
-%ADD141R,0.1970X0.0010*%
-%ADD142R,0.1980X0.0010*%
-%ADD143R,0.2000X0.0010*%
-%ADD144R,0.2010X0.0010*%
-%ADD145R,0.2020X0.0010*%
-%ADD146R,0.2060X0.0010*%
-%ADD147R,0.2050X0.0010*%
-%ADD148R,0.2030X0.0010*%
-%ADD149R,0.1790X0.0010*%
-%ADD150R,0.1770X0.0010*%
-%ADD151R,0.1450X0.0010*%
-%ADD152R,0.1440X0.0010*%
-%ADD153R,0.1670X0.0010*%
-%ADD154R,0.1650X0.0010*%
-%ADD155R,0.1630X0.0010*%
-%ADD156R,0.1390X0.0010*%
-%ADD157R,0.1370X0.0010*%
-%ADD158R,0.3140X0.0010*%
-%ADD159R,0.1240X0.0010*%
-%ADD160C,0.0004*%
-D10*
-X000303Y003014D02*
-X000310Y003014D01*
-X000313Y003018D01*
-X000318Y003018D02*
-X000318Y003014D01*
-X000322Y003014D01*
-X000322Y003018D01*
-X000318Y003018D01*
-X000318Y003024D02*
-X000318Y003028D01*
-X000322Y003028D01*
-X000322Y003024D01*
-X000318Y003024D01*
-X000313Y003031D02*
-X000310Y003034D01*
-X000303Y003034D01*
-X000300Y003031D01*
-X000300Y003018D01*
-X000303Y003014D01*
-X000328Y003014D02*
-X000341Y003034D01*
-X000346Y003034D02*
-X000346Y003018D01*
-X000349Y003014D01*
-X000356Y003014D01*
-X000359Y003018D01*
-X000359Y003034D01*
-X000368Y003028D02*
-X000378Y003028D01*
-X000383Y003024D02*
-X000386Y003028D01*
-X000393Y003028D01*
-X000396Y003024D01*
-X000396Y003021D01*
-X000383Y003021D01*
-X000383Y003018D02*
-X000383Y003024D01*
-X000383Y003018D02*
-X000386Y003014D01*
-X000393Y003014D01*
-X000401Y003014D02*
-X000401Y003028D01*
-X000408Y003028D02*
-X000411Y003028D01*
-X000408Y003028D02*
-X000401Y003021D01*
-X000417Y003024D02*
-X000420Y003028D01*
-X000430Y003028D01*
-X000427Y003021D02*
-X000420Y003021D01*
-X000417Y003024D01*
-X000417Y003014D02*
-X000427Y003014D01*
-X000430Y003018D01*
-X000427Y003021D01*
-X000435Y003014D02*
-X000448Y003034D01*
-X000453Y003034D02*
-X000453Y003014D01*
-X000453Y003024D02*
-X000457Y003028D01*
-X000463Y003028D01*
-X000467Y003024D01*
-X000467Y003014D01*
-X000472Y003018D02*
-X000475Y003021D01*
-X000485Y003021D01*
-X000485Y003024D02*
-X000485Y003014D01*
-X000475Y003014D01*
-X000472Y003018D01*
-X000475Y003028D02*
-X000482Y003028D01*
-X000485Y003024D01*
-X000490Y003028D02*
-X000494Y003028D01*
-X000497Y003024D01*
-X000500Y003028D01*
-X000504Y003024D01*
-X000504Y003014D01*
-X000509Y003014D02*
-X000515Y003014D01*
-X000512Y003014D02*
-X000512Y003028D01*
-X000509Y003028D01*
-X000512Y003034D02*
-X000512Y003038D01*
-X000521Y003034D02*
-X000524Y003034D01*
-X000524Y003014D01*
-X000521Y003014D02*
-X000528Y003014D01*
-X000537Y003018D02*
-X000540Y003014D01*
-X000537Y003018D02*
-X000537Y003031D01*
-X000540Y003028D02*
-X000533Y003028D01*
-X000546Y003024D02*
-X000546Y003018D01*
-X000549Y003014D01*
-X000556Y003014D01*
-X000559Y003018D01*
-X000559Y003024D01*
-X000556Y003028D01*
-X000549Y003028D01*
-X000546Y003024D01*
-X000564Y003028D02*
-X000574Y003028D01*
-X000577Y003024D01*
-X000577Y003014D01*
-X000582Y003014D02*
-X000586Y003014D01*
-X000586Y003018D01*
-X000582Y003018D01*
-X000582Y003014D01*
-X000592Y003014D02*
-X000592Y003034D01*
-X000602Y003028D02*
-X000592Y003021D01*
-X000602Y003014D01*
-X000607Y003014D02*
-X000614Y003014D01*
-X000610Y003014D02*
-X000610Y003028D01*
-X000607Y003028D01*
-X000610Y003034D02*
-X000610Y003038D01*
-X000619Y003034D02*
-X000619Y003014D01*
-X000629Y003014D01*
-X000633Y003018D01*
-X000633Y003024D01*
-X000629Y003028D01*
-X000619Y003028D01*
-X000638Y003028D02*
-X000648Y003028D01*
-X000651Y003024D01*
-X000651Y003018D01*
-X000648Y003014D01*
-X000638Y003014D01*
-X000638Y003034D01*
-X000656Y003024D02*
-X000659Y003028D01*
-X000666Y003028D01*
-X000669Y003024D01*
-X000669Y003021D01*
-X000656Y003021D01*
-X000656Y003018D02*
-X000656Y003024D01*
-X000656Y003018D02*
-X000659Y003014D01*
-X000666Y003014D01*
-X000674Y003014D02*
-X000688Y003034D01*
-X000693Y003034D02*
-X000703Y003034D01*
-X000706Y003031D01*
-X000706Y003018D01*
-X000703Y003014D01*
-X000693Y003014D01*
-X000693Y003034D01*
-X000711Y003024D02*
-X000715Y003028D01*
-X000721Y003028D01*
-X000725Y003024D01*
-X000725Y003021D01*
-X000711Y003021D01*
-X000711Y003018D02*
-X000711Y003024D01*
-X000711Y003018D02*
-X000715Y003014D01*
-X000721Y003014D01*
-X000730Y003014D02*
-X000740Y003014D01*
-X000743Y003018D01*
-X000740Y003021D01*
-X000733Y003021D01*
-X000730Y003024D01*
-X000733Y003028D01*
-X000743Y003028D01*
-X000748Y003034D02*
-X000748Y003014D01*
-X000748Y003021D02*
-X000758Y003028D01*
-X000763Y003028D02*
-X000770Y003028D01*
-X000767Y003031D02*
-X000767Y003018D01*
-X000770Y003014D01*
-X000776Y003018D02*
-X000779Y003014D01*
-X000786Y003014D01*
-X000789Y003018D01*
-X000789Y003024D01*
-X000786Y003028D01*
-X000779Y003028D01*
-X000776Y003024D01*
-X000776Y003018D01*
-X000758Y003014D02*
-X000748Y003021D01*
-X000794Y003014D02*
-X000804Y003014D01*
-X000807Y003018D01*
-X000807Y003024D01*
-X000804Y003028D01*
-X000794Y003028D01*
-X000794Y003008D01*
-X000813Y003014D02*
-X000826Y003034D01*
-X000831Y003034D02*
-X000831Y003014D01*
-X000841Y003014D01*
-X000844Y003018D01*
-X000844Y003024D01*
-X000841Y003028D01*
-X000831Y003028D01*
-X000849Y003028D02*
-X000856Y003028D01*
-X000853Y003031D02*
-X000853Y003018D01*
-X000856Y003014D01*
-X000865Y003014D02*
-X000865Y003031D01*
-X000868Y003034D01*
-X000874Y003028D02*
-X000887Y003014D01*
-X000892Y003014D02*
-X000896Y003014D01*
-X000896Y003018D01*
-X000892Y003018D01*
-X000892Y003014D01*
-X000902Y003014D02*
-X000912Y003014D01*
-X000915Y003018D01*
-X000915Y003021D01*
-X000912Y003024D01*
-X000902Y003024D01*
-X000912Y003024D02*
-X000915Y003028D01*
-X000915Y003031D01*
-X000912Y003034D01*
-X000902Y003034D01*
-X000902Y003014D01*
-X000920Y003014D02*
-X000920Y003034D01*
-X000927Y003028D01*
-X000933Y003034D01*
-X000933Y003014D01*
-X000938Y003014D02*
-X000938Y003034D01*
-X000948Y003034D01*
-X000952Y003031D01*
-X000952Y003024D01*
-X000948Y003021D01*
-X000938Y003021D01*
-X000887Y003028D02*
-X000874Y003014D01*
-X000868Y003024D02*
-X000862Y003024D01*
-X000564Y003014D02*
-X000564Y003028D01*
-X000497Y003024D02*
-X000497Y003014D01*
-X000490Y003014D02*
-X000490Y003028D01*
-X000378Y003018D02*
-X000374Y003021D01*
-X000368Y003021D01*
-X000364Y003024D01*
-X000368Y003028D01*
-X000364Y003014D02*
-X000374Y003014D01*
-X000378Y003018D01*
-X000300Y003064D02*
-X000300Y018064D01*
-X022800Y018064D01*
-X022800Y003064D01*
-X000300Y003064D01*
-X001720Y005114D02*
-X001722Y005164D01*
-X001728Y005214D01*
-X001738Y005263D01*
-X001752Y005311D01*
-X001769Y005358D01*
-X001790Y005403D01*
-X001815Y005447D01*
-X001843Y005488D01*
-X001875Y005527D01*
-X001909Y005564D01*
-X001946Y005598D01*
-X001986Y005628D01*
-X002028Y005655D01*
-X002072Y005679D01*
-X002118Y005700D01*
-X002165Y005716D01*
-X002213Y005729D01*
-X002263Y005738D01*
-X002312Y005743D01*
-X002363Y005744D01*
-X002413Y005741D01*
-X002462Y005734D01*
-X002511Y005723D01*
-X002559Y005708D01*
-X002605Y005690D01*
-X002650Y005668D01*
-X002693Y005642D01*
-X002734Y005613D01*
-X002773Y005581D01*
-X002809Y005546D01*
-X002841Y005508D01*
-X002871Y005468D01*
-X002898Y005425D01*
-X002921Y005381D01*
-X002940Y005335D01*
-X002956Y005287D01*
-X002968Y005238D01*
-X002976Y005189D01*
-X002980Y005139D01*
-X002980Y005089D01*
-X002976Y005039D01*
-X002968Y004990D01*
-X002956Y004941D01*
-X002940Y004893D01*
-X002921Y004847D01*
-X002898Y004803D01*
-X002871Y004760D01*
-X002841Y004720D01*
-X002809Y004682D01*
-X002773Y004647D01*
-X002734Y004615D01*
-X002693Y004586D01*
-X002650Y004560D01*
-X002605Y004538D01*
-X002559Y004520D01*
-X002511Y004505D01*
-X002462Y004494D01*
-X002413Y004487D01*
-X002363Y004484D01*
-X002312Y004485D01*
-X002263Y004490D01*
-X002213Y004499D01*
-X002165Y004512D01*
-X002118Y004528D01*
-X002072Y004549D01*
-X002028Y004573D01*
-X001986Y004600D01*
-X001946Y004630D01*
-X001909Y004664D01*
-X001875Y004701D01*
-X001843Y004740D01*
-X001815Y004781D01*
-X001790Y004825D01*
-X001769Y004870D01*
-X001752Y004917D01*
-X001738Y004965D01*
-X001728Y005014D01*
-X001722Y005064D01*
-X001720Y005114D01*
-X001670Y016064D02*
-X001672Y016114D01*
-X001678Y016164D01*
-X001688Y016213D01*
-X001702Y016261D01*
-X001719Y016308D01*
-X001740Y016353D01*
-X001765Y016397D01*
-X001793Y016438D01*
-X001825Y016477D01*
-X001859Y016514D01*
-X001896Y016548D01*
-X001936Y016578D01*
-X001978Y016605D01*
-X002022Y016629D01*
-X002068Y016650D01*
-X002115Y016666D01*
-X002163Y016679D01*
-X002213Y016688D01*
-X002262Y016693D01*
-X002313Y016694D01*
-X002363Y016691D01*
-X002412Y016684D01*
-X002461Y016673D01*
-X002509Y016658D01*
-X002555Y016640D01*
-X002600Y016618D01*
-X002643Y016592D01*
-X002684Y016563D01*
-X002723Y016531D01*
-X002759Y016496D01*
-X002791Y016458D01*
-X002821Y016418D01*
-X002848Y016375D01*
-X002871Y016331D01*
-X002890Y016285D01*
-X002906Y016237D01*
-X002918Y016188D01*
-X002926Y016139D01*
-X002930Y016089D01*
-X002930Y016039D01*
-X002926Y015989D01*
-X002918Y015940D01*
-X002906Y015891D01*
-X002890Y015843D01*
-X002871Y015797D01*
-X002848Y015753D01*
-X002821Y015710D01*
-X002791Y015670D01*
-X002759Y015632D01*
-X002723Y015597D01*
-X002684Y015565D01*
-X002643Y015536D01*
-X002600Y015510D01*
-X002555Y015488D01*
-X002509Y015470D01*
-X002461Y015455D01*
-X002412Y015444D01*
-X002363Y015437D01*
-X002313Y015434D01*
-X002262Y015435D01*
-X002213Y015440D01*
-X002163Y015449D01*
-X002115Y015462D01*
-X002068Y015478D01*
-X002022Y015499D01*
-X001978Y015523D01*
-X001936Y015550D01*
-X001896Y015580D01*
-X001859Y015614D01*
-X001825Y015651D01*
-X001793Y015690D01*
-X001765Y015731D01*
-X001740Y015775D01*
-X001719Y015820D01*
-X001702Y015867D01*
-X001688Y015915D01*
-X001678Y015964D01*
-X001672Y016014D01*
-X001670Y016064D01*
-X020060Y012714D02*
-X020062Y012764D01*
-X020068Y012814D01*
-X020078Y012863D01*
-X020091Y012912D01*
-X020109Y012959D01*
-X020130Y013005D01*
-X020154Y013048D01*
-X020182Y013090D01*
-X020213Y013130D01*
-X020247Y013167D01*
-X020284Y013201D01*
-X020324Y013232D01*
-X020366Y013260D01*
-X020409Y013284D01*
-X020455Y013305D01*
-X020502Y013323D01*
-X020551Y013336D01*
-X020600Y013346D01*
-X020650Y013352D01*
-X020700Y013354D01*
-X020750Y013352D01*
-X020800Y013346D01*
-X020849Y013336D01*
-X020898Y013323D01*
-X020945Y013305D01*
-X020991Y013284D01*
-X021034Y013260D01*
-X021076Y013232D01*
-X021116Y013201D01*
-X021153Y013167D01*
-X021187Y013130D01*
-X021218Y013090D01*
-X021246Y013048D01*
-X021270Y013005D01*
-X021291Y012959D01*
-X021309Y012912D01*
-X021322Y012863D01*
-X021332Y012814D01*
-X021338Y012764D01*
-X021340Y012714D01*
-X021338Y012664D01*
-X021332Y012614D01*
-X021322Y012565D01*
-X021309Y012516D01*
-X021291Y012469D01*
-X021270Y012423D01*
-X021246Y012380D01*
-X021218Y012338D01*
-X021187Y012298D01*
-X021153Y012261D01*
-X021116Y012227D01*
-X021076Y012196D01*
-X021034Y012168D01*
-X020991Y012144D01*
-X020945Y012123D01*
-X020898Y012105D01*
-X020849Y012092D01*
-X020800Y012082D01*
-X020750Y012076D01*
-X020700Y012074D01*
-X020650Y012076D01*
-X020600Y012082D01*
-X020551Y012092D01*
-X020502Y012105D01*
-X020455Y012123D01*
-X020409Y012144D01*
-X020366Y012168D01*
-X020324Y012196D01*
-X020284Y012227D01*
-X020247Y012261D01*
-X020213Y012298D01*
-X020182Y012338D01*
-X020154Y012380D01*
-X020130Y012423D01*
-X020109Y012469D01*
-X020091Y012516D01*
-X020078Y012565D01*
-X020068Y012614D01*
-X020062Y012664D01*
-X020060Y012714D01*
-X020170Y016064D02*
-X020172Y016114D01*
-X020178Y016164D01*
-X020188Y016213D01*
-X020202Y016261D01*
-X020219Y016308D01*
-X020240Y016353D01*
-X020265Y016397D01*
-X020293Y016438D01*
-X020325Y016477D01*
-X020359Y016514D01*
-X020396Y016548D01*
-X020436Y016578D01*
-X020478Y016605D01*
-X020522Y016629D01*
-X020568Y016650D01*
-X020615Y016666D01*
-X020663Y016679D01*
-X020713Y016688D01*
-X020762Y016693D01*
-X020813Y016694D01*
-X020863Y016691D01*
-X020912Y016684D01*
-X020961Y016673D01*
-X021009Y016658D01*
-X021055Y016640D01*
-X021100Y016618D01*
-X021143Y016592D01*
-X021184Y016563D01*
-X021223Y016531D01*
-X021259Y016496D01*
-X021291Y016458D01*
-X021321Y016418D01*
-X021348Y016375D01*
-X021371Y016331D01*
-X021390Y016285D01*
-X021406Y016237D01*
-X021418Y016188D01*
-X021426Y016139D01*
-X021430Y016089D01*
-X021430Y016039D01*
-X021426Y015989D01*
-X021418Y015940D01*
-X021406Y015891D01*
-X021390Y015843D01*
-X021371Y015797D01*
-X021348Y015753D01*
-X021321Y015710D01*
-X021291Y015670D01*
-X021259Y015632D01*
-X021223Y015597D01*
-X021184Y015565D01*
-X021143Y015536D01*
-X021100Y015510D01*
-X021055Y015488D01*
-X021009Y015470D01*
-X020961Y015455D01*
-X020912Y015444D01*
-X020863Y015437D01*
-X020813Y015434D01*
-X020762Y015435D01*
-X020713Y015440D01*
-X020663Y015449D01*
-X020615Y015462D01*
-X020568Y015478D01*
-X020522Y015499D01*
-X020478Y015523D01*
-X020436Y015550D01*
-X020396Y015580D01*
-X020359Y015614D01*
-X020325Y015651D01*
-X020293Y015690D01*
-X020265Y015731D01*
-X020240Y015775D01*
-X020219Y015820D01*
-X020202Y015867D01*
-X020188Y015915D01*
-X020178Y015964D01*
-X020172Y016014D01*
-X020170Y016064D01*
-X020060Y008714D02*
-X020062Y008764D01*
-X020068Y008814D01*
-X020078Y008863D01*
-X020091Y008912D01*
-X020109Y008959D01*
-X020130Y009005D01*
-X020154Y009048D01*
-X020182Y009090D01*
-X020213Y009130D01*
-X020247Y009167D01*
-X020284Y009201D01*
-X020324Y009232D01*
-X020366Y009260D01*
-X020409Y009284D01*
-X020455Y009305D01*
-X020502Y009323D01*
-X020551Y009336D01*
-X020600Y009346D01*
-X020650Y009352D01*
-X020700Y009354D01*
-X020750Y009352D01*
-X020800Y009346D01*
-X020849Y009336D01*
-X020898Y009323D01*
-X020945Y009305D01*
-X020991Y009284D01*
-X021034Y009260D01*
-X021076Y009232D01*
-X021116Y009201D01*
-X021153Y009167D01*
-X021187Y009130D01*
-X021218Y009090D01*
-X021246Y009048D01*
-X021270Y009005D01*
-X021291Y008959D01*
-X021309Y008912D01*
-X021322Y008863D01*
-X021332Y008814D01*
-X021338Y008764D01*
-X021340Y008714D01*
-X021338Y008664D01*
-X021332Y008614D01*
-X021322Y008565D01*
-X021309Y008516D01*
-X021291Y008469D01*
-X021270Y008423D01*
-X021246Y008380D01*
-X021218Y008338D01*
-X021187Y008298D01*
-X021153Y008261D01*
-X021116Y008227D01*
-X021076Y008196D01*
-X021034Y008168D01*
-X020991Y008144D01*
-X020945Y008123D01*
-X020898Y008105D01*
-X020849Y008092D01*
-X020800Y008082D01*
-X020750Y008076D01*
-X020700Y008074D01*
-X020650Y008076D01*
-X020600Y008082D01*
-X020551Y008092D01*
-X020502Y008105D01*
-X020455Y008123D01*
-X020409Y008144D01*
-X020366Y008168D01*
-X020324Y008196D01*
-X020284Y008227D01*
-X020247Y008261D01*
-X020213Y008298D01*
-X020182Y008338D01*
-X020154Y008380D01*
-X020130Y008423D01*
-X020109Y008469D01*
-X020091Y008516D01*
-X020078Y008565D01*
-X020068Y008614D01*
-X020062Y008664D01*
-X020060Y008714D01*
-X020170Y005064D02*
-X020172Y005114D01*
-X020178Y005164D01*
-X020188Y005213D01*
-X020202Y005261D01*
-X020219Y005308D01*
-X020240Y005353D01*
-X020265Y005397D01*
-X020293Y005438D01*
-X020325Y005477D01*
-X020359Y005514D01*
-X020396Y005548D01*
-X020436Y005578D01*
-X020478Y005605D01*
-X020522Y005629D01*
-X020568Y005650D01*
-X020615Y005666D01*
-X020663Y005679D01*
-X020713Y005688D01*
-X020762Y005693D01*
-X020813Y005694D01*
-X020863Y005691D01*
-X020912Y005684D01*
-X020961Y005673D01*
-X021009Y005658D01*
-X021055Y005640D01*
-X021100Y005618D01*
-X021143Y005592D01*
-X021184Y005563D01*
-X021223Y005531D01*
-X021259Y005496D01*
-X021291Y005458D01*
-X021321Y005418D01*
-X021348Y005375D01*
-X021371Y005331D01*
-X021390Y005285D01*
-X021406Y005237D01*
-X021418Y005188D01*
-X021426Y005139D01*
-X021430Y005089D01*
-X021430Y005039D01*
-X021426Y004989D01*
-X021418Y004940D01*
-X021406Y004891D01*
-X021390Y004843D01*
-X021371Y004797D01*
-X021348Y004753D01*
-X021321Y004710D01*
-X021291Y004670D01*
-X021259Y004632D01*
-X021223Y004597D01*
-X021184Y004565D01*
-X021143Y004536D01*
-X021100Y004510D01*
-X021055Y004488D01*
-X021009Y004470D01*
-X020961Y004455D01*
-X020912Y004444D01*
-X020863Y004437D01*
-X020813Y004434D01*
-X020762Y004435D01*
-X020713Y004440D01*
-X020663Y004449D01*
-X020615Y004462D01*
-X020568Y004478D01*
-X020522Y004499D01*
-X020478Y004523D01*
-X020436Y004550D01*
-X020396Y004580D01*
-X020359Y004614D01*
-X020325Y004651D01*
-X020293Y004690D01*
-X020265Y004731D01*
-X020240Y004775D01*
-X020219Y004820D01*
-X020202Y004867D01*
-X020188Y004915D01*
-X020178Y004964D01*
-X020172Y005014D01*
-X020170Y005064D01*
-D11*
-X015810Y007044D03*
-X015810Y007054D03*
-X015810Y007064D03*
-X015810Y007074D03*
-X015810Y007084D03*
-X015810Y007094D03*
-X015810Y007104D03*
-X015810Y007114D03*
-X015810Y007124D03*
-X015810Y007134D03*
-X015810Y007144D03*
-X015810Y007154D03*
-X015810Y007164D03*
-X015810Y007174D03*
-X015810Y007184D03*
-X015810Y007194D03*
-X015810Y007204D03*
-X015810Y007214D03*
-X015810Y007224D03*
-X015810Y007234D03*
-X015810Y007244D03*
-X015810Y007254D03*
-X015810Y007264D03*
-X015810Y007274D03*
-X015810Y007284D03*
-X015810Y007294D03*
-X015810Y007304D03*
-X015810Y007314D03*
-X015810Y007324D03*
-X015810Y007334D03*
-X015810Y007344D03*
-X015810Y007354D03*
-X015810Y007364D03*
-X015810Y007374D03*
-X015810Y007384D03*
-X015810Y007394D03*
-X015810Y007404D03*
-X015810Y007414D03*
-X015810Y007424D03*
-X015810Y007434D03*
-X015810Y007444D03*
-X015810Y007454D03*
-X015810Y007464D03*
-X015810Y007474D03*
-X015810Y007484D03*
-X015810Y007494D03*
-X015810Y007504D03*
-X015810Y007514D03*
-X015810Y007524D03*
-X015810Y007534D03*
-X015810Y007544D03*
-X015810Y007554D03*
-X015810Y007564D03*
-X015810Y007574D03*
-X015810Y007584D03*
-X015810Y007594D03*
-X015810Y007604D03*
-X015810Y007614D03*
-X015810Y007624D03*
-X015810Y007634D03*
-X015810Y007644D03*
-X015810Y007654D03*
-X015810Y007664D03*
-X015810Y007674D03*
-X015810Y007684D03*
-X015810Y007694D03*
-X015810Y007704D03*
-X015810Y007714D03*
-X015810Y007724D03*
-X015810Y007734D03*
-X015810Y007744D03*
-X015810Y007754D03*
-X015810Y007764D03*
-X015810Y007774D03*
-X015810Y007784D03*
-X015810Y007794D03*
-X015810Y007804D03*
-X015810Y007814D03*
-X015810Y007824D03*
-X015810Y007834D03*
-X015810Y007844D03*
-X015810Y007854D03*
-X015810Y007864D03*
-X015810Y007874D03*
-X015810Y007884D03*
-X015810Y007894D03*
-X015810Y007904D03*
-X015810Y007914D03*
-X015810Y007924D03*
-X015810Y007934D03*
-X015810Y007944D03*
-X015810Y007954D03*
-X015810Y007964D03*
-X015810Y007974D03*
-X015810Y007984D03*
-X015810Y007994D03*
-X015810Y008004D03*
-X015810Y008014D03*
-X015810Y008024D03*
-X015810Y008034D03*
-X015810Y008044D03*
-X015810Y008054D03*
-X015810Y008064D03*
-X015810Y008074D03*
-X015810Y008084D03*
-X015810Y008094D03*
-X015810Y008104D03*
-X015810Y008114D03*
-X015810Y008124D03*
-X015810Y008134D03*
-X015810Y008144D03*
-X015810Y008154D03*
-X015810Y008164D03*
-X015810Y008174D03*
-X015810Y008184D03*
-X015810Y008194D03*
-X015810Y008204D03*
-X015810Y008214D03*
-X015810Y008224D03*
-X015810Y008234D03*
-X015810Y008244D03*
-X015810Y008254D03*
-X015810Y008264D03*
-X015810Y008274D03*
-X015810Y008284D03*
-X015810Y008294D03*
-X015810Y008304D03*
-X015810Y008314D03*
-X015810Y008324D03*
-X015810Y008334D03*
-X015810Y008344D03*
-X015810Y008354D03*
-X015810Y008364D03*
-X015810Y008374D03*
-X015810Y008384D03*
-X015810Y008394D03*
-X015810Y008404D03*
-X015810Y008414D03*
-X015810Y008424D03*
-X015810Y008434D03*
-X015810Y008444D03*
-X015810Y008454D03*
-X015810Y008464D03*
-X015810Y008474D03*
-X015810Y008484D03*
-X015810Y008494D03*
-X015810Y008504D03*
-X015810Y008514D03*
-X015810Y008524D03*
-X015810Y008534D03*
-X015810Y008544D03*
-X015810Y008554D03*
-X015810Y008564D03*
-X015810Y008574D03*
-X015810Y008584D03*
-X015810Y008594D03*
-X015810Y008604D03*
-X015810Y008614D03*
-X015810Y008624D03*
-X015810Y008634D03*
-X010930Y008634D03*
-X010930Y008624D03*
-X010930Y008614D03*
-X010930Y008604D03*
-X010930Y008594D03*
-X010930Y008584D03*
-X010930Y008574D03*
-X010930Y008564D03*
-X010930Y008554D03*
-X010930Y008544D03*
-X010930Y008534D03*
-X010930Y008524D03*
-X010930Y008514D03*
-X010930Y008504D03*
-X010930Y008494D03*
-X010930Y008484D03*
-X010930Y008474D03*
-X010930Y008464D03*
-X010930Y008454D03*
-X010930Y008444D03*
-X010930Y008434D03*
-X010930Y008424D03*
-X010930Y008414D03*
-X010930Y008404D03*
-X010930Y008394D03*
-X010930Y008384D03*
-X010930Y008374D03*
-X010930Y008364D03*
-X010930Y008354D03*
-X010930Y008344D03*
-X010930Y008334D03*
-X010930Y008324D03*
-X010930Y008314D03*
-X010930Y008304D03*
-X010930Y008294D03*
-X010930Y008284D03*
-X010930Y008274D03*
-X010930Y008264D03*
-X010930Y008254D03*
-X010930Y008244D03*
-X010930Y008234D03*
-X010930Y008224D03*
-X010930Y008214D03*
-X010930Y008204D03*
-X010930Y008194D03*
-X010930Y008184D03*
-X010930Y008174D03*
-X010930Y008164D03*
-X010930Y008154D03*
-X010930Y008144D03*
-X010930Y008134D03*
-X010930Y008124D03*
-X010930Y008114D03*
-X010930Y008104D03*
-X010930Y008094D03*
-X010930Y008084D03*
-X010930Y008074D03*
-X010930Y008064D03*
-X010930Y008054D03*
-X010930Y008044D03*
-X010930Y008034D03*
-X010930Y008024D03*
-X010930Y008014D03*
-X010930Y008004D03*
-X010930Y007994D03*
-X010930Y007984D03*
-X010930Y007974D03*
-X010930Y007964D03*
-X010930Y007954D03*
-X010930Y007944D03*
-X010930Y007934D03*
-X010930Y007924D03*
-X010930Y007914D03*
-X010930Y007904D03*
-X010930Y007894D03*
-X010930Y007884D03*
-X010930Y007874D03*
-X010930Y007864D03*
-X010930Y007854D03*
-X010930Y007844D03*
-X010930Y007834D03*
-X010930Y007824D03*
-X010930Y007814D03*
-X010930Y007804D03*
-X010930Y007794D03*
-X010930Y007784D03*
-X010930Y007774D03*
-X010930Y007764D03*
-X010930Y007754D03*
-X010930Y007744D03*
-X010930Y007734D03*
-X010930Y007724D03*
-X010930Y007714D03*
-X010930Y007704D03*
-X010930Y007694D03*
-X010930Y007684D03*
-X010930Y007674D03*
-X010930Y007664D03*
-X010930Y007654D03*
-X010930Y007644D03*
-X010930Y007634D03*
-X010930Y007624D03*
-X010930Y007614D03*
-X010930Y007604D03*
-X010930Y007594D03*
-X010930Y007584D03*
-X010930Y007574D03*
-X010930Y007564D03*
-X010930Y007554D03*
-X010930Y007544D03*
-X010930Y007534D03*
-X010930Y007524D03*
-X010930Y007514D03*
-X010930Y007504D03*
-X010930Y007494D03*
-X010930Y007484D03*
-X010930Y007474D03*
-X010930Y007464D03*
-X010930Y007454D03*
-X010930Y007444D03*
-X010930Y007434D03*
-X010930Y007424D03*
-X010930Y007414D03*
-X010930Y007404D03*
-X010930Y007394D03*
-X010930Y007384D03*
-X010930Y007374D03*
-X010930Y007364D03*
-X010930Y007354D03*
-X010930Y007344D03*
-X010930Y007334D03*
-X010930Y007324D03*
-X010930Y007314D03*
-X010930Y007304D03*
-X010930Y007294D03*
-X010930Y007284D03*
-X010930Y007274D03*
-X010930Y007264D03*
-X010930Y007254D03*
-X010930Y007244D03*
-X010930Y007234D03*
-X010930Y007224D03*
-X010930Y007214D03*
-X010930Y007204D03*
-X010930Y007194D03*
-X010930Y007184D03*
-X010930Y007174D03*
-X010930Y007164D03*
-X010930Y007154D03*
-X010930Y007144D03*
-X010930Y007134D03*
-X010930Y007124D03*
-X010930Y007114D03*
-X010930Y007104D03*
-X010930Y007094D03*
-X010930Y007084D03*
-X010930Y007074D03*
-X010930Y007064D03*
-X010930Y007054D03*
-X010930Y007044D03*
-X010930Y007034D03*
-X010930Y007024D03*
-X010930Y007014D03*
-X010930Y007004D03*
-X010930Y006994D03*
-X010930Y006984D03*
-X010930Y006974D03*
-X010930Y008644D03*
-X010930Y008654D03*
-X010930Y008664D03*
-X010930Y008674D03*
-X010930Y008684D03*
-X010930Y008694D03*
-X010930Y008704D03*
-X010930Y008714D03*
-X010930Y008724D03*
-X010930Y008734D03*
-X010930Y008744D03*
-X010930Y008754D03*
-X010930Y008764D03*
-X010930Y008774D03*
-X010930Y008784D03*
-X010930Y008794D03*
-X010930Y008804D03*
-X010930Y008814D03*
-X010930Y008824D03*
-X010930Y008834D03*
-X010930Y008844D03*
-X010930Y008854D03*
-X010930Y008864D03*
-X010930Y008874D03*
-X010930Y008884D03*
-X010930Y008894D03*
-X010930Y008904D03*
-X010930Y008914D03*
-X010930Y008924D03*
-X010930Y008934D03*
-X010930Y008944D03*
-X010930Y008954D03*
-X010930Y008964D03*
-X010930Y008974D03*
-X010930Y008984D03*
-X010930Y008994D03*
-X010930Y009004D03*
-X010930Y009014D03*
-X010930Y009024D03*
-X010930Y009034D03*
-X010930Y009044D03*
-X010930Y009054D03*
-X010930Y009064D03*
-X010930Y009074D03*
-X010930Y009084D03*
-X010930Y009094D03*
-X010930Y009104D03*
-X010930Y009114D03*
-X010930Y009124D03*
-X010930Y009134D03*
-X010930Y009144D03*
-X010930Y009154D03*
-X010930Y009164D03*
-X010930Y009174D03*
-X010930Y009184D03*
-X010930Y009194D03*
-X010930Y009204D03*
-X010930Y009214D03*
-X010930Y009224D03*
-X010930Y009234D03*
-X010930Y009244D03*
-X010930Y009254D03*
-X010930Y009264D03*
-X010930Y009274D03*
-X010930Y009284D03*
-X010930Y009294D03*
-X010930Y009304D03*
-X010930Y009314D03*
-X010930Y009324D03*
-X010930Y009334D03*
-X010930Y009344D03*
-X010930Y009354D03*
-X010930Y009364D03*
-X010930Y009374D03*
-X010930Y009384D03*
-X010930Y009394D03*
-X010930Y009404D03*
-X010930Y009414D03*
-X010930Y009424D03*
-X010930Y009434D03*
-X010930Y009444D03*
-X010930Y009454D03*
-X010930Y009464D03*
-X010930Y009474D03*
-X010930Y009484D03*
-X010930Y009494D03*
-X010930Y009504D03*
-X010930Y009514D03*
-X010930Y009524D03*
-X010930Y009534D03*
-X010930Y009544D03*
-X010930Y009554D03*
-X010930Y009564D03*
-X010930Y009574D03*
-X010930Y009584D03*
-X010930Y009594D03*
-X010930Y009604D03*
-X010930Y009614D03*
-X010930Y009624D03*
-X010930Y009634D03*
-X010930Y009644D03*
-X010930Y009654D03*
-X010930Y009664D03*
-X010930Y009674D03*
-X010930Y009684D03*
-X010930Y009694D03*
-X010930Y009704D03*
-X010930Y009714D03*
-X010930Y009724D03*
-X010930Y009734D03*
-X010930Y009744D03*
-X010930Y009754D03*
-X010930Y009764D03*
-X010930Y009774D03*
-X010930Y009784D03*
-X010930Y009794D03*
-X010930Y009804D03*
-X010930Y009814D03*
-X010930Y009824D03*
-X010930Y009834D03*
-X010930Y009844D03*
-X010930Y009854D03*
-X010930Y009864D03*
-X010930Y009874D03*
-X010930Y009884D03*
-X010930Y009894D03*
-X010930Y009904D03*
-X010930Y009914D03*
-X010930Y009924D03*
-X010930Y009934D03*
-X010930Y009944D03*
-X010930Y009954D03*
-X010930Y009964D03*
-X010930Y009974D03*
-X010930Y009984D03*
-X010930Y009994D03*
-X010930Y010004D03*
-X010930Y010014D03*
-X010930Y010024D03*
-X010930Y010034D03*
-X010930Y010044D03*
-X010930Y010054D03*
-X010930Y010064D03*
-X010930Y010074D03*
-X010930Y010084D03*
-X010930Y010094D03*
-X010930Y010534D03*
-X010930Y010544D03*
-X010930Y010554D03*
-X010930Y010564D03*
-X010930Y010574D03*
-X010930Y010584D03*
-X010930Y010594D03*
-X010930Y010604D03*
-X010930Y010614D03*
-X010930Y010624D03*
-X010930Y010634D03*
-X010930Y010644D03*
-X010930Y010654D03*
-X010930Y010664D03*
-X010930Y010674D03*
-X010930Y010684D03*
-X010930Y010694D03*
-X010930Y010704D03*
-X010930Y010714D03*
-X010930Y010724D03*
-X010930Y010734D03*
-X010930Y010744D03*
-X010930Y010754D03*
-X010930Y010764D03*
-X010930Y010774D03*
-X010930Y010784D03*
-X010930Y010794D03*
-X010930Y010804D03*
-X010930Y010814D03*
-X010930Y010824D03*
-X010930Y010834D03*
-X010930Y010844D03*
-X010930Y010854D03*
-X010930Y010864D03*
-X010930Y010874D03*
-X010930Y010884D03*
-X010930Y010894D03*
-X010930Y010904D03*
-X010930Y010914D03*
-X010930Y010924D03*
-X010930Y010934D03*
-X010930Y010944D03*
-X010930Y010954D03*
-X010930Y010964D03*
-X010930Y010974D03*
-X010930Y010984D03*
-X010930Y010994D03*
-X010930Y011004D03*
-X010930Y011014D03*
-X010930Y011024D03*
-X010930Y011034D03*
-X010930Y011044D03*
-X010930Y011054D03*
-X010930Y011064D03*
-X010930Y011074D03*
-X010930Y011084D03*
-X010930Y011094D03*
-X010930Y011104D03*
-X010930Y011114D03*
-X010930Y011124D03*
-X010930Y011134D03*
-X010930Y011144D03*
-X010930Y011154D03*
-X010930Y011164D03*
-X010930Y011174D03*
-X010930Y011184D03*
-X010930Y011194D03*
-X010930Y011204D03*
-X010930Y011214D03*
-X010930Y011224D03*
-X010930Y011234D03*
-X010930Y011244D03*
-X010930Y011254D03*
-X010930Y011264D03*
-X010930Y011274D03*
-X010930Y011284D03*
-X010930Y011294D03*
-X010930Y011304D03*
-X010930Y011314D03*
-X010930Y011324D03*
-X010930Y011334D03*
-X010930Y011344D03*
-X010930Y011354D03*
-X010930Y011364D03*
-X010930Y011374D03*
-X010930Y011384D03*
-X010930Y011394D03*
-X010930Y011404D03*
-X010930Y011414D03*
-X010930Y011424D03*
-X010930Y011434D03*
-X010930Y011444D03*
-X010930Y011454D03*
-X010930Y011464D03*
-X010930Y011474D03*
-X010930Y011484D03*
-X010930Y011494D03*
-X010930Y011504D03*
-X010930Y011514D03*
-X010930Y011524D03*
-X010930Y011534D03*
-X010930Y011544D03*
-X010930Y011554D03*
-X010930Y011564D03*
-X010930Y011574D03*
-X010930Y011584D03*
-X010930Y011594D03*
-X010930Y011604D03*
-X010930Y011614D03*
-X010930Y011624D03*
-X010930Y011634D03*
-X010930Y011644D03*
-X010930Y011654D03*
-X010930Y011664D03*
-X010930Y011674D03*
-X010930Y011684D03*
-X010930Y011694D03*
-X010930Y011704D03*
-X010930Y011714D03*
-X010930Y011724D03*
-X010930Y011734D03*
-X010930Y011744D03*
-X010930Y011754D03*
-X010930Y011764D03*
-X010930Y011774D03*
-X010930Y011784D03*
-X010930Y011794D03*
-X010930Y011804D03*
-X010930Y011814D03*
-X010930Y011824D03*
-X010930Y011834D03*
-X010930Y011844D03*
-X010930Y011854D03*
-X010930Y011864D03*
-X010930Y011874D03*
-X010930Y011884D03*
-X010930Y011894D03*
-X010930Y011904D03*
-X010930Y011914D03*
-X010930Y011924D03*
-X010930Y011934D03*
-X010930Y011944D03*
-X010930Y011954D03*
-X010930Y011964D03*
-X010930Y011974D03*
-X010930Y011984D03*
-X010930Y011994D03*
-X010930Y012004D03*
-X010930Y012014D03*
-X010930Y012024D03*
-X010930Y012034D03*
-X010930Y012044D03*
-X010930Y012054D03*
-X010930Y012064D03*
-X010930Y012074D03*
-X010930Y012084D03*
-X010930Y012094D03*
-X010930Y012104D03*
-X010930Y012114D03*
-X010930Y012124D03*
-X010930Y012134D03*
-X010930Y012144D03*
-X010930Y012154D03*
-X010930Y012164D03*
-X010930Y012174D03*
-X010930Y012184D03*
-X010930Y012194D03*
-X010930Y012204D03*
-X010930Y012214D03*
-X010930Y012224D03*
-X010930Y012234D03*
-X010930Y012244D03*
-X010930Y012254D03*
-X010930Y012264D03*
-X010930Y012274D03*
-X010930Y012284D03*
-X010930Y012294D03*
-X010930Y012304D03*
-X010930Y012314D03*
-X010930Y012324D03*
-X010930Y012334D03*
-X010930Y012344D03*
-X010930Y012354D03*
-X010930Y012364D03*
-X010930Y012374D03*
-X010930Y012384D03*
-X010930Y012394D03*
-X010930Y012404D03*
-X010930Y012414D03*
-X010930Y012424D03*
-X010930Y012434D03*
-X010930Y012444D03*
-X010930Y012454D03*
-X010930Y012464D03*
-X010930Y012474D03*
-X010930Y012484D03*
-X010930Y012494D03*
-X010930Y012504D03*
-X010930Y012514D03*
-X010930Y012524D03*
-X010930Y012534D03*
-X010930Y012544D03*
-X010930Y012554D03*
-X010930Y012564D03*
-X010930Y012574D03*
-X010930Y012584D03*
-X010930Y012594D03*
-X010930Y012604D03*
-X010930Y012614D03*
-X010930Y012624D03*
-X010930Y012634D03*
-X010930Y012644D03*
-X010930Y012654D03*
-X010930Y012664D03*
-X010930Y012674D03*
-X010930Y012684D03*
-X010930Y012694D03*
-X010930Y012704D03*
-X010930Y012714D03*
-X010930Y012724D03*
-X010930Y012734D03*
-X010930Y012744D03*
-X010930Y012754D03*
-X010930Y012764D03*
-X010930Y012774D03*
-X010930Y012784D03*
-X010930Y012794D03*
-X010930Y012804D03*
-X010930Y012814D03*
-X010930Y012824D03*
-X010930Y012834D03*
-X010930Y012844D03*
-X010930Y012854D03*
-X010930Y012864D03*
-X010930Y012874D03*
-X010930Y012884D03*
-X010930Y012894D03*
-X010930Y012904D03*
-X010930Y012914D03*
-X010930Y012924D03*
-X010930Y012934D03*
-X010930Y012944D03*
-X010930Y012954D03*
-X010930Y012964D03*
-X010930Y012974D03*
-X010930Y012984D03*
-X010930Y012994D03*
-X010930Y013004D03*
-X010930Y013014D03*
-X010930Y013024D03*
-X010930Y013034D03*
-X010930Y013044D03*
-X010930Y013054D03*
-X010930Y013064D03*
-X010930Y013074D03*
-X010930Y013084D03*
-X010930Y013094D03*
-X010930Y013104D03*
-X010930Y013114D03*
-X010930Y013124D03*
-X010930Y013134D03*
-X010930Y013144D03*
-X010930Y013154D03*
-X010930Y013164D03*
-X010930Y013174D03*
-X010930Y013184D03*
-X010930Y013194D03*
-X010930Y013204D03*
-X010930Y013214D03*
-X010930Y013224D03*
-X010930Y013234D03*
-X010930Y013244D03*
-X010930Y013254D03*
-D12*
-X013355Y012004D03*
-X014305Y011544D03*
-X014285Y011494D03*
-X014275Y011464D03*
-X014265Y011444D03*
-X014255Y011414D03*
-X014245Y011394D03*
-X014235Y011364D03*
-X014225Y011344D03*
-X014225Y011334D03*
-X014215Y011314D03*
-X014205Y011294D03*
-X014205Y011284D03*
-X014195Y011264D03*
-X014185Y011244D03*
-X014185Y011234D03*
-X014175Y011214D03*
-X015765Y009434D03*
-X015765Y009424D03*
-X015275Y008624D03*
-X015265Y008604D03*
-X015255Y008594D03*
-X015245Y008574D03*
-X015225Y008544D03*
-X015215Y008524D03*
-X015205Y008514D03*
-X015195Y008494D03*
-X015185Y008474D03*
-X015175Y008464D03*
-X015165Y008444D03*
-X015145Y008414D03*
-X015135Y008394D03*
-X015125Y008384D03*
-X015115Y008364D03*
-X015105Y008344D03*
-X015095Y008334D03*
-X015085Y008314D03*
-X015075Y008304D03*
-X015075Y008294D03*
-X015065Y008284D03*
-X015055Y008264D03*
-X015045Y008254D03*
-X015035Y008234D03*
-X015025Y008214D03*
-X015015Y008204D03*
-X015005Y008184D03*
-X014995Y008174D03*
-X014995Y008164D03*
-X014985Y008154D03*
-X014975Y008134D03*
-X014965Y008124D03*
-X014955Y008104D03*
-X014945Y008094D03*
-X014945Y008084D03*
-X014935Y008074D03*
-X014925Y008054D03*
-X014915Y008044D03*
-X014915Y008034D03*
-X014905Y008024D03*
-X014895Y008004D03*
-X014885Y007994D03*
-X014885Y007984D03*
-X014875Y007974D03*
-X014865Y007964D03*
-X014865Y007954D03*
-X014855Y007944D03*
-X014845Y007924D03*
-X014835Y007914D03*
-X014835Y007904D03*
-X014345Y007124D03*
-X014345Y007114D03*
-X016765Y007834D03*
-X016775Y007814D03*
-X016775Y007804D03*
-X016785Y007784D03*
-X016785Y007774D03*
-X016795Y007764D03*
-X016785Y008794D03*
-X016795Y008814D03*
-X016795Y008824D03*
-X016805Y008844D03*
-X018495Y008824D03*
-X018495Y008814D03*
-X018505Y008804D03*
-X018505Y008794D03*
-X018515Y008774D03*
-X018525Y007834D03*
-X018515Y007814D03*
-X018515Y007804D03*
-X018505Y007794D03*
-X018505Y007784D03*
-X019175Y011774D03*
-X019195Y012154D03*
-X019195Y012164D03*
-X019205Y012174D03*
-X019295Y013194D03*
-X015165Y013674D03*
-X006695Y007474D03*
-X006705Y007454D03*
-X006715Y007444D03*
-X006725Y007424D03*
-X006745Y007394D03*
-X006755Y007374D03*
-X006765Y007364D03*
-X006775Y007344D03*
-X006795Y007314D03*
-X006825Y007264D03*
-X006845Y007234D03*
-X006875Y007184D03*
-X006925Y007104D03*
-X007005Y006974D03*
-X006685Y007494D03*
-X006675Y007504D03*
-X006665Y007524D03*
-X006655Y007534D03*
-X006655Y007544D03*
-X006645Y007554D03*
-X006635Y007574D03*
-X006625Y007584D03*
-X006615Y007604D03*
-X006605Y007614D03*
-X006605Y007624D03*
-X006595Y007634D03*
-X006585Y007654D03*
-X006575Y007664D03*
-X006575Y007674D03*
-X006565Y007684D03*
-X006555Y007694D03*
-X006555Y007704D03*
-X006545Y007714D03*
-X006535Y007734D03*
-X006525Y007744D03*
-X006525Y007754D03*
-X006515Y007764D03*
-X006505Y007774D03*
-X006505Y007784D03*
-X006495Y007794D03*
-X006495Y007804D03*
-X006485Y007814D03*
-X006475Y007824D03*
-X006475Y007834D03*
-X006465Y007844D03*
-X006455Y007854D03*
-X006455Y007864D03*
-X006445Y007874D03*
-X006445Y007884D03*
-X006435Y007894D03*
-X006425Y007904D03*
-X006425Y007914D03*
-X006415Y007924D03*
-X006405Y007944D03*
-X006395Y007954D03*
-X006395Y007964D03*
-X006385Y007974D03*
-X006375Y007984D03*
-X006375Y007994D03*
-X006365Y008004D03*
-X006355Y008024D03*
-X006345Y008034D03*
-X006345Y008044D03*
-X006335Y008054D03*
-X006325Y008074D03*
-X006315Y008084D03*
-X006305Y008104D03*
-X006295Y008124D03*
-X006285Y008134D03*
-X006275Y008154D03*
-X006265Y008164D03*
-X006255Y008184D03*
-X006235Y008214D03*
-X006225Y008234D03*
-X006205Y008264D03*
-X006185Y008294D03*
-X006175Y008314D03*
-X006155Y008344D03*
-X006125Y008394D03*
-X006105Y008424D03*
-X006075Y008474D03*
-X006025Y008554D03*
-X004355Y009554D03*
-X004345Y009544D03*
-X004335Y009524D03*
-X004315Y009494D03*
-X004305Y009474D03*
-X004285Y009444D03*
-X004275Y009424D03*
-X004255Y009394D03*
-X004245Y009374D03*
-X004225Y009344D03*
-X004215Y009324D03*
-X004195Y009294D03*
-X004185Y009274D03*
-X004165Y009244D03*
-X004155Y009224D03*
-X004135Y009194D03*
-X004105Y009144D03*
-X004075Y009094D03*
-X004045Y009044D03*
-X004015Y008994D03*
-X003995Y008964D03*
-X003985Y008944D03*
-X003965Y008914D03*
-X003935Y008864D03*
-X003905Y008814D03*
-X003875Y008764D03*
-X003845Y008714D03*
-X003815Y008664D03*
-X003645Y008384D03*
-X004365Y009574D03*
-X004375Y009594D03*
-X004385Y009604D03*
-X004395Y009624D03*
-X004405Y009644D03*
-X004415Y009654D03*
-X004415Y009664D03*
-X004425Y009674D03*
-X004435Y009694D03*
-X004445Y009704D03*
-X004445Y009714D03*
-X004455Y009724D03*
-X004455Y009734D03*
-X004465Y009744D03*
-X004475Y009764D03*
-X004485Y009774D03*
-X004485Y009784D03*
-X004495Y009794D03*
-X004505Y009814D03*
-X005235Y010924D03*
-X005245Y010944D03*
-X005265Y010974D03*
-X005275Y010994D03*
-X005285Y011014D03*
-X005295Y011024D03*
-X005295Y011034D03*
-X005305Y011044D03*
-X005305Y011054D03*
-X005315Y011064D03*
-X005325Y011074D03*
-X005325Y011084D03*
-X005335Y011094D03*
-X005335Y011104D03*
-X005345Y011114D03*
-X005355Y011124D03*
-X005355Y011134D03*
-X005365Y011144D03*
-X005365Y011154D03*
-X005375Y011164D03*
-X005385Y011184D03*
-X005395Y011194D03*
-X005395Y011204D03*
-X005405Y011214D03*
-X005415Y011234D03*
-X005425Y011244D03*
-X005425Y011254D03*
-X005435Y011264D03*
-X005445Y011284D03*
-X005455Y011294D03*
-X005455Y011304D03*
-X005465Y011314D03*
-X005475Y011334D03*
-X005485Y011344D03*
-X005485Y011354D03*
-X005495Y011364D03*
-X005505Y011384D03*
-X005515Y011394D03*
-X005515Y011404D03*
-X005525Y011414D03*
-X005535Y011434D03*
-X005545Y011444D03*
-X005545Y011454D03*
-X005555Y011464D03*
-X005565Y011484D03*
-X005575Y011494D03*
-X005575Y011504D03*
-X005585Y011514D03*
-X005595Y011534D03*
-X005605Y011554D03*
-X005615Y011564D03*
-X005625Y011584D03*
-X005635Y011604D03*
-X005645Y011614D03*
-X005645Y011624D03*
-X005655Y011634D03*
-X005665Y011654D03*
-X005675Y011664D03*
-X005675Y011674D03*
-X005685Y011684D03*
-X005695Y011704D03*
-X005705Y011714D03*
-X005705Y011724D03*
-X005715Y011734D03*
-X005725Y011754D03*
-X005735Y011764D03*
-X005735Y011774D03*
-X005745Y011784D03*
-X005755Y011804D03*
-X005765Y011814D03*
-X005765Y011824D03*
-X005775Y011834D03*
-X005785Y011854D03*
-X005795Y011864D03*
-X005795Y011874D03*
-X005805Y011884D03*
-X005815Y011904D03*
-X005825Y011914D03*
-X005825Y011924D03*
-X005835Y011934D03*
-X005845Y011954D03*
-X005855Y011974D03*
-X005865Y011984D03*
-X005875Y012004D03*
-X005885Y012024D03*
-X005895Y012034D03*
-X005905Y012054D03*
-X005915Y012074D03*
-X005925Y012084D03*
-X005935Y012104D03*
-X005945Y012124D03*
-X005955Y012134D03*
-X005965Y012154D03*
-X005975Y012174D03*
-X005985Y012184D03*
-X005995Y012204D03*
-X006005Y012224D03*
-X006015Y012234D03*
-X006025Y012254D03*
-X006035Y012274D03*
-X006045Y012284D03*
-X006055Y012304D03*
-X006065Y012324D03*
-X006075Y012334D03*
-X006085Y012354D03*
-X006095Y012374D03*
-X006105Y012384D03*
-X006115Y012404D03*
-X006125Y012424D03*
-X006145Y012454D03*
-X006155Y012474D03*
-X006175Y012504D03*
-X006185Y012524D03*
-X006205Y012554D03*
-X006215Y012574D03*
-X006235Y012604D03*
-X006245Y012624D03*
-X006265Y012654D03*
-X006275Y012674D03*
-X006295Y012704D03*
-X006305Y012724D03*
-X006325Y012754D03*
-X006335Y012774D03*
-X006355Y012804D03*
-X006365Y012824D03*
-X006395Y012874D03*
-X006425Y012924D03*
-X006455Y012974D03*
-X006485Y013024D03*
-X006515Y013074D03*
-X006545Y013124D03*
-X006575Y013174D03*
-X006605Y013224D03*
-D13*
-X006600Y013214D03*
-X006590Y013204D03*
-X006590Y013194D03*
-X006580Y013184D03*
-X006570Y013164D03*
-X006560Y013154D03*
-X006560Y013144D03*
-X006550Y013134D03*
-X006540Y013114D03*
-X006530Y013104D03*
-X006530Y013094D03*
-X006520Y013084D03*
-X006510Y013064D03*
-X006500Y013054D03*
-X006500Y013044D03*
-X006490Y013034D03*
-X006480Y013014D03*
-X006470Y013004D03*
-X006470Y012994D03*
-X006460Y012984D03*
-X006450Y012964D03*
-X006440Y012954D03*
-X006440Y012944D03*
-X006430Y012934D03*
-X006420Y012914D03*
-X006410Y012904D03*
-X006410Y012894D03*
-X006400Y012884D03*
-X006390Y012864D03*
-X006380Y012854D03*
-X006380Y012844D03*
-X006370Y012834D03*
-X006360Y012814D03*
-X006350Y012794D03*
-X006340Y012784D03*
-X006330Y012764D03*
-X006320Y012744D03*
-X006310Y012734D03*
-X006300Y012714D03*
-X006290Y012694D03*
-X006280Y012684D03*
-X006270Y012664D03*
-X006260Y012644D03*
-X006250Y012634D03*
-X006240Y012614D03*
-X006230Y012594D03*
-X006220Y012584D03*
-X006210Y012564D03*
-X006200Y012544D03*
-X006190Y012534D03*
-X006180Y012514D03*
-X006170Y012494D03*
-X006160Y012484D03*
-X006150Y012464D03*
-X006140Y012444D03*
-X006130Y012434D03*
-X006120Y012414D03*
-X006110Y012394D03*
-X006090Y012364D03*
-X006080Y012344D03*
-X006060Y012314D03*
-X006050Y012294D03*
-X006030Y012264D03*
-X006020Y012244D03*
-X006000Y012214D03*
-X005990Y012194D03*
-X005970Y012164D03*
-X005960Y012144D03*
-X005940Y012114D03*
-X005930Y012094D03*
-X005910Y012064D03*
-X005900Y012044D03*
-X005880Y012014D03*
-X005870Y011994D03*
-X005850Y011964D03*
-X005840Y011944D03*
-X005810Y011894D03*
-X005780Y011844D03*
-X005750Y011794D03*
-X005720Y011744D03*
-X005690Y011694D03*
-X005660Y011644D03*
-X005630Y011594D03*
-X005620Y011574D03*
-X005600Y011544D03*
-X005590Y011524D03*
-X005560Y011474D03*
-X005530Y011424D03*
-X005500Y011374D03*
-X005470Y011324D03*
-X005440Y011274D03*
-X005410Y011224D03*
-X005380Y011174D03*
-X004430Y009684D03*
-X004400Y009634D03*
-X004390Y009614D03*
-X004370Y009584D03*
-X004360Y009564D03*
-X004340Y009534D03*
-X004330Y009514D03*
-X004320Y009504D03*
-X004310Y009484D03*
-X004300Y009464D03*
-X004290Y009454D03*
-X004280Y009434D03*
-X004270Y009414D03*
-X004260Y009404D03*
-X004250Y009384D03*
-X004240Y009364D03*
-X004230Y009354D03*
-X004220Y009334D03*
-X004210Y009314D03*
-X004200Y009304D03*
-X004190Y009284D03*
-X004180Y009264D03*
-X004170Y009254D03*
-X004160Y009234D03*
-X004150Y009214D03*
-X004140Y009204D03*
-X004130Y009184D03*
-X004120Y009174D03*
-X004120Y009164D03*
-X004110Y009154D03*
-X004100Y009134D03*
-X004090Y009124D03*
-X004090Y009114D03*
-X004080Y009104D03*
-X004070Y009084D03*
-X004060Y009074D03*
-X004060Y009064D03*
-X004050Y009054D03*
-X004040Y009034D03*
-X004030Y009024D03*
-X004030Y009014D03*
-X004020Y009004D03*
-X004010Y008984D03*
-X004000Y008974D03*
-X003990Y008954D03*
-X003980Y008934D03*
-X003970Y008924D03*
-X003960Y008904D03*
-X003950Y008894D03*
-X003950Y008884D03*
-X003940Y008874D03*
-X003930Y008854D03*
-X003920Y008844D03*
-X003920Y008834D03*
-X003910Y008824D03*
-X003900Y008804D03*
-X003890Y008794D03*
-X003890Y008784D03*
-X003880Y008774D03*
-X003870Y008754D03*
-X003860Y008744D03*
-X003860Y008734D03*
-X003850Y008724D03*
-X003840Y008704D03*
-X003830Y008694D03*
-X003830Y008684D03*
-X003820Y008674D03*
-X003810Y008654D03*
-X003800Y008644D03*
-X003800Y008634D03*
-X003790Y008624D03*
-X003780Y008614D03*
-X003780Y008604D03*
-X003770Y008594D03*
-X003770Y008584D03*
-X003760Y008574D03*
-X003750Y008564D03*
-X003750Y008554D03*
-X003740Y008544D03*
-X003740Y008534D03*
-X003730Y008524D03*
-X003720Y008514D03*
-X003720Y008504D03*
-X003710Y008494D03*
-X003710Y008484D03*
-X003700Y008474D03*
-X003690Y008464D03*
-X003690Y008454D03*
-X003680Y008444D03*
-X003680Y008434D03*
-X003670Y008424D03*
-X003660Y008414D03*
-X003660Y008404D03*
-X003650Y008394D03*
-X003640Y008374D03*
-X003630Y008364D03*
-X003630Y008354D03*
-X003620Y008344D03*
-X003610Y008334D03*
-X003610Y008324D03*
-X003600Y008314D03*
-X003600Y008304D03*
-X003590Y008294D03*
-X003580Y008284D03*
-X003580Y008274D03*
-X003570Y008264D03*
-X003570Y008254D03*
-X003560Y008244D03*
-X003550Y008234D03*
-X003550Y008224D03*
-X003540Y008214D03*
-X003540Y008204D03*
-X003530Y008194D03*
-X003520Y008184D03*
-X003520Y008174D03*
-X003510Y008164D03*
-X003510Y008154D03*
-X003500Y008144D03*
-X003490Y008134D03*
-X003490Y008124D03*
-X003480Y008114D03*
-X003480Y008104D03*
-X003470Y008094D03*
-X003460Y008084D03*
-X003460Y008074D03*
-X003450Y008064D03*
-X003450Y008054D03*
-X003440Y008044D03*
-X003430Y008034D03*
-X003430Y008024D03*
-X003420Y008014D03*
-X003410Y007994D03*
-X003400Y007984D03*
-X003400Y007974D03*
-X003390Y007964D03*
-X003380Y007944D03*
-X003370Y007934D03*
-X003370Y007924D03*
-X003360Y007914D03*
-X003350Y007894D03*
-X003340Y007884D03*
-X003340Y007874D03*
-X003330Y007864D03*
-X003320Y007844D03*
-X003310Y007834D03*
-X003310Y007824D03*
-X003300Y007814D03*
-X003290Y007804D03*
-X003290Y007794D03*
-X003280Y007784D03*
-X003280Y007774D03*
-X003270Y007764D03*
-X003260Y007754D03*
-X003260Y007744D03*
-X003250Y007734D03*
-X003240Y007714D03*
-X003230Y007704D03*
-X003230Y007694D03*
-X003220Y007684D03*
-X003210Y007664D03*
-X003200Y007654D03*
-X003200Y007644D03*
-X003190Y007634D03*
-X003180Y007614D03*
-X003170Y007604D03*
-X003170Y007594D03*
-X003160Y007584D03*
-X003150Y007564D03*
-X003140Y007554D03*
-X003140Y007544D03*
-X003130Y007534D03*
-X003120Y007514D03*
-X003110Y007504D03*
-X003110Y007494D03*
-X003100Y007484D03*
-X003090Y007464D03*
-X003080Y007454D03*
-X003070Y007434D03*
-X003060Y007414D03*
-X003050Y007404D03*
-X003040Y007384D03*
-X003030Y007364D03*
-X003020Y007354D03*
-X003010Y007334D03*
-X003000Y007314D03*
-X002990Y007304D03*
-X002980Y007284D03*
-X002970Y007264D03*
-X002960Y007254D03*
-X002950Y007234D03*
-X002940Y007224D03*
-X002940Y007214D03*
-X002930Y007204D03*
-X002920Y007184D03*
-X002910Y007174D03*
-X002900Y007154D03*
-X002890Y007134D03*
-X002880Y007124D03*
-X002870Y007104D03*
-X002860Y007084D03*
-X002850Y007074D03*
-X002840Y007054D03*
-X002830Y007034D03*
-X002820Y007024D03*
-X002810Y007004D03*
-X002800Y006984D03*
-X002790Y006974D03*
-X006540Y007724D03*
-X006590Y007644D03*
-X006620Y007594D03*
-X006640Y007564D03*
-X006670Y007514D03*
-X006690Y007484D03*
-X006700Y007464D03*
-X006720Y007434D03*
-X006730Y007414D03*
-X006740Y007404D03*
-X006750Y007384D03*
-X006770Y007354D03*
-X006780Y007334D03*
-X006790Y007324D03*
-X006800Y007304D03*
-X006810Y007294D03*
-X006810Y007284D03*
-X006820Y007274D03*
-X006830Y007254D03*
-X006840Y007244D03*
-X006850Y007224D03*
-X006860Y007214D03*
-X006860Y007204D03*
-X006870Y007194D03*
-X006880Y007174D03*
-X006890Y007164D03*
-X006890Y007154D03*
-X006900Y007144D03*
-X006910Y007134D03*
-X006910Y007124D03*
-X006920Y007114D03*
-X006930Y007094D03*
-X006940Y007084D03*
-X006940Y007074D03*
-X006950Y007064D03*
-X006960Y007054D03*
-X006960Y007044D03*
-X006970Y007034D03*
-X006970Y007024D03*
-X006980Y007014D03*
-X006990Y007004D03*
-X006990Y006994D03*
-X007000Y006984D03*
-X013350Y012014D03*
-X015160Y013654D03*
-X015160Y013664D03*
-X019300Y013204D03*
-X019370Y012754D03*
-X019220Y012194D03*
-X019210Y012184D03*
-X019180Y011764D03*
-X019190Y011744D03*
-X015760Y009414D03*
-X015760Y009404D03*
-X015280Y008634D03*
-X015270Y008614D03*
-X015250Y008584D03*
-X015240Y008564D03*
-X015230Y008554D03*
-X015220Y008534D03*
-X015200Y008504D03*
-X015190Y008484D03*
-X015170Y008454D03*
-X015160Y008434D03*
-X015150Y008424D03*
-X015140Y008404D03*
-X015120Y008374D03*
-X015110Y008354D03*
-X015090Y008324D03*
-X015060Y008274D03*
-X015040Y008244D03*
-X015030Y008224D03*
-X015010Y008194D03*
-X014980Y008144D03*
-X014960Y008114D03*
-X014930Y008064D03*
-X014900Y008014D03*
-X014850Y007934D03*
-X014350Y007134D03*
-X016800Y007744D03*
-X016800Y007754D03*
-X016810Y007724D03*
-X016800Y008834D03*
-X016810Y008854D03*
-X016820Y008864D03*
-X016820Y008874D03*
-X018480Y008854D03*
-X018480Y008844D03*
-X018490Y008834D03*
-X018500Y007774D03*
-X018500Y007764D03*
-X018490Y007754D03*
-X018490Y007744D03*
-X006860Y013644D03*
-X006860Y013654D03*
-X006870Y013664D03*
-X006870Y013674D03*
-X006880Y013684D03*
-X006850Y013634D03*
-X006840Y013624D03*
-X006840Y013614D03*
-X006830Y013604D03*
-X006830Y013594D03*
-X006820Y013584D03*
-X006810Y013574D03*
-X006810Y013564D03*
-X006800Y013554D03*
-X006800Y013544D03*
-X006790Y013534D03*
-X006780Y013524D03*
-X006780Y013514D03*
-X006770Y013504D03*
-X006770Y013494D03*
-X006760Y013484D03*
-X006750Y013474D03*
-X006750Y013464D03*
-X006740Y013454D03*
-X006740Y013444D03*
-X006730Y013434D03*
-X006720Y013424D03*
-X006720Y013414D03*
-X006710Y013404D03*
-X006710Y013394D03*
-X006700Y013384D03*
-X006690Y013374D03*
-X006690Y013364D03*
-X006680Y013354D03*
-X006680Y013344D03*
-X006670Y013334D03*
-X006660Y013324D03*
-X006660Y013314D03*
-X006650Y013304D03*
-X006650Y013294D03*
-X006640Y013284D03*
-X006630Y013274D03*
-X006630Y013264D03*
-X006620Y013254D03*
-X006620Y013244D03*
-X006610Y013234D03*
-D14*
-X013345Y012024D03*
-X015755Y009394D03*
-X016825Y008884D03*
-X016835Y008894D03*
-X016845Y008914D03*
-X016805Y007734D03*
-X016815Y007714D03*
-X016825Y007704D03*
-X016835Y007684D03*
-X017645Y007024D03*
-X018465Y007704D03*
-X018475Y007714D03*
-X018475Y007724D03*
-X018485Y007734D03*
-X018475Y008864D03*
-X018465Y008874D03*
-X018465Y008884D03*
-X018455Y008894D03*
-X019195Y011734D03*
-X019185Y011754D03*
-X019385Y012744D03*
-X019305Y013214D03*
-X014355Y007154D03*
-X014355Y007144D03*
-X003415Y008004D03*
-X003385Y007954D03*
-X003355Y007904D03*
-X003325Y007854D03*
-X003245Y007724D03*
-X003215Y007674D03*
-X003185Y007624D03*
-X003155Y007574D03*
-X003125Y007524D03*
-X003095Y007474D03*
-X003075Y007444D03*
-X003065Y007424D03*
-X003045Y007394D03*
-X003035Y007374D03*
-X003015Y007344D03*
-X003005Y007324D03*
-X002985Y007294D03*
-X002975Y007274D03*
-X002955Y007244D03*
-X002925Y007194D03*
-X002905Y007164D03*
-X002895Y007144D03*
-X002875Y007114D03*
-X002865Y007094D03*
-X002845Y007064D03*
-X002835Y007044D03*
-X002815Y007014D03*
-X002805Y006994D03*
-D15*
-X017645Y007004D03*
-D16*
-X017640Y007014D03*
-D17*
-X017640Y007034D03*
-X016950Y007534D03*
-X016940Y007544D03*
-X016950Y009044D03*
-X015710Y009244D03*
-X015710Y009254D03*
-X014400Y007294D03*
-X018330Y009044D03*
-X013300Y012114D03*
-X012690Y012714D03*
-X012260Y013184D03*
-X012250Y013194D03*
-X012240Y013204D03*
-X012230Y013214D03*
-X012220Y013224D03*
-X012210Y013234D03*
-X012200Y013244D03*
-X012190Y013254D03*
-X012180Y013264D03*
-X012170Y013274D03*
-X012160Y013284D03*
-X012150Y013294D03*
-X012090Y013364D03*
-X012080Y013374D03*
-X012070Y013384D03*
-X012060Y013394D03*
-X012050Y013404D03*
-X012040Y013414D03*
-X011720Y011224D03*
-X015160Y013524D03*
-X015160Y013534D03*
-X015160Y013544D03*
-X004870Y010414D03*
-D18*
-X005140Y009994D03*
-X005150Y009984D03*
-X005160Y009964D03*
-X005170Y009944D03*
-X004470Y010934D03*
-X004440Y010984D03*
-X004430Y011004D03*
-X004420Y011024D03*
-X004410Y011034D03*
-X004410Y011044D03*
-X004400Y011054D03*
-X004390Y011074D03*
-X004380Y011084D03*
-X004380Y011094D03*
-X004370Y011104D03*
-X004360Y011124D03*
-X004350Y011144D03*
-X004340Y011154D03*
-X004330Y011174D03*
-X004320Y011194D03*
-X004300Y011224D03*
-X004290Y011244D03*
-X004270Y011274D03*
-X004260Y011294D03*
-X004240Y011324D03*
-X004230Y011344D03*
-X004210Y011374D03*
-X004200Y011394D03*
-X004180Y011424D03*
-X013380Y011424D03*
-X013380Y011414D03*
-X013380Y011404D03*
-X013380Y011394D03*
-X013380Y011384D03*
-X013380Y011374D03*
-X013380Y011364D03*
-X013380Y011354D03*
-X013380Y011344D03*
-X013380Y011334D03*
-X013380Y011324D03*
-X013380Y011314D03*
-X013380Y011304D03*
-X013380Y011294D03*
-X013380Y011284D03*
-X013380Y011274D03*
-X013380Y011264D03*
-X013380Y011254D03*
-X013380Y011244D03*
-X013380Y011234D03*
-X013380Y011224D03*
-X013380Y011214D03*
-X013380Y011434D03*
-X013380Y011444D03*
-X013380Y011454D03*
-X013380Y011464D03*
-X013380Y011474D03*
-X013380Y011484D03*
-X013380Y011494D03*
-X013380Y011504D03*
-X013380Y011514D03*
-X013380Y011524D03*
-X013380Y011534D03*
-X013380Y011544D03*
-X013380Y011554D03*
-X013380Y011564D03*
-X013380Y011574D03*
-X013380Y011584D03*
-X013380Y011594D03*
-X013380Y011604D03*
-X013380Y011614D03*
-X013380Y011624D03*
-X013380Y011634D03*
-X013380Y011644D03*
-X013380Y011654D03*
-X013380Y011664D03*
-X013380Y011674D03*
-X013380Y011684D03*
-X013380Y011694D03*
-X013380Y011704D03*
-X013380Y011714D03*
-X013380Y011724D03*
-X013380Y011734D03*
-X013380Y011744D03*
-X013380Y011754D03*
-X013380Y011764D03*
-X013380Y011774D03*
-X013380Y011784D03*
-X013380Y011794D03*
-X013380Y011804D03*
-X013380Y011814D03*
-X013380Y011824D03*
-X013380Y011834D03*
-X013380Y011844D03*
-X013380Y011854D03*
-X013380Y011864D03*
-X013380Y011874D03*
-X013380Y011884D03*
-X013380Y011894D03*
-X013380Y011904D03*
-X013380Y011914D03*
-X013380Y011924D03*
-X013380Y011934D03*
-X013380Y011944D03*
-X013380Y011954D03*
-X013380Y012614D03*
-X013380Y012624D03*
-X013380Y012634D03*
-X013380Y012644D03*
-X013380Y012654D03*
-X013380Y012664D03*
-X013380Y012674D03*
-X013380Y012684D03*
-X013380Y012694D03*
-X013380Y012704D03*
-X013380Y012714D03*
-X013380Y012724D03*
-X013380Y012734D03*
-X013380Y012744D03*
-X013380Y012754D03*
-X013380Y012764D03*
-X013380Y012774D03*
-X013380Y012784D03*
-X013380Y012794D03*
-X013380Y012804D03*
-X013380Y012814D03*
-X013380Y012824D03*
-X013380Y012834D03*
-X013380Y012844D03*
-X013380Y012854D03*
-X013380Y012864D03*
-X013380Y012874D03*
-X013380Y012884D03*
-X013380Y012894D03*
-X013380Y012904D03*
-X013380Y012914D03*
-X013380Y012924D03*
-X013380Y012934D03*
-X013380Y012944D03*
-X013380Y012954D03*
-X013380Y012964D03*
-X013380Y012974D03*
-X013380Y012984D03*
-X013380Y012994D03*
-X013380Y013004D03*
-X013380Y013014D03*
-X013380Y013024D03*
-X013380Y013034D03*
-X013380Y013044D03*
-X013380Y013054D03*
-X013380Y013064D03*
-X013380Y013074D03*
-X013380Y013084D03*
-X013380Y013094D03*
-X013380Y013104D03*
-X013380Y013114D03*
-X013380Y013124D03*
-X013380Y013134D03*
-X013380Y013144D03*
-X013380Y013154D03*
-X013380Y013164D03*
-X013380Y013174D03*
-X013380Y013184D03*
-X013380Y013194D03*
-X013380Y013204D03*
-X013380Y013214D03*
-X013380Y013224D03*
-X013380Y013234D03*
-X013380Y013244D03*
-X013380Y013254D03*
-X013380Y013264D03*
-X013380Y013274D03*
-X013380Y013284D03*
-X013380Y013294D03*
-X013380Y013304D03*
-X013380Y013314D03*
-X013380Y013324D03*
-X013380Y013334D03*
-X013380Y013344D03*
-X013380Y013354D03*
-X013380Y013364D03*
-X013380Y013374D03*
-X013380Y013384D03*
-X013380Y013394D03*
-X013380Y013404D03*
-X013380Y013414D03*
-X013380Y013424D03*
-X013380Y013434D03*
-X013380Y013444D03*
-X013380Y013454D03*
-X013380Y013464D03*
-X013380Y013474D03*
-X013380Y013484D03*
-X013380Y013494D03*
-X013380Y013504D03*
-X013380Y013514D03*
-X013380Y013524D03*
-X013380Y013534D03*
-X013380Y013544D03*
-X013380Y013554D03*
-X013380Y013564D03*
-X013380Y013574D03*
-X013380Y013584D03*
-X013380Y013594D03*
-X013380Y013604D03*
-X013380Y013614D03*
-X013380Y013624D03*
-X013380Y013634D03*
-X013380Y013644D03*
-X013380Y013654D03*
-X013380Y013664D03*
-X013380Y013674D03*
-X013380Y013684D03*
-X013380Y013694D03*
-X014900Y013074D03*
-X014880Y013024D03*
-X015490Y012904D03*
-X015500Y012874D03*
-X015510Y012854D03*
-X015520Y012824D03*
-X015530Y012804D03*
-X015530Y012794D03*
-X015540Y012774D03*
-X015540Y012764D03*
-X015550Y012744D03*
-X015560Y012724D03*
-X015560Y012714D03*
-X015570Y012694D03*
-X015570Y012684D03*
-X015580Y012674D03*
-X015580Y012664D03*
-X015580Y012654D03*
-X015590Y012644D03*
-X015590Y012634D03*
-X015600Y012624D03*
-X015600Y012614D03*
-X015600Y012604D03*
-X015610Y012594D03*
-X015610Y012584D03*
-X015610Y012574D03*
-X015620Y012564D03*
-X015620Y012554D03*
-X015630Y012534D03*
-X015630Y012524D03*
-X015640Y012514D03*
-X015640Y012504D03*
-X015650Y012484D03*
-X015650Y012474D03*
-X015660Y012454D03*
-X015670Y012434D03*
-X015670Y012424D03*
-X015680Y012404D03*
-X015680Y012394D03*
-X015690Y012374D03*
-X015700Y012344D03*
-X015710Y012324D03*
-X015720Y012294D03*
-X015750Y012214D03*
-X018210Y012214D03*
-X018210Y012204D03*
-X018210Y012194D03*
-X018210Y012184D03*
-X018210Y012174D03*
-X018210Y012164D03*
-X018210Y012154D03*
-X018210Y012144D03*
-X018210Y012134D03*
-X018210Y012124D03*
-X018210Y012114D03*
-X018210Y012104D03*
-X018210Y012094D03*
-X018210Y012084D03*
-X018210Y012074D03*
-X018210Y012064D03*
-X018210Y012054D03*
-X018210Y012044D03*
-X018210Y012034D03*
-X018210Y012024D03*
-X018210Y012014D03*
-X018210Y012004D03*
-X018210Y011994D03*
-X018210Y011984D03*
-X018210Y011974D03*
-X018210Y011964D03*
-X018210Y011954D03*
-X018210Y011944D03*
-X018210Y011934D03*
-X018210Y011924D03*
-X018210Y011914D03*
-X018210Y011904D03*
-X018210Y011894D03*
-X018210Y011884D03*
-X018210Y011874D03*
-X018210Y011864D03*
-X018210Y011854D03*
-X018210Y011844D03*
-X018210Y011834D03*
-X018210Y011824D03*
-X018210Y011814D03*
-X018210Y011804D03*
-X018210Y011794D03*
-X018210Y011784D03*
-X018210Y011774D03*
-X018210Y011764D03*
-X018210Y011754D03*
-X018210Y011744D03*
-X018210Y011734D03*
-X018210Y011724D03*
-X018210Y011714D03*
-X018210Y011704D03*
-X018210Y011694D03*
-X018210Y011684D03*
-X018210Y011674D03*
-X018210Y011664D03*
-X018210Y011654D03*
-X018210Y011644D03*
-X018210Y011634D03*
-X018210Y012224D03*
-X018210Y012234D03*
-X018210Y012244D03*
-X018210Y012254D03*
-X018210Y012264D03*
-X018210Y012274D03*
-X018210Y012284D03*
-X018210Y012294D03*
-X018210Y012734D03*
-X018210Y012744D03*
-X018210Y012754D03*
-X018210Y012764D03*
-X018210Y012774D03*
-X018210Y012784D03*
-X018210Y012794D03*
-X018210Y012804D03*
-X018210Y012814D03*
-X018210Y012824D03*
-X018210Y012834D03*
-X018210Y012844D03*
-X018210Y012854D03*
-X018210Y012864D03*
-X018210Y012874D03*
-X018210Y012884D03*
-X018210Y012894D03*
-X018210Y012904D03*
-X018210Y012914D03*
-X018210Y012924D03*
-X018210Y012934D03*
-X018210Y012944D03*
-X018210Y012954D03*
-X018210Y012964D03*
-X018210Y012974D03*
-X018210Y012984D03*
-X018210Y012994D03*
-X018210Y013004D03*
-X018210Y013014D03*
-X018210Y013024D03*
-X018210Y013034D03*
-X018210Y013044D03*
-X018210Y013054D03*
-X018210Y013064D03*
-X018210Y013074D03*
-X018210Y013084D03*
-X018210Y013094D03*
-X018210Y013104D03*
-X018210Y013114D03*
-X018210Y013124D03*
-X018210Y013134D03*
-X018210Y013144D03*
-X018210Y013154D03*
-X018210Y013164D03*
-X018210Y013174D03*
-X018210Y013184D03*
-X018210Y013194D03*
-X018210Y013204D03*
-X018210Y013214D03*
-X018210Y013224D03*
-X018210Y013234D03*
-X018210Y013244D03*
-X018210Y013254D03*
-X018210Y013264D03*
-X019260Y013114D03*
-X019260Y013104D03*
-X019260Y013094D03*
-X019310Y012814D03*
-X019320Y012804D03*
-X020710Y012804D03*
-X020710Y012794D03*
-X020710Y012784D03*
-X020710Y012774D03*
-X020710Y012764D03*
-X020710Y012754D03*
-X020710Y012744D03*
-X020710Y012734D03*
-X020710Y012724D03*
-X020710Y012714D03*
-X020710Y012814D03*
-X020710Y012824D03*
-X020710Y012834D03*
-X020710Y012844D03*
-X020710Y012854D03*
-X020710Y012864D03*
-X020710Y012874D03*
-X020710Y012884D03*
-X020710Y012894D03*
-X020710Y012904D03*
-X020710Y012914D03*
-X020710Y012924D03*
-X020710Y012934D03*
-X020710Y012944D03*
-X020710Y012954D03*
-X020710Y012964D03*
-X020710Y012974D03*
-X020710Y012984D03*
-X020710Y012994D03*
-X020710Y013004D03*
-X020710Y013014D03*
-X020710Y013024D03*
-X020710Y013034D03*
-X020710Y013044D03*
-X020710Y013054D03*
-X020710Y013064D03*
-X020710Y013074D03*
-X020710Y013084D03*
-X020710Y013094D03*
-X020710Y013104D03*
-X020710Y013114D03*
-X020710Y013124D03*
-X020710Y013134D03*
-X020710Y013144D03*
-X020710Y013154D03*
-X020710Y013164D03*
-X020710Y013174D03*
-X020710Y013184D03*
-X020710Y013194D03*
-X020710Y013204D03*
-X020710Y013214D03*
-X020710Y013224D03*
-X020710Y013234D03*
-X020710Y013244D03*
-X020710Y013254D03*
-X020710Y013264D03*
-X020710Y013274D03*
-X020710Y012284D03*
-X020710Y012274D03*
-X020710Y012264D03*
-X020710Y012254D03*
-X020710Y012244D03*
-X020710Y012234D03*
-X020710Y012224D03*
-X020710Y012214D03*
-X020710Y012204D03*
-X020710Y012194D03*
-X020710Y012184D03*
-X020710Y012174D03*
-X020710Y012164D03*
-X020710Y012154D03*
-X020710Y012144D03*
-X020710Y012134D03*
-X020710Y012124D03*
-X020710Y012114D03*
-X020710Y012104D03*
-X020710Y012094D03*
-X020710Y012084D03*
-X020710Y012074D03*
-X020710Y012064D03*
-X020710Y012054D03*
-X020710Y012044D03*
-X020710Y012034D03*
-X020710Y012024D03*
-X020710Y012014D03*
-X020710Y012004D03*
-X020710Y011994D03*
-X020710Y011984D03*
-X020710Y011974D03*
-X020710Y011964D03*
-X020710Y011954D03*
-X020710Y011944D03*
-X020710Y011934D03*
-X020710Y011924D03*
-X020710Y011914D03*
-X020710Y011904D03*
-X020710Y011894D03*
-X020710Y011884D03*
-X020710Y011874D03*
-X020710Y011864D03*
-X020710Y011854D03*
-X020710Y011844D03*
-X020710Y011834D03*
-X020710Y011824D03*
-X020710Y011814D03*
-X020710Y011804D03*
-X020710Y011794D03*
-X020710Y011784D03*
-X020710Y011774D03*
-X020710Y011764D03*
-X020710Y011754D03*
-X020710Y011744D03*
-X020710Y011734D03*
-X020710Y011724D03*
-X020710Y011714D03*
-X020710Y011704D03*
-X020710Y011694D03*
-X020710Y011684D03*
-X020710Y011674D03*
-X020710Y011664D03*
-X020710Y011654D03*
-X020710Y011644D03*
-X020710Y011634D03*
-X020060Y009104D03*
-X020060Y009094D03*
-X020060Y009084D03*
-X020060Y009074D03*
-X020060Y009064D03*
-X020060Y009054D03*
-X020060Y009044D03*
-X020060Y009034D03*
-X020060Y009024D03*
-X020060Y009014D03*
-X020060Y009004D03*
-X020060Y008994D03*
-X020060Y008984D03*
-X020060Y008974D03*
-X020060Y008964D03*
-X020060Y008954D03*
-X020060Y008944D03*
-X020060Y008934D03*
-X020060Y008924D03*
-X020060Y008914D03*
-X020060Y008904D03*
-X020060Y008894D03*
-X020060Y008884D03*
-X020060Y008874D03*
-X020060Y008864D03*
-X020060Y008854D03*
-X020060Y008844D03*
-X020060Y008834D03*
-X020060Y008824D03*
-X020060Y008814D03*
-X020060Y008804D03*
-X020060Y008794D03*
-X020060Y008784D03*
-X020060Y008774D03*
-X020060Y008764D03*
-X020060Y008754D03*
-X020060Y008744D03*
-X020060Y008734D03*
-X020060Y008724D03*
-X020060Y008714D03*
-X020060Y008704D03*
-X020060Y008694D03*
-X020060Y008684D03*
-X020060Y008674D03*
-X020060Y008664D03*
-X020060Y008654D03*
-X020060Y008644D03*
-X020060Y008634D03*
-X020060Y008624D03*
-X020060Y008614D03*
-X020060Y008604D03*
-X020060Y008594D03*
-X020060Y008584D03*
-X020060Y008574D03*
-X020060Y008564D03*
-X020060Y008554D03*
-X020060Y008544D03*
-X020060Y008534D03*
-X020060Y008524D03*
-X020060Y008514D03*
-X020060Y008504D03*
-X020060Y008494D03*
-X020060Y008484D03*
-X020060Y008474D03*
-X020060Y008464D03*
-X020060Y008454D03*
-X020060Y008444D03*
-X020060Y008434D03*
-X020060Y008424D03*
-X020060Y008414D03*
-X020060Y008404D03*
-X020060Y008394D03*
-X020060Y008384D03*
-X020060Y008374D03*
-X020060Y008364D03*
-X020060Y008354D03*
-X020060Y008344D03*
-X020060Y008334D03*
-X020060Y008324D03*
-X020060Y008314D03*
-X020060Y008304D03*
-X020060Y008294D03*
-X020060Y008284D03*
-X020060Y008274D03*
-X020060Y008264D03*
-X020060Y008254D03*
-X020060Y008244D03*
-X020060Y008234D03*
-X020060Y008224D03*
-X020060Y008214D03*
-X020060Y008204D03*
-X020060Y008194D03*
-X020060Y008184D03*
-X020060Y008174D03*
-X020060Y008164D03*
-X020060Y008154D03*
-X020060Y008144D03*
-X020060Y008134D03*
-X020060Y008124D03*
-X020060Y008114D03*
-X020060Y008104D03*
-X020060Y008094D03*
-X020060Y008084D03*
-X020060Y008074D03*
-X020060Y008064D03*
-X020060Y008054D03*
-X020060Y008044D03*
-X020060Y008034D03*
-X020060Y008024D03*
-X020060Y008014D03*
-X020060Y008004D03*
-X020060Y007994D03*
-X020060Y007984D03*
-X020060Y007974D03*
-X020060Y007964D03*
-X020060Y007954D03*
-X020060Y007944D03*
-X020060Y007934D03*
-X020060Y007924D03*
-X020060Y007914D03*
-X020060Y007904D03*
-X020060Y007894D03*
-X020060Y007884D03*
-X020060Y007874D03*
-X020060Y007864D03*
-X020060Y007854D03*
-X020060Y007844D03*
-X020060Y007834D03*
-X020060Y007824D03*
-X020060Y007814D03*
-X020060Y007804D03*
-X020060Y007794D03*
-X020060Y007784D03*
-X020060Y007774D03*
-X020060Y007764D03*
-X020060Y007754D03*
-X020060Y007744D03*
-X020060Y007734D03*
-X020060Y007724D03*
-X020060Y007714D03*
-X020060Y007704D03*
-X020060Y007694D03*
-X020060Y007684D03*
-X020060Y007674D03*
-X020060Y007664D03*
-X020060Y007654D03*
-X020060Y007644D03*
-X020060Y007634D03*
-X020060Y007624D03*
-X020060Y007614D03*
-X020060Y007604D03*
-X020060Y007594D03*
-X020060Y007584D03*
-X020060Y007574D03*
-X020060Y007564D03*
-X020060Y007554D03*
-X020060Y007544D03*
-X020060Y007534D03*
-X020060Y007524D03*
-X020060Y007514D03*
-X020060Y007504D03*
-X020060Y007494D03*
-X020060Y007484D03*
-X020060Y007474D03*
-X020060Y007464D03*
-X020060Y007454D03*
-X020060Y007444D03*
-X020060Y007434D03*
-X020060Y007424D03*
-X020060Y007414D03*
-X020060Y007404D03*
-X020060Y007394D03*
-X020060Y007384D03*
-X020060Y007374D03*
-X020060Y007364D03*
-X020060Y007354D03*
-X020060Y007344D03*
-X020060Y007334D03*
-X020060Y007324D03*
-X020060Y007314D03*
-X020060Y007304D03*
-X020060Y007294D03*
-X020060Y007284D03*
-X020060Y007274D03*
-X020060Y007264D03*
-X020060Y007254D03*
-X020060Y007244D03*
-X020060Y007234D03*
-X020060Y007224D03*
-X020060Y007214D03*
-X020060Y007204D03*
-X020060Y007194D03*
-X020060Y007184D03*
-X020060Y007174D03*
-X020060Y007164D03*
-X020060Y007154D03*
-X020060Y007144D03*
-X020060Y007134D03*
-X020060Y007124D03*
-X020060Y007114D03*
-X020060Y007104D03*
-X020060Y007094D03*
-X020060Y007084D03*
-X020060Y007074D03*
-X020060Y007064D03*
-X020060Y007054D03*
-X020060Y007044D03*
-X015790Y009504D03*
-X015790Y009514D03*
-X013290Y009104D03*
-X013290Y009094D03*
-X013290Y009084D03*
-X013290Y009074D03*
-X013290Y009064D03*
-X013290Y009054D03*
-X013290Y009044D03*
-X013290Y009034D03*
-X013290Y009024D03*
-X013290Y009014D03*
-X013290Y009004D03*
-X013290Y008994D03*
-X013290Y008984D03*
-X013290Y008974D03*
-X013290Y008964D03*
-X013290Y008954D03*
-X013290Y008944D03*
-X013290Y008934D03*
-X013290Y008924D03*
-X013290Y008914D03*
-X013290Y008904D03*
-X013290Y008894D03*
-X013290Y008884D03*
-X013290Y008874D03*
-X013290Y008864D03*
-X013290Y008854D03*
-X013290Y008844D03*
-X013290Y008834D03*
-X013290Y008824D03*
-X013290Y008814D03*
-X013290Y008804D03*
-X013290Y008794D03*
-X013290Y008784D03*
-X013290Y008774D03*
-X013290Y008764D03*
-X013290Y008754D03*
-X013290Y008744D03*
-X013290Y008734D03*
-X013290Y008724D03*
-X013290Y008714D03*
-X013290Y008704D03*
-X013290Y008694D03*
-X013290Y008684D03*
-X013290Y008674D03*
-X013290Y008664D03*
-X013290Y008654D03*
-X013290Y008644D03*
-X013290Y008634D03*
-X013290Y008624D03*
-X013290Y008614D03*
-X013290Y008604D03*
-X013290Y008594D03*
-X013290Y008584D03*
-X013290Y008574D03*
-X013290Y008564D03*
-X013290Y008134D03*
-X013290Y008124D03*
-X013290Y008114D03*
-X013290Y008104D03*
-X013290Y008094D03*
-X013290Y008084D03*
-X013290Y008074D03*
-X013290Y008064D03*
-X013290Y008054D03*
-X013290Y008044D03*
-X013290Y008034D03*
-X013290Y008024D03*
-X013290Y008014D03*
-X013290Y008004D03*
-X013290Y007994D03*
-X013290Y007984D03*
-X013290Y007974D03*
-X013290Y007964D03*
-X013290Y007954D03*
-X013290Y007944D03*
-X013290Y007934D03*
-X013290Y007924D03*
-X013290Y007914D03*
-X013290Y007904D03*
-X013290Y007894D03*
-X013290Y007884D03*
-X013290Y007874D03*
-X013290Y007864D03*
-X013290Y007854D03*
-X013290Y007844D03*
-X013290Y007834D03*
-X013290Y007824D03*
-X013290Y007814D03*
-X013290Y007804D03*
-X013290Y007794D03*
-X013290Y007784D03*
-X013290Y007774D03*
-X013290Y007764D03*
-X013290Y007754D03*
-X013290Y007744D03*
-X013290Y007734D03*
-X013290Y007724D03*
-X013290Y007714D03*
-X013290Y007704D03*
-X013290Y007694D03*
-X013290Y007684D03*
-X013290Y007674D03*
-X013290Y007664D03*
-X013290Y007654D03*
-X013290Y007644D03*
-X013290Y007634D03*
-X013290Y007624D03*
-X013290Y007614D03*
-X013290Y007604D03*
-X013290Y007594D03*
-X013290Y007584D03*
-X013290Y007574D03*
-X013290Y007564D03*
-X013290Y007554D03*
-X013290Y007544D03*
-X013290Y007534D03*
-X013290Y007524D03*
-X013290Y007514D03*
-X013290Y007504D03*
-X013290Y007494D03*
-X013290Y007484D03*
-X013290Y007474D03*
-D19*
-X014445Y007444D03*
-X014445Y007434D03*
-X015665Y009094D03*
-X015665Y009104D03*
-X017035Y009104D03*
-X017035Y007474D03*
-X017645Y007044D03*
-X018255Y007474D03*
-X013255Y012214D03*
-X012695Y012654D03*
-X019495Y012714D03*
-X004865Y010244D03*
-X004865Y010234D03*
-D20*
-X005155Y009974D03*
-X005165Y009954D03*
-X005175Y009934D03*
-X005185Y009924D03*
-X005185Y009914D03*
-X005195Y009904D03*
-X005205Y009884D03*
-X004365Y011114D03*
-X004355Y011134D03*
-X004335Y011164D03*
-X004325Y011184D03*
-X004315Y011204D03*
-X004305Y011214D03*
-X004295Y011234D03*
-X004285Y011254D03*
-X004275Y011264D03*
-X004265Y011284D03*
-X004255Y011304D03*
-X004245Y011314D03*
-X004235Y011334D03*
-X004225Y011354D03*
-X004215Y011364D03*
-X004205Y011384D03*
-X004195Y011404D03*
-X004185Y011414D03*
-X004175Y011434D03*
-X004165Y011444D03*
-X004165Y011454D03*
-X004155Y011464D03*
-X004155Y011474D03*
-X004145Y011484D03*
-X004135Y011494D03*
-X004135Y011504D03*
-X004125Y011514D03*
-X004125Y011524D03*
-X004115Y011534D03*
-X004105Y011544D03*
-X004105Y011554D03*
-X004095Y011564D03*
-X004095Y011574D03*
-X004085Y011584D03*
-X004075Y011594D03*
-X004075Y011604D03*
-X004065Y011614D03*
-X004065Y011624D03*
-X004055Y011634D03*
-X004045Y011644D03*
-X004045Y011654D03*
-X004035Y011664D03*
-X004035Y011674D03*
-X004025Y011684D03*
-X004015Y011704D03*
-X004005Y011714D03*
-X003995Y011734D03*
-X003985Y011754D03*
-X003975Y011764D03*
-X003965Y011784D03*
-X003955Y011804D03*
-X003945Y011814D03*
-X003935Y011834D03*
-X003925Y011854D03*
-X003915Y011864D03*
-X003905Y011884D03*
-X003895Y011904D03*
-X003885Y011914D03*
-X003875Y011934D03*
-X003865Y011954D03*
-X003845Y011984D03*
-X003815Y012034D03*
-X003785Y012084D03*
-X003755Y012134D03*
-X013375Y011964D03*
-X014745Y012674D03*
-X014765Y012724D03*
-X014775Y012754D03*
-X014785Y012774D03*
-X014785Y012784D03*
-X014795Y012804D03*
-X014805Y012824D03*
-X014805Y012834D03*
-X014815Y012854D03*
-X014815Y012864D03*
-X014825Y012874D03*
-X014825Y012884D03*
-X014835Y012904D03*
-X014835Y012914D03*
-X014845Y012924D03*
-X014845Y012934D03*
-X014845Y012944D03*
-X014855Y012954D03*
-X014855Y012964D03*
-X014865Y012974D03*
-X014865Y012984D03*
-X014865Y012994D03*
-X014875Y013004D03*
-X014875Y013014D03*
-X014885Y013034D03*
-X014885Y013044D03*
-X014895Y013054D03*
-X014895Y013064D03*
-X014905Y013084D03*
-X015625Y012544D03*
-X015645Y012494D03*
-X015655Y012464D03*
-X015665Y012444D03*
-X015675Y012414D03*
-X015685Y012384D03*
-X015695Y012364D03*
-X015695Y012354D03*
-X015705Y012334D03*
-X015715Y012314D03*
-X015715Y012304D03*
-X015725Y012284D03*
-X015725Y012274D03*
-X015735Y012264D03*
-X015735Y012254D03*
-X015735Y012244D03*
-X015745Y012234D03*
-X015745Y012224D03*
-X015755Y012204D03*
-X015785Y009494D03*
-X016705Y008444D03*
-X018585Y008434D03*
-X018585Y008424D03*
-X018585Y008414D03*
-X014325Y007054D03*
-X014325Y007044D03*
-X019145Y011994D03*
-X019145Y012004D03*
-X019325Y012794D03*
-X019265Y013124D03*
-X019265Y013134D03*
-D21*
-X020015Y013334D03*
-X020015Y013344D03*
-X020015Y012384D03*
-X020015Y011434D03*
-X017515Y011434D03*
-X017515Y011424D03*
-X017515Y011414D03*
-X017515Y011404D03*
-X017515Y011394D03*
-X017515Y011384D03*
-X017515Y011374D03*
-X017515Y011364D03*
-X017515Y011354D03*
-X017515Y011344D03*
-X017515Y011334D03*
-X017515Y011324D03*
-X017515Y011314D03*
-X017515Y011304D03*
-X017515Y011294D03*
-X017515Y011284D03*
-X017515Y011274D03*
-X017515Y011264D03*
-X017515Y011254D03*
-X017515Y011244D03*
-X017515Y011234D03*
-X017515Y011224D03*
-X017515Y011214D03*
-X017515Y011444D03*
-X017515Y011454D03*
-X017515Y011464D03*
-X017515Y011474D03*
-X017515Y011484D03*
-X017515Y011494D03*
-X017515Y011504D03*
-X017515Y011514D03*
-X017515Y011524D03*
-X017515Y011534D03*
-X017515Y011544D03*
-X017515Y011554D03*
-X017515Y011564D03*
-X017515Y011574D03*
-X017515Y011584D03*
-X017515Y011594D03*
-X017515Y011604D03*
-X017515Y011614D03*
-X017515Y011624D03*
-X015155Y011974D03*
-X017645Y009154D03*
-X017645Y007414D03*
-X012595Y007414D03*
-X012595Y007404D03*
-X012595Y007394D03*
-X012595Y007384D03*
-X012595Y007374D03*
-X012595Y007364D03*
-X012595Y007354D03*
-X012595Y007344D03*
-X012595Y007334D03*
-X012595Y007324D03*
-X012595Y007314D03*
-X012595Y007304D03*
-X012595Y007294D03*
-X012595Y007284D03*
-X012595Y007274D03*
-X012595Y007264D03*
-X012595Y007254D03*
-X012595Y007244D03*
-X012595Y007234D03*
-X012595Y007224D03*
-X012595Y007214D03*
-X012595Y007204D03*
-X012595Y007194D03*
-X012595Y007184D03*
-X012595Y007174D03*
-X012595Y007164D03*
-X012595Y007154D03*
-X012595Y007144D03*
-X012595Y007134D03*
-X012595Y007124D03*
-X012595Y007114D03*
-X012595Y007104D03*
-X012595Y007094D03*
-X012595Y007084D03*
-X012595Y007074D03*
-X012595Y007064D03*
-X012595Y007054D03*
-X012595Y007044D03*
-X012595Y007424D03*
-X012595Y007434D03*
-X012595Y007444D03*
-X012595Y007454D03*
-X012595Y007464D03*
-D22*
-X014475Y007534D03*
-X014475Y007544D03*
-X015635Y009004D03*
-X017085Y009124D03*
-X018205Y009124D03*
-X018215Y007454D03*
-X017645Y007054D03*
-X013225Y012274D03*
-X012705Y012614D03*
-X015155Y013344D03*
-X004865Y010194D03*
-X004865Y010184D03*
-D23*
-X004865Y010144D03*
-X013195Y012334D03*
-X017115Y009134D03*
-X018165Y009134D03*
-X015605Y008904D03*
-X014505Y007634D03*
-X017645Y007064D03*
-D24*
-X016730Y007964D03*
-X016720Y008004D03*
-X016720Y008014D03*
-X016720Y008024D03*
-X016720Y008034D03*
-X016710Y008064D03*
-X016710Y008074D03*
-X016710Y008084D03*
-X016710Y008094D03*
-X016710Y008104D03*
-X016710Y008114D03*
-X016710Y008124D03*
-X016700Y008144D03*
-X016700Y008154D03*
-X016700Y008164D03*
-X016700Y008174D03*
-X016700Y008184D03*
-X016700Y008194D03*
-X016700Y008204D03*
-X016700Y008214D03*
-X016700Y008224D03*
-X016700Y008234D03*
-X016700Y008244D03*
-X016700Y008254D03*
-X016700Y008264D03*
-X016700Y008274D03*
-X016700Y008284D03*
-X016700Y008294D03*
-X016700Y008304D03*
-X016700Y008314D03*
-X016700Y008324D03*
-X016700Y008334D03*
-X016700Y008344D03*
-X016700Y008354D03*
-X016700Y008364D03*
-X016700Y008374D03*
-X016700Y008384D03*
-X016700Y008394D03*
-X016700Y008404D03*
-X016700Y008414D03*
-X016700Y008424D03*
-X016700Y008434D03*
-X016710Y008454D03*
-X016710Y008464D03*
-X016710Y008474D03*
-X016710Y008484D03*
-X016710Y008494D03*
-X016710Y008504D03*
-X016710Y008514D03*
-X016710Y008524D03*
-X016720Y008534D03*
-X016720Y008544D03*
-X016720Y008554D03*
-X016720Y008564D03*
-X016720Y008574D03*
-X016720Y008584D03*
-X016730Y008604D03*
-X016730Y008614D03*
-X016730Y008624D03*
-X016740Y008654D03*
-X016740Y008664D03*
-X015780Y009474D03*
-X015780Y009484D03*
-X017650Y009554D03*
-X018550Y008654D03*
-X018560Y008614D03*
-X018560Y008604D03*
-X018570Y008574D03*
-X018570Y008564D03*
-X018570Y008554D03*
-X018570Y008544D03*
-X018570Y008534D03*
-X018580Y008514D03*
-X018580Y008504D03*
-X018580Y008494D03*
-X018580Y008484D03*
-X018580Y008474D03*
-X018580Y008464D03*
-X018580Y008454D03*
-X018580Y008444D03*
-X018590Y008404D03*
-X018590Y008394D03*
-X018590Y008384D03*
-X018590Y008374D03*
-X018590Y008364D03*
-X018590Y008354D03*
-X018590Y008344D03*
-X018590Y008334D03*
-X018590Y008324D03*
-X018590Y008314D03*
-X018590Y008304D03*
-X018590Y008294D03*
-X018590Y008284D03*
-X018590Y008274D03*
-X018590Y008264D03*
-X018590Y008254D03*
-X018590Y008244D03*
-X018590Y008234D03*
-X018590Y008224D03*
-X018590Y008214D03*
-X018590Y008204D03*
-X018590Y008194D03*
-X018590Y008184D03*
-X018590Y008174D03*
-X018590Y008164D03*
-X018590Y008154D03*
-X018590Y008144D03*
-X018580Y008134D03*
-X018580Y008124D03*
-X018580Y008114D03*
-X018580Y008104D03*
-X018580Y008094D03*
-X018580Y008084D03*
-X018580Y008074D03*
-X018580Y008064D03*
-X018570Y008034D03*
-X018570Y008024D03*
-X018570Y008014D03*
-X018570Y008004D03*
-X018560Y007964D03*
-X014330Y007074D03*
-X014330Y007064D03*
-X016060Y011394D03*
-X016040Y011444D03*
-X016030Y011474D03*
-X016020Y011504D03*
-X016010Y011524D03*
-X016000Y011554D03*
-X015990Y011574D03*
-X015990Y011584D03*
-X015980Y011604D03*
-X015980Y011614D03*
-X015970Y011624D03*
-X015970Y011634D03*
-X015960Y011654D03*
-X015960Y011664D03*
-X015950Y011684D03*
-X015950Y011694D03*
-X015940Y011704D03*
-X015940Y011714D03*
-X015940Y011724D03*
-X015930Y011734D03*
-X015930Y011744D03*
-X015920Y011754D03*
-X015920Y011764D03*
-X014610Y012324D03*
-X014630Y012374D03*
-X014640Y012404D03*
-X014650Y012424D03*
-X014650Y012434D03*
-X014660Y012454D03*
-X014670Y012474D03*
-X014670Y012484D03*
-X014680Y012504D03*
-X014680Y012514D03*
-X014690Y012524D03*
-X014690Y012534D03*
-X014700Y012554D03*
-X014700Y012564D03*
-X014710Y012574D03*
-X014710Y012584D03*
-X014710Y012594D03*
-X014720Y012604D03*
-X014720Y012614D03*
-X014730Y012624D03*
-X014730Y012634D03*
-X014730Y012644D03*
-X014740Y012654D03*
-X014740Y012664D03*
-X014750Y012684D03*
-X014750Y012694D03*
-X014760Y012704D03*
-X014760Y012714D03*
-X014770Y012734D03*
-X014770Y012744D03*
-X014780Y012764D03*
-X014790Y012794D03*
-X014800Y012814D03*
-X014810Y012844D03*
-X014830Y012894D03*
-X013370Y011974D03*
-X019140Y011974D03*
-X019140Y011964D03*
-X019140Y011954D03*
-X019140Y011944D03*
-X019140Y011934D03*
-X019140Y011924D03*
-X019140Y011914D03*
-X019140Y011904D03*
-X019140Y011894D03*
-X019150Y011884D03*
-X019150Y011874D03*
-X019150Y011864D03*
-X019150Y011854D03*
-X019140Y011984D03*
-X019150Y012014D03*
-X019150Y012024D03*
-X019150Y012034D03*
-X019150Y012044D03*
-X019150Y012054D03*
-X019160Y012074D03*
-X019160Y012084D03*
-X019340Y012784D03*
-X019270Y013144D03*
-X019270Y013154D03*
-X005430Y009514D03*
-X005300Y009724D03*
-X005280Y009754D03*
-X005270Y009774D03*
-X005260Y009794D03*
-X005250Y009804D03*
-X005240Y009824D03*
-X005230Y009834D03*
-X005230Y009844D03*
-X005220Y009854D03*
-X005220Y009864D03*
-X005210Y009874D03*
-X005200Y009894D03*
-X004600Y009984D03*
-X004600Y009994D03*
-X004590Y009974D03*
-X004580Y009954D03*
-X004570Y009934D03*
-X005130Y010734D03*
-X005140Y010754D03*
-X004020Y011694D03*
-X004000Y011724D03*
-X003990Y011744D03*
-X003970Y011774D03*
-X003960Y011794D03*
-X003940Y011824D03*
-X003930Y011844D03*
-X003910Y011874D03*
-X003900Y011894D03*
-X003880Y011924D03*
-X003870Y011944D03*
-X003860Y011964D03*
-X003850Y011974D03*
-X003840Y011994D03*
-X003830Y012004D03*
-X003830Y012014D03*
-X003820Y012024D03*
-X003810Y012044D03*
-X003800Y012054D03*
-X003800Y012064D03*
-X003790Y012074D03*
-X003780Y012094D03*
-X003770Y012104D03*
-X003770Y012114D03*
-X003760Y012124D03*
-X003750Y012144D03*
-X003740Y012154D03*
-X003740Y012164D03*
-X003730Y012174D03*
-X003730Y012184D03*
-X003720Y012194D03*
-X003710Y012204D03*
-X003710Y012214D03*
-X003700Y012224D03*
-X003700Y012234D03*
-X003690Y012244D03*
-X003680Y012254D03*
-X003680Y012264D03*
-X003670Y012274D03*
-X003670Y012284D03*
-X003660Y012294D03*
-X003650Y012304D03*
-X003650Y012314D03*
-X003640Y012324D03*
-X003630Y012344D03*
-X003620Y012354D03*
-X003620Y012364D03*
-X003610Y012374D03*
-X003600Y012394D03*
-X003590Y012404D03*
-X003590Y012414D03*
-X003580Y012424D03*
-X003570Y012444D03*
-X003560Y012464D03*
-X003550Y012474D03*
-X003540Y012494D03*
-X003530Y012514D03*
-X003520Y012524D03*
-X003510Y012544D03*
-X003500Y012564D03*
-X003490Y012574D03*
-X003480Y012594D03*
-X003460Y012624D03*
-X003450Y012644D03*
-X003420Y012694D03*
-X003390Y012744D03*
-X003360Y012794D03*
-X003330Y012844D03*
-X003300Y012894D03*
-D25*
-X004865Y010094D03*
-X014535Y007734D03*
-X014535Y007724D03*
-X015575Y008804D03*
-X015575Y008814D03*
-X017645Y007074D03*
-X015155Y013194D03*
-D26*
-X015155Y013114D03*
-X015545Y008714D03*
-X015545Y008704D03*
-X014565Y007834D03*
-X014565Y007824D03*
-X017645Y007084D03*
-X004865Y010044D03*
-D27*
-X004585Y009964D03*
-X004575Y009944D03*
-X004565Y009924D03*
-X004555Y009914D03*
-X004555Y009904D03*
-X004545Y009884D03*
-X005245Y009814D03*
-X005265Y009784D03*
-X005275Y009764D03*
-X005285Y009744D03*
-X005295Y009734D03*
-X005305Y009714D03*
-X005315Y009704D03*
-X005315Y009694D03*
-X005325Y009684D03*
-X005325Y009674D03*
-X005335Y009664D03*
-X005345Y009654D03*
-X005345Y009644D03*
-X005355Y009634D03*
-X005365Y009624D03*
-X005365Y009614D03*
-X005375Y009604D03*
-X005375Y009594D03*
-X005385Y009584D03*
-X005395Y009574D03*
-X005395Y009564D03*
-X005405Y009554D03*
-X005415Y009544D03*
-X005415Y009534D03*
-X005425Y009524D03*
-X005435Y009504D03*
-X005445Y009494D03*
-X005445Y009484D03*
-X005455Y009474D03*
-X005465Y009464D03*
-X005465Y009454D03*
-X005475Y009444D03*
-X005475Y009434D03*
-X005485Y009424D03*
-X005495Y009414D03*
-X005495Y009404D03*
-X005505Y009394D03*
-X005515Y009384D03*
-X005515Y009374D03*
-X005525Y009364D03*
-X005525Y009354D03*
-X005535Y009344D03*
-X005545Y009334D03*
-X005545Y009324D03*
-X005555Y009314D03*
-X005565Y009294D03*
-X005575Y009284D03*
-X005575Y009274D03*
-X005585Y009264D03*
-X005595Y009244D03*
-X005605Y009234D03*
-X005615Y009214D03*
-X005625Y009204D03*
-X005625Y009194D03*
-X005635Y009184D03*
-X005645Y009164D03*
-X005655Y009154D03*
-X005665Y009134D03*
-X005675Y009114D03*
-X005685Y009104D03*
-X005695Y009084D03*
-X005705Y009074D03*
-X005715Y009054D03*
-X005725Y009034D03*
-X005735Y009024D03*
-X005745Y009004D03*
-X005765Y008974D03*
-X005785Y008944D03*
-X005795Y008924D03*
-X005815Y008894D03*
-X005845Y008844D03*
-X005865Y008814D03*
-X005895Y008764D03*
-X005125Y010724D03*
-X005135Y010744D03*
-X005145Y010764D03*
-X005155Y010774D03*
-X005155Y010784D03*
-X005165Y010794D03*
-X005165Y010804D03*
-X005175Y010814D03*
-X005185Y010834D03*
-X005195Y010854D03*
-X003635Y012334D03*
-X003605Y012384D03*
-X003575Y012434D03*
-X003565Y012454D03*
-X003545Y012484D03*
-X003535Y012504D03*
-X003515Y012534D03*
-X003505Y012554D03*
-X003485Y012584D03*
-X003475Y012604D03*
-X003465Y012614D03*
-X003455Y012634D03*
-X003445Y012654D03*
-X003435Y012664D03*
-X003435Y012674D03*
-X003425Y012684D03*
-X003415Y012704D03*
-X003405Y012714D03*
-X003405Y012724D03*
-X003395Y012734D03*
-X003385Y012754D03*
-X003375Y012764D03*
-X003375Y012774D03*
-X003365Y012784D03*
-X003355Y012804D03*
-X003345Y012814D03*
-X003345Y012824D03*
-X003335Y012834D03*
-X003325Y012854D03*
-X003315Y012864D03*
-X003315Y012874D03*
-X003305Y012884D03*
-X003295Y012904D03*
-X003285Y012914D03*
-X003285Y012924D03*
-X003275Y012934D03*
-X003265Y012954D03*
-X003255Y012964D03*
-X003255Y012974D03*
-X003245Y012984D03*
-X003235Y013004D03*
-X003225Y013014D03*
-X003225Y013024D03*
-X003215Y013034D03*
-X003205Y013054D03*
-X003195Y013064D03*
-X003195Y013074D03*
-X003185Y013084D03*
-X003175Y013104D03*
-X003165Y013114D03*
-X003165Y013124D03*
-X003155Y013134D03*
-X003145Y013154D03*
-X003135Y013174D03*
-X003125Y013184D03*
-X003115Y013204D03*
-X003095Y013234D03*
-X003085Y013254D03*
-X003065Y013284D03*
-X003055Y013304D03*
-X003035Y013334D03*
-X003025Y013354D03*
-X003005Y013384D03*
-X002995Y013404D03*
-X002965Y013454D03*
-X002935Y013504D03*
-X013365Y011984D03*
-X014375Y011724D03*
-X014565Y012204D03*
-X014565Y012214D03*
-X014575Y012224D03*
-X014575Y012234D03*
-X014575Y012244D03*
-X014585Y012254D03*
-X014585Y012264D03*
-X014595Y012274D03*
-X014595Y012284D03*
-X014595Y012294D03*
-X014605Y012304D03*
-X014605Y012314D03*
-X014615Y012334D03*
-X014615Y012344D03*
-X014625Y012354D03*
-X014625Y012364D03*
-X014635Y012384D03*
-X014635Y012394D03*
-X014645Y012414D03*
-X014655Y012444D03*
-X014665Y012464D03*
-X014675Y012494D03*
-X014695Y012544D03*
-X015955Y011674D03*
-X015965Y011644D03*
-X015985Y011594D03*
-X015995Y011564D03*
-X016005Y011544D03*
-X016005Y011534D03*
-X016015Y011514D03*
-X016025Y011494D03*
-X016025Y011484D03*
-X016035Y011464D03*
-X016035Y011454D03*
-X016045Y011434D03*
-X016045Y011424D03*
-X016055Y011414D03*
-X016055Y011404D03*
-X016065Y011384D03*
-X016065Y011374D03*
-X016075Y011364D03*
-X016075Y011354D03*
-X016075Y011344D03*
-X016085Y011334D03*
-X016085Y011324D03*
-X016095Y011304D03*
-X016095Y011294D03*
-X016105Y011284D03*
-X016105Y011274D03*
-X016105Y011264D03*
-X016115Y011254D03*
-X016115Y011244D03*
-X016125Y011224D03*
-X016125Y011214D03*
-X015775Y009464D03*
-X015775Y009454D03*
-X016765Y008744D03*
-X016755Y008724D03*
-X016755Y008714D03*
-X016755Y008704D03*
-X016745Y008694D03*
-X016745Y008684D03*
-X016745Y008674D03*
-X016735Y008644D03*
-X016735Y008634D03*
-X016725Y008594D03*
-X016705Y008134D03*
-X016715Y008054D03*
-X016715Y008044D03*
-X016725Y007994D03*
-X016725Y007984D03*
-X016725Y007974D03*
-X016735Y007954D03*
-X016735Y007944D03*
-X016735Y007934D03*
-X016735Y007924D03*
-X016745Y007914D03*
-X016745Y007904D03*
-X016745Y007894D03*
-X016755Y007864D03*
-X018545Y007904D03*
-X018545Y007914D03*
-X018555Y007924D03*
-X018555Y007934D03*
-X018555Y007944D03*
-X018555Y007954D03*
-X018565Y007974D03*
-X018565Y007984D03*
-X018565Y007994D03*
-X018575Y008044D03*
-X018575Y008054D03*
-X018575Y008524D03*
-X018565Y008584D03*
-X018565Y008594D03*
-X018555Y008624D03*
-X018555Y008634D03*
-X018555Y008644D03*
-X018545Y008664D03*
-X018545Y008674D03*
-X018545Y008684D03*
-X018535Y008704D03*
-X018535Y008714D03*
-X018525Y008744D03*
-X019155Y011824D03*
-X019155Y011834D03*
-X019155Y011844D03*
-X019155Y012064D03*
-X019165Y012094D03*
-X019165Y012104D03*
-X019175Y012114D03*
-X019175Y012124D03*
-X019345Y012774D03*
-X019275Y013164D03*
-X014335Y007084D03*
-D28*
-X017640Y007094D03*
-X004870Y010004D03*
-D29*
-X004550Y009894D03*
-X004540Y009874D03*
-X004530Y009864D03*
-X004530Y009854D03*
-X004520Y009844D03*
-X004520Y009834D03*
-X004510Y009824D03*
-X004500Y009804D03*
-X004470Y009754D03*
-X005560Y009304D03*
-X005590Y009254D03*
-X005610Y009224D03*
-X005640Y009174D03*
-X005660Y009144D03*
-X005670Y009124D03*
-X005690Y009094D03*
-X005710Y009064D03*
-X005720Y009044D03*
-X005740Y009014D03*
-X005750Y008994D03*
-X005760Y008984D03*
-X005770Y008964D03*
-X005780Y008954D03*
-X005790Y008934D03*
-X005800Y008914D03*
-X005810Y008904D03*
-X005820Y008884D03*
-X005830Y008874D03*
-X005830Y008864D03*
-X005840Y008854D03*
-X005850Y008834D03*
-X005860Y008824D03*
-X005870Y008804D03*
-X005880Y008794D03*
-X005880Y008784D03*
-X005890Y008774D03*
-X005900Y008754D03*
-X005910Y008744D03*
-X005910Y008734D03*
-X005920Y008724D03*
-X005930Y008714D03*
-X005930Y008704D03*
-X005940Y008694D03*
-X005940Y008684D03*
-X005950Y008674D03*
-X005960Y008664D03*
-X005960Y008654D03*
-X005970Y008644D03*
-X005980Y008634D03*
-X005980Y008624D03*
-X005990Y008614D03*
-X005990Y008604D03*
-X006000Y008594D03*
-X006010Y008584D03*
-X006010Y008574D03*
-X006020Y008564D03*
-X006030Y008544D03*
-X006040Y008534D03*
-X006040Y008524D03*
-X006050Y008514D03*
-X006060Y008504D03*
-X006060Y008494D03*
-X006070Y008484D03*
-X006080Y008464D03*
-X006090Y008454D03*
-X006090Y008444D03*
-X006100Y008434D03*
-X006110Y008414D03*
-X006120Y008404D03*
-X006130Y008384D03*
-X006140Y008374D03*
-X006140Y008364D03*
-X006150Y008354D03*
-X006160Y008334D03*
-X006170Y008324D03*
-X006180Y008304D03*
-X006190Y008284D03*
-X006200Y008274D03*
-X006210Y008254D03*
-X006220Y008244D03*
-X006230Y008224D03*
-X006240Y008204D03*
-X006250Y008194D03*
-X006260Y008174D03*
-X006280Y008144D03*
-X006300Y008114D03*
-X006310Y008094D03*
-X006330Y008064D03*
-X006360Y008014D03*
-X006410Y007934D03*
-X005180Y010824D03*
-X005190Y010844D03*
-X005200Y010864D03*
-X005210Y010874D03*
-X005210Y010884D03*
-X005220Y010894D03*
-X005220Y010904D03*
-X005230Y010914D03*
-X005240Y010934D03*
-X005250Y010954D03*
-X005260Y010964D03*
-X005270Y010984D03*
-X005280Y011004D03*
-X003270Y012944D03*
-X003240Y012994D03*
-X003210Y013044D03*
-X003180Y013094D03*
-X003150Y013144D03*
-X003140Y013164D03*
-X003120Y013194D03*
-X003110Y013214D03*
-X003100Y013224D03*
-X003090Y013244D03*
-X003080Y013264D03*
-X003070Y013274D03*
-X003060Y013294D03*
-X003050Y013314D03*
-X003040Y013324D03*
-X003030Y013344D03*
-X003020Y013364D03*
-X003010Y013374D03*
-X003000Y013394D03*
-X002990Y013414D03*
-X002980Y013424D03*
-X002980Y013434D03*
-X002970Y013444D03*
-X002960Y013464D03*
-X002950Y013474D03*
-X002950Y013484D03*
-X002940Y013494D03*
-X002930Y013514D03*
-X002920Y013524D03*
-X002920Y013534D03*
-X002910Y013544D03*
-X002900Y013554D03*
-X002900Y013564D03*
-X002890Y013574D03*
-X002890Y013584D03*
-X002880Y013594D03*
-X002870Y013604D03*
-X002870Y013614D03*
-X002860Y013624D03*
-X002860Y013634D03*
-X002850Y013644D03*
-X002840Y013654D03*
-X002840Y013664D03*
-X002830Y013674D03*
-X002830Y013684D03*
-X013360Y011994D03*
-X014320Y011594D03*
-X014320Y011584D03*
-X014320Y011574D03*
-X014310Y011564D03*
-X014310Y011554D03*
-X014300Y011534D03*
-X014300Y011524D03*
-X014290Y011514D03*
-X014290Y011504D03*
-X014280Y011484D03*
-X014280Y011474D03*
-X014270Y011454D03*
-X014260Y011434D03*
-X014260Y011424D03*
-X014250Y011404D03*
-X014240Y011384D03*
-X014240Y011374D03*
-X014230Y011354D03*
-X014220Y011324D03*
-X014210Y011304D03*
-X014200Y011274D03*
-X014190Y011254D03*
-X014180Y011224D03*
-X014330Y011604D03*
-X014330Y011614D03*
-X014340Y011624D03*
-X014340Y011634D03*
-X014340Y011644D03*
-X014350Y011654D03*
-X014350Y011664D03*
-X014360Y011674D03*
-X014360Y011684D03*
-X014360Y011694D03*
-X014370Y011704D03*
-X014370Y011714D03*
-X014380Y011734D03*
-X014380Y011744D03*
-X014390Y011754D03*
-X014390Y011764D03*
-X016090Y011314D03*
-X016120Y011234D03*
-X015770Y009444D03*
-X016790Y008804D03*
-X016780Y008784D03*
-X016780Y008774D03*
-X016770Y008764D03*
-X016770Y008754D03*
-X016760Y008734D03*
-X016750Y007884D03*
-X016750Y007874D03*
-X016760Y007854D03*
-X016760Y007844D03*
-X016770Y007824D03*
-X016780Y007794D03*
-X018520Y007824D03*
-X018530Y007844D03*
-X018530Y007854D03*
-X018530Y007864D03*
-X018540Y007874D03*
-X018540Y007884D03*
-X018540Y007894D03*
-X018540Y008694D03*
-X018530Y008724D03*
-X018530Y008734D03*
-X018520Y008754D03*
-X018520Y008764D03*
-X018510Y008784D03*
-X019170Y011784D03*
-X019170Y011794D03*
-X019160Y011804D03*
-X019160Y011814D03*
-X019180Y012134D03*
-X019190Y012144D03*
-X019360Y012764D03*
-X019280Y013174D03*
-X019290Y013184D03*
-X015160Y013684D03*
-X015160Y013694D03*
-X014340Y007104D03*
-X014340Y007094D03*
-D30*
-X017645Y007104D03*
-D31*
-X017645Y007114D03*
-D32*
-X017645Y007124D03*
-D33*
-X017645Y007134D03*
-D34*
-X017645Y007144D03*
-D35*
-X017645Y007154D03*
-D36*
-X017645Y007164D03*
-D37*
-X016840Y007674D03*
-X016830Y007694D03*
-X016840Y008904D03*
-X016850Y008924D03*
-X016860Y008934D03*
-X015750Y009374D03*
-X015750Y009384D03*
-X014360Y007164D03*
-X018450Y007674D03*
-X018460Y007694D03*
-X018450Y008904D03*
-X018440Y008914D03*
-X018440Y008924D03*
-X018430Y008934D03*
-X019200Y011724D03*
-X019230Y012204D03*
-X019240Y012214D03*
-X015160Y013634D03*
-X015160Y013644D03*
-X013340Y012034D03*
-X012420Y012354D03*
-X012420Y012364D03*
-X012410Y012344D03*
-X012410Y012334D03*
-X012400Y012324D03*
-X012390Y012314D03*
-X012390Y012304D03*
-X012380Y012294D03*
-X012370Y012274D03*
-X012360Y012264D03*
-X012350Y012244D03*
-D38*
-X012955Y012584D03*
-X012955Y012594D03*
-X017645Y007174D03*
-D39*
-X016855Y007654D03*
-X016845Y007664D03*
-X016865Y008944D03*
-X015745Y009364D03*
-X014365Y007184D03*
-X014365Y007174D03*
-X018435Y007654D03*
-X018445Y007664D03*
-X018455Y007684D03*
-X018425Y008944D03*
-X018415Y008954D03*
-X019215Y011714D03*
-X019405Y012734D03*
-X019315Y013224D03*
-X019325Y013234D03*
-X015165Y013624D03*
-X013335Y012044D03*
-X012375Y012284D03*
-X012355Y012254D03*
-X012345Y012234D03*
-X012335Y012224D03*
-X012335Y012214D03*
-X012325Y012204D03*
-X012315Y012194D03*
-X012315Y012184D03*
-X012305Y012174D03*
-X012295Y012154D03*
-X012285Y012144D03*
-X012285Y012134D03*
-X012275Y012124D03*
-X012265Y012104D03*
-X012245Y012074D03*
-D40*
-X012935Y012524D03*
-X017645Y007184D03*
-D41*
-X017645Y007194D03*
-X012925Y012494D03*
-D42*
-X013330Y012054D03*
-X012300Y012164D03*
-X012270Y012114D03*
-X012260Y012094D03*
-X012250Y012084D03*
-X012240Y012064D03*
-X012230Y012054D03*
-X012230Y012044D03*
-X012220Y012034D03*
-X012210Y012024D03*
-X012210Y012014D03*
-X012200Y012004D03*
-X012190Y011984D03*
-X012180Y011974D03*
-X012170Y011954D03*
-X012160Y011934D03*
-X012140Y011904D03*
-X015160Y013604D03*
-X015160Y013614D03*
-X019250Y012224D03*
-X019260Y012234D03*
-X019220Y011704D03*
-X018400Y008974D03*
-X018410Y008964D03*
-X018430Y007644D03*
-X018420Y007634D03*
-X016870Y007634D03*
-X016860Y007644D03*
-X016870Y008954D03*
-X016880Y008964D03*
-X016890Y008974D03*
-X015740Y009344D03*
-X015740Y009354D03*
-X014370Y007204D03*
-X014370Y007194D03*
-X004870Y010364D03*
-D43*
-X012905Y012434D03*
-X017645Y007204D03*
-X020235Y011224D03*
-D44*
-X017645Y007214D03*
-X012895Y012404D03*
-D45*
-X013325Y012064D03*
-X012195Y011994D03*
-X012175Y011964D03*
-X012165Y011944D03*
-X012155Y011924D03*
-X012145Y011914D03*
-X012135Y011894D03*
-X012125Y011884D03*
-X012125Y011874D03*
-X012115Y011864D03*
-X012105Y011854D03*
-X012105Y011844D03*
-X012095Y011834D03*
-X012085Y011814D03*
-X012065Y011784D03*
-X012035Y011734D03*
-X015735Y009334D03*
-X015735Y009324D03*
-X016895Y008984D03*
-X016905Y008994D03*
-X016875Y007624D03*
-X016885Y007614D03*
-X018415Y007624D03*
-X014375Y007214D03*
-X019225Y011694D03*
-X019335Y013244D03*
-X004865Y010354D03*
-D46*
-X012885Y012374D03*
-X017645Y007224D03*
-D47*
-X016900Y007594D03*
-X016890Y007604D03*
-X018390Y007594D03*
-X018400Y007604D03*
-X018410Y007614D03*
-X018390Y008984D03*
-X018380Y008994D03*
-X017650Y009544D03*
-X015730Y009314D03*
-X014380Y007234D03*
-X014380Y007224D03*
-X011960Y011614D03*
-X011980Y011644D03*
-X011990Y011664D03*
-X012000Y011674D03*
-X012010Y011694D03*
-X012020Y011704D03*
-X012020Y011714D03*
-X012030Y011724D03*
-X012040Y011744D03*
-X012050Y011754D03*
-X012050Y011764D03*
-X012060Y011774D03*
-X012070Y011794D03*
-X012080Y011804D03*
-X012090Y011824D03*
-X013320Y012074D03*
-X015160Y013584D03*
-X015160Y013594D03*
-X019430Y012724D03*
-X004870Y010384D03*
-X004870Y010374D03*
-X004860Y010344D03*
-D48*
-X017645Y007234D03*
-D49*
-X017645Y007244D03*
-D50*
-X016915Y007574D03*
-X016905Y007584D03*
-X018375Y007574D03*
-X018385Y007584D03*
-X018375Y009004D03*
-X018365Y009014D03*
-X016925Y009014D03*
-X016915Y009004D03*
-X015725Y009294D03*
-X015725Y009304D03*
-X014385Y007244D03*
-X011855Y011444D03*
-X011875Y011474D03*
-X011885Y011494D03*
-X011895Y011504D03*
-X011905Y011524D03*
-X011915Y011534D03*
-X011915Y011544D03*
-X011925Y011554D03*
-X011925Y011564D03*
-X011935Y011574D03*
-X011945Y011584D03*
-X011945Y011594D03*
-X011955Y011604D03*
-X011965Y011624D03*
-X011975Y011634D03*
-X011985Y011654D03*
-X012005Y011684D03*
-X013315Y012084D03*
-X012655Y012764D03*
-X012645Y012774D03*
-X012635Y012784D03*
-X012625Y012794D03*
-X012615Y012804D03*
-X015165Y013574D03*
-X019275Y012244D03*
-X019235Y011684D03*
-X004865Y010334D03*
-D51*
-X017645Y007254D03*
-X020175Y013634D03*
-D52*
-X019350Y013254D03*
-X019290Y012254D03*
-X019250Y011674D03*
-X016930Y009024D03*
-X015720Y009274D03*
-X015720Y009284D03*
-X016920Y007564D03*
-X016930Y007554D03*
-X014390Y007264D03*
-X014390Y007254D03*
-X011770Y011304D03*
-X011780Y011324D03*
-X011790Y011334D03*
-X011800Y011354D03*
-X011810Y011374D03*
-X011820Y011384D03*
-X011830Y011404D03*
-X011840Y011414D03*
-X011840Y011424D03*
-X011850Y011434D03*
-X011860Y011454D03*
-X011870Y011464D03*
-X011880Y011484D03*
-X011900Y011514D03*
-X013310Y012094D03*
-X012690Y012724D03*
-X012680Y012734D03*
-X012670Y012744D03*
-X012660Y012754D03*
-X012610Y012814D03*
-X012600Y012824D03*
-X012590Y012834D03*
-X012580Y012844D03*
-X012570Y012854D03*
-X012560Y012864D03*
-X012550Y012874D03*
-X012540Y012884D03*
-X012530Y012894D03*
-X012520Y012904D03*
-X012510Y012914D03*
-X012500Y012924D03*
-X015160Y013554D03*
-X015160Y013564D03*
-X004870Y010404D03*
-X004870Y010394D03*
-D53*
-X017645Y009314D03*
-X017645Y007264D03*
-X020165Y013624D03*
-D54*
-X020145Y013604D03*
-X020145Y011264D03*
-X017645Y009294D03*
-X017645Y007274D03*
-D55*
-X018355Y007554D03*
-X018365Y007564D03*
-X018355Y009024D03*
-X018345Y009034D03*
-X016945Y009034D03*
-X015715Y009264D03*
-X014395Y007284D03*
-X014395Y007274D03*
-X011715Y011214D03*
-X011725Y011234D03*
-X011735Y011244D03*
-X011735Y011254D03*
-X011745Y011264D03*
-X011755Y011274D03*
-X011755Y011284D03*
-X011765Y011294D03*
-X011775Y011314D03*
-X011795Y011344D03*
-X011805Y011364D03*
-X011825Y011394D03*
-X013305Y012104D03*
-X012495Y012934D03*
-X012485Y012944D03*
-X012475Y012954D03*
-X012465Y012964D03*
-X012455Y012974D03*
-X012445Y012984D03*
-X012435Y012994D03*
-X012425Y013004D03*
-X012415Y013014D03*
-X012405Y013024D03*
-X012395Y013034D03*
-X012385Y013044D03*
-X012375Y013054D03*
-X012375Y013064D03*
-X012365Y013074D03*
-X012355Y013084D03*
-X012345Y013094D03*
-X012335Y013104D03*
-X012325Y013114D03*
-X012315Y013124D03*
-X012305Y013134D03*
-X012295Y013144D03*
-X012285Y013154D03*
-X012275Y013164D03*
-X012265Y013174D03*
-X004865Y010324D03*
-X004865Y010314D03*
-D56*
-X017645Y009284D03*
-X017645Y007284D03*
-X020135Y011274D03*
-X020135Y013594D03*
-D57*
-X020125Y013584D03*
-X020125Y012564D03*
-X017645Y009274D03*
-X017645Y007294D03*
-D58*
-X017645Y007304D03*
-X017645Y009264D03*
-X020115Y012504D03*
-X020115Y012574D03*
-D59*
-X019375Y013264D03*
-X019265Y011664D03*
-X018325Y009054D03*
-X016965Y009054D03*
-X015705Y009224D03*
-X015705Y009234D03*
-X014405Y007314D03*
-X014405Y007304D03*
-X018335Y007534D03*
-X018345Y007544D03*
-X013295Y012124D03*
-X012695Y012704D03*
-X012145Y013304D03*
-X012135Y013314D03*
-X012125Y013324D03*
-X012115Y013334D03*
-X012105Y013344D03*
-X012095Y013354D03*
-X012035Y013424D03*
-X012025Y013434D03*
-X012015Y013444D03*
-X012005Y013454D03*
-X011995Y013464D03*
-X011985Y013474D03*
-X011975Y013484D03*
-X011965Y013494D03*
-X011955Y013504D03*
-X011945Y013514D03*
-X011935Y013524D03*
-X011925Y013534D03*
-X011915Y013544D03*
-X011805Y013664D03*
-X004865Y010304D03*
-D60*
-X017645Y009254D03*
-X017645Y007314D03*
-X020105Y013554D03*
-D61*
-X020095Y013544D03*
-X020095Y012604D03*
-X017645Y009244D03*
-X017645Y007324D03*
-X015155Y012174D03*
-X015155Y012184D03*
-D62*
-X015160Y013504D03*
-X015160Y013514D03*
-X013290Y012134D03*
-X011910Y013554D03*
-X011900Y013564D03*
-X011890Y013574D03*
-X011880Y013584D03*
-X011870Y013594D03*
-X011860Y013604D03*
-X011850Y013614D03*
-X011840Y013624D03*
-X011830Y013634D03*
-X011820Y013644D03*
-X011810Y013654D03*
-X011800Y013674D03*
-X011790Y013684D03*
-X011780Y013694D03*
-X015700Y009214D03*
-X016980Y009064D03*
-X018310Y009064D03*
-X016960Y007524D03*
-X014410Y007334D03*
-X014410Y007324D03*
-X019310Y012264D03*
-X004870Y010434D03*
-X004870Y010424D03*
-D63*
-X012665Y008554D03*
-X012665Y008544D03*
-X012665Y008534D03*
-X012665Y008524D03*
-X012665Y008514D03*
-X012665Y008504D03*
-X012665Y008494D03*
-X012665Y008484D03*
-X012665Y008474D03*
-X012665Y008464D03*
-X012665Y008454D03*
-X012665Y008444D03*
-X012665Y008434D03*
-X012665Y008424D03*
-X012665Y008414D03*
-X012665Y008404D03*
-X012665Y008394D03*
-X012665Y008384D03*
-X012665Y008374D03*
-X012665Y008364D03*
-X012665Y008354D03*
-X012665Y008344D03*
-X012665Y008334D03*
-X012665Y008324D03*
-X012665Y008314D03*
-X012665Y008304D03*
-X012665Y008294D03*
-X012665Y008284D03*
-X012665Y008274D03*
-X012665Y008264D03*
-X012665Y008254D03*
-X012665Y008244D03*
-X012665Y008234D03*
-X012665Y008224D03*
-X012665Y008214D03*
-X012665Y008204D03*
-X012665Y008194D03*
-X012665Y008184D03*
-X012665Y008174D03*
-X012665Y008164D03*
-X012665Y008154D03*
-X012665Y008144D03*
-X017645Y007334D03*
-X017645Y009234D03*
-X020085Y011324D03*
-X020085Y012474D03*
-X020085Y013524D03*
-X017585Y012724D03*
-X017585Y012714D03*
-X017585Y012704D03*
-X017585Y012694D03*
-X017585Y012684D03*
-X017585Y012674D03*
-X017585Y012664D03*
-X017585Y012654D03*
-X017585Y012644D03*
-X017585Y012634D03*
-X017585Y012624D03*
-X017585Y012614D03*
-X017585Y012604D03*
-X017585Y012594D03*
-X017585Y012584D03*
-X017585Y012574D03*
-X017585Y012564D03*
-X017585Y012554D03*
-X017585Y012544D03*
-X017585Y012534D03*
-X017585Y012524D03*
-X017585Y012514D03*
-X017585Y012504D03*
-X017585Y012494D03*
-X017585Y012484D03*
-X017585Y012474D03*
-X017585Y012464D03*
-X017585Y012454D03*
-X017585Y012444D03*
-X017585Y012434D03*
-X017585Y012424D03*
-X017585Y012414D03*
-X017585Y012404D03*
-X017585Y012394D03*
-X017585Y012384D03*
-X017585Y012374D03*
-X017585Y012364D03*
-X017585Y012354D03*
-X017585Y012344D03*
-X017585Y012334D03*
-X017585Y012324D03*
-X017585Y012314D03*
-X017585Y012304D03*
-X015155Y012154D03*
-D64*
-X015155Y012134D03*
-X015155Y012124D03*
-X017645Y009224D03*
-X017645Y007344D03*
-X020075Y011334D03*
-X020075Y012464D03*
-X020075Y012634D03*
-X020075Y013504D03*
-D65*
-X019285Y011654D03*
-X015695Y009204D03*
-X015695Y009194D03*
-X016975Y007514D03*
-X018315Y007514D03*
-X018325Y007524D03*
-X014415Y007344D03*
-X013285Y012144D03*
-X012695Y012694D03*
-X004865Y010294D03*
-X004865Y010284D03*
-D66*
-X015155Y012104D03*
-X017645Y009214D03*
-X017645Y007354D03*
-X020065Y011344D03*
-X020065Y012454D03*
-X020065Y012654D03*
-X020065Y013484D03*
-D67*
-X015160Y013484D03*
-X015160Y013474D03*
-X015160Y013494D03*
-X013280Y012154D03*
-X012690Y012684D03*
-X015690Y009184D03*
-X016990Y009074D03*
-X017650Y009534D03*
-X018300Y009074D03*
-X018300Y007504D03*
-X016990Y007504D03*
-X014420Y007364D03*
-X014420Y007354D03*
-X004870Y010444D03*
-X004870Y010454D03*
-D68*
-X015155Y012074D03*
-X015155Y012084D03*
-X017645Y009204D03*
-X017645Y007374D03*
-X017645Y007364D03*
-X020055Y011364D03*
-X020055Y012444D03*
-X020055Y012674D03*
-X020055Y013464D03*
-D69*
-X015685Y009174D03*
-X015685Y009164D03*
-X017005Y009084D03*
-X018285Y009084D03*
-X014425Y007374D03*
-X013275Y012164D03*
-X013275Y012174D03*
-X012695Y012674D03*
-X004865Y010274D03*
-X004865Y010264D03*
-D70*
-X015155Y012054D03*
-X017645Y009194D03*
-X017645Y007384D03*
-X020045Y012424D03*
-X020045Y012704D03*
-X020045Y013434D03*
-D71*
-X015160Y013454D03*
-X015160Y013464D03*
-X013270Y012184D03*
-X015680Y009154D03*
-X015680Y009144D03*
-X017020Y009094D03*
-X017000Y007494D03*
-X018290Y007494D03*
-X014430Y007394D03*
-X014430Y007384D03*
-X004870Y010464D03*
-D72*
-X015155Y012024D03*
-X017645Y009174D03*
-X017645Y007394D03*
-X020035Y011394D03*
-X020035Y012414D03*
-X020035Y013404D03*
-X020035Y013414D03*
-D73*
-X020025Y013384D03*
-X020025Y013374D03*
-X020025Y012394D03*
-X020025Y011414D03*
-X017645Y009164D03*
-X017645Y007404D03*
-X015155Y012004D03*
-D74*
-X013265Y012194D03*
-X012695Y012664D03*
-X015675Y009134D03*
-X014435Y007414D03*
-X014435Y007404D03*
-X017015Y007484D03*
-X018275Y007484D03*
-X018265Y009094D03*
-X019345Y012274D03*
-X004865Y010254D03*
-D75*
-X015155Y011954D03*
-X017645Y009144D03*
-X017645Y007424D03*
-X020005Y011454D03*
-X020005Y011464D03*
-X020005Y012354D03*
-X020005Y013294D03*
-X020005Y013304D03*
-D76*
-X019310Y011644D03*
-X015670Y009124D03*
-X015670Y009114D03*
-X014440Y007424D03*
-X013260Y012204D03*
-X015160Y013424D03*
-X015160Y013434D03*
-X015160Y013444D03*
-X004870Y010484D03*
-X004870Y010474D03*
-D77*
-X015155Y011924D03*
-X019995Y012334D03*
-X019995Y011484D03*
-X017645Y007434D03*
-D78*
-X018185Y007444D03*
-X017655Y009514D03*
-X015615Y008944D03*
-X015615Y008934D03*
-X014495Y007604D03*
-X014495Y007594D03*
-X013205Y012314D03*
-X015155Y013294D03*
-X004865Y010154D03*
-D79*
-X004870Y010164D03*
-X004870Y010564D03*
-X013210Y012304D03*
-X015160Y013304D03*
-X015160Y013314D03*
-X015620Y008954D03*
-X014490Y007584D03*
-X017100Y007444D03*
-D80*
-X017070Y007454D03*
-X015640Y009014D03*
-X015640Y009024D03*
-X014470Y007524D03*
-X014470Y007514D03*
-X013230Y012264D03*
-X012700Y012624D03*
-X015160Y013354D03*
-X015160Y013364D03*
-X004870Y010534D03*
-X004870Y010524D03*
-D81*
-X004870Y010494D03*
-X012700Y012644D03*
-X013250Y012224D03*
-X015160Y013404D03*
-X015160Y013414D03*
-X015660Y009084D03*
-X014450Y007454D03*
-X018250Y009104D03*
-D82*
-X018230Y009114D03*
-X017060Y009114D03*
-X015650Y009054D03*
-X014460Y007494D03*
-X014460Y007484D03*
-X018240Y007464D03*
-X013240Y012244D03*
-X012700Y012634D03*
-X015160Y013374D03*
-X015160Y013384D03*
-X015160Y013394D03*
-X004870Y010514D03*
-X004870Y010214D03*
-D83*
-X004865Y010224D03*
-X004865Y010504D03*
-X013245Y012234D03*
-X015655Y009074D03*
-X015655Y009064D03*
-X014455Y007474D03*
-X014455Y007464D03*
-X017045Y007464D03*
-X019425Y013274D03*
-D84*
-X017655Y009524D03*
-X015645Y009044D03*
-X015645Y009034D03*
-X014465Y007504D03*
-X013235Y012254D03*
-X004865Y010204D03*
-D85*
-X004870Y010544D03*
-X013220Y012284D03*
-X015160Y013324D03*
-X015160Y013334D03*
-X015630Y008994D03*
-X015630Y008984D03*
-X014480Y007554D03*
-D86*
-X014485Y007564D03*
-X014485Y007574D03*
-X015625Y008964D03*
-X015625Y008974D03*
-X013215Y012294D03*
-X019405Y012284D03*
-X004865Y010554D03*
-X004865Y010174D03*
-D87*
-X004870Y010574D03*
-X004870Y010584D03*
-X013200Y012324D03*
-X015160Y013274D03*
-X015160Y013284D03*
-X015610Y008924D03*
-X015610Y008914D03*
-X014500Y007624D03*
-X014500Y007614D03*
-D88*
-X014510Y007644D03*
-X014510Y007654D03*
-X015600Y008884D03*
-X015600Y008894D03*
-X013190Y012344D03*
-X015160Y013254D03*
-X015160Y013264D03*
-X004870Y010594D03*
-X004870Y010134D03*
-D89*
-X004865Y010124D03*
-X004865Y010604D03*
-X013185Y012354D03*
-X015155Y013244D03*
-X015595Y008874D03*
-X014515Y007664D03*
-D90*
-X014520Y007674D03*
-X014520Y007684D03*
-X015590Y008854D03*
-X015590Y008864D03*
-X013180Y012364D03*
-X015160Y013224D03*
-X015160Y013234D03*
-X004870Y010614D03*
-X004870Y010114D03*
-D91*
-X004865Y010104D03*
-X014525Y007704D03*
-X014525Y007694D03*
-X015585Y008834D03*
-X015585Y008844D03*
-X017655Y009504D03*
-D92*
-X015580Y008824D03*
-X014530Y007714D03*
-X019410Y011634D03*
-X015160Y013204D03*
-X015160Y013214D03*
-X004870Y010634D03*
-X004870Y010624D03*
-D93*
-X004870Y010644D03*
-X004870Y010084D03*
-X014540Y007754D03*
-X014540Y007744D03*
-X015570Y008784D03*
-X015570Y008794D03*
-X015160Y013174D03*
-X015160Y013184D03*
-D94*
-X015155Y013164D03*
-X015565Y008774D03*
-X014545Y007764D03*
-X004865Y010074D03*
-X004865Y010654D03*
-D95*
-X004870Y010664D03*
-X014550Y007784D03*
-X014550Y007774D03*
-X015560Y008754D03*
-X015560Y008764D03*
-X015160Y013154D03*
-D96*
-X015155Y013144D03*
-X017655Y009494D03*
-X015555Y008744D03*
-X014555Y007794D03*
-X004865Y010064D03*
-D97*
-X004870Y010054D03*
-X004870Y010674D03*
-X004870Y010684D03*
-X014560Y007814D03*
-X014560Y007804D03*
-X015550Y008724D03*
-X015550Y008734D03*
-X015160Y013124D03*
-X015160Y013134D03*
-D98*
-X015160Y013104D03*
-X015540Y008694D03*
-X014570Y007844D03*
-X004870Y010034D03*
-X004870Y010694D03*
-D99*
-X004865Y010704D03*
-X004865Y010024D03*
-X014575Y007864D03*
-X014575Y007854D03*
-X015535Y008674D03*
-X015535Y008684D03*
-X015155Y013094D03*
-D100*
-X017650Y009484D03*
-X015530Y008664D03*
-X015530Y008654D03*
-X014580Y007874D03*
-X004870Y010714D03*
-D101*
-X004865Y010014D03*
-X014585Y007894D03*
-X014585Y007884D03*
-X015525Y008644D03*
-D102*
-X014305Y008644D03*
-X014305Y008634D03*
-X014305Y008624D03*
-X014305Y008614D03*
-X014305Y008604D03*
-X014305Y008594D03*
-X014305Y008584D03*
-X014305Y008574D03*
-X014305Y008564D03*
-X014305Y008554D03*
-X014305Y008544D03*
-X014305Y008534D03*
-X014305Y008524D03*
-X014305Y008514D03*
-X014305Y008504D03*
-X014305Y008494D03*
-X014305Y008484D03*
-X014305Y008474D03*
-X014305Y008464D03*
-X014305Y008454D03*
-X014305Y008444D03*
-X014305Y008434D03*
-X014305Y008424D03*
-X014305Y008414D03*
-X014305Y008404D03*
-X014305Y008394D03*
-X014305Y008384D03*
-X014305Y008374D03*
-X014305Y008364D03*
-X014305Y008354D03*
-X014305Y008344D03*
-X014305Y008334D03*
-X014305Y008324D03*
-X014305Y008314D03*
-X014305Y008304D03*
-X014305Y008294D03*
-X014305Y008284D03*
-X014305Y008274D03*
-X014305Y008264D03*
-X014305Y008254D03*
-X014305Y008244D03*
-X014305Y008234D03*
-X014305Y008224D03*
-X014305Y008214D03*
-X014305Y008204D03*
-X014305Y008194D03*
-X014305Y008184D03*
-X014305Y008174D03*
-X014305Y008164D03*
-X014305Y008154D03*
-X014305Y008144D03*
-X014305Y008134D03*
-X014305Y008124D03*
-X014305Y008114D03*
-X014305Y008104D03*
-X014305Y008094D03*
-X014305Y008084D03*
-X014305Y008074D03*
-X014305Y008064D03*
-X014305Y008054D03*
-X014305Y008044D03*
-X014305Y008034D03*
-X014305Y008024D03*
-X014305Y008014D03*
-X014305Y008004D03*
-X014305Y007994D03*
-X014305Y007984D03*
-X014305Y007974D03*
-X014305Y007964D03*
-X014305Y007954D03*
-X014305Y007944D03*
-X014305Y007934D03*
-X014305Y007924D03*
-X014305Y007914D03*
-X014305Y007904D03*
-X014305Y008654D03*
-X014305Y008664D03*
-X014305Y008674D03*
-X014305Y008684D03*
-X014305Y008694D03*
-X014305Y008704D03*
-X014305Y008714D03*
-X014305Y008724D03*
-X014305Y008734D03*
-X014305Y008744D03*
-X014305Y008754D03*
-X014305Y008764D03*
-X014305Y008774D03*
-X014305Y008784D03*
-X014305Y008794D03*
-X014305Y008804D03*
-X014305Y008814D03*
-X014305Y008824D03*
-X014305Y008834D03*
-X014305Y008844D03*
-X014305Y008854D03*
-X014305Y008864D03*
-X014305Y008874D03*
-X014305Y008884D03*
-X014305Y008894D03*
-X014305Y008904D03*
-X014305Y008914D03*
-X014305Y008924D03*
-X014305Y008934D03*
-X014305Y008944D03*
-X014305Y008954D03*
-X014305Y008964D03*
-X014305Y008974D03*
-X014305Y008984D03*
-X014305Y008994D03*
-X014305Y009004D03*
-X014305Y009014D03*
-X014305Y009024D03*
-X014305Y009034D03*
-X014305Y009044D03*
-X014305Y009054D03*
-X014305Y009064D03*
-X014305Y009074D03*
-X014305Y009084D03*
-X014305Y009094D03*
-X014305Y009104D03*
-X014305Y009114D03*
-X014305Y009124D03*
-X014305Y009134D03*
-X014305Y009144D03*
-X014305Y009154D03*
-X014305Y009164D03*
-X014305Y009174D03*
-X014305Y009184D03*
-X014305Y009194D03*
-X014305Y009204D03*
-X014305Y009214D03*
-X014305Y009224D03*
-X014305Y009234D03*
-X014305Y009244D03*
-X014305Y009254D03*
-X014305Y009264D03*
-X014305Y009274D03*
-X014305Y009284D03*
-X014305Y009294D03*
-X014305Y009304D03*
-X014305Y009314D03*
-X014305Y009324D03*
-X014305Y009334D03*
-X014305Y009344D03*
-X014305Y009354D03*
-X014305Y009364D03*
-X014305Y009374D03*
-X014305Y009384D03*
-X014305Y009394D03*
-X014305Y009404D03*
-X014305Y009414D03*
-X014305Y009424D03*
-X014305Y009434D03*
-X014305Y009444D03*
-X014305Y009454D03*
-X014305Y009464D03*
-X014305Y009474D03*
-X014305Y009484D03*
-X014305Y009494D03*
-X014305Y009504D03*
-X014305Y009514D03*
-X014305Y009524D03*
-D103*
-X015150Y011864D03*
-X019970Y011564D03*
-X020060Y009524D03*
-X020060Y009514D03*
-X020060Y009504D03*
-X020060Y009494D03*
-X020060Y009484D03*
-X020060Y009474D03*
-X020060Y009464D03*
-X020060Y009454D03*
-X020060Y009444D03*
-X020060Y009434D03*
-X020060Y009424D03*
-X020060Y009414D03*
-X020060Y009404D03*
-X020060Y009394D03*
-X020060Y009384D03*
-X020060Y009374D03*
-X020060Y009364D03*
-X020060Y009354D03*
-X020060Y009344D03*
-X020060Y009334D03*
-X020060Y009324D03*
-X020060Y009314D03*
-X020060Y009304D03*
-X020060Y009294D03*
-X020060Y009284D03*
-X020060Y009274D03*
-X020060Y009264D03*
-X020060Y009254D03*
-X020060Y009244D03*
-X020060Y009234D03*
-X020060Y009224D03*
-X020060Y009214D03*
-X020060Y009204D03*
-X020060Y009194D03*
-X020060Y009184D03*
-X020060Y009174D03*
-X020060Y009164D03*
-X020060Y009154D03*
-X020060Y009144D03*
-X020060Y009134D03*
-X020060Y009124D03*
-X020060Y009114D03*
-D104*
-X017650Y009184D03*
-X020040Y011384D03*
-X017540Y013274D03*
-X017540Y013284D03*
-X017540Y013294D03*
-X017540Y013304D03*
-X017540Y013314D03*
-X017540Y013324D03*
-X017540Y013334D03*
-X017540Y013344D03*
-X017540Y013354D03*
-X017540Y013364D03*
-X017540Y013374D03*
-X017540Y013384D03*
-X017540Y013394D03*
-X017540Y013404D03*
-X017540Y013414D03*
-X017540Y013424D03*
-X017540Y013434D03*
-X017540Y013444D03*
-X017540Y013454D03*
-X017540Y013464D03*
-X017540Y013474D03*
-X017540Y013484D03*
-X017540Y013494D03*
-X017540Y013504D03*
-X017540Y013514D03*
-X017540Y013524D03*
-X017540Y013534D03*
-X017540Y013544D03*
-X017540Y013554D03*
-X017540Y013564D03*
-X017540Y013574D03*
-X017540Y013584D03*
-X017540Y013594D03*
-X017540Y013604D03*
-X017540Y013614D03*
-X017540Y013624D03*
-X017540Y013634D03*
-X017540Y013644D03*
-X017540Y013654D03*
-X017540Y013664D03*
-X017540Y013674D03*
-X017540Y013684D03*
-X017540Y013694D03*
-X020040Y013424D03*
-X015150Y012044D03*
-X015150Y012034D03*
-X012620Y009524D03*
-X012620Y009514D03*
-X012620Y009504D03*
-X012620Y009494D03*
-X012620Y009484D03*
-X012620Y009474D03*
-X012620Y009464D03*
-X012620Y009454D03*
-X012620Y009444D03*
-X012620Y009434D03*
-X012620Y009424D03*
-X012620Y009414D03*
-X012620Y009404D03*
-X012620Y009394D03*
-X012620Y009384D03*
-X012620Y009374D03*
-X012620Y009364D03*
-X012620Y009354D03*
-X012620Y009344D03*
-X012620Y009334D03*
-X012620Y009324D03*
-X012620Y009314D03*
-X012620Y009304D03*
-X012620Y009294D03*
-X012620Y009284D03*
-X012620Y009274D03*
-X012620Y009264D03*
-X012620Y009254D03*
-X012620Y009244D03*
-X012620Y009234D03*
-X012620Y009224D03*
-X012620Y009214D03*
-X012620Y009204D03*
-X012620Y009194D03*
-X012620Y009184D03*
-X012620Y009174D03*
-X012620Y009164D03*
-X012620Y009154D03*
-X012620Y009144D03*
-X012620Y009134D03*
-X012620Y009124D03*
-X012620Y009114D03*
-D105*
-X017645Y009304D03*
-D106*
-X017650Y009324D03*
-X020180Y011244D03*
-D107*
-X017650Y009334D03*
-X020190Y013644D03*
-D108*
-X020205Y013654D03*
-X020205Y011234D03*
-X017645Y009344D03*
-D109*
-X017650Y009354D03*
-X012890Y012384D03*
-X012890Y012394D03*
-X020220Y013664D03*
-D110*
-X017650Y009364D03*
-X012900Y012414D03*
-X012900Y012424D03*
-D111*
-X012920Y012474D03*
-X012920Y012484D03*
-X017650Y009374D03*
-D112*
-X017650Y009384D03*
-X012930Y012504D03*
-X012930Y012514D03*
-D113*
-X012945Y012554D03*
-X012945Y012564D03*
-X017655Y009394D03*
-D114*
-X017650Y009404D03*
-X012960Y012604D03*
-D115*
-X017650Y009414D03*
-D116*
-X017655Y009424D03*
-D117*
-X017650Y009434D03*
-D118*
-X017650Y009444D03*
-D119*
-X017650Y009454D03*
-D120*
-X017655Y009464D03*
-D121*
-X017655Y009474D03*
-D122*
-X015795Y009524D03*
-X015565Y012704D03*
-X015555Y012734D03*
-X015545Y012754D03*
-X015535Y012784D03*
-X015525Y012814D03*
-X015515Y012834D03*
-X015515Y012844D03*
-X015505Y012864D03*
-X015495Y012884D03*
-X015495Y012894D03*
-X015485Y012914D03*
-X015485Y012924D03*
-X015475Y012934D03*
-X015475Y012944D03*
-X015475Y012954D03*
-X015465Y012964D03*
-X015465Y012974D03*
-X015455Y012984D03*
-X015455Y012994D03*
-X015455Y013004D03*
-X015445Y013014D03*
-X015445Y013024D03*
-X015445Y013034D03*
-X015435Y013044D03*
-X015435Y013054D03*
-X015425Y013074D03*
-X015425Y013084D03*
-X019255Y013084D03*
-X019255Y013074D03*
-X019255Y013064D03*
-X019255Y013054D03*
-X019255Y013044D03*
-X019275Y012884D03*
-X019285Y012864D03*
-X019295Y012844D03*
-X019295Y012834D03*
-X019305Y012824D03*
-X004575Y010754D03*
-X004545Y010804D03*
-X004535Y010824D03*
-X004525Y010834D03*
-X004525Y010844D03*
-X004515Y010854D03*
-X004505Y010874D03*
-X004495Y010884D03*
-X004495Y010894D03*
-X004485Y010904D03*
-X004485Y010914D03*
-X004475Y010924D03*
-X004465Y010944D03*
-X004455Y010954D03*
-X004455Y010964D03*
-X004445Y010974D03*
-X004435Y010994D03*
-X004425Y011014D03*
-X004395Y011064D03*
-D123*
-X017650Y009564D03*
-D124*
-X017650Y009574D03*
-D125*
-X009690Y010104D03*
-X009690Y010114D03*
-X009690Y010124D03*
-X009690Y010134D03*
-X009690Y010144D03*
-X009690Y010154D03*
-X009690Y010164D03*
-X009690Y010174D03*
-X009690Y010184D03*
-X009690Y010194D03*
-X009690Y010204D03*
-X009690Y010214D03*
-X009690Y010224D03*
-X009690Y010234D03*
-X009690Y010244D03*
-X009690Y010254D03*
-X009690Y010264D03*
-X009690Y010274D03*
-X009690Y010284D03*
-X009690Y010294D03*
-X009690Y010304D03*
-X009690Y010314D03*
-X009690Y010324D03*
-X009690Y010334D03*
-X009690Y010344D03*
-X009690Y010354D03*
-X009690Y010364D03*
-X009690Y010374D03*
-X009690Y010384D03*
-X009690Y010394D03*
-X009690Y010404D03*
-X009690Y010414D03*
-X009690Y010424D03*
-X009690Y010434D03*
-X009690Y010444D03*
-X009690Y010454D03*
-X009690Y010464D03*
-X009690Y010474D03*
-X009690Y010484D03*
-X009690Y010494D03*
-X009690Y010504D03*
-X009690Y010514D03*
-X009690Y010524D03*
-D126*
-X004590Y010724D03*
-X004590Y010734D03*
-X004580Y010744D03*
-X004570Y010764D03*
-X004560Y010774D03*
-X004560Y010784D03*
-X004550Y010794D03*
-X004540Y010814D03*
-X004510Y010864D03*
-X015430Y013064D03*
-X019250Y013034D03*
-X019250Y013024D03*
-X019250Y013014D03*
-X019250Y013004D03*
-X019250Y012994D03*
-X019250Y012984D03*
-X019250Y012974D03*
-X019250Y012964D03*
-X019260Y012954D03*
-X019260Y012944D03*
-X019260Y012934D03*
-X019260Y012924D03*
-X019260Y012914D03*
-X019270Y012904D03*
-X019270Y012894D03*
-X019280Y012874D03*
-X019290Y012854D03*
-D127*
-X020320Y011214D03*
-D128*
-X020160Y011254D03*
-D129*
-X020120Y011284D03*
-X020120Y013574D03*
-D130*
-X020110Y013564D03*
-X020110Y012584D03*
-X020110Y011294D03*
-D131*
-X020100Y011304D03*
-X020100Y012494D03*
-X020100Y012594D03*
-X015150Y012194D03*
-D132*
-X015150Y012164D03*
-X020090Y012484D03*
-X020090Y012614D03*
-X020090Y013534D03*
-X020090Y011314D03*
-D133*
-X020060Y011354D03*
-X020060Y012664D03*
-X020060Y013474D03*
-X015150Y012094D03*
-D134*
-X015150Y012064D03*
-X020050Y012434D03*
-X020050Y012684D03*
-X020050Y012694D03*
-X020050Y013444D03*
-X020050Y013454D03*
-X020050Y011374D03*
-D135*
-X020030Y011404D03*
-X020030Y012404D03*
-X020030Y013394D03*
-X015150Y012014D03*
-D136*
-X015150Y011994D03*
-X015150Y011984D03*
-X020020Y011424D03*
-X020020Y013354D03*
-X020020Y013364D03*
-D137*
-X020010Y013324D03*
-X020010Y013314D03*
-X020010Y012374D03*
-X020010Y012364D03*
-X020010Y011444D03*
-X015150Y011964D03*
-D138*
-X015150Y011944D03*
-X015150Y011934D03*
-X020000Y012344D03*
-X020000Y011474D03*
-X020000Y013284D03*
-D139*
-X019990Y012324D03*
-X019990Y011504D03*
-X019990Y011494D03*
-X015150Y011914D03*
-D140*
-X015155Y011904D03*
-X019985Y011514D03*
-X019985Y012314D03*
-D141*
-X019980Y012304D03*
-X019980Y011534D03*
-X019980Y011524D03*
-X015150Y011884D03*
-X015150Y011894D03*
-D142*
-X015155Y011874D03*
-X019975Y011554D03*
-X019975Y011544D03*
-X019975Y012294D03*
-D143*
-X019965Y011584D03*
-X019965Y011574D03*
-X015155Y011854D03*
-D144*
-X015150Y011844D03*
-X015150Y011834D03*
-X019960Y011604D03*
-X019960Y011594D03*
-D145*
-X019955Y011614D03*
-X019955Y011624D03*
-X015155Y011824D03*
-D146*
-X015155Y011774D03*
-D147*
-X015150Y011784D03*
-X015150Y011794D03*
-D148*
-X015150Y011804D03*
-X015150Y011814D03*
-D149*
-X015150Y012114D03*
-X020070Y012644D03*
-X020070Y013494D03*
-D150*
-X020080Y013514D03*
-X020080Y012624D03*
-X015150Y012144D03*
-D151*
-X012910Y012444D03*
-X012910Y012454D03*
-D152*
-X012915Y012464D03*
-X020245Y013674D03*
-D153*
-X020130Y012554D03*
-X020130Y012514D03*
-D154*
-X020140Y012524D03*
-X020140Y012544D03*
-D155*
-X020150Y012534D03*
-X020150Y013614D03*
-D156*
-X012940Y012544D03*
-X012940Y012534D03*
-D157*
-X012950Y012574D03*
-X020280Y013684D03*
-D158*
-X009595Y013684D03*
-X009595Y013674D03*
-X009595Y013664D03*
-X009595Y013654D03*
-X009595Y013644D03*
-X009595Y013634D03*
-X009595Y013624D03*
-X009595Y013614D03*
-X009595Y013604D03*
-X009595Y013594D03*
-X009595Y013584D03*
-X009595Y013574D03*
-X009595Y013564D03*
-X009595Y013554D03*
-X009595Y013544D03*
-X009595Y013534D03*
-X009595Y013524D03*
-X009595Y013514D03*
-X009595Y013504D03*
-X009595Y013494D03*
-X009595Y013484D03*
-X009595Y013474D03*
-X009595Y013464D03*
-X009595Y013454D03*
-X009595Y013444D03*
-X009595Y013434D03*
-X009595Y013424D03*
-X009595Y013414D03*
-X009595Y013404D03*
-X009595Y013394D03*
-X009595Y013384D03*
-X009595Y013374D03*
-X009595Y013364D03*
-X009595Y013354D03*
-X009595Y013344D03*
-X009595Y013334D03*
-X009595Y013324D03*
-X009595Y013314D03*
-X009595Y013304D03*
-X009595Y013294D03*
-X009595Y013284D03*
-X009595Y013274D03*
-X009595Y013264D03*
-D159*
-X020345Y013694D03*
-D160*
-X022869Y013789D02*
-X022869Y007639D01*
-M02*
diff --git a/gerber/tests/resources/example_am_exposure_modifier.gbr b/gerber/tests/resources/example_am_exposure_modifier.gbr
deleted file mode 100644
index 5f3f3dd..0000000
--- a/gerber/tests/resources/example_am_exposure_modifier.gbr
+++ /dev/null
@@ -1,16 +0,0 @@
-G04 Umaco example for exposure modifier and clearing area*
-%FSLAX26Y26*%
-%MOIN*%
-%AMSQUAREWITHHOLE*
-21,0.1,1,1,0,0,0*
-1,0,0.5,0,0*%
-%ADD10SQUAREWITHHOLE*%
-%ADD11C,1*%
-G01*
-%LPD*%
-D11*
-X-1000000Y-250000D02*
-X1000000Y250000D01*
-D10*
-X0Y0D03*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_coincident_hole.gbr b/gerber/tests/resources/example_coincident_hole.gbr
deleted file mode 100644
index 4f896ea..0000000
--- a/gerber/tests/resources/example_coincident_hole.gbr
+++ /dev/null
@@ -1,24 +0,0 @@
-G04 ex2: overlapping*
-%FSLAX24Y24*%
-%MOMM*%
-%SRX1Y1I0.000J0.000*%
-%ADD10C,1.00000*%
-G01*
-%LPD*%
-G36*
-X0Y50000D02*
-Y100000D01*
-X100000D01*
-Y0D01*
-X0D01*
-Y50000D01*
-G04 first fully coincident linear segment*
-X10000D01*
-X50000Y10000D01*
-X90000Y50000D01*
-X50000Y90000D01*
-X10000Y50000D01*
-G04 second fully coincident linear segment*
-X0D01*
-G37*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_cutin.gbr b/gerber/tests/resources/example_cutin.gbr
deleted file mode 100644
index 365e5e1..0000000
--- a/gerber/tests/resources/example_cutin.gbr
+++ /dev/null
@@ -1,18 +0,0 @@
-G04 Umaco uut-in example*
-%FSLAX24Y24*%
-G75*
-G36*
-X20000Y100000D02*
-G01*
-X120000D01*
-Y20000D01*
-X20000D01*
-Y60000D01*
-X50000D01*
-G03*
-X50000Y60000I30000J0D01*
-G01*
-X20000D01*
-Y100000D01*
-G37*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_cutin_multiple.gbr b/gerber/tests/resources/example_cutin_multiple.gbr
deleted file mode 100644
index 8e19429..0000000
--- a/gerber/tests/resources/example_cutin_multiple.gbr
+++ /dev/null
@@ -1,28 +0,0 @@
-G04 multiple cutins*
-%FSLAX24Y24*%
-%MOMM*%
-%SRX1Y1I0.000J0.000*%
-%ADD10C,1.00000*%
-%LPD*%
-G36*
-X1220000Y2570000D02*
-G01*
-Y2720000D01*
-X1310000D01*
-Y2570000D01*
-X1250000D01*
-Y2600000D01*
-X1290000D01*
-Y2640000D01*
-X1250000D01*
-Y2670000D01*
-X1290000D01*
-Y2700000D01*
-X1250000D01*
-Y2670000D01*
-Y2640000D01*
-Y2600000D01*
-Y2570000D01*
-X1220000D01*
-G37*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_flash_circle.gbr b/gerber/tests/resources/example_flash_circle.gbr
deleted file mode 100644
index 20b2566..0000000
--- a/gerber/tests/resources/example_flash_circle.gbr
+++ /dev/null
@@ -1,10 +0,0 @@
-G04 Flashes of circular apertures*
-%FSLAX24Y24*%
-%MOMM*%
-%ADD10C,0.5*%
-%ADD11C,0.5X0.25*%
-D10*
-X000000Y000000D03*
-D11*
-X010000D03*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_flash_obround.gbr b/gerber/tests/resources/example_flash_obround.gbr
deleted file mode 100644
index 5313f82..0000000
--- a/gerber/tests/resources/example_flash_obround.gbr
+++ /dev/null
@@ -1,10 +0,0 @@
-G04 Flashes of rectangular apertures*
-%FSLAX24Y24*%
-%MOMM*%
-%ADD10O,0.46X0.26*%
-%ADD11O,0.46X0.26X0.19*%
-D10*
-X000000Y000000D03*
-D11*
-X010000D03*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_flash_polygon.gbr b/gerber/tests/resources/example_flash_polygon.gbr
deleted file mode 100644
index 177cf9b..0000000
--- a/gerber/tests/resources/example_flash_polygon.gbr
+++ /dev/null
@@ -1,10 +0,0 @@
-G04 Flashes of rectangular apertures*
-%FSLAX24Y24*%
-%MOMM*%
-%ADD10P,.40X6*%
-%ADD11P,.40X6X0.0X0.19*%
-D10*
-X000000Y000000D03*
-D11*
-X010000D03*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_flash_rectangle.gbr b/gerber/tests/resources/example_flash_rectangle.gbr
deleted file mode 100644
index 8fde812..0000000
--- a/gerber/tests/resources/example_flash_rectangle.gbr
+++ /dev/null
@@ -1,10 +0,0 @@
-G04 Flashes of rectangular apertures*
-%FSLAX24Y24*%
-%MOMM*%
-%ADD10R,0.44X0.25*%
-%ADD11R,0.44X0.25X0.19*%
-D10*
-X000000Y000000D03*
-D11*
-X010000D03*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_fully_coincident.gbr b/gerber/tests/resources/example_fully_coincident.gbr
deleted file mode 100644
index 3764128..0000000
--- a/gerber/tests/resources/example_fully_coincident.gbr
+++ /dev/null
@@ -1,23 +0,0 @@
-G04 ex1: non overlapping*
-%FSLAX24Y24*%
-%MOMM*%
-%ADD10C,1.00000*%
-G01*
-%LPD*%
-G36*
-X0Y50000D02*
-Y100000D01*
-X100000D01*
-Y0D01*
-X0D01*
-Y50000D01*
-G04 first fully coincident linear segment*
-X-10000D01*
-X-50000Y10000D01*
-X-90000Y50000D01*
-X-50000Y90000D01*
-X-10000Y50000D01*
-G04 second fully coincident linear segment*
-X0D01*
-G37*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_guess_by_content.g0 b/gerber/tests/resources/example_guess_by_content.g0
deleted file mode 100644
index 5b26afe..0000000
--- a/gerber/tests/resources/example_guess_by_content.g0
+++ /dev/null
@@ -1,166 +0,0 @@
-G04 ULTIpost, Date: Nov. 01, 2017 09:40 *
-G04 Design file: C:\example_guess_by_content.g0 *
-G04 Layer name: Bottom *
-G04 Scale: 100 percent, Rotated: Yes, Reflected: No *
-G75*
-%MOIN*%
-%OFA0B0*%
-%FSLAX24Y24*%
-%IPPOS*%
-%LPD*%
-%AMOC8*
-5,1,8,0,0,1.08239X$1,22.5*
-%
-%ADD10R,0.0340X0.0880*%
-%ADD11R,0.0671X0.0237*%
-%ADD12R,0.4178X0.4332*%
-%ADD13R,0.0930X0.0500*%
-%ADD14R,0.0710X0.1655*%
-%ADD15R,0.0671X0.0592*%
-%ADD16R,0.0592X0.0671*%
-%ADD17R,0.0710X0.1615*%
-%ADD18R,0.1419X0.0828*%
-%ADD19C,0.0634*%
-%ADD20C,0.1360*%
-%ADD21R,0.0474X0.0580*%
-%ADD22C,0.0680*%
-%ADD23R,0.0552X0.0552*%
-%ADD24C,0.1340*%
-%ADD25C,0.0476*%
-D10*
-X005000Y010604D03*
-X005500Y010604D03*
-X006000Y010604D03*
-X006500Y010604D03*
-X006500Y013024D03*
-X006000Y013024D03*
-X005500Y013024D03*
-X005000Y013024D03*
-D11*
-X011423Y007128D03*
-X011423Y006872D03*
-X011423Y006616D03*
-X011423Y006360D03*
-X011423Y006104D03*
-X011423Y005848D03*
-X011423Y005592D03*
-X011423Y005336D03*
-X011423Y005080D03*
-X011423Y004825D03*
-X011423Y004569D03*
-X011423Y004313D03*
-X011423Y004057D03*
-X011423Y003801D03*
-X014277Y003801D03*
-X014277Y004057D03*
-X014277Y004313D03*
-X014277Y004569D03*
-X014277Y004825D03*
-X014277Y005080D03*
-X014277Y005336D03*
-X014277Y005592D03*
-X014277Y005848D03*
-X014277Y006104D03*
-X014277Y006360D03*
-X014277Y006616D03*
-X014277Y006872D03*
-X014277Y007128D03*
-D12*
-X009350Y010114D03*
-D13*
-X012630Y010114D03*
-X012630Y010784D03*
-X012630Y011454D03*
-X012630Y009444D03*
-X012630Y008774D03*
-D14*
-X010000Y013467D03*
-X010000Y016262D03*
-D15*
-X004150Y012988D03*
-X004150Y012240D03*
-X009900Y005688D03*
-X009900Y004940D03*
-X015000Y006240D03*
-X015000Y006988D03*
-D16*
-X014676Y008364D03*
-X015424Y008364D03*
-X017526Y004514D03*
-X018274Y004514D03*
-X010674Y004064D03*
-X009926Y004064D03*
-X004174Y009564D03*
-X003426Y009564D03*
-X005376Y014564D03*
-X006124Y014564D03*
-D17*
-X014250Y016088D03*
-X014250Y012741D03*
-D18*
-X014250Y010982D03*
-X014250Y009447D03*
-D19*
-X017200Y009464D03*
-X018200Y009964D03*
-X018200Y010964D03*
-X017200Y010464D03*
-X017200Y011464D03*
-X018200Y011964D03*
-D20*
-X020700Y012714D03*
-X020700Y008714D03*
-D21*
-X005004Y003814D03*
-X005004Y004864D03*
-X005004Y005864D03*
-X005004Y006914D03*
-X008696Y006914D03*
-X008696Y005864D03*
-X008696Y004864D03*
-X008696Y003814D03*
-D22*
-X001800Y008564D02*
-X001200Y008564D01*
-X001200Y009564D02*
-X001800Y009564D01*
-X001800Y010564D02*
-X001200Y010564D01*
-X001200Y011564D02*
-X001800Y011564D01*
-X001800Y012564D02*
-X001200Y012564D01*
-X005350Y016664D02*
-X005350Y017264D01*
-X006350Y017264D02*
-X006350Y016664D01*
-X007350Y016664D02*
-X007350Y017264D01*
-X017350Y017114D02*
-X017350Y016514D01*
-X018350Y016514D02*
-X018350Y017114D01*
-D23*
-X016613Y004514D03*
-X015787Y004514D03*
-D24*
-X020800Y005064D03*
-X020800Y016064D03*
-X002300Y016064D03*
-X002350Y005114D03*
-D25*
-X009250Y004064D03*
-X012100Y005314D03*
-X013500Y006864D03*
-X015650Y006264D03*
-X015200Y004514D03*
-X013550Y008764D03*
-X013350Y010114D03*
-X013300Y011464D03*
-X011650Y013164D03*
-X010000Y015114D03*
-X006500Y013714D03*
-X004150Y011564D03*
-X014250Y014964D03*
-X015850Y009914D03*
-M02*
diff --git a/gerber/tests/resources/example_holes_dont_clear.gbr b/gerber/tests/resources/example_holes_dont_clear.gbr
deleted file mode 100644
index deeebd0..0000000
--- a/gerber/tests/resources/example_holes_dont_clear.gbr
+++ /dev/null
@@ -1,13 +0,0 @@
-G04 Demonstrates that apertures with holes do not clear the area - only the aperture hole*
-%FSLAX26Y26*%
-%MOIN*%
-%ADD10C,1X0.5*%
-%ADD11C,0.1*%
-G01*
-%LPD*%
-D11*
-X-1000000Y-250000D02*
-X1000000Y250000D01*
-D10*
-X0Y0D03*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_level_holes.gbr b/gerber/tests/resources/example_level_holes.gbr
deleted file mode 100644
index 1b4e189..0000000
--- a/gerber/tests/resources/example_level_holes.gbr
+++ /dev/null
@@ -1,39 +0,0 @@
-G04 This file illustrates how to use levels to create holes*
-%FSLAX25Y25*%
-%MOMM*%
-G01*
-G04 First level: big square - dark polarity*
-%LPD*%
-G36*
-X250000Y250000D02*
-X1750000D01*
-Y1750000D01*
-X250000D01*
-Y250000D01*
-G37*
-G04 Second level: big circle - clear polarity*
-%LPC*%
-G36*
-G75*
-X500000Y1000000D02*
-G03*
-X500000Y1000000I500000J0D01*
-G37*
-G04 Third level: small square - dark polarity*
-%LPD*%
-G36*
-X750000Y750000D02*
-X1250000D01*
-Y1250000D01*
-X750000D01*
-Y750000D01*
-G37*
-G04 Fourth level: small circle - clear polarity*
-%LPC*%
-G36*
-G75*
-X1150000Y1000000D02*
-G03*
-X1150000Y1000000I250000J0D01*
-G37*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_not_overlapping_contour.gbr b/gerber/tests/resources/example_not_overlapping_contour.gbr
deleted file mode 100644
index e3ea631..0000000
--- a/gerber/tests/resources/example_not_overlapping_contour.gbr
+++ /dev/null
@@ -1,20 +0,0 @@
-G04 Non-overlapping contours*
-%FSLAX24Y24*%
-%MOMM*%
-%ADD10C,1.00000*%
-G01*
-%LPD*%
-G36*
-X0Y50000D02*
-Y100000D01*
-X100000D01*
-Y0D01*
-X0D01*
-Y50000D01*
-X-10000D02*
-X-50000Y10000D01*
-X-90000Y50000D01*
-X-50000Y90000D01*
-X-10000Y50000D01*
-G37*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_not_overlapping_touching.gbr b/gerber/tests/resources/example_not_overlapping_touching.gbr
deleted file mode 100644
index 3b9b955..0000000
--- a/gerber/tests/resources/example_not_overlapping_touching.gbr
+++ /dev/null
@@ -1,20 +0,0 @@
-G04 Non-overlapping and touching*
-%FSLAX24Y24*%
-%MOMM*%
-%ADD10C,1.00000*%
-G01*
-%LPD*%
-G36*
-X0Y50000D02*
-Y100000D01*
-X100000D01*
-Y0D01*
-X0D01*
-Y50000D01*
-D02*
-X-50000Y10000D01*
-X-90000Y50000D01*
-X-50000Y90000D01*
-X0Y50000D01*
-G37*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_overlapping_contour.gbr b/gerber/tests/resources/example_overlapping_contour.gbr
deleted file mode 100644
index 74886a2..0000000
--- a/gerber/tests/resources/example_overlapping_contour.gbr
+++ /dev/null
@@ -1,20 +0,0 @@
-G04 Overlapping contours*
-%FSLAX24Y24*%
-%MOMM*%
-%ADD10C,1.00000*%
-G01*
-%LPD*%
-G36*
-X0Y50000D02*
-Y100000D01*
-X100000D01*
-Y0D01*
-X0D01*
-Y50000D01*
-X10000D02*
-X50000Y10000D01*
-X90000Y50000D01*
-X50000Y90000D01*
-X10000Y50000D01*
-G37*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_overlapping_touching.gbr b/gerber/tests/resources/example_overlapping_touching.gbr
deleted file mode 100644
index 27fce15..0000000
--- a/gerber/tests/resources/example_overlapping_touching.gbr
+++ /dev/null
@@ -1,20 +0,0 @@
-G04 Overlapping and touching*
-%FSLAX24Y24*%
-%MOMM*%
-%ADD10C,1.00000*%
-G01*
-%LPD*%
-G36*
-X0Y50000D02*
-Y100000D01*
-X100000D01*
-Y0D01*
-X0D01*
-Y50000D01*
-D02*
-X50000Y10000D01*
-X90000Y50000D01*
-X50000Y90000D01*
-X0Y50000D01*
-G37*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_simple_contour.gbr b/gerber/tests/resources/example_simple_contour.gbr
deleted file mode 100644
index d851760..0000000
--- a/gerber/tests/resources/example_simple_contour.gbr
+++ /dev/null
@@ -1,16 +0,0 @@
-G04 Ucamco ex. 4.6.4: Simple contour*
-%FSLAX25Y25*%
-%MOIN*%
-%ADD10C,0.010*%
-G36*
-X200000Y300000D02*
-G01*
-X700000D01*
-Y100000D01*
-X1100000Y500000D01*
-X700000Y900000D01*
-Y700000D01*
-X200000D01*
-Y300000D01*
-G37*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_single_contour_1.gbr b/gerber/tests/resources/example_single_contour_1.gbr
deleted file mode 100644
index e9f9a75..0000000
--- a/gerber/tests/resources/example_single_contour_1.gbr
+++ /dev/null
@@ -1,15 +0,0 @@
-G04 Ucamco ex. 4.6.5: Single contour #1*
-%FSLAX25Y25*%
-%MOMM*%
-%ADD11C,0.01*%
-G01*
-D11*
-X3000Y5000D01*
-G36*
-X50000Y50000D02*
-X60000D01*
-Y60000D01*
-X50000D01*
-Y50000Y50000D01*
-G37*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_single_contour_2.gbr b/gerber/tests/resources/example_single_contour_2.gbr
deleted file mode 100644
index 085c72c..0000000
--- a/gerber/tests/resources/example_single_contour_2.gbr
+++ /dev/null
@@ -1,15 +0,0 @@
-G04 Ucamco ex. 4.6.5: Single contour #2*
-%FSLAX25Y25*%
-%MOMM*%
-%ADD11C,0.01*%
-G01*
-D11*
-X3000Y5000D01*
-X50000Y50000D02*
-G36*
-X60000D01*
-Y60000D01*
-X50000D01*
-Y50000Y50000D01*
-G37*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_single_contour_3.gbr b/gerber/tests/resources/example_single_contour_3.gbr
deleted file mode 100644
index 40de149..0000000
--- a/gerber/tests/resources/example_single_contour_3.gbr
+++ /dev/null
@@ -1,15 +0,0 @@
-G04 Ucamco ex. 4.6.5: Single contour #2*
-%FSLAX25Y25*%
-%MOMM*%
-%ADD11C,0.01*%
-G01*
-D11*
-X3000Y5000D01*
-X50000Y50000D01*
-G36*
-X60000D01*
-Y60000D01*
-X50000D01*
-Y50000Y50000D01*
-G37*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_single_quadrant.gbr b/gerber/tests/resources/example_single_quadrant.gbr
deleted file mode 100644
index c398601..0000000
--- a/gerber/tests/resources/example_single_quadrant.gbr
+++ /dev/null
@@ -1,18 +0,0 @@
-G04 Ucamco ex. 4.5.8: Single quadrant*
-%FSLAX23Y23*%
-%MOIN*%
-%ADD10C,0.010*%
-G74*
-D10*
-X1100Y600D02*
-G03*
-X700Y1000I400J0D01*
-X300Y600I0J400D01*
-X700Y200I400J0D01*
-X1100Y600I0J400D01*
-X300D02*
-G01*
-X1100D01*
-X700Y200D02*
-Y1000D01*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/example_two_square_boxes.gbr b/gerber/tests/resources/example_two_square_boxes.gbr
deleted file mode 100644
index 54a8ac1..0000000
--- a/gerber/tests/resources/example_two_square_boxes.gbr
+++ /dev/null
@@ -1,19 +0,0 @@
-G04 Ucamco ex. 1: Two square boxes*
-%FSLAX25Y25*%
-%MOMM*%
-%TF.Part,Other*%
-%LPD*%
-%ADD10C,0.010*%
-D10*
-X0Y0D02*
-G01*
-X500000Y0D01*
-Y500000D01*
-X0D01*
-Y0D01*
-X600000D02*
-X1100000D01*
-Y500000D01*
-X600000D01*
-Y0D01*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/ipc-d-356.ipc b/gerber/tests/resources/ipc-d-356.ipc
deleted file mode 100644
index 2ed3f49..0000000
--- a/gerber/tests/resources/ipc-d-356.ipc
+++ /dev/null
@@ -1,115 +0,0 @@
-C IPC-D-356 generated by EAGLE Version 7.1.0 Copyright (c) 1988-2014 CadSoft
-C Database /Some/Path/To/File
-C
-P JOB EAGLE 7.1 NETLIST, DATE: 2/20/15 12:00 AM
-P UNITS CUST 0
-P DIM N
-P NNAME1 A_REALLY_LONG_NET_NAME
-317GND VIA D 24PA00X 14900Y 1450X 396Y 396
-317GND VIA D 24PA00X 3850Y 8500X 396Y 396
-317GND VIA D 24PA00X 6200Y 10650X 396Y 396
-317GND VIA D 24PA00X 8950Y 1000X 396Y 396
-317GND VIA D 24PA00X 11800Y 2250X 396Y 396
-317GND VIA D 24PA00X 15350Y 3200X 396Y 396
-317GND VIA D 24PA00X 13200Y 3800X 396Y 396
-317GND VIA D 24PA00X 9700Y 12050X 396Y 396
-317GND VIA D 24PA00X 13950Y 11900X 396Y 396
-317GND VIA D 24PA00X 13050Y 7050X 396Y 396
-317GND VIA D 24PA00X 13000Y 8400X 396Y 396
-317N$3 VIA D 24PA00X 11350Y 10100X 396Y 396
-317N$3 VIA D 24PA00X 13250Y 5700X 396Y 396
-317VCC VIA D 24PA00X 15550Y 6850X 396Y 396
-327N$3 C1 -+ A01X 9700Y 10402X1575Y 630R270
-327GND C1 -- A01X 9700Y 13198X1575Y 630R270
-327VCC C2 -+ A01X 13950Y 9677X1535Y 630R270
-327GND C2 -- A01X 13950Y 13023X1535Y 630R270
-327VCC C3 -1 A01X 3850Y 9924X 512Y 591R270
-327GND C3 -2 A01X 3850Y 9176X 512Y 591R270
-327VCC C4 -1 A01X 10374Y 1000X 512Y 591R180
-327GND C4 -2 A01X 9626Y 1000X 512Y 591R180
-327VCC C5 -1 A01X 14700Y 3924X 512Y 591R270
-327GND C5 -2 A01X 14700Y 3176X 512Y 591R270
-317DMX+ DMX -1 D 40PA00X 5050Y 13900X 600Y1200R 90
-317DMX- DMX -2 D 40PA00X 6050Y 13900X 600Y1200R 90
-317GND DMX -3 D 40PA00X 7050Y 13900X 600Y1200R 90
-317PIC_MCLR J1 -1 D 35PA00X 16900Y 6400X 554Y 554R 90
-317VCC J1 -2 D 35PA00X 17900Y 6900X 554Y 554R 90
-317GND J1 -3 D 35PA00X 16900Y 7400X 554Y 554R 90
-317PIC_PGD J1 -4 D 35PA00X 17900Y 7900X 554Y 554R 90
-317PIC_PGC J1 -5 D 35PA00X 16900Y 8400X 554Y 554R 90
-317 J1 -6 D 35PA00X 17900Y 8900X 554Y 554R 90
-327N$4 L1 -1 A01X 13950Y 6382X 748Y1339R 90
-327VCC L1 -2 A01X 13950Y 7918X 748Y1339R 90
-327N$5 LED1 -A A01X 16313Y 1450X 472Y 472R 0
-327GND LED1 -C A01X 15487Y 1450X 472Y 472R 0
-317 MIDI -1 D 40PA00X 1200Y 9500X 600Y1200R 0
-317 MIDI -2 D 40PA00X 1200Y 8500X 600Y1200R 0
-317 MIDI -3 D 40PA00X 1200Y 7500X 600Y1200R 0
-317N$9 MIDI -4 D 40PA00X 1200Y 6500X 600Y1200R 0
-317N$10 MIDI -5 D 40PA00X 1200Y 5500X 600Y1200R 0
-317N$3 PWR -1 D 40PA00X 17050Y 13750X 600Y1200R 90
-317GND PWR -2 D 40PA00X 18050Y 13750X 600Y1200R 90
-327DMX+ R1 -1 A01X 5076Y 11500X 512Y 591R 0
-327DMX- R1 -2 A01X 5824Y 11500X 512Y 591R 0
-327VCC R2 -1 A01X 14376Y 5300X 512Y 591R 0
-327PIC_MCLR R2 -2 A01X 15124Y 5300X 512Y 591R 0
-327N$9 R3 -1 A01X 3126Y 6500X 512Y 591R 0
-327N$6 R3 -2 A01X 3874Y 6500X 512Y 591R 0
-327PIC_RX R4 -1 A01X 9600Y 2624X 512Y 591R270
-327VCC R4 -2 A01X 9600Y 1876X 512Y 591R270
-327VCC R5 -1 A01X 17974Y 1450X 512Y 591R180
-327N$5 R5 -2 A01X 17226Y 1450X 512Y 591R180
-327N$3 U1 -1 A01X 12330Y 5710X 420Y 850R 90
-327N$4 U1 -2 A01X 12330Y 6380X 420Y 850R 90
-327GND U1 -3 A01X 12330Y 7050X 420Y 850R 90
-327VCC U1 -4 A01X 12330Y 7720X 420Y 850R 90
-327GND U1 -5 A01X 12330Y 8390X 420Y 850R 90
-327 U1 -6 A01X 9050Y 7050X4252Y4098R 90
-327PIC_MCLR U2 -1 A01X 11123Y 4063X 157Y 591R270
-327 U2 -2 A01X 11123Y 3807X 157Y 591R270
-327 U2 -3 A01X 11123Y 3552X 157Y 591R270
-327N$1 U2 -4 A01X 11123Y 3296X 157Y 591R270
-327N$2 U2 -5 A01X 11123Y 3040X 157Y 591R270
-327PIC_RX U2 -6 A01X 11123Y 2784X 157Y 591R270
-327 U2 -7 A01X 11123Y 2528X 157Y 591R270
-327GND U2 -8 A01X 11123Y 2272X 157Y 591R270
-327 U2 -9 A01X 11123Y 2016X 157Y 591R270
-327 U2 -10 A01X 11123Y 1760X 157Y 591R270
-327 U2 -11 A01X 11123Y 1504X 157Y 591R270
-327 U2 -12 A01X 11123Y 1248X 157Y 591R270
-327VCC U2 -13 A01X 11123Y 993X 157Y 591R270
-327 U2 -14 A01X 11123Y 737X 157Y 591R270
-327 U2 -15 A01X 13977Y 737X 157Y 591R270
-327 U2 -16 A01X 13977Y 993X 157Y 591R270
-327 U2 -17 A01X 13977Y 1248X 157Y 591R270
-327 U2 -18 A01X 13977Y 1504X 157Y 591R270
-327 U2 -19 A01X 13977Y 1760X 157Y 591R270
-327 U2 -20 A01X 13977Y 2016X 157Y 591R270
-327PIC_PGD U2 -21 A01X 13977Y 2272X 157Y 591R270
-327PIC_PGC U2 -22 A01X 13977Y 2528X 157Y 591R270
-327 U2 -23 A01X 13977Y 2784X 157Y 591R270
-327 U2 -24 A01X 13977Y 3040X 157Y 591R270
-327 U2 -25 A01X 13977Y 3296X 157Y 591R270
-327 U2 -26 A01X 13977Y 3552X 157Y 591R270
-327GND U2 -27 A01X 13977Y 3807X 157Y 591R270
-327VCC U2 -28 A01X 13977Y 4063X 157Y 591R270
-327N$2 U3 -1 A01X 4700Y 7540X 260Y 800R 0
-327VCC U3 -2 A01X 5200Y 7540X 260Y 800R 0
-327VCC U3 -3 A01X 5700Y 7540X 260Y 800R 0
-327N$1 U3 -4 A01X 6200Y 7540X 260Y 800R 0
-327GND U3 -5 A01X 6200Y 9960X 260Y 800R 0
-327DMX- U3 -6 A01X 5700Y 9960X 260Y 800R 0
-327DMX+ U3 -7 A01X 5200Y 9960X 260Y 800R 0
-327VCC U3 -8 A01X 4700Y 9960X 260Y 800R 0
-327 U4 -1 A01X 4704Y 3850X 394Y 500R 0
-327N$6 U4 -2 A01X 4704Y 2800X 394Y 500R 0
-327N$10 U4 -3 A01X 4704Y 1800X 394Y 500R 0
-327 U4 -4 A01X 4704Y 750X 394Y 500R 0
-327GND U4 -5 A01X 8396Y 750X 394Y 500R 0
-327PIC_RX U4 -6 A01X 8396Y 1800X 394Y 500R 0
-327 U4 -7 A01X 8396Y 2800X 394Y 500R 0
-327VCC U4 -8 A01X 8396Y 3850X 394Y 500R 0
-327NNAME1 NA -69 A01X 8396Y 3850X 394Y 500R 0
-389BOARD_EDGE X0Y0 X22500 Y15000 X0
-089 X1300Y240
-999
diff --git a/gerber/tests/resources/multiline_read.ger b/gerber/tests/resources/multiline_read.ger
deleted file mode 100644
index 02242e4..0000000
--- a/gerber/tests/resources/multiline_read.ger
+++ /dev/null
@@ -1,9 +0,0 @@
-G75*
-G71*
-%OFA0B0*%
-%FSLAX23Y23*%
-%IPPOS*%
-%LPD*%
-%ADD10C,0.1*%
-%LPD*%D10*
-M02* \ No newline at end of file
diff --git a/gerber/tests/resources/ncdrill.DRD b/gerber/tests/resources/ncdrill.DRD
deleted file mode 100644
index ced00ca..0000000
--- a/gerber/tests/resources/ncdrill.DRD
+++ /dev/null
@@ -1,51 +0,0 @@
-%
-M48
-M72
-T01C0.0236
-T02C0.0354
-T03C0.0400
-T04C0.1260
-T05C0.1280
-%
-T01
-X9250Y4064
-X12100Y5314
-X13500Y6864
-X15650Y6264
-X15200Y4514
-X13550Y8764
-X13350Y10114
-X13300Y11464
-X11650Y13164
-X10000Y15114
-X6500Y13714
-X4150Y11564
-X14250Y14964
-X15850Y9914
-T02
-X17200Y9464
-X18200Y9964
-X18200Y10964
-X17200Y10464
-X17200Y11464
-X18200Y11964
-T03
-X18350Y16814
-X17350Y16814
-X7350Y16964
-X6350Y16964
-X5350Y16964
-X1500Y12564
-X1500Y11564
-X1500Y10564
-X1500Y9564
-X1500Y8564
-T04
-X2350Y5114
-X2300Y16064
-X20800Y16064
-X20800Y5064
-T05
-X20700Y8714
-X20700Y12714
-M30
diff --git a/gerber/tests/resources/top_copper.GTL b/gerber/tests/resources/top_copper.GTL
deleted file mode 100644
index 01c848e..0000000
--- a/gerber/tests/resources/top_copper.GTL
+++ /dev/null
@@ -1,27 +0,0 @@
-G75*
-%MOIN*%
-%OFA0B0*%
-%FSLAX24Y24*%
-%IPPOS*%
-%LPD*%
-G04This is a comment,:*
-%AMOC8*5,1,8,0,0,1.08239,22.5*%
-%ADD10C,0.0000*%
-%ADD11R,0.0260X0.0800*%
-%ADD12R,0.0591X0.0157*%
-%ADD13R,0.4098X0.4252*%
-%ADD14R,0.0850X0.0420*%
-%ADD15R,0.0630X0.1575*%
-%ADD16R,0.0591X0.0512*%
-%ADD17R,0.0512X0.0591*%
-%ADD18R,0.0630X0.1535*%
-%ADD19R,0.1339X0.0748*%
-%ADD20C,0.0004*%
-%ADD21C,0.0554*%
-%ADD22R,0.0394X0.0500*%
-%ADD23C,0.0600*%
-%ADD24R,0.0472X0.0472*%
-%ADD25C,0.0160*%
-%ADD26C,0.0396*%
-%ADD27C,0.0240*%
-D10*X000300Y003064D02*X000300Y018064D01*X022800Y018064D01*X022800Y003064D01*X000300Y003064D01*X001720Y005114D02*X001722Y005164D01*X001728Y005214D01*X001738Y005263D01*X001752Y005311D01*X001769Y005358D01*X001790Y005403D01*X001815Y005447D01*X001843Y005488D01*X001875Y005527D01*X001909Y005564D01*X001946Y005598D01*X001986Y005628D01*X002028Y005655D01*X002072Y005679D01*X002118Y005700D01*X002165Y005716D01*X002213Y005729D01*X002263Y005738D01*X002312Y005743D01*X002363Y005744D01*X002413Y005741D01*X002462Y005734D01*X002511Y005723D01*X002559Y005708D01*X002605Y005690D01*X002650Y005668D01*X002693Y005642D01*X002734Y005613D01*X002773Y005581D01*X002809Y005546D01*X002841Y005508D01*X002871Y005468D01*X002898Y005425D01*X002921Y005381D01*X002940Y005335D01*X002956Y005287D01*X002968Y005238D01*X002976Y005189D01*X002980Y005139D01*X002980Y005089D01*X002976Y005039D01*X002968Y004990D01*X002956Y004941D01*X002940Y004893D01*X002921Y004847D01*X002898Y004803D01*X002871Y004760D01*X002841Y004720D01*X002809Y004682D01*X002773Y004647D01*X002734Y004615D01*X002693Y004586D01*X002650Y004560D01*X002605Y004538D01*X002559Y004520D01*X002511Y004505D01*X002462Y004494D01*X002413Y004487D01*X002363Y004484D01*X002312Y004485D01*X002263Y004490D01*X002213Y004499D01*X002165Y004512D01*X002118Y004528D01*X002072Y004549D01*X002028Y004573D01*X001986Y004600D01*X001946Y004630D01*X001909Y004664D01*X001875Y004701D01*X001843Y004740D01*X001815Y004781D01*X001790Y004825D01*X001769Y004870D01*X001752Y004917D01*X001738Y004965D01*X001728Y005014D01*X001722Y005064D01*X001720Y005114D01*X001670Y016064D02*X001672Y016114D01*X001678Y016164D01*X001688Y016213D01*X001702Y016261D01*X001719Y016308D01*X001740Y016353D01*X001765Y016397D01*X001793Y016438D01*X001825Y016477D01*X001859Y016514D01*X001896Y016548D01*X001936Y016578D01*X001978Y016605D01*X002022Y016629D01*X002068Y016650D01*X002115Y016666D01*X002163Y016679D01*X002213Y016688D01*X002262Y016693D01*X002313Y016694D01*X002363Y016691D01*X002412Y016684D01*X002461Y016673D01*X002509Y016658D01*X002555Y016640D01*X002600Y016618D01*X002643Y016592D01*X002684Y016563D01*X002723Y016531D01*X002759Y016496D01*X002791Y016458D01*X002821Y016418D01*X002848Y016375D01*X002871Y016331D01*X002890Y016285D01*X002906Y016237D01*X002918Y016188D01*X002926Y016139D01*X002930Y016089D01*X002930Y016039D01*X002926Y015989D01*X002918Y015940D01*X002906Y015891D01*X002890Y015843D01*X002871Y015797D01*X002848Y015753D01*X002821Y015710D01*X002791Y015670D01*X002759Y015632D01*X002723Y015597D01*X002684Y015565D01*X002643Y015536D01*X002600Y015510D01*X002555Y015488D01*X002509Y015470D01*X002461Y015455D01*X002412Y015444D01*X002363Y015437D01*X002313Y015434D01*X002262Y015435D01*X002213Y015440D01*X002163Y015449D01*X002115Y015462D01*X002068Y015478D01*X002022Y015499D01*X001978Y015523D01*X001936Y015550D01*X001896Y015580D01*X001859Y015614D01*X001825Y015651D01*X001793Y015690D01*X001765Y015731D01*X001740Y015775D01*X001719Y015820D01*X001702Y015867D01*X001688Y015915D01*X001678Y015964D01*X001672Y016014D01*X001670Y016064D01*X020060Y012714D02*X020062Y012764D01*X020068Y012814D01*X020078Y012863D01*X020091Y012912D01*X020109Y012959D01*X020130Y013005D01*X020154Y013048D01*X020182Y013090D01*X020213Y013130D01*X020247Y013167D01*X020284Y013201D01*X020324Y013232D01*X020366Y013260D01*X020409Y013284D01*X020455Y013305D01*X020502Y013323D01*X020551Y013336D01*X020600Y013346D01*X020650Y013352D01*X020700Y013354D01*X020750Y013352D01*X020800Y013346D01*X020849Y013336D01*X020898Y013323D01*X020945Y013305D01*X020991Y013284D01*X021034Y013260D01*X021076Y013232D01*X021116Y013201D01*X021153Y013167D01*X021187Y013130D01*X021218Y013090D01*X021246Y013048D01*X021270Y013005D01*X021291Y012959D01*X021309Y012912D01*X021322Y012863D01*X021332Y012814D01*X021338Y012764D01*X021340Y012714D01*X021338Y012664D01*X021332Y012614D01*X021322Y012565D01*X021309Y012516D01*X021291Y012469D01*X021270Y012423D01*X021246Y012380D01*X021218Y012338D01*X021187Y012298D01*X021153Y012261D01*X021116Y012227D01*X021076Y012196D01*X021034Y012168D01*X020991Y012144D01*X020945Y012123D01*X020898Y012105D01*X020849Y012092D01*X020800Y012082D01*X020750Y012076D01*X020700Y012074D01*X020650Y012076D01*X020600Y012082D01*X020551Y012092D01*X020502Y012105D01*X020455Y012123D01*X020409Y012144D01*X020366Y012168D01*X020324Y012196D01*X020284Y012227D01*X020247Y012261D01*X020213Y012298D01*X020182Y012338D01*X020154Y012380D01*X020130Y012423D01*X020109Y012469D01*X020091Y012516D01*X020078Y012565D01*X020068Y012614D01*X020062Y012664D01*X020060Y012714D01*X020170Y016064D02*X020172Y016114D01*X020178Y016164D01*X020188Y016213D01*X020202Y016261D01*X020219Y016308D01*X020240Y016353D01*X020265Y016397D01*X020293Y016438D01*X020325Y016477D01*X020359Y016514D01*X020396Y016548D01*X020436Y016578D01*X020478Y016605D01*X020522Y016629D01*X020568Y016650D01*X020615Y016666D01*X020663Y016679D01*X020713Y016688D01*X020762Y016693D01*X020813Y016694D01*X020863Y016691D01*X020912Y016684D01*X020961Y016673D01*X021009Y016658D01*X021055Y016640D01*X021100Y016618D01*X021143Y016592D01*X021184Y016563D01*X021223Y016531D01*X021259Y016496D01*X021291Y016458D01*X021321Y016418D01*X021348Y016375D01*X021371Y016331D01*X021390Y016285D01*X021406Y016237D01*X021418Y016188D01*X021426Y016139D01*X021430Y016089D01*X021430Y016039D01*X021426Y015989D01*X021418Y015940D01*X021406Y015891D01*X021390Y015843D01*X021371Y015797D01*X021348Y015753D01*X021321Y015710D01*X021291Y015670D01*X021259Y015632D01*X021223Y015597D01*X021184Y015565D01*X021143Y015536D01*X021100Y015510D01*X021055Y015488D01*X021009Y015470D01*X020961Y015455D01*X020912Y015444D01*X020863Y015437D01*X020813Y015434D01*X020762Y015435D01*X020713Y015440D01*X020663Y015449D01*X020615Y015462D01*X020568Y015478D01*X020522Y015499D01*X020478Y015523D01*X020436Y015550D01*X020396Y015580D01*X020359Y015614D01*X020325Y015651D01*X020293Y015690D01*X020265Y015731D01*X020240Y015775D01*X020219Y015820D01*X020202Y015867D01*X020188Y015915D01*X020178Y015964D01*X020172Y016014D01*X020170Y016064D01*X020060Y008714D02*X020062Y008764D01*X020068Y008814D01*X020078Y008863D01*X020091Y008912D01*X020109Y008959D01*X020130Y009005D01*X020154Y009048D01*X020182Y009090D01*X020213Y009130D01*X020247Y009167D01*X020284Y009201D01*X020324Y009232D01*X020366Y009260D01*X020409Y009284D01*X020455Y009305D01*X020502Y009323D01*X020551Y009336D01*X020600Y009346D01*X020650Y009352D01*X020700Y009354D01*X020750Y009352D01*X020800Y009346D01*X020849Y009336D01*X020898Y009323D01*X020945Y009305D01*X020991Y009284D01*X021034Y009260D01*X021076Y009232D01*X021116Y009201D01*X021153Y009167D01*X021187Y009130D01*X021218Y009090D01*X021246Y009048D01*X021270Y009005D01*X021291Y008959D01*X021309Y008912D01*X021322Y008863D01*X021332Y008814D01*X021338Y008764D01*X021340Y008714D01*X021338Y008664D01*X021332Y008614D01*X021322Y008565D01*X021309Y008516D01*X021291Y008469D01*X021270Y008423D01*X021246Y008380D01*X021218Y008338D01*X021187Y008298D01*X021153Y008261D01*X021116Y008227D01*X021076Y008196D01*X021034Y008168D01*X020991Y008144D01*X020945Y008123D01*X020898Y008105D01*X020849Y008092D01*X020800Y008082D01*X020750Y008076D01*X020700Y008074D01*X020650Y008076D01*X020600Y008082D01*X020551Y008092D01*X020502Y008105D01*X020455Y008123D01*X020409Y008144D01*X020366Y008168D01*X020324Y008196D01*X020284Y008227D01*X020247Y008261D01*X020213Y008298D01*X020182Y008338D01*X020154Y008380D01*X020130Y008423D01*X020109Y008469D01*X020091Y008516D01*X020078Y008565D01*X020068Y008614D01*X020062Y008664D01*X020060Y008714D01*X020170Y005064D02*X020172Y005114D01*X020178Y005164D01*X020188Y005213D01*X020202Y005261D01*X020219Y005308D01*X020240Y005353D01*X020265Y005397D01*X020293Y005438D01*X020325Y005477D01*X020359Y005514D01*X020396Y005548D01*X020436Y005578D01*X020478Y005605D01*X020522Y005629D01*X020568Y005650D01*X020615Y005666D01*X020663Y005679D01*X020713Y005688D01*X020762Y005693D01*X020813Y005694D01*X020863Y005691D01*X020912Y005684D01*X020961Y005673D01*X021009Y005658D01*X021055Y005640D01*X021100Y005618D01*X021143Y005592D01*X021184Y005563D01*X021223Y005531D01*X021259Y005496D01*X021291Y005458D01*X021321Y005418D01*X021348Y005375D01*X021371Y005331D01*X021390Y005285D01*X021406Y005237D01*X021418Y005188D01*X021426Y005139D01*X021430Y005089D01*X021430Y005039D01*X021426Y004989D01*X021418Y004940D01*X021406Y004891D01*X021390Y004843D01*X021371Y004797D01*X021348Y004753D01*X021321Y004710D01*X021291Y004670D01*X021259Y004632D01*X021223Y004597D01*X021184Y004565D01*X021143Y004536D01*X021100Y004510D01*X021055Y004488D01*X021009Y004470D01*X020961Y004455D01*X020912Y004444D01*X020863Y004437D01*X020813Y004434D01*X020762Y004435D01*X020713Y004440D01*X020663Y004449D01*X020615Y004462D01*X020568Y004478D01*X020522Y004499D01*X020478Y004523D01*X020436Y004550D01*X020396Y004580D01*X020359Y004614D01*X020325Y004651D01*X020293Y004690D01*X020265Y004731D01*X020240Y004775D01*X020219Y004820D01*X020202Y004867D01*X020188Y004915D01*X020178Y004964D01*X020172Y005014D01*X020170Y005064D01*D11*X006500Y010604D03*X006000Y010604D03*X005500Y010604D03*X005000Y010604D03*X005000Y013024D03*X005500Y013024D03*X006000Y013024D03*X006500Y013024D03*D12*X011423Y007128D03*X011423Y006872D03*X011423Y006616D03*X011423Y006360D03*X011423Y006104D03*X011423Y005848D03*X011423Y005592D03*X011423Y005336D03*X011423Y005080D03*X011423Y004825D03*X011423Y004569D03*X011423Y004313D03*X011423Y004057D03*X011423Y003801D03*X014277Y003801D03*X014277Y004057D03*X014277Y004313D03*X014277Y004569D03*X014277Y004825D03*X014277Y005080D03*X014277Y005336D03*X014277Y005592D03*X014277Y005848D03*X014277Y006104D03*X014277Y006360D03*X014277Y006616D03*X014277Y006872D03*X014277Y007128D03*D13*X009350Y010114D03*D14*X012630Y010114D03*X012630Y010784D03*X012630Y011454D03*X012630Y009444D03*X012630Y008774D03*D15*X010000Y013467D03*X010000Y016262D03*D16*X004150Y012988D03*X004150Y012240D03*X009900Y005688D03*X009900Y004940D03*X015000Y006240D03*X015000Y006988D03*D17*X014676Y008364D03*X015424Y008364D03*X017526Y004514D03*X018274Y004514D03*X010674Y004064D03*X009926Y004064D03*X004174Y009564D03*X003426Y009564D03*X005376Y014564D03*X006124Y014564D03*D18*X014250Y016088D03*X014250Y012741D03*D19*X014250Y010982D03*X014250Y009447D03*D20*X022869Y007639D02*X022869Y013789D01*D21*X018200Y011964D03*X017200Y011464D03*X017200Y010464D03*X018200Y009964D03*X018200Y010964D03*X017200Y009464D03*D22*X008696Y006914D03*X008696Y005864D03*X008696Y004864D03*X008696Y003814D03*X005004Y003814D03*X005004Y004864D03*X005004Y005864D03*X005004Y006914D03*D23*X001800Y008564D02*X001200Y008564D01*X001200Y009564D02*X001800Y009564D01*X001800Y010564D02*X001200Y010564D01*X001200Y011564D02*X001800Y011564D01*X001800Y012564D02*X001200Y012564D01*X005350Y016664D02*X005350Y017264D01*X006350Y017264D02*X006350Y016664D01*X007350Y016664D02*X007350Y017264D01*X017350Y017114D02*X017350Y016514D01*X018350Y016514D02*X018350Y017114D01*D24*X016613Y004514D03*X015787Y004514D03*D25*X015200Y004514D01*X014868Y004649D02*X014732Y004649D01*X014842Y004586D02*X014842Y004443D01*X014896Y004311D01*X014997Y004211D01*X015129Y004156D01*X015271Y004156D01*X015395Y004207D01*X015484Y004118D01*X016089Y004118D01*X016183Y004212D01*X016183Y004817D01*X016089Y004911D01*X015484Y004911D01*X015395Y004821D01*X015271Y004872D01*X015129Y004872D01*X014997Y004818D01*X014896Y004717D01*X014842Y004586D01*X014842Y004491D02*X014732Y004491D01*X014732Y004332D02*X014888Y004332D01*X014732Y004174D02*X015086Y004174D01*X015314Y004174D02*X015428Y004174D01*X014732Y004015D02*X019505Y004015D01*X019568Y003922D02*X019568Y003922D01*X019568Y003922D01*X019286Y004335D01*X019286Y004335D01*X019139Y004814D01*X019139Y005315D01*X019286Y005793D01*X019286Y005793D01*X019568Y006207D01*X019568Y006207D01*X019960Y006519D01*X019960Y006519D01*X020426Y006702D01*X020926Y006740D01*X020926Y006740D01*X021414Y006628D01*X021414Y006628D01*X021847Y006378D01*X021847Y006378D01*X022188Y006011D01*X022188Y006011D01*X022320Y005737D01*X022320Y015392D01*X022188Y015118D01*X022188Y015118D01*X021847Y014751D01*X021847Y014751D01*X021414Y014500D01*X021414Y014500D01*X020926Y014389D01*X020926Y014389D01*X020426Y014426D01*X020426Y014426D01*X019960Y014609D01*X019960Y014609D01*X019568Y014922D01*X019568Y014922D01*X019568Y014922D01*X019286Y015335D01*X019286Y015335D01*X019139Y015814D01*X019139Y016315D01*X019286Y016793D01*X019286Y016793D01*X019568Y017207D01*X019568Y017207D01*X019568Y017207D01*X019960Y017519D01*X019960Y017519D01*X020126Y017584D01*X016626Y017584D01*X016637Y017573D01*X016924Y017287D01*X016960Y017375D01*X017089Y017504D01*X017258Y017574D01*X017441Y017574D01*X017611Y017504D01*X017740Y017375D01*X017810Y017206D01*X017810Y016423D01*X017740Y016254D01*X017611Y016124D01*X017441Y016054D01*X017258Y016054D01*X017089Y016124D01*X016960Y016254D01*X016890Y016423D01*X016890Y016557D01*X016841Y016577D01*X016284Y017134D01*X010456Y017134D01*X010475Y017116D01*X010475Y016310D01*X010475Y016310D01*X010495Y016216D01*X010477Y016123D01*X010475Y016120D01*X010475Y015408D01*X010381Y015315D01*X010305Y015315D01*X010358Y015186D01*X010358Y015043D01*X010304Y014911D01*X010203Y014811D01*X010071Y014756D01*X009929Y014756D01*X009797Y014811D01*X009696Y014911D01*X009642Y015043D01*X009642Y015186D01*X009695Y015315D01*X009619Y015315D01*X009525Y015408D01*X009525Y017116D01*X009544Y017134D01*X009416Y017134D01*X009330Y017048D01*X009330Y014080D01*X009525Y013885D01*X009525Y014320D01*X009619Y014414D01*X010381Y014414D01*X010475Y014320D01*X010475Y013747D01*X011403Y013747D01*X011506Y013704D01*X011688Y013522D01*X011721Y013522D01*X011853Y013468D01*X011954Y013367D01*X013755Y013367D01*X013755Y013525D02*X011685Y013525D01*X011526Y013684D02*X013893Y013684D01*X013911Y013689D02*X013866Y013677D01*X013825Y013653D01*X013791Y013619D01*X013767Y013578D01*X013755Y013533D01*X013755Y012819D01*X014173Y012819D01*X014173Y013689D01*X013911Y013689D01*X014173Y013684D02*X014327Y013684D01*X014327Y013689D02*X014327Y012819D01*X014173Y012819D01*X014173Y012664D01*X014327Y012664D01*X014327Y011793D01*X014589Y011793D01*X014634Y011806D01*X014675Y011829D01*X014709Y011863D01*X014733Y011904D01*X014745Y011950D01*X014745Y012664D01*X014327Y012664D01*X014327Y012819D01*X014745Y012819D01*X014745Y013533D01*X014733Y013578D01*X014709Y013619D01*X014675Y013653D01*X014634Y013677D01*X014589Y013689D01*X014327Y013689D01*X014327Y013525D02*X014173Y013525D01*X014173Y013367D02*X014327Y013367D01*X014327Y013208D02*X014173Y013208D01*X014173Y013050D02*X014327Y013050D01*X014327Y012891D02*X014173Y012891D01*X014173Y012733D02*X010475Y012733D01*X010475Y012613D02*X010475Y013187D01*X011232Y013187D01*X011292Y013126D01*X011292Y013093D01*X011346Y012961D01*X011447Y012861D01*X011579Y012806D01*X011721Y012806D01*X011853Y012861D01*X011954Y012961D01*X012008Y013093D01*X012008Y013236D01*X011954Y013367D01*X012008Y013208D02*X013755Y013208D01*X013755Y013050D02*X011990Y013050D01*X011883Y012891D02*X013755Y012891D01*X013755Y012664D02*X013755Y011950D01*X013767Y011904D01*X013791Y011863D01*X013825Y011829D01*X013866Y011806D01*X013911Y011793D01*X014173Y011793D01*X014173Y012664D01*X013755Y012664D01*X013755Y012574D02*X010436Y012574D01*X010475Y012613D02*X010381Y012519D01*X009619Y012519D01*X009525Y012613D01*X009525Y013234D01*X009444Y013234D01*X009341Y013277D01*X009263Y013356D01*X009263Y013356D01*X008813Y013806D01*X008770Y013909D01*X008770Y017220D01*X008813Y017323D01*X009074Y017584D01*X007681Y017584D01*X007740Y017525D01*X007810Y017356D01*X007810Y016573D01*X007740Y016404D01*X007611Y016274D01*X007441Y016204D01*X007258Y016204D01*X007089Y016274D01*X006960Y016404D01*X006890Y016573D01*X006890Y017356D01*X006960Y017525D01*X007019Y017584D01*X006681Y017584D01*X006740Y017525D01*X006810Y017356D01*X006810Y016573D01*X006740Y016404D01*X006611Y016274D01*X006590Y016266D01*X006590Y015367D01*X006553Y015278D01*X006340Y015065D01*X006340Y015020D01*X006446Y015020D01*X006540Y014926D01*X006540Y014203D01*X006446Y014109D01*X006240Y014109D01*X006240Y013961D01*X006297Y014018D01*X006429Y014072D01*X006571Y014072D01*X006703Y014018D01*X006804Y013917D01*X006858Y013786D01*X006858Y013643D01*X006804Y013511D01*X006786Y013494D01*X006790Y013491D01*X006790Y012558D01*X006696Y012464D01*X006304Y012464D01*X006250Y012518D01*X006196Y012464D01*X005804Y012464D01*X005750Y012518D01*X005696Y012464D01*X005304Y012464D01*X005264Y012504D01*X005241Y012480D01*X005199Y012457D01*X005154Y012444D01*X005000Y012444D01*X005000Y013024D01*X005000Y013024D01*X005000Y012444D01*X004846Y012444D01*X004801Y012457D01*X004759Y012480D01*X004726Y012514D01*X004702Y012555D01*X004690Y012601D01*X004690Y013024D01*X005000Y013024D01*X005000Y013024D01*X004964Y012988D01*X004150Y012988D01*X004198Y012940D02*X004198Y013036D01*X004625Y013036D01*X004625Y013268D01*X004613Y013314D01*X004589Y013355D01*X004556Y013388D01*X004515Y013412D01*X004469Y013424D01*X004198Y013424D01*X004198Y013036D01*X004102Y013036D01*X004102Y012940D01*X003675Y012940D01*X003675Y012709D01*X003687Y012663D01*X003711Y012622D01*X003732Y012600D01*X003695Y012562D01*X003695Y011918D01*X003788Y011824D01*X003904Y011824D01*X003846Y011767D01*X003792Y011636D01*X003792Y011493D01*X003846Y011361D01*X003947Y011261D01*X004079Y011206D01*X004221Y011206D01*X004353Y011261D01*X004454Y011361D01*X004508Y011493D01*X004508Y011636D01*X004454Y011767D01*X004396Y011824D01*X004512Y011824D01*X004605Y011918D01*X004605Y012562D01*X004568Y012600D01*X004589Y012622D01*X004613Y012663D01*X004625Y012709D01*X004625Y012940D01*X004198Y012940D01*X004198Y013050D02*X004102Y013050D01*X004102Y013036D02*X004102Y013424D01*X003831Y013424D01*X003785Y013412D01*X003744Y013388D01*X003711Y013355D01*X003687Y013314D01*X003675Y013268D01*X003675Y013036D01*X004102Y013036D01*X004102Y013208D02*X004198Y013208D01*X004198Y013367D02*X004102Y013367D01*X003723Y013367D02*X000780Y013367D01*X000780Y013525D02*X004720Y013525D01*X004726Y013535D02*X004702Y013494D01*X004690Y013448D01*X004690Y013024D01*X005000Y013024D01*X005000Y012264D01*X005750Y011514D01*X005750Y010604D01*X005500Y010604D01*X005500Y010024D01*X005654Y010024D01*X005699Y010037D01*X005741Y010060D01*X005750Y010070D01*X005759Y010060D01*X005801Y010037D01*X005846Y010024D01*X006000Y010024D01*X006154Y010024D01*X006199Y010037D01*X006241Y010060D01*X006260Y010080D01*X006260Y008267D01*X006297Y008178D01*X006364Y008111D01*X006364Y008111D01*X006821Y007654D01*X006149Y007654D01*X005240Y008564D01*X005240Y010080D01*X005259Y010060D01*X005301Y010037D01*X005346Y010024D01*X005500Y010024D01*X005500Y010604D01*X005500Y010604D01*X005500Y010604D01*X005690Y010604D01*X006000Y010604D01*X006000Y010024D01*X006000Y010604D01*X006000Y010604D01*X006000Y010604D01*X005750Y010604D01*X005500Y010604D02*X006000Y010604D01*X006000Y011184D01*X005846Y011184D01*X005801Y011172D01*X005759Y011148D01*X005741Y011148D01*X005699Y011172D01*X005654Y011184D01*X005500Y011184D01*X005346Y011184D01*X005301Y011172D01*X005259Y011148D01*X005213Y011148D01*X005196Y011164D02*X005236Y011125D01*X005259Y011148D01*X005196Y011164D02*X004804Y011164D01*X004710Y011071D01*X004710Y010138D01*X004760Y010088D01*X004760Y009309D01*X004753Y009324D01*X004590Y009488D01*X004590Y009926D01*X004496Y010020D01*X003852Y010020D01*X003800Y009968D01*X003748Y010020D01*X003104Y010020D01*X003010Y009926D01*X003010Y009804D01*X002198Y009804D01*X002190Y009825D01*X002061Y009954D01*X001891Y010024D01*X001108Y010024D01*X000939Y009954D01*X000810Y009825D01*X000780Y009752D01*X000780Y010376D01*X000810Y010304D01*X000939Y010174D01*X001108Y010104D01*X001891Y010104D01*X002061Y010174D01*X002190Y010304D01*X002260Y010473D01*X002260Y010656D01*X002190Y010825D01*X002061Y010954D01*X001891Y011024D01*X001108Y011024D01*X000939Y010954D01*X000810Y010825D01*X000780Y010752D01*X000780Y011376D01*X000810Y011304D01*X000939Y011174D01*X001108Y011104D01*X001891Y011104D01*X002061Y011174D01*X002190Y011304D01*X002260Y011473D01*X002260Y011656D01*X002190Y011825D01*X002061Y011954D01*X001891Y012024D01*X001108Y012024D01*X000939Y011954D01*X000810Y011825D01*X000780Y011752D01*X000780Y012376D01*X000810Y012304D01*X000939Y012174D01*X001108Y012104D01*X001891Y012104D01*X002061Y012174D01*X002190Y012304D01*X002260Y012473D01*X002260Y012656D01*X002190Y012825D01*X002061Y012954D01*X001891Y013024D01*X001108Y013024D01*X000939Y012954D01*X000810Y012825D01*X000780Y012752D01*X000780Y015356D01*X000786Y015335D01*X001068Y014922D01*X001068Y014922D01*X001068Y014922D01*X001460Y014609D01*X001926Y014426D01*X002426Y014389D01*X002914Y014500D01*X003347Y014751D01*X003347Y014751D01*X003688Y015118D01*X003905Y015569D01*X003980Y016064D01*X003905Y016560D01*X003688Y017011D01*X003347Y017378D01*X002990Y017584D01*X005019Y017584D01*X004960Y017525D01*X004890Y017356D01*X004890Y016573D01*X004960Y016404D01*X005089Y016274D01*X005110Y016266D01*X005110Y015020D01*X005054Y015020D01*X004960Y014926D01*X004960Y014203D01*X005054Y014109D01*X005260Y014109D01*X005260Y013549D01*X005241Y013568D01*X005199Y013592D01*X005154Y013604D01*X005000Y013604D01*X004846Y013604D01*X004801Y013592D01*X004759Y013568D01*X004726Y013535D01*X004690Y013367D02*X004577Y013367D01*X004625Y013208D02*X004690Y013208D01*X004690Y013050D02*X004625Y013050D01*X004625Y012891D02*X004690Y012891D01*X004690Y012733D02*X004625Y012733D01*X004593Y012574D02*X004697Y012574D01*X004605Y012416D02*X013755Y012416D01*X013755Y012257D02*X011559Y012257D01*X011559Y012307D02*X011465Y012400D01*X007235Y012400D01*X007141Y012307D01*X007141Y008013D01*X006740Y008414D01*X006740Y010088D01*X006790Y010138D01*X006790Y011071D01*X006696Y011164D01*X006304Y011164D01*X006264Y011125D01*X006241Y011148D01*X006287Y011148D01*X006241Y011148D02*X006199Y011172D01*X006154Y011184D01*X006000Y011184D01*X006000Y010604D01*X006000Y010604D01*X006000Y010672D02*X006000Y010672D01*X006000Y010514D02*X006000Y010514D01*X006000Y010355D02*X006000Y010355D01*X006000Y010197D02*X006000Y010197D01*X006000Y010038D02*X006000Y010038D01*X006202Y010038D02*X006260Y010038D01*X006260Y009880D02*X005240Y009880D01*X005240Y010038D02*X005297Y010038D01*X005500Y010038D02*X005500Y010038D01*X005500Y010197D02*X005500Y010197D01*X005500Y010355D02*X005500Y010355D01*X005500Y010514D02*X005500Y010514D01*X005500Y010604D02*X005500Y011184D01*X005500Y010604D01*X005500Y010604D01*X005500Y010672D02*X005500Y010672D01*X005500Y010831D02*X005500Y010831D01*X005500Y010989D02*X005500Y010989D01*X005500Y011148D02*X005500Y011148D01*X005741Y011148D02*X005750Y011139D01*X005759Y011148D01*X006000Y011148D02*X006000Y011148D01*X006000Y010989D02*X006000Y010989D01*X006000Y010831D02*X006000Y010831D01*X006500Y010604D02*X006500Y008314D01*X007150Y007664D01*X009450Y007664D01*X010750Y006364D01*X011419Y006364D01*X011423Y006360D01*X011377Y006364D01*X011423Y006104D02*X010660Y006104D01*X009350Y007414D01*X006050Y007414D01*X005000Y008464D01*X005000Y010604D01*X004710Y010672D02*X002253Y010672D01*X002260Y010514D02*X004710Y010514D01*X004710Y010355D02*X002211Y010355D01*X002083Y010197D02*X004710Y010197D01*X004760Y010038D02*X000780Y010038D01*X000780Y009880D02*X000865Y009880D01*X000917Y010197D02*X000780Y010197D01*X000780Y010355D02*X000789Y010355D01*X000780Y010831D02*X000816Y010831D01*X000780Y010989D02*X001024Y010989D01*X001003Y011148D02*X000780Y011148D01*X000780Y011306D02*X000809Y011306D01*X000780Y011782D02*X000792Y011782D01*X000780Y011940D02*X000925Y011940D01*X000780Y012099D02*X003695Y012099D01*X003695Y012257D02*X002144Y012257D01*X002236Y012416D02*X003695Y012416D01*X003707Y012574D02*X002260Y012574D01*X002228Y012733D02*X003675Y012733D01*X003675Y012891D02*X002124Y012891D01*X002075Y011940D02*X003695Y011940D01*X003861Y011782D02*X002208Y011782D01*X002260Y011623D02*X003792Y011623D01*X003804Y011465D02*X002257Y011465D01*X002191Y011306D02*X003902Y011306D01*X004150Y011564D02*X004150Y012240D01*X004605Y012257D02*X007141Y012257D01*X007141Y012099D02*X004605Y012099D01*X004605Y011940D02*X007141Y011940D01*X007141Y011782D02*X004439Y011782D01*X004508Y011623D02*X007141Y011623D01*X007141Y011465D02*X004496Y011465D01*X004398Y011306D02*X007141Y011306D01*X007141Y011148D02*X006713Y011148D01*X006790Y010989D02*X007141Y010989D01*X007141Y010831D02*X006790Y010831D01*X006790Y010672D02*X007141Y010672D01*X007141Y010514D02*X006790Y010514D01*X006790Y010355D02*X007141Y010355D01*X007141Y010197D02*X006790Y010197D01*X006740Y010038D02*X007141Y010038D01*X007141Y009880D02*X006740Y009880D01*X006740Y009721D02*X007141Y009721D01*X007141Y009563D02*X006740Y009563D01*X006740Y009404D02*X007141Y009404D01*X007141Y009246D02*X006740Y009246D01*X006740Y009087D02*X007141Y009087D01*X007141Y008929D02*X006740Y008929D01*X006740Y008770D02*X007141Y008770D01*X007141Y008612D02*X006740Y008612D01*X006740Y008453D02*X007141Y008453D01*X007141Y008295D02*X006859Y008295D01*X007017Y008136D02*X007141Y008136D01*X006656Y007819D02*X005984Y007819D01*X005826Y007978D02*X006497Y007978D01*X006339Y008136D02*X005667Y008136D01*X005509Y008295D02*X006260Y008295D01*X006260Y008453D02*X005350Y008453D01*X005240Y008612D02*X006260Y008612D01*X006260Y008770D02*X005240Y008770D01*X005240Y008929D02*X006260Y008929D01*X006260Y009087D02*X005240Y009087D01*X005240Y009246D02*X006260Y009246D01*X006260Y009404D02*X005240Y009404D01*X005240Y009563D02*X006260Y009563D01*X006260Y009721D02*X005240Y009721D01*X004760Y009721D02*X004590Y009721D01*X004590Y009563D02*X004760Y009563D01*X004760Y009404D02*X004673Y009404D01*X004550Y009188D02*X004174Y009564D01*X004590Y009880D02*X004760Y009880D01*X004550Y009188D02*X004550Y006114D01*X004800Y005864D01*X005004Y005864D01*X004647Y005678D02*X004647Y005548D01*X004740Y005454D01*X005267Y005454D01*X005360Y005548D01*X005360Y006181D01*X005267Y006274D01*X004790Y006274D01*X004790Y006504D01*X005267Y006504D01*X005360Y006598D01*X005360Y007231D01*X005267Y007324D01*X004790Y007324D01*X004790Y008344D01*X004797Y008328D01*X005847Y007278D01*X005914Y007211D01*X006002Y007174D01*X008320Y007174D01*X008320Y006933D01*X008678Y006933D01*X008678Y006896D01*X008320Y006896D01*X008320Y006641D01*X008332Y006595D01*X008356Y006554D01*X008389Y006520D01*X008430Y006497D01*X008476Y006484D01*X008678Y006484D01*X008678Y006896D01*X008715Y006896D01*X008715Y006933D01*X009073Y006933D01*X009073Y007174D01*X009251Y007174D01*X010337Y006088D01*X010278Y006088D01*X010262Y006104D01*X009538Y006104D01*X009445Y006011D01*X009445Y005928D01*X009276Y005928D01*X009188Y005892D01*X009064Y005768D01*X009053Y005757D01*X009053Y006181D01*X008960Y006274D01*X008433Y006274D01*X008340Y006181D01*X008340Y005548D01*X008433Y005454D01*X008960Y005454D01*X008960Y005455D01*X008960Y005274D01*X008960Y005274D01*X008433Y005274D01*X008340Y005181D01*X008340Y004548D01*X008433Y004454D01*X008960Y004454D01*X009053Y004548D01*X009053Y004627D01*X009136Y004661D01*X009203Y004728D01*X009403Y004928D01*X009428Y004988D01*X009852Y004988D01*X009852Y004892D01*X009425Y004892D01*X009425Y004661D01*X009437Y004615D01*X009461Y004574D01*X009494Y004540D01*X009535Y004517D01*X009581Y004504D01*X009589Y004504D01*X009510Y004426D01*X009510Y004311D01*X009453Y004368D01*X009321Y004422D01*X009179Y004422D01*X009047Y004368D01*X008984Y004304D01*X008899Y004304D01*X008811Y004268D01*X008767Y004224D01*X008433Y004224D01*X008340Y004131D01*X008340Y003544D01*X005360Y003544D01*X005360Y004131D01*X005267Y004224D01*X004740Y004224D01*X004647Y004131D01*X004647Y003544D01*X002937Y003544D01*X002964Y003550D01*X003397Y003801D01*X003397Y003801D01*X003738Y004168D01*X003955Y004619D01*X004030Y005114D01*X003955Y005610D01*X003738Y006061D01*X003397Y006428D01*X002964Y006678D01*X002964Y006678D01*X002476Y006790D01*X002476Y006790D01*X001976Y006752D01*X001510Y006569D01*X001118Y006257D01*X000836Y005843D01*X000780Y005660D01*X000780Y008376D01*X000810Y008304D01*X000939Y008174D01*X001108Y008104D01*X001891Y008104D01*X002061Y008174D01*X002190Y008304D01*X002198Y008324D01*X003701Y008324D01*X004060Y007965D01*X004060Y005267D01*X004097Y005178D01*X004164Y005111D01*X004497Y004778D01*X004564Y004711D01*X004647Y004677D01*X004647Y004548D01*X004740Y004454D01*X005267Y004454D01*X005360Y004548D01*X005360Y005181D01*X005267Y005274D01*X004740Y005274D01*X004710Y005244D01*X004540Y005414D01*X004540Y005785D01*X004647Y005678D01*X004647Y005600D02*X004540Y005600D01*X004540Y005442D02*X008960Y005442D01*X008960Y005283D02*X004670Y005283D01*X004309Y004966D02*X004008Y004966D01*X004030Y005114D02*X004030Y005114D01*X004028Y005125D02*X004150Y005125D01*X004060Y005283D02*X004005Y005283D01*X003981Y005442D02*X004060Y005442D01*X004060Y005600D02*X003957Y005600D01*X003883Y005759D02*X004060Y005759D01*X004060Y005917D02*X003807Y005917D01*X003738Y006061D02*X003738Y006061D01*X003724Y006076D02*X004060Y006076D01*X004060Y006234D02*X003577Y006234D01*X003430Y006393D02*X004060Y006393D01*X004060Y006551D02*X003184Y006551D01*X003397Y006428D02*X003397Y006428D01*X002825Y006710D02*X004060Y006710D01*X004060Y006868D02*X000780Y006868D01*X000780Y006710D02*X001868Y006710D01*X001976Y006752D02*X001976Y006752D01*X001510Y006569D02*X001510Y006569D01*X001488Y006551D02*X000780Y006551D01*X000780Y006393D02*X001289Y006393D01*X001118Y006257D02*X001118Y006257D01*X001118Y006257D01*X001103Y006234D02*X000780Y006234D01*X000780Y006076D02*X000995Y006076D01*X000887Y005917D02*X000780Y005917D01*X000836Y005843D02*X000836Y005843D01*X000810Y005759D02*X000780Y005759D01*X000780Y007027D02*X004060Y007027D01*X004060Y007185D02*X000780Y007185D01*X000780Y007344D02*X004060Y007344D01*X004060Y007502D02*X000780Y007502D01*X000780Y007661D02*X004060Y007661D01*X004060Y007819D02*X000780Y007819D01*X000780Y007978D02*X004047Y007978D01*X003889Y008136D02*X001969Y008136D01*X002181Y008295D02*X003730Y008295D01*X003800Y008564D02*X001500Y008564D01*X001031Y008136D02*X000780Y008136D01*X000780Y008295D02*X000819Y008295D01*X001500Y009564D02*X003426Y009564D01*X003010Y009880D02*X002135Y009880D01*X002184Y010831D02*X004710Y010831D01*X004710Y010989D02*X001976Y010989D01*X001997Y011148D02*X004787Y011148D01*X005702Y010038D02*X005797Y010038D01*X004830Y008295D02*X004790Y008295D01*X004790Y008136D02*X004989Y008136D01*X005147Y007978D02*X004790Y007978D01*X004790Y007819D02*X005306Y007819D01*X005464Y007661D02*X004790Y007661D01*X004790Y007502D02*X005623Y007502D01*X005781Y007344D02*X004790Y007344D01*X005360Y007185D02*X005976Y007185D01*X006143Y007661D02*X006814Y007661D01*X005360Y007027D02*X008320Y007027D01*X008320Y006868D02*X005360Y006868D01*X005360Y006710D02*X008320Y006710D01*X008358Y006551D02*X005314Y006551D01*X005307Y006234D02*X008393Y006234D01*X008340Y006076D02*X005360Y006076D01*X005360Y005917D02*X008340Y005917D01*X008340Y005759D02*X005360Y005759D01*X005360Y005600D02*X008340Y005600D01*X008340Y005125D02*X005360Y005125D01*X005360Y004966D02*X008340Y004966D01*X008340Y004808D02*X005360Y004808D01*X005360Y004649D02*X008340Y004649D01*X008397Y004491D02*X005303Y004491D01*X005317Y004174D02*X008383Y004174D01*X008340Y004015D02*X005360Y004015D01*X005360Y003857D02*X008340Y003857D01*X008340Y003698D02*X005360Y003698D01*X004647Y003698D02*X003220Y003698D01*X003449Y003857D02*X004647Y003857D01*X004647Y004015D02*X003596Y004015D01*X003738Y004168D02*X003738Y004168D01*X003741Y004174D02*X004690Y004174D01*X004704Y004491D02*X003894Y004491D01*X003955Y004619D02*X003955Y004619D01*X003960Y004649D02*X004647Y004649D01*X004467Y004808D02*X003984Y004808D01*X003817Y004332D02*X009012Y004332D01*X008996Y004491D02*X009575Y004491D01*X009510Y004332D02*X009488Y004332D01*X009250Y004064D02*X008946Y004064D01*X008696Y003814D01*X009053Y003758D02*X009053Y003544D01*X020126Y003544D01*X019960Y003609D01*X019960Y003609D01*X019568Y003922D01*X019650Y003857D02*X014732Y003857D01*X014732Y003698D02*X019848Y003698D01*X019397Y004174D02*X018704Y004174D01*X018710Y004195D02*X018710Y004466D01*X018322Y004466D01*X018322Y004039D01*X018554Y004039D01*X018599Y004051D01*X018640Y004075D01*X018674Y004109D01*X018698Y004150D01*X018710Y004195D01*X018710Y004332D02*X019288Y004332D01*X019238Y004491D02*X018322Y004491D01*X018322Y004466D02*X018322Y004562D01*X018710Y004562D01*X018710Y004833D01*X018698Y004879D01*X018674Y004920D01*X018640Y004954D01*X018599Y004977D01*X018554Y004990D01*X018322Y004990D01*X018322Y004562D01*X018226Y004562D01*X018226Y004990D01*X017994Y004990D01*X017949Y004977D01*X017908Y004954D01*X017886Y004932D01*X017848Y004970D01*X017204Y004970D01*X017110Y004876D01*X017110Y004754D01*X017010Y004754D01*X017010Y004817D01*X016916Y004911D01*X016311Y004911D01*X016217Y004817D01*X016217Y004212D01*X016311Y004118D01*X016916Y004118D01*X017010Y004212D01*X017010Y004274D01*X017110Y004274D01*X017110Y004153D01*X017204Y004059D01*X017848Y004059D01*X017886Y004097D01*X017908Y004075D01*X017949Y004051D01*X017994Y004039D01*X018226Y004039D01*X018226Y004466D01*X018322Y004466D01*X018322Y004332D02*X018226Y004332D01*X018226Y004174D02*X018322Y004174D01*X018322Y004649D02*X018226Y004649D01*X018226Y004808D02*X018322Y004808D01*X018322Y004966D02*X018226Y004966D01*X017930Y004966D02*X017851Y004966D01*X017526Y004514D02*X016613Y004514D01*X016217Y004491D02*X016183Y004491D01*X016183Y004649D02*X016217Y004649D01*X016217Y004808D02*X016183Y004808D01*X016670Y005096D02*X016758Y005133D01*X018836Y007211D01*X018903Y007278D01*X018940Y007367D01*X018940Y010512D01*X018903Y010600D01*X018634Y010870D01*X018637Y010877D01*X018637Y011051D01*X018571Y011212D01*X018448Y011335D01*X018287Y011401D01*X018113Y011401D01*X017952Y011335D01*X017829Y011212D01*X017818Y011185D01*X017634Y011370D01*X017637Y011377D01*X017637Y011551D01*X017571Y011712D01*X017448Y011835D01*X017287Y011901D01*X017113Y011901D01*X016952Y011835D01*X016829Y011712D01*X016763Y011551D01*X016763Y011377D01*X016829Y011217D01*X016952Y011094D01*X017113Y011027D01*X017287Y011027D01*X017295Y011030D01*X017460Y010865D01*X017460Y010823D01*X017448Y010835D01*X017287Y010901D01*X017113Y010901D01*X016952Y010835D01*X016829Y010712D01*X016763Y010551D01*X016763Y010377D01*X016829Y010217D01*X016952Y010094D01*X017113Y010027D01*X017287Y010027D01*X017448Y010094D01*X017460Y010106D01*X017460Y009823D01*X017448Y009835D01*X017287Y009901D01*X017113Y009901D01*X016952Y009835D01*X016829Y009712D01*X016763Y009551D01*X016763Y009377D01*X016829Y009217D01*X016952Y009094D01*X016960Y009091D01*X016960Y008914D01*X016651Y008604D01*X015840Y008604D01*X015840Y008726D01*X015746Y008820D01*X015102Y008820D01*X015064Y008782D01*X015042Y008804D01*X015001Y008827D01*X014956Y008840D01*X014724Y008840D01*X014724Y008412D01*X014628Y008412D01*X014628Y008316D01*X014240Y008316D01*X014240Y008045D01*X014252Y008000D01*X014276Y007959D01*X014310Y007925D01*X014345Y007904D01*X013152Y007904D01*X013064Y007868D01*X012997Y007800D01*X012564Y007368D01*X011375Y007368D01*X011372Y007366D01*X011061Y007366D01*X010968Y007273D01*X010968Y006604D01*X010849Y006604D01*X009625Y007828D01*X011465Y007828D01*X011559Y007922D01*X011559Y012307D01*X011559Y012099D02*X013755Y012099D01*X013758Y011940D02*X011559Y011940D01*X011559Y011782D02*X012096Y011782D01*X012139Y011824D02*X012045Y011731D01*X012045Y011178D01*X012090Y011133D01*X012061Y011105D01*X012037Y011064D01*X012025Y011018D01*X012025Y010809D01*X012605Y010809D01*X012605Y010759D01*X012025Y010759D01*X012025Y010551D01*X012037Y010505D01*X012061Y010464D01*X012090Y010435D01*X012045Y010391D01*X012045Y009838D01*X012104Y009779D01*X012045Y009721D01*X012045Y009168D01*X012104Y009109D01*X012045Y009051D01*X012045Y008498D01*X012139Y008404D01*X013121Y008404D01*X013201Y008484D01*X013324Y008484D01*X013347Y008461D01*X013479Y008406D01*X013621Y008406D01*X013753Y008461D01*X013854Y008561D01*X013908Y008693D01*X013908Y008836D01*X013876Y008913D01*X014986Y008913D01*X015079Y009006D01*X015079Y009887D01*X014986Y009981D01*X013682Y009981D01*X013708Y010043D01*X013708Y010186D01*X013654Y010317D01*X013553Y010418D01*X013421Y010472D01*X013279Y010472D01*X013176Y010430D01*X013170Y010435D01*X013199Y010464D01*X013223Y010505D01*X013235Y010551D01*X013235Y010759D01*X012655Y010759D01*X012655Y010809D01*X013235Y010809D01*X013235Y011018D01*X013223Y011064D01*X013199Y011105D01*X013176Y011128D01*X013229Y011106D01*X013371Y011106D01*X013401Y011118D01*X013401Y011062D01*X014170Y011062D01*X014170Y010902D01*X014330Y010902D01*X014330Y010428D01*X014943Y010428D01*X014989Y010440D01*X015030Y010464D01*X015063Y010498D01*X015087Y010539D01*X015099Y010584D01*X015099Y010902D01*X014330Y010902D01*X014330Y011062D01*X015099Y011062D01*X015099Y011380D01*X015087Y011426D01*X015063Y011467D01*X015030Y011500D01*X014989Y011524D01*X014943Y011536D01*X014330Y011536D01*X014330Y011062D01*X014170Y011062D01*X014170Y011536D01*X013658Y011536D01*X013604Y011667D01*X013503Y011768D01*X013371Y011822D01*X013229Y011822D01*X013154Y011792D01*X013121Y011824D01*X012139Y011824D01*X012045Y011623D02*X011559Y011623D01*X011559Y011465D02*X012045Y011465D01*X012045Y011306D02*X011559Y011306D01*X011559Y011148D02*X012075Y011148D01*X012025Y010989D02*X011559Y010989D01*X011559Y010831D02*X012025Y010831D01*X012025Y010672D02*X011559Y010672D01*X011559Y010514D02*X012035Y010514D01*X012045Y010355D02*X011559Y010355D01*X011559Y010197D02*X012045Y010197D01*X012045Y010038D02*X011559Y010038D01*X011559Y009880D02*X012045Y009880D01*X012046Y009721D02*X011559Y009721D01*X011559Y009563D02*X012045Y009563D01*X012045Y009404D02*X011559Y009404D01*X011559Y009246D02*X012045Y009246D01*X012082Y009087D02*X011559Y009087D01*X011559Y008929D02*X012045Y008929D01*X012045Y008770D02*X011559Y008770D01*X011559Y008612D02*X012045Y008612D01*X012090Y008453D02*X011559Y008453D01*X011559Y008295D02*X014240Y008295D01*X014240Y008412D02*X014628Y008412D01*X014628Y008840D01*X014396Y008840D01*X014351Y008827D01*X014310Y008804D01*X014276Y008770D01*X014252Y008729D01*X014240Y008683D01*X014240Y008412D01*X014240Y008453D02*X013735Y008453D01*X013874Y008612D02*X014240Y008612D01*X014276Y008770D02*X013908Y008770D01*X013365Y008453D02*X013170Y008453D01*X013016Y007819D02*X009634Y007819D01*X009793Y007661D02*X012857Y007661D01*X012699Y007502D02*X009951Y007502D01*X010110Y007344D02*X011039Y007344D01*X010968Y007185D02*X010268Y007185D01*X010427Y007027D02*X010968Y007027D01*X010968Y006868D02*X010585Y006868D01*X010744Y006710D02*X010968Y006710D01*X011423Y007128D02*X012663Y007128D01*X013200Y007664D01*X015250Y007664D01*X015424Y007838D01*X015424Y008364D01*X016750Y008364D01*X017200Y008814D01*X017200Y009464D01*X016817Y009246D02*X015079Y009246D01*X015079Y009404D02*X016763Y009404D01*X016768Y009563D02*X015079Y009563D01*X015079Y009721D02*X016839Y009721D01*X017061Y009880D02*X015079Y009880D01*X015073Y010514D02*X016763Y010514D01*X016772Y010355D02*X013615Y010355D01*X013557Y010428D02*X014170Y010428D01*X014170Y010902D01*X013401Y010902D01*X013401Y010584D01*X013413Y010539D01*X013437Y010498D01*X013470Y010464D01*X013511Y010440D01*X013557Y010428D01*X013427Y010514D02*X013225Y010514D01*X013235Y010672D02*X013401Y010672D01*X013401Y010831D02*X013235Y010831D01*X013235Y010989D02*X014170Y010989D01*X014170Y010831D02*X014330Y010831D01*X014330Y010989D02*X017336Y010989D01*X017452Y010831D02*X017460Y010831D01*X017700Y010964D02*X017200Y011464D01*X016792Y011306D02*X015099Y011306D01*X015099Y011148D02*X016898Y011148D01*X016948Y010831D02*X015099Y010831D01*X015099Y010672D02*X016813Y010672D01*X016849Y010197D02*X013703Y010197D01*X013706Y010038D02*X017086Y010038D01*X017314Y010038D02*X017460Y010038D01*X017460Y009880D02*X017339Y009880D01*X017940Y009588D02*X017960Y009573D01*X018025Y009541D01*X018093Y009518D01*X018164Y009507D01*X018191Y009507D01*X018191Y009956D01*X018209Y009956D01*X018209Y009507D01*X018236Y009507D01*X018307Y009518D01*X018375Y009541D01*X018440Y009573D01*X018460Y009588D01*X018460Y007514D01*X017940Y006994D01*X017940Y009588D01*X017940Y009563D02*X017981Y009563D01*X017940Y009404D02*X018460Y009404D01*X018460Y009246D02*X017940Y009246D01*X017940Y009087D02*X018460Y009087D01*X018460Y008929D02*X017940Y008929D01*X017940Y008770D02*X018460Y008770D01*X018460Y008612D02*X017940Y008612D01*X017940Y008453D02*X018460Y008453D01*X018460Y008295D02*X017940Y008295D01*X017940Y008136D02*X018460Y008136D01*X018460Y007978D02*X017940Y007978D01*X017940Y007819D02*X018460Y007819D01*X018460Y007661D02*X017940Y007661D01*X017940Y007502D02*X018449Y007502D01*X018290Y007344D02*X017940Y007344D01*X017940Y007185D02*X018132Y007185D01*X017973Y007027D02*X017940Y007027D01*X017700Y006814D02*X017700Y010964D01*X017697Y011306D02*X017924Y011306D01*X017952Y011594D02*X018113Y011527D01*X018287Y011527D01*X018448Y011594D01*X018571Y011717D01*X018637Y011877D01*X018637Y012051D01*X018571Y012212D01*X018448Y012335D01*X018287Y012401D01*X018113Y012401D01*X017952Y012335D01*X017829Y012212D01*X017763Y012051D01*X017763Y011877D01*X017829Y011717D01*X017952Y011594D01*X017923Y011623D02*X017607Y011623D01*X017637Y011465D02*X022320Y011465D01*X022320Y011623D02*X020956Y011623D01*X020847Y011594D02*X021132Y011671D01*X021388Y011818D01*X021596Y012027D01*X021744Y012282D01*X021820Y012567D01*X021820Y012862D01*X021744Y013147D01*X021596Y013402D01*X021388Y013611D01*X021132Y013758D01*X020847Y013834D01*X020553Y013834D01*X020268Y013758D01*X020012Y013611D01*X019804Y013402D01*X019656Y013147D01*X019580Y012862D01*X019580Y012567D01*X019656Y012282D01*X019804Y012027D01*X020012Y011818D01*X020268Y011671D01*X020553Y011594D01*X020847Y011594D01*X020444Y011623D02*X018477Y011623D01*X018598Y011782D02*X020075Y011782D01*X019890Y011940D02*X018637Y011940D01*X018617Y012099D02*X019762Y012099D01*X019671Y012257D02*X018525Y012257D01*X017875Y012257D02*X014745Y012257D01*X014745Y012099D02*X017783Y012099D01*X017763Y011940D02*X014742Y011940D01*X014327Y011940D02*X014173Y011940D01*X014173Y012099D02*X014327Y012099D01*X014327Y012257D02*X014173Y012257D01*X014173Y012416D02*X014327Y012416D01*X014327Y012574D02*X014173Y012574D01*X014327Y012733D02*X019580Y012733D01*X019588Y012891D02*X014745Y012891D01*X014745Y013050D02*X019630Y013050D01*X019692Y013208D02*X014745Y013208D01*X014745Y013367D02*X019783Y013367D01*X019927Y013525D02*X014745Y013525D01*X014607Y013684D02*X020139Y013684D01*X021261Y013684D02*X022320Y013684D01*X022320Y013842D02*X010475Y013842D01*X010475Y014001D02*X022320Y014001D01*X022320Y014159D02*X010475Y014159D01*X010475Y014318D02*X022320Y014318D01*X022320Y014476D02*X021308Y014476D01*X021647Y014635D02*X022320Y014635D01*X022320Y014793D02*X021887Y014793D01*X021847Y014751D02*X021847Y014751D01*X022034Y014952D02*X022320Y014952D01*X022320Y015110D02*X022181Y015110D01*X022261Y015269D02*X022320Y015269D01*X020299Y014476D02*X009330Y014476D01*X009330Y014318D02*X009525Y014318D01*X009525Y014159D02*X009330Y014159D01*X009409Y014001D02*X009525Y014001D01*X008935Y013684D02*X006858Y013684D01*X006835Y013842D02*X008797Y013842D01*X008770Y014001D02*X006720Y014001D01*X006496Y014159D02*X008770Y014159D01*X008770Y014318D02*X006540Y014318D01*X006540Y014476D02*X008770Y014476D01*X008770Y014635D02*X006540Y014635D01*X006540Y014793D02*X008770Y014793D01*X008770Y014952D02*X006514Y014952D01*X006385Y015110D02*X008770Y015110D01*X008770Y015269D02*X006544Y015269D01*X006590Y015427D02*X008770Y015427D01*X008770Y015586D02*X006590Y015586D01*X006590Y015744D02*X008770Y015744D01*X008770Y015903D02*X006590Y015903D01*X006590Y016061D02*X008770Y016061D01*X008770Y016220D02*X007479Y016220D01*X007221Y016220D02*X006590Y016220D01*X006715Y016378D02*X006985Y016378D01*X006905Y016537D02*X006795Y016537D01*X006810Y016695D02*X006890Y016695D01*X006890Y016854D02*X006810Y016854D01*X006810Y017012D02*X006890Y017012D01*X006890Y017171D02*X006810Y017171D01*X006810Y017329D02*X006890Y017329D01*X006945Y017488D02*X006755Y017488D01*X006350Y016964D02*X006350Y015414D01*X006100Y015164D01*X006100Y014588D01*X006124Y014564D01*X006000Y014490D01*X006000Y013024D01*X005500Y013024D02*X005500Y014440D01*X005376Y014564D01*X005350Y014590D01*X005350Y016964D01*X004890Y017012D02*X003687Y017012D01*X003688Y017011D02*X003688Y017011D01*X003764Y016854D02*X004890Y016854D01*X004890Y016695D02*X003840Y016695D01*X003905Y016560D02*X003905Y016560D01*X003909Y016537D02*X004905Y016537D01*X004985Y016378D02*X003933Y016378D01*X003957Y016220D02*X005110Y016220D01*X005110Y016061D02*X003980Y016061D01*X003980Y016064D02*X003980Y016064D01*X003956Y015903D02*X005110Y015903D01*X005110Y015744D02*X003932Y015744D01*X003908Y015586D02*X005110Y015586D01*X005110Y015427D02*X003837Y015427D01*X003761Y015269D02*X005110Y015269D01*X005110Y015110D02*X003681Y015110D01*X003688Y015118D02*X003688Y015118D01*X003534Y014952D02*X004986Y014952D01*X004960Y014793D02*X003387Y014793D01*X003347Y014751D02*X003347Y014751D01*X003147Y014635D02*X004960Y014635D01*X004960Y014476D02*X002808Y014476D01*X002914Y014500D02*X002914Y014500D01*X002426Y014389D02*X002426Y014389D01*X001926Y014426D02*X001926Y014426D01*X001799Y014476D02*X000780Y014476D01*X000780Y014318D02*X004960Y014318D01*X005004Y014159D02*X000780Y014159D01*X000780Y014001D02*X005260Y014001D01*X005260Y013842D02*X000780Y013842D01*X000780Y013684D02*X005260Y013684D01*X005000Y013604D02*X005000Y013024D01*X005000Y013604D01*X005000Y013525D02*X005000Y013525D01*X005000Y013367D02*X005000Y013367D01*X005000Y013208D02*X005000Y013208D01*X005000Y013050D02*X005000Y013050D01*X005000Y013024D02*X005000Y013024D01*X005000Y012891D02*X005000Y012891D01*X005000Y012733D02*X005000Y012733D01*X005000Y012574D02*X005000Y012574D01*X003675Y013050D02*X000780Y013050D01*X000780Y013208D02*X003675Y013208D01*X001460Y014609D02*X001460Y014609D01*X001428Y014635D02*X000780Y014635D01*X000780Y014793D02*X001229Y014793D01*X001048Y014952D02*X000780Y014952D01*X000780Y015110D02*X000940Y015110D01*X000832Y015269D02*X000780Y015269D01*X000786Y015335D02*X000786Y015335D01*X003347Y017378D02*X003347Y017378D01*X003392Y017329D02*X004890Y017329D01*X004890Y017171D02*X003539Y017171D01*X003157Y017488D02*X004945Y017488D01*X007755Y017488D02*X008978Y017488D01*X008819Y017329D02*X007810Y017329D01*X007810Y017171D02*X008770Y017171D01*X008770Y017012D02*X007810Y017012D01*X007810Y016854D02*X008770Y016854D01*X008770Y016695D02*X007810Y016695D01*X007795Y016537D02*X008770Y016537D01*X008770Y016378D02*X007715Y016378D01*X009330Y016378D02*X009525Y016378D01*X009525Y016220D02*X009330Y016220D01*X009330Y016061D02*X009525Y016061D01*X009525Y015903D02*X009330Y015903D01*X009330Y015744D02*X009525Y015744D01*X009525Y015586D02*X009330Y015586D01*X009330Y015427D02*X009525Y015427D01*X009676Y015269D02*X009330Y015269D01*X009330Y015110D02*X009642Y015110D01*X009680Y014952D02*X009330Y014952D01*X009330Y014793D02*X009839Y014793D01*X010161Y014793D02*X013933Y014793D01*X013946Y014761D02*X014047Y014661D01*X014179Y014606D01*X014321Y014606D01*X014453Y014661D01*X014554Y014761D01*X014608Y014893D01*X014608Y015036D01*X014557Y015160D01*X014631Y015160D01*X014725Y015254D01*X014725Y016922D01*X014631Y017015D01*X013869Y017015D01*X013775Y016922D01*X013775Y015254D01*X013869Y015160D01*X013943Y015160D01*X013892Y015036D01*X013892Y014893D01*X013946Y014761D01*X013892Y014952D02*X010320Y014952D01*X010358Y015110D02*X013923Y015110D01*X013775Y015269D02*X010324Y015269D01*X010475Y015427D02*X013775Y015427D01*X013775Y015586D02*X010475Y015586D01*X010475Y015744D02*X013775Y015744D01*X013775Y015903D02*X010475Y015903D01*X010475Y016061D02*X013775Y016061D01*X013775Y016220D02*X010494Y016220D01*X010475Y016378D02*X013775Y016378D01*X013775Y016537D02*X010475Y016537D01*X010475Y016695D02*X013775Y016695D01*X013775Y016854D02*X010475Y016854D01*X010475Y017012D02*X013866Y017012D01*X014634Y017012D02*X016406Y017012D01*X016564Y016854D02*X014725Y016854D01*X014725Y016695D02*X016723Y016695D01*X016890Y016537D02*X014725Y016537D01*X014725Y016378D02*X016908Y016378D01*X016994Y016220D02*X014725Y016220D01*X014725Y016061D02*X017242Y016061D01*X017458Y016061D02*X018242Y016061D01*X018258Y016054D02*X018441Y016054D01*X018611Y016124D01*X018740Y016254D01*X018810Y016423D01*X018810Y017206D01*X018740Y017375D01*X018611Y017504D01*X018441Y017574D01*X018258Y017574D01*X018089Y017504D01*X017960Y017375D01*X017890Y017206D01*X017890Y016423D01*X017960Y016254D01*X018089Y016124D01*X018258Y016054D01*X018458Y016061D02*X019139Y016061D01*X019139Y015903D02*X014725Y015903D01*X014725Y015744D02*X019160Y015744D01*X019209Y015586D02*X014725Y015586D01*X014725Y015427D02*X019258Y015427D01*X019332Y015269D02*X014725Y015269D01*X014577Y015110D02*X019440Y015110D01*X019548Y014952D02*X014608Y014952D01*X014567Y014793D02*X019729Y014793D01*X019928Y014635D02*X014390Y014635D01*X014110Y014635D02*X009330Y014635D01*X010000Y015114D02*X010000Y016262D01*X010250Y016214D01*X009525Y016537D02*X009330Y016537D01*X009330Y016695D02*X009525Y016695D01*X009525Y016854D02*X009330Y016854D01*X009330Y017012D02*X009525Y017012D01*X006280Y014001D02*X006240Y014001D01*X006500Y013714D02*X006500Y013024D01*X006790Y013050D02*X009525Y013050D01*X009525Y013208D02*X006790Y013208D01*X006790Y013367D02*X009252Y013367D01*X009093Y013525D02*X006809Y013525D01*X006790Y012891D02*X009525Y012891D01*X009525Y012733D02*X006790Y012733D01*X006790Y012574D02*X009564Y012574D01*X010475Y012891D02*X011417Y012891D01*X011310Y013050D02*X010475Y013050D01*X012630Y011454D02*X013290Y011454D01*X013300Y011464D01*X013622Y011623D02*X016793Y011623D01*X016763Y011465D02*X015064Y011465D01*X014330Y011465D02*X014170Y011465D01*X014170Y011306D02*X014330Y011306D01*X014330Y011148D02*X014170Y011148D01*X014170Y010672D02*X014330Y010672D01*X014330Y010514D02*X014170Y010514D01*X013350Y010114D02*X012630Y010114D01*X013469Y011782D02*X016899Y011782D01*X017501Y011782D02*X017802Y011782D01*X018476Y011306D02*X022320Y011306D01*X022320Y011148D02*X018597Y011148D01*X018637Y010989D02*X022320Y010989D01*X022320Y010831D02*X018673Y010831D01*X018831Y010672D02*X022320Y010672D01*X022320Y010514D02*X018939Y010514D01*X018940Y010355D02*X022320Y010355D01*X022320Y010197D02*X018940Y010197D01*X018940Y010038D02*X022320Y010038D01*X022320Y009880D02*X018940Y009880D01*X018940Y009721D02*X020204Y009721D01*X020268Y009758D02*X020012Y009611D01*X019804Y009402D01*X019656Y009147D01*X019580Y008862D01*X019580Y008567D01*X019656Y008282D01*X019804Y008027D01*X020012Y007818D01*X020268Y007671D01*X020553Y007594D01*X020847Y007594D01*X021132Y007671D01*X021388Y007818D01*X021596Y008027D01*X021744Y008282D01*X021820Y008567D01*X021820Y008862D01*X021744Y009147D01*X021596Y009402D01*X021388Y009611D01*X021132Y009758D01*X020847Y009834D01*X020553Y009834D01*X020268Y009758D01*X019965Y009563D02*X018940Y009563D01*X018940Y009404D02*X019806Y009404D01*X019714Y009246D02*X018940Y009246D01*X018940Y009087D02*X019640Y009087D01*X019598Y008929D02*X018940Y008929D01*X018940Y008770D02*X019580Y008770D01*X019580Y008612D02*X018940Y008612D01*X018940Y008453D02*X019610Y008453D01*X019653Y008295D02*X018940Y008295D01*X018940Y008136D02*X019740Y008136D01*X019853Y007978D02*X018940Y007978D01*X018940Y007819D02*X020011Y007819D01*X020304Y007661D02*X018940Y007661D01*X018940Y007502D02*X022320Y007502D01*X022320Y007344D02*X018931Y007344D01*X018810Y007185D02*X022320Y007185D01*X022320Y007027D02*X018652Y007027D01*X018493Y006868D02*X022320Y006868D01*X022320Y006710D02*X021056Y006710D01*X021547Y006551D02*X022320Y006551D01*X022320Y006393D02*X021821Y006393D01*X021981Y006234D02*X022320Y006234D01*X022320Y006076D02*X022128Y006076D01*X022233Y005917D02*X022320Y005917D01*X022309Y005759D02*X022320Y005759D01*X020528Y006710D02*X018335Y006710D01*X018176Y006551D02*X020042Y006551D01*X019801Y006393D02*X018018Y006393D01*X017859Y006234D02*X019603Y006234D01*X019479Y006076D02*X017701Y006076D01*X017542Y005917D02*X019371Y005917D01*X019276Y005759D02*X017384Y005759D01*X017225Y005600D02*X019227Y005600D01*X019178Y005442D02*X017067Y005442D01*X016908Y005283D02*X019139Y005283D01*X019139Y005125D02*X016738Y005125D01*X016670Y005096D02*X014732Y005096D01*X014732Y003656D01*X014639Y003562D01*X013916Y003562D01*X013822Y003656D01*X013822Y006632D01*X013774Y006632D01*X013703Y006561D01*X013571Y006506D01*X013429Y006506D01*X013297Y006561D01*X013196Y006661D01*X013142Y006793D01*X013142Y006936D01*X013196Y007067D01*X013297Y007168D01*X013429Y007222D01*X013571Y007222D01*X013703Y007168D01*X013759Y007112D01*X013802Y007112D01*X013802Y007128D01*X014277Y007128D01*X014277Y007386D01*X013958Y007386D01*X013912Y007374D01*X013871Y007350D01*X013838Y007317D01*X013814Y007276D01*X013802Y007230D01*X013802Y007128D01*X014277Y007128D01*X014277Y007128D01*X014277Y007128D01*X014277Y007386D01*X014592Y007386D01*X014594Y007388D01*X014635Y007412D01*X014681Y007424D01*X014952Y007424D01*X014952Y007036D01*X015048Y007036D01*X015475Y007036D01*X015475Y007268D01*X015463Y007314D01*X015439Y007355D01*X015406Y007388D01*X015365Y007412D01*X015319Y007424D01*X015048Y007424D01*X015048Y007036D01*X015048Y006940D01*X015475Y006940D01*X015475Y006709D01*X015463Y006663D01*X015439Y006622D01*X015418Y006600D01*X015449Y006569D01*X015579Y006622D01*X015721Y006622D01*X015853Y006568D01*X015954Y006467D01*X016008Y006336D01*X016008Y006193D01*X015954Y006061D01*X015853Y005961D01*X015721Y005906D01*X015579Y005906D01*X015455Y005957D01*X015455Y005918D01*X015369Y005832D01*X016379Y005832D01*X017460Y006914D01*X017460Y009106D01*X017448Y009094D01*X017440Y009091D01*X017440Y008767D01*X017403Y008678D01*X017336Y008611D01*X016886Y008161D01*X016798Y008124D01*X015840Y008124D01*X015840Y008003D01*X015746Y007909D01*X015664Y007909D01*X015664Y007791D01*X015627Y007702D01*X015453Y007528D01*X015453Y007528D01*X015386Y007461D01*X015298Y007424D01*X013299Y007424D01*X012799Y006924D01*X012711Y006888D01*X011878Y006888D01*X011878Y005599D01*X011897Y005618D01*X012029Y005672D01*X012171Y005672D01*X012303Y005618D01*X012404Y005517D01*X012458Y005386D01*X012458Y005243D01*X012404Y005111D01*X012303Y005011D01*X012171Y004956D01*X012029Y004956D01*X011897Y005011D01*X011878Y005030D01*X011878Y004218D01*X011886Y004205D01*X011898Y004159D01*X011898Y004057D01*X011423Y004057D01*X011423Y004057D01*X011898Y004057D01*X011898Y003954D01*X011886Y003909D01*X011878Y003895D01*X011878Y003656D01*X011784Y003562D01*X011061Y003562D01*X011014Y003610D01*X010999Y003601D01*X010954Y003589D01*X010722Y003589D01*X010722Y004016D01*X010626Y004016D01*X010626Y003589D01*X010394Y003589D01*X010349Y003601D01*X010308Y003625D01*X010286Y003647D01*X010248Y003609D01*X009604Y003609D01*X009510Y003703D01*X009510Y003818D01*X009453Y003761D01*X009321Y003706D01*X009179Y003706D01*X009053Y003758D01*X009053Y003698D02*X009515Y003698D01*X009250Y004064D02*X009926Y004064D01*X010286Y004482D02*X010254Y004514D01*X010265Y004517D01*X010306Y004540D01*X010339Y004574D01*X010363Y004615D01*X010375Y004661D01*X010375Y004892D01*X009948Y004892D01*X009948Y004988D01*X010375Y004988D01*X010375Y005220D01*X010363Y005266D01*X010339Y005307D01*X010318Y005328D01*X010355Y005366D01*X010355Y005608D01*X010968Y005608D01*X010968Y005481D01*X010968Y004536D01*X010954Y004540D01*X010722Y004540D01*X010722Y004112D01*X010948Y004112D01*X010948Y004057D01*X011423Y004057D01*X011406Y004040D01*X010674Y004064D01*X010722Y004016D02*X010722Y004112D01*X010626Y004112D01*X010626Y004540D01*X010394Y004540D01*X010349Y004527D01*X010308Y004504D01*X010286Y004482D01*X010277Y004491D02*X010295Y004491D01*X010372Y004649D02*X010968Y004649D01*X010968Y004808D02*X010375Y004808D01*X010375Y005125D02*X010968Y005125D01*X010968Y005283D02*X010353Y005283D01*X010355Y005442D02*X010968Y005442D01*X010968Y005600D02*X010355Y005600D01*X010060Y005848D02*X009900Y005688D01*X009324Y005688D01*X009200Y005564D01*X009200Y005064D01*X009000Y004864D01*X008696Y004864D01*X009108Y004649D02*X009428Y004649D01*X009425Y004808D02*X009283Y004808D01*X009419Y004966D02*X009852Y004966D01*X009948Y004966D02*X010968Y004966D01*X011423Y005336D02*X011445Y005314D01*X012100Y005314D01*X011880Y005600D02*X011878Y005600D01*X011878Y005759D02*X013822Y005759D01*X013822Y005917D02*X011878Y005917D01*X011878Y006076D02*X013822Y006076D01*X013822Y006234D02*X011878Y006234D01*X011878Y006393D02*X013822Y006393D01*X013822Y006551D02*X013680Y006551D01*X013320Y006551D02*X011878Y006551D01*X011878Y006710D02*X013176Y006710D01*X013142Y006868D02*X011878Y006868D01*X012902Y007027D02*X013180Y007027D01*X013060Y007185D02*X013339Y007185D01*X013219Y007344D02*X013865Y007344D01*X013802Y007185D02*X013661Y007185D01*X013507Y006872D02*X013500Y006864D01*X013507Y006872D02*X014277Y006872D01*X014277Y007128D02*X014861Y007128D01*X015000Y006988D01*X015048Y007027D02*X017460Y007027D01*X017460Y007185D02*X015475Y007185D01*X015446Y007344D02*X017460Y007344D01*X017460Y007502D02*X015427Y007502D01*X015586Y007661D02*X017460Y007661D01*X017460Y007819D02*X015664Y007819D01*X015815Y007978D02*X017460Y007978D01*X017460Y008136D02*X016827Y008136D01*X017020Y008295D02*X017460Y008295D01*X017460Y008453D02*X017178Y008453D01*X017337Y008612D02*X017460Y008612D01*X017460Y008770D02*X017440Y008770D01*X017440Y008929D02*X017460Y008929D01*X017460Y009087D02*X017440Y009087D01*X016960Y009087D02*X015079Y009087D01*X015002Y008929D02*X016960Y008929D01*X016817Y008770D02*X015795Y008770D01*X015840Y008612D02*X016658Y008612D01*X018191Y009563D02*X018209Y009563D01*X018209Y009721D02*X018191Y009721D01*X018191Y009880D02*X018209Y009880D01*X018209Y009973D02*X018191Y009973D01*X018191Y010421D01*X018164Y010421D01*X018093Y010410D01*X018025Y010388D01*X017960Y010355D01*X017940Y010341D01*X017940Y010606D01*X017952Y010594D01*X018113Y010527D01*X018287Y010527D01*X018295Y010530D01*X018460Y010365D01*X018460Y010341D01*X018440Y010355D01*X018375Y010388D01*X018307Y010410D01*X018236Y010421D01*X018209Y010421D01*X018209Y009973D01*X018209Y010038D02*X018191Y010038D01*X018191Y010197D02*X018209Y010197D01*X018209Y010355D02*X018191Y010355D01*X018311Y010514D02*X017940Y010514D01*X017940Y010355D02*X017960Y010355D01*X018440Y010355D02*X018460Y010355D01*X018700Y010464D02*X018200Y010964D01*X018700Y010464D02*X018700Y007414D01*X016622Y005336D01*X014277Y005336D01*X014277Y005592D02*X016478Y005592D01*X017700Y006814D01*X017415Y006868D02*X015475Y006868D01*X015475Y006710D02*X017256Y006710D01*X017098Y006551D02*X015869Y006551D01*X015984Y006393D02*X016939Y006393D01*X016781Y006234D02*X016008Y006234D01*X015960Y006076D02*X016622Y006076D01*X016464Y005917D02*X015748Y005917D01*X015552Y005917D02*X015454Y005917D01*X015650Y006264D02*X015024Y006264D01*X015000Y006240D01*X014952Y007185D02*X015048Y007185D01*X015048Y007344D02*X014952Y007344D01*X014277Y007344D02*X014277Y007344D01*X014277Y007185D02*X014277Y007185D01*X014265Y007978D02*X011559Y007978D01*X011559Y008136D02*X014240Y008136D01*X014628Y008453D02*X014724Y008453D01*X014724Y008612D02*X014628Y008612D01*X014628Y008770D02*X014724Y008770D01*X018419Y009563D02*X018460Y009563D01*X021196Y009721D02*X022320Y009721D01*X022320Y009563D02*X021435Y009563D01*X021594Y009404D02*X022320Y009404D01*X022320Y009246D02*X021686Y009246D01*X021760Y009087D02*X022320Y009087D01*X022320Y008929D02*X021802Y008929D01*X021820Y008770D02*X022320Y008770D01*X022320Y008612D02*X021820Y008612D01*X021790Y008453D02*X022320Y008453D01*X022320Y008295D02*X021747Y008295D01*X021660Y008136D02*X022320Y008136D01*X022320Y007978D02*X021547Y007978D01*X021389Y007819D02*X022320Y007819D01*X022320Y007661D02*X021096Y007661D01*X019139Y004966D02*X018618Y004966D01*X018710Y004808D02*X019141Y004808D01*X019190Y004649D02*X018710Y004649D01*X017201Y004966D02*X014732Y004966D01*X014732Y004808D02*X014987Y004808D01*X013822Y004808D02*X011878Y004808D01*X011878Y004966D02*X012004Y004966D01*X012196Y004966D02*X013822Y004966D01*X013822Y005125D02*X012409Y005125D01*X012458Y005283D02*X013822Y005283D01*X013822Y005442D02*X012435Y005442D01*X012320Y005600D02*X013822Y005600D01*X013822Y004649D02*X011878Y004649D01*X011878Y004491D02*X013822Y004491D01*X013822Y004332D02*X011878Y004332D01*X011894Y004174D02*X013822Y004174D01*X013822Y004015D02*X011898Y004015D01*X011878Y003857D02*X013822Y003857D01*X013822Y003698D02*X011878Y003698D01*X011423Y004057D02*X010948Y004057D01*X010948Y004016D01*X010722Y004016D01*X010722Y004015D02*X010626Y004015D01*X010626Y003857D02*X010722Y003857D01*X010722Y003698D02*X010626Y003698D01*X010626Y004174D02*X010722Y004174D01*X010722Y004332D02*X010626Y004332D01*X010626Y004491D02*X010722Y004491D01*X011423Y004057D02*X011423Y004057D01*X011423Y005848D02*X010060Y005848D01*X009890Y005848D02*X009900Y005688D01*X009510Y006076D02*X009053Y006076D01*X009053Y005917D02*X009250Y005917D01*X009055Y005759D02*X009053Y005759D01*X009000Y006234D02*X010191Y006234D01*X010032Y006393D02*X004790Y006393D01*X004566Y005759D02*X004540Y005759D01*X004300Y005314D02*X004300Y008064D01*X003800Y008564D01*X004300Y005314D02*X004700Y004914D01*X004954Y004914D01*X005004Y004864D01*X002964Y003550D02*X002964Y003550D01*X008678Y006551D02*X008715Y006551D01*X008715Y006484D02*X008917Y006484D01*X008963Y006497D01*X009004Y006520D01*X009037Y006554D01*X009061Y006595D01*X009073Y006641D01*X009073Y006896D01*X008715Y006896D01*X008715Y006484D01*X008715Y006710D02*X008678Y006710D01*X008678Y006868D02*X008715Y006868D01*X009073Y006868D02*X009557Y006868D01*X009715Y006710D02*X009073Y006710D01*X009035Y006551D02*X009874Y006551D01*X009398Y007027D02*X009073Y007027D01*X014745Y012416D02*X019620Y012416D01*X019580Y012574D02*X014745Y012574D01*X014250Y014964D02*X014250Y016088D01*X016722Y017488D02*X017073Y017488D01*X016941Y017329D02*X016881Y017329D01*X017627Y017488D02*X018073Y017488D01*X017941Y017329D02*X017759Y017329D01*X017810Y017171D02*X017890Y017171D01*X017890Y017012D02*X017810Y017012D01*X017810Y016854D02*X017890Y016854D01*X017890Y016695D02*X017810Y016695D01*X017810Y016537D02*X017890Y016537D01*X017908Y016378D02*X017792Y016378D01*X017706Y016220D02*X017994Y016220D01*X018706Y016220D02*X019139Y016220D01*X019158Y016378D02*X018792Y016378D01*X018810Y016537D02*X019207Y016537D01*X019256Y016695D02*X018810Y016695D01*X018810Y016854D02*X019328Y016854D01*X019436Y017012D02*X018810Y017012D01*X018810Y017171D02*X019544Y017171D01*X019722Y017329D02*X018759Y017329D01*X018627Y017488D02*X019921Y017488D01*X021473Y013525D02*X022320Y013525D01*X022320Y013367D02*X021617Y013367D01*X021708Y013208D02*X022320Y013208D01*X022320Y013050D02*X021770Y013050D01*X021812Y012891D02*X022320Y012891D01*X022320Y012733D02*X021820Y012733D01*X021820Y012574D02*X022320Y012574D01*X022320Y012416D02*X021780Y012416D01*X021729Y012257D02*X022320Y012257D01*X022320Y012099D02*X021638Y012099D01*X021510Y011940D02*X022320Y011940D01*X022320Y011782D02*X021325Y011782D01*X017110Y004808D02*X017010Y004808D01*X016972Y004174D02*X017110Y004174D01*X016255Y004174D02*X016145Y004174D01*X016183Y004332D02*X016217Y004332D01*X000856Y012257D02*X000780Y012257D01*X000780Y012891D02*X000876Y012891D01*D26*X004150Y011564D03*X006500Y013714D03*X010000Y015114D03*X011650Y013164D03*X013300Y011464D03*X013350Y010114D03*X013550Y008764D03*X013500Y006864D03*X012100Y005314D03*X009250Y004064D03*X015200Y004514D03*X015650Y006264D03*X015850Y009914D03*X014250Y014964D03*D27*X011650Y013164D02*X011348Y013467D01*X010000Y013467D01*X009952Y013514D01*X009500Y013514D01*X009050Y013964D01*X009050Y017164D01*X009300Y017414D01*X016400Y017414D01*X017000Y016814D01*X017350Y016814D01*X014250Y010982D02*X014052Y010784D01*X012630Y010784D01*X012632Y009447D02*X012630Y009444D01*X012632Y009447D02*X014250Y009447D01*X013550Y008764D02*X012640Y008764D01*X012630Y008774D01*M02*
diff --git a/gerber/tests/resources/top_mask.GTS b/gerber/tests/resources/top_mask.GTS
deleted file mode 100644
index a3886f5..0000000
--- a/gerber/tests/resources/top_mask.GTS
+++ /dev/null
@@ -1,162 +0,0 @@
-G75*
-%MOIN*%
-%OFA0B0*%
-%FSLAX24Y24*%
-%IPPOS*%
-%LPD*%
-%AMOC8*
-5,1,8,0,0,1.08239X$1,22.5*
-%
-%ADD10R,0.0340X0.0880*%
-%ADD11R,0.0671X0.0237*%
-%ADD12R,0.4178X0.4332*%
-%ADD13R,0.0930X0.0500*%
-%ADD14R,0.0710X0.1655*%
-%ADD15R,0.0671X0.0592*%
-%ADD16R,0.0592X0.0671*%
-%ADD17R,0.0710X0.1615*%
-%ADD18R,0.1419X0.0828*%
-%ADD19C,0.0634*%
-%ADD20C,0.1360*%
-%ADD21R,0.0474X0.0580*%
-%ADD22C,0.0680*%
-%ADD23R,0.0552X0.0552*%
-%ADD24C,0.1340*%
-%ADD25C,0.0476*%
-D10*
-X005000Y010604D03*
-X005500Y010604D03*
-X006000Y010604D03*
-X006500Y010604D03*
-X006500Y013024D03*
-X006000Y013024D03*
-X005500Y013024D03*
-X005000Y013024D03*
-D11*
-X011423Y007128D03*
-X011423Y006872D03*
-X011423Y006616D03*
-X011423Y006360D03*
-X011423Y006104D03*
-X011423Y005848D03*
-X011423Y005592D03*
-X011423Y005336D03*
-X011423Y005080D03*
-X011423Y004825D03*
-X011423Y004569D03*
-X011423Y004313D03*
-X011423Y004057D03*
-X011423Y003801D03*
-X014277Y003801D03*
-X014277Y004057D03*
-X014277Y004313D03*
-X014277Y004569D03*
-X014277Y004825D03*
-X014277Y005080D03*
-X014277Y005336D03*
-X014277Y005592D03*
-X014277Y005848D03*
-X014277Y006104D03*
-X014277Y006360D03*
-X014277Y006616D03*
-X014277Y006872D03*
-X014277Y007128D03*
-D12*
-X009350Y010114D03*
-D13*
-X012630Y010114D03*
-X012630Y010784D03*
-X012630Y011454D03*
-X012630Y009444D03*
-X012630Y008774D03*
-D14*
-X010000Y013467D03*
-X010000Y016262D03*
-D15*
-X004150Y012988D03*
-X004150Y012240D03*
-X009900Y005688D03*
-X009900Y004940D03*
-X015000Y006240D03*
-X015000Y006988D03*
-D16*
-X014676Y008364D03*
-X015424Y008364D03*
-X017526Y004514D03*
-X018274Y004514D03*
-X010674Y004064D03*
-X009926Y004064D03*
-X004174Y009564D03*
-X003426Y009564D03*
-X005376Y014564D03*
-X006124Y014564D03*
-D17*
-X014250Y016088D03*
-X014250Y012741D03*
-D18*
-X014250Y010982D03*
-X014250Y009447D03*
-D19*
-X017200Y009464D03*
-X018200Y009964D03*
-X018200Y010964D03*
-X017200Y010464D03*
-X017200Y011464D03*
-X018200Y011964D03*
-D20*
-X020700Y012714D03*
-X020700Y008714D03*
-D21*
-X005004Y003814D03*
-X005004Y004864D03*
-X005004Y005864D03*
-X005004Y006914D03*
-X008696Y006914D03*
-X008696Y005864D03*
-X008696Y004864D03*
-X008696Y003814D03*
-D22*
-X001800Y008564D02*
-X001200Y008564D01*
-X001200Y009564D02*
-X001800Y009564D01*
-X001800Y010564D02*
-X001200Y010564D01*
-X001200Y011564D02*
-X001800Y011564D01*
-X001800Y012564D02*
-X001200Y012564D01*
-X005350Y016664D02*
-X005350Y017264D01*
-X006350Y017264D02*
-X006350Y016664D01*
-X007350Y016664D02*
-X007350Y017264D01*
-X017350Y017114D02*
-X017350Y016514D01*
-X018350Y016514D02*
-X018350Y017114D01*
-D23*
-X016613Y004514D03*
-X015787Y004514D03*
-D24*
-X020800Y005064D03*
-X020800Y016064D03*
-X002300Y016064D03*
-X002350Y005114D03*
-D25*
-X009250Y004064D03*
-X012100Y005314D03*
-X013500Y006864D03*
-X015650Y006264D03*
-X015200Y004514D03*
-X013550Y008764D03*
-X013350Y010114D03*
-X013300Y011464D03*
-X011650Y013164D03*
-X010000Y015114D03*
-X006500Y013714D03*
-X004150Y011564D03*
-X014250Y014964D03*
-X015850Y009914D03*
-M02*
diff --git a/gerber/tests/resources/top_silk.GTO b/gerber/tests/resources/top_silk.GTO
deleted file mode 100644
index ea46f80..0000000
--- a/gerber/tests/resources/top_silk.GTO
+++ /dev/null
@@ -1,2099 +0,0 @@
-G75*
-%MOIN*%
-%OFA0B0*%
-%FSLAX24Y24*%
-%IPPOS*%
-%LPD*%
-%AMOC8*
-5,1,8,0,0,1.08239X$1,22.5*
-%
-%ADD10C,0.0000*%
-%ADD11C,0.0060*%
-%ADD12C,0.0020*%
-%ADD13C,0.0050*%
-%ADD14C,0.0080*%
-%ADD15C,0.0040*%
-%ADD16R,0.0660X0.0380*%
-%ADD17C,0.0030*%
-%ADD18C,0.0004*%
-%ADD19R,0.0450X0.0364*%
-%ADD20C,0.0025*%
-%ADD21C,0.0098*%
-D10*
-X000300Y003064D02*
-X000300Y018064D01*
-X022800Y018064D01*
-X022800Y003064D01*
-X000300Y003064D01*
-X001720Y005114D02*
-X001722Y005164D01*
-X001728Y005214D01*
-X001738Y005263D01*
-X001752Y005311D01*
-X001769Y005358D01*
-X001790Y005403D01*
-X001815Y005447D01*
-X001843Y005488D01*
-X001875Y005527D01*
-X001909Y005564D01*
-X001946Y005598D01*
-X001986Y005628D01*
-X002028Y005655D01*
-X002072Y005679D01*
-X002118Y005700D01*
-X002165Y005716D01*
-X002213Y005729D01*
-X002263Y005738D01*
-X002312Y005743D01*
-X002363Y005744D01*
-X002413Y005741D01*
-X002462Y005734D01*
-X002511Y005723D01*
-X002559Y005708D01*
-X002605Y005690D01*
-X002650Y005668D01*
-X002693Y005642D01*
-X002734Y005613D01*
-X002773Y005581D01*
-X002809Y005546D01*
-X002841Y005508D01*
-X002871Y005468D01*
-X002898Y005425D01*
-X002921Y005381D01*
-X002940Y005335D01*
-X002956Y005287D01*
-X002968Y005238D01*
-X002976Y005189D01*
-X002980Y005139D01*
-X002980Y005089D01*
-X002976Y005039D01*
-X002968Y004990D01*
-X002956Y004941D01*
-X002940Y004893D01*
-X002921Y004847D01*
-X002898Y004803D01*
-X002871Y004760D01*
-X002841Y004720D01*
-X002809Y004682D01*
-X002773Y004647D01*
-X002734Y004615D01*
-X002693Y004586D01*
-X002650Y004560D01*
-X002605Y004538D01*
-X002559Y004520D01*
-X002511Y004505D01*
-X002462Y004494D01*
-X002413Y004487D01*
-X002363Y004484D01*
-X002312Y004485D01*
-X002263Y004490D01*
-X002213Y004499D01*
-X002165Y004512D01*
-X002118Y004528D01*
-X002072Y004549D01*
-X002028Y004573D01*
-X001986Y004600D01*
-X001946Y004630D01*
-X001909Y004664D01*
-X001875Y004701D01*
-X001843Y004740D01*
-X001815Y004781D01*
-X001790Y004825D01*
-X001769Y004870D01*
-X001752Y004917D01*
-X001738Y004965D01*
-X001728Y005014D01*
-X001722Y005064D01*
-X001720Y005114D01*
-X001670Y016064D02*
-X001672Y016114D01*
-X001678Y016164D01*
-X001688Y016213D01*
-X001702Y016261D01*
-X001719Y016308D01*
-X001740Y016353D01*
-X001765Y016397D01*
-X001793Y016438D01*
-X001825Y016477D01*
-X001859Y016514D01*
-X001896Y016548D01*
-X001936Y016578D01*
-X001978Y016605D01*
-X002022Y016629D01*
-X002068Y016650D01*
-X002115Y016666D01*
-X002163Y016679D01*
-X002213Y016688D01*
-X002262Y016693D01*
-X002313Y016694D01*
-X002363Y016691D01*
-X002412Y016684D01*
-X002461Y016673D01*
-X002509Y016658D01*
-X002555Y016640D01*
-X002600Y016618D01*
-X002643Y016592D01*
-X002684Y016563D01*
-X002723Y016531D01*
-X002759Y016496D01*
-X002791Y016458D01*
-X002821Y016418D01*
-X002848Y016375D01*
-X002871Y016331D01*
-X002890Y016285D01*
-X002906Y016237D01*
-X002918Y016188D01*
-X002926Y016139D01*
-X002930Y016089D01*
-X002930Y016039D01*
-X002926Y015989D01*
-X002918Y015940D01*
-X002906Y015891D01*
-X002890Y015843D01*
-X002871Y015797D01*
-X002848Y015753D01*
-X002821Y015710D01*
-X002791Y015670D01*
-X002759Y015632D01*
-X002723Y015597D01*
-X002684Y015565D01*
-X002643Y015536D01*
-X002600Y015510D01*
-X002555Y015488D01*
-X002509Y015470D01*
-X002461Y015455D01*
-X002412Y015444D01*
-X002363Y015437D01*
-X002313Y015434D01*
-X002262Y015435D01*
-X002213Y015440D01*
-X002163Y015449D01*
-X002115Y015462D01*
-X002068Y015478D01*
-X002022Y015499D01*
-X001978Y015523D01*
-X001936Y015550D01*
-X001896Y015580D01*
-X001859Y015614D01*
-X001825Y015651D01*
-X001793Y015690D01*
-X001765Y015731D01*
-X001740Y015775D01*
-X001719Y015820D01*
-X001702Y015867D01*
-X001688Y015915D01*
-X001678Y015964D01*
-X001672Y016014D01*
-X001670Y016064D01*
-X020060Y012714D02*
-X020062Y012764D01*
-X020068Y012814D01*
-X020078Y012863D01*
-X020091Y012912D01*
-X020109Y012959D01*
-X020130Y013005D01*
-X020154Y013048D01*
-X020182Y013090D01*
-X020213Y013130D01*
-X020247Y013167D01*
-X020284Y013201D01*
-X020324Y013232D01*
-X020366Y013260D01*
-X020409Y013284D01*
-X020455Y013305D01*
-X020502Y013323D01*
-X020551Y013336D01*
-X020600Y013346D01*
-X020650Y013352D01*
-X020700Y013354D01*
-X020750Y013352D01*
-X020800Y013346D01*
-X020849Y013336D01*
-X020898Y013323D01*
-X020945Y013305D01*
-X020991Y013284D01*
-X021034Y013260D01*
-X021076Y013232D01*
-X021116Y013201D01*
-X021153Y013167D01*
-X021187Y013130D01*
-X021218Y013090D01*
-X021246Y013048D01*
-X021270Y013005D01*
-X021291Y012959D01*
-X021309Y012912D01*
-X021322Y012863D01*
-X021332Y012814D01*
-X021338Y012764D01*
-X021340Y012714D01*
-X021338Y012664D01*
-X021332Y012614D01*
-X021322Y012565D01*
-X021309Y012516D01*
-X021291Y012469D01*
-X021270Y012423D01*
-X021246Y012380D01*
-X021218Y012338D01*
-X021187Y012298D01*
-X021153Y012261D01*
-X021116Y012227D01*
-X021076Y012196D01*
-X021034Y012168D01*
-X020991Y012144D01*
-X020945Y012123D01*
-X020898Y012105D01*
-X020849Y012092D01*
-X020800Y012082D01*
-X020750Y012076D01*
-X020700Y012074D01*
-X020650Y012076D01*
-X020600Y012082D01*
-X020551Y012092D01*
-X020502Y012105D01*
-X020455Y012123D01*
-X020409Y012144D01*
-X020366Y012168D01*
-X020324Y012196D01*
-X020284Y012227D01*
-X020247Y012261D01*
-X020213Y012298D01*
-X020182Y012338D01*
-X020154Y012380D01*
-X020130Y012423D01*
-X020109Y012469D01*
-X020091Y012516D01*
-X020078Y012565D01*
-X020068Y012614D01*
-X020062Y012664D01*
-X020060Y012714D01*
-X020170Y016064D02*
-X020172Y016114D01*
-X020178Y016164D01*
-X020188Y016213D01*
-X020202Y016261D01*
-X020219Y016308D01*
-X020240Y016353D01*
-X020265Y016397D01*
-X020293Y016438D01*
-X020325Y016477D01*
-X020359Y016514D01*
-X020396Y016548D01*
-X020436Y016578D01*
-X020478Y016605D01*
-X020522Y016629D01*
-X020568Y016650D01*
-X020615Y016666D01*
-X020663Y016679D01*
-X020713Y016688D01*
-X020762Y016693D01*
-X020813Y016694D01*
-X020863Y016691D01*
-X020912Y016684D01*
-X020961Y016673D01*
-X021009Y016658D01*
-X021055Y016640D01*
-X021100Y016618D01*
-X021143Y016592D01*
-X021184Y016563D01*
-X021223Y016531D01*
-X021259Y016496D01*
-X021291Y016458D01*
-X021321Y016418D01*
-X021348Y016375D01*
-X021371Y016331D01*
-X021390Y016285D01*
-X021406Y016237D01*
-X021418Y016188D01*
-X021426Y016139D01*
-X021430Y016089D01*
-X021430Y016039D01*
-X021426Y015989D01*
-X021418Y015940D01*
-X021406Y015891D01*
-X021390Y015843D01*
-X021371Y015797D01*
-X021348Y015753D01*
-X021321Y015710D01*
-X021291Y015670D01*
-X021259Y015632D01*
-X021223Y015597D01*
-X021184Y015565D01*
-X021143Y015536D01*
-X021100Y015510D01*
-X021055Y015488D01*
-X021009Y015470D01*
-X020961Y015455D01*
-X020912Y015444D01*
-X020863Y015437D01*
-X020813Y015434D01*
-X020762Y015435D01*
-X020713Y015440D01*
-X020663Y015449D01*
-X020615Y015462D01*
-X020568Y015478D01*
-X020522Y015499D01*
-X020478Y015523D01*
-X020436Y015550D01*
-X020396Y015580D01*
-X020359Y015614D01*
-X020325Y015651D01*
-X020293Y015690D01*
-X020265Y015731D01*
-X020240Y015775D01*
-X020219Y015820D01*
-X020202Y015867D01*
-X020188Y015915D01*
-X020178Y015964D01*
-X020172Y016014D01*
-X020170Y016064D01*
-X020060Y008714D02*
-X020062Y008764D01*
-X020068Y008814D01*
-X020078Y008863D01*
-X020091Y008912D01*
-X020109Y008959D01*
-X020130Y009005D01*
-X020154Y009048D01*
-X020182Y009090D01*
-X020213Y009130D01*
-X020247Y009167D01*
-X020284Y009201D01*
-X020324Y009232D01*
-X020366Y009260D01*
-X020409Y009284D01*
-X020455Y009305D01*
-X020502Y009323D01*
-X020551Y009336D01*
-X020600Y009346D01*
-X020650Y009352D01*
-X020700Y009354D01*
-X020750Y009352D01*
-X020800Y009346D01*
-X020849Y009336D01*
-X020898Y009323D01*
-X020945Y009305D01*
-X020991Y009284D01*
-X021034Y009260D01*
-X021076Y009232D01*
-X021116Y009201D01*
-X021153Y009167D01*
-X021187Y009130D01*
-X021218Y009090D01*
-X021246Y009048D01*
-X021270Y009005D01*
-X021291Y008959D01*
-X021309Y008912D01*
-X021322Y008863D01*
-X021332Y008814D01*
-X021338Y008764D01*
-X021340Y008714D01*
-X021338Y008664D01*
-X021332Y008614D01*
-X021322Y008565D01*
-X021309Y008516D01*
-X021291Y008469D01*
-X021270Y008423D01*
-X021246Y008380D01*
-X021218Y008338D01*
-X021187Y008298D01*
-X021153Y008261D01*
-X021116Y008227D01*
-X021076Y008196D01*
-X021034Y008168D01*
-X020991Y008144D01*
-X020945Y008123D01*
-X020898Y008105D01*
-X020849Y008092D01*
-X020800Y008082D01*
-X020750Y008076D01*
-X020700Y008074D01*
-X020650Y008076D01*
-X020600Y008082D01*
-X020551Y008092D01*
-X020502Y008105D01*
-X020455Y008123D01*
-X020409Y008144D01*
-X020366Y008168D01*
-X020324Y008196D01*
-X020284Y008227D01*
-X020247Y008261D01*
-X020213Y008298D01*
-X020182Y008338D01*
-X020154Y008380D01*
-X020130Y008423D01*
-X020109Y008469D01*
-X020091Y008516D01*
-X020078Y008565D01*
-X020068Y008614D01*
-X020062Y008664D01*
-X020060Y008714D01*
-X020170Y005064D02*
-X020172Y005114D01*
-X020178Y005164D01*
-X020188Y005213D01*
-X020202Y005261D01*
-X020219Y005308D01*
-X020240Y005353D01*
-X020265Y005397D01*
-X020293Y005438D01*
-X020325Y005477D01*
-X020359Y005514D01*
-X020396Y005548D01*
-X020436Y005578D01*
-X020478Y005605D01*
-X020522Y005629D01*
-X020568Y005650D01*
-X020615Y005666D01*
-X020663Y005679D01*
-X020713Y005688D01*
-X020762Y005693D01*
-X020813Y005694D01*
-X020863Y005691D01*
-X020912Y005684D01*
-X020961Y005673D01*
-X021009Y005658D01*
-X021055Y005640D01*
-X021100Y005618D01*
-X021143Y005592D01*
-X021184Y005563D01*
-X021223Y005531D01*
-X021259Y005496D01*
-X021291Y005458D01*
-X021321Y005418D01*
-X021348Y005375D01*
-X021371Y005331D01*
-X021390Y005285D01*
-X021406Y005237D01*
-X021418Y005188D01*
-X021426Y005139D01*
-X021430Y005089D01*
-X021430Y005039D01*
-X021426Y004989D01*
-X021418Y004940D01*
-X021406Y004891D01*
-X021390Y004843D01*
-X021371Y004797D01*
-X021348Y004753D01*
-X021321Y004710D01*
-X021291Y004670D01*
-X021259Y004632D01*
-X021223Y004597D01*
-X021184Y004565D01*
-X021143Y004536D01*
-X021100Y004510D01*
-X021055Y004488D01*
-X021009Y004470D01*
-X020961Y004455D01*
-X020912Y004444D01*
-X020863Y004437D01*
-X020813Y004434D01*
-X020762Y004435D01*
-X020713Y004440D01*
-X020663Y004449D01*
-X020615Y004462D01*
-X020568Y004478D01*
-X020522Y004499D01*
-X020478Y004523D01*
-X020436Y004550D01*
-X020396Y004580D01*
-X020359Y004614D01*
-X020325Y004651D01*
-X020293Y004690D01*
-X020265Y004731D01*
-X020240Y004775D01*
-X020219Y004820D01*
-X020202Y004867D01*
-X020188Y004915D01*
-X020178Y004964D01*
-X020172Y005014D01*
-X020170Y005064D01*
-D11*
-X019450Y005064D02*
-X019452Y005137D01*
-X019458Y005210D01*
-X019468Y005282D01*
-X019482Y005354D01*
-X019499Y005425D01*
-X019521Y005495D01*
-X019546Y005564D01*
-X019575Y005631D01*
-X019607Y005696D01*
-X019643Y005760D01*
-X019683Y005822D01*
-X019725Y005881D01*
-X019771Y005938D01*
-X019820Y005992D01*
-X019872Y006044D01*
-X019926Y006093D01*
-X019983Y006139D01*
-X020042Y006181D01*
-X020104Y006221D01*
-X020168Y006257D01*
-X020233Y006289D01*
-X020300Y006318D01*
-X020369Y006343D01*
-X020439Y006365D01*
-X020510Y006382D01*
-X020582Y006396D01*
-X020654Y006406D01*
-X020727Y006412D01*
-X020800Y006414D01*
-X020873Y006412D01*
-X020946Y006406D01*
-X021018Y006396D01*
-X021090Y006382D01*
-X021161Y006365D01*
-X021231Y006343D01*
-X021300Y006318D01*
-X021367Y006289D01*
-X021432Y006257D01*
-X021496Y006221D01*
-X021558Y006181D01*
-X021617Y006139D01*
-X021674Y006093D01*
-X021728Y006044D01*
-X021780Y005992D01*
-X021829Y005938D01*
-X021875Y005881D01*
-X021917Y005822D01*
-X021957Y005760D01*
-X021993Y005696D01*
-X022025Y005631D01*
-X022054Y005564D01*
-X022079Y005495D01*
-X022101Y005425D01*
-X022118Y005354D01*
-X022132Y005282D01*
-X022142Y005210D01*
-X022148Y005137D01*
-X022150Y005064D01*
-X022148Y004991D01*
-X022142Y004918D01*
-X022132Y004846D01*
-X022118Y004774D01*
-X022101Y004703D01*
-X022079Y004633D01*
-X022054Y004564D01*
-X022025Y004497D01*
-X021993Y004432D01*
-X021957Y004368D01*
-X021917Y004306D01*
-X021875Y004247D01*
-X021829Y004190D01*
-X021780Y004136D01*
-X021728Y004084D01*
-X021674Y004035D01*
-X021617Y003989D01*
-X021558Y003947D01*
-X021496Y003907D01*
-X021432Y003871D01*
-X021367Y003839D01*
-X021300Y003810D01*
-X021231Y003785D01*
-X021161Y003763D01*
-X021090Y003746D01*
-X021018Y003732D01*
-X020946Y003722D01*
-X020873Y003716D01*
-X020800Y003714D01*
-X020727Y003716D01*
-X020654Y003722D01*
-X020582Y003732D01*
-X020510Y003746D01*
-X020439Y003763D01*
-X020369Y003785D01*
-X020300Y003810D01*
-X020233Y003839D01*
-X020168Y003871D01*
-X020104Y003907D01*
-X020042Y003947D01*
-X019983Y003989D01*
-X019926Y004035D01*
-X019872Y004084D01*
-X019820Y004136D01*
-X019771Y004190D01*
-X019725Y004247D01*
-X019683Y004306D01*
-X019643Y004368D01*
-X019607Y004432D01*
-X019575Y004497D01*
-X019546Y004564D01*
-X019521Y004633D01*
-X019499Y004703D01*
-X019482Y004774D01*
-X019468Y004846D01*
-X019458Y004918D01*
-X019452Y004991D01*
-X019450Y005064D01*
-X019798Y007044D02*
-X019904Y007044D01*
-X020011Y007151D01*
-X020011Y007685D01*
-X019904Y007685D02*
-X020118Y007685D01*
-X020335Y007471D02*
-X020549Y007685D01*
-X020549Y007044D01*
-X020762Y007044D02*
-X020335Y007044D01*
-X019798Y007044D02*
-X019691Y007151D01*
-X019450Y016064D02*
-X019452Y016137D01*
-X019458Y016210D01*
-X019468Y016282D01*
-X019482Y016354D01*
-X019499Y016425D01*
-X019521Y016495D01*
-X019546Y016564D01*
-X019575Y016631D01*
-X019607Y016696D01*
-X019643Y016760D01*
-X019683Y016822D01*
-X019725Y016881D01*
-X019771Y016938D01*
-X019820Y016992D01*
-X019872Y017044D01*
-X019926Y017093D01*
-X019983Y017139D01*
-X020042Y017181D01*
-X020104Y017221D01*
-X020168Y017257D01*
-X020233Y017289D01*
-X020300Y017318D01*
-X020369Y017343D01*
-X020439Y017365D01*
-X020510Y017382D01*
-X020582Y017396D01*
-X020654Y017406D01*
-X020727Y017412D01*
-X020800Y017414D01*
-X020873Y017412D01*
-X020946Y017406D01*
-X021018Y017396D01*
-X021090Y017382D01*
-X021161Y017365D01*
-X021231Y017343D01*
-X021300Y017318D01*
-X021367Y017289D01*
-X021432Y017257D01*
-X021496Y017221D01*
-X021558Y017181D01*
-X021617Y017139D01*
-X021674Y017093D01*
-X021728Y017044D01*
-X021780Y016992D01*
-X021829Y016938D01*
-X021875Y016881D01*
-X021917Y016822D01*
-X021957Y016760D01*
-X021993Y016696D01*
-X022025Y016631D01*
-X022054Y016564D01*
-X022079Y016495D01*
-X022101Y016425D01*
-X022118Y016354D01*
-X022132Y016282D01*
-X022142Y016210D01*
-X022148Y016137D01*
-X022150Y016064D01*
-X022148Y015991D01*
-X022142Y015918D01*
-X022132Y015846D01*
-X022118Y015774D01*
-X022101Y015703D01*
-X022079Y015633D01*
-X022054Y015564D01*
-X022025Y015497D01*
-X021993Y015432D01*
-X021957Y015368D01*
-X021917Y015306D01*
-X021875Y015247D01*
-X021829Y015190D01*
-X021780Y015136D01*
-X021728Y015084D01*
-X021674Y015035D01*
-X021617Y014989D01*
-X021558Y014947D01*
-X021496Y014907D01*
-X021432Y014871D01*
-X021367Y014839D01*
-X021300Y014810D01*
-X021231Y014785D01*
-X021161Y014763D01*
-X021090Y014746D01*
-X021018Y014732D01*
-X020946Y014722D01*
-X020873Y014716D01*
-X020800Y014714D01*
-X020727Y014716D01*
-X020654Y014722D01*
-X020582Y014732D01*
-X020510Y014746D01*
-X020439Y014763D01*
-X020369Y014785D01*
-X020300Y014810D01*
-X020233Y014839D01*
-X020168Y014871D01*
-X020104Y014907D01*
-X020042Y014947D01*
-X019983Y014989D01*
-X019926Y015035D01*
-X019872Y015084D01*
-X019820Y015136D01*
-X019771Y015190D01*
-X019725Y015247D01*
-X019683Y015306D01*
-X019643Y015368D01*
-X019607Y015432D01*
-X019575Y015497D01*
-X019546Y015564D01*
-X019521Y015633D01*
-X019499Y015703D01*
-X019482Y015774D01*
-X019468Y015846D01*
-X019458Y015918D01*
-X019452Y015991D01*
-X019450Y016064D01*
-X018850Y016564D02*
-X018600Y016314D01*
-X018100Y016314D01*
-X017850Y016564D01*
-X017600Y016314D01*
-X017100Y016314D01*
-X016850Y016564D01*
-X016850Y017064D01*
-X017100Y017314D01*
-X017600Y017314D01*
-X017850Y017064D01*
-X018100Y017314D01*
-X018600Y017314D01*
-X018850Y017064D01*
-X018850Y016564D01*
-X017850Y016564D02*
-X017850Y017064D01*
-X007850Y017214D02*
-X007850Y016714D01*
-X007600Y016464D01*
-X007100Y016464D01*
-X006850Y016714D01*
-X006600Y016464D01*
-X006100Y016464D01*
-X005850Y016714D01*
-X005600Y016464D01*
-X005100Y016464D01*
-X004850Y016714D01*
-X004850Y017214D01*
-X005100Y017464D01*
-X005600Y017464D01*
-X005850Y017214D01*
-X006100Y017464D01*
-X006600Y017464D01*
-X006850Y017214D01*
-X007100Y017464D01*
-X007600Y017464D01*
-X007850Y017214D01*
-X006850Y017214D02*
-X006850Y016714D01*
-X005850Y016714D02*
-X005850Y017214D01*
-X000950Y016064D02*
-X000952Y016137D01*
-X000958Y016210D01*
-X000968Y016282D01*
-X000982Y016354D01*
-X000999Y016425D01*
-X001021Y016495D01*
-X001046Y016564D01*
-X001075Y016631D01*
-X001107Y016696D01*
-X001143Y016760D01*
-X001183Y016822D01*
-X001225Y016881D01*
-X001271Y016938D01*
-X001320Y016992D01*
-X001372Y017044D01*
-X001426Y017093D01*
-X001483Y017139D01*
-X001542Y017181D01*
-X001604Y017221D01*
-X001668Y017257D01*
-X001733Y017289D01*
-X001800Y017318D01*
-X001869Y017343D01*
-X001939Y017365D01*
-X002010Y017382D01*
-X002082Y017396D01*
-X002154Y017406D01*
-X002227Y017412D01*
-X002300Y017414D01*
-X002373Y017412D01*
-X002446Y017406D01*
-X002518Y017396D01*
-X002590Y017382D01*
-X002661Y017365D01*
-X002731Y017343D01*
-X002800Y017318D01*
-X002867Y017289D01*
-X002932Y017257D01*
-X002996Y017221D01*
-X003058Y017181D01*
-X003117Y017139D01*
-X003174Y017093D01*
-X003228Y017044D01*
-X003280Y016992D01*
-X003329Y016938D01*
-X003375Y016881D01*
-X003417Y016822D01*
-X003457Y016760D01*
-X003493Y016696D01*
-X003525Y016631D01*
-X003554Y016564D01*
-X003579Y016495D01*
-X003601Y016425D01*
-X003618Y016354D01*
-X003632Y016282D01*
-X003642Y016210D01*
-X003648Y016137D01*
-X003650Y016064D01*
-X003648Y015991D01*
-X003642Y015918D01*
-X003632Y015846D01*
-X003618Y015774D01*
-X003601Y015703D01*
-X003579Y015633D01*
-X003554Y015564D01*
-X003525Y015497D01*
-X003493Y015432D01*
-X003457Y015368D01*
-X003417Y015306D01*
-X003375Y015247D01*
-X003329Y015190D01*
-X003280Y015136D01*
-X003228Y015084D01*
-X003174Y015035D01*
-X003117Y014989D01*
-X003058Y014947D01*
-X002996Y014907D01*
-X002932Y014871D01*
-X002867Y014839D01*
-X002800Y014810D01*
-X002731Y014785D01*
-X002661Y014763D01*
-X002590Y014746D01*
-X002518Y014732D01*
-X002446Y014722D01*
-X002373Y014716D01*
-X002300Y014714D01*
-X002227Y014716D01*
-X002154Y014722D01*
-X002082Y014732D01*
-X002010Y014746D01*
-X001939Y014763D01*
-X001869Y014785D01*
-X001800Y014810D01*
-X001733Y014839D01*
-X001668Y014871D01*
-X001604Y014907D01*
-X001542Y014947D01*
-X001483Y014989D01*
-X001426Y015035D01*
-X001372Y015084D01*
-X001320Y015136D01*
-X001271Y015190D01*
-X001225Y015247D01*
-X001183Y015306D01*
-X001143Y015368D01*
-X001107Y015432D01*
-X001075Y015497D01*
-X001046Y015564D01*
-X001021Y015633D01*
-X000999Y015703D01*
-X000982Y015774D01*
-X000968Y015846D01*
-X000958Y015918D01*
-X000952Y015991D01*
-X000950Y016064D01*
-X001250Y013064D02*
-X001000Y012814D01*
-X001000Y012314D01*
-X001250Y012064D01*
-X001000Y011814D01*
-X001000Y011314D01*
-X001250Y011064D01*
-X001750Y011064D01*
-X002000Y011314D01*
-X002000Y011814D01*
-X001750Y012064D01*
-X001250Y012064D01*
-X001750Y012064D02*
-X002000Y012314D01*
-X002000Y012814D01*
-X001750Y013064D01*
-X001250Y013064D01*
-X001250Y011064D02*
-X001000Y010814D01*
-X001000Y010314D01*
-X001250Y010064D01*
-X001000Y009814D01*
-X001000Y009314D01*
-X001250Y009064D01*
-X001000Y008814D01*
-X001000Y008314D01*
-X001250Y008064D01*
-X001750Y008064D01*
-X002000Y008314D01*
-X002000Y008814D01*
-X001750Y009064D01*
-X001250Y009064D01*
-X001750Y009064D02*
-X002000Y009314D01*
-X002000Y009814D01*
-X001750Y010064D01*
-X001250Y010064D01*
-X001750Y010064D02*
-X002000Y010314D01*
-X002000Y010814D01*
-X001750Y011064D01*
-X004750Y011194D02*
-X004750Y011614D01*
-X004750Y012014D01*
-X004750Y012434D01*
-X004752Y012457D01*
-X004757Y012480D01*
-X004766Y012502D01*
-X004779Y012522D01*
-X004794Y012540D01*
-X004812Y012555D01*
-X004832Y012568D01*
-X004854Y012577D01*
-X004877Y012582D01*
-X004900Y012584D01*
-X006600Y012584D01*
-X006623Y012582D01*
-X006646Y012577D01*
-X006668Y012568D01*
-X006688Y012555D01*
-X006706Y012540D01*
-X006721Y012522D01*
-X006734Y012502D01*
-X006743Y012480D01*
-X006748Y012457D01*
-X006750Y012434D01*
-X006750Y011194D01*
-X006748Y011171D01*
-X006743Y011148D01*
-X006734Y011126D01*
-X006721Y011106D01*
-X006706Y011088D01*
-X006688Y011073D01*
-X006668Y011060D01*
-X006646Y011051D01*
-X006623Y011046D01*
-X006600Y011044D01*
-X004900Y011044D01*
-X004877Y011046D01*
-X004854Y011051D01*
-X004832Y011060D01*
-X004812Y011073D01*
-X004794Y011088D01*
-X004779Y011106D01*
-X004766Y011126D01*
-X004757Y011148D01*
-X004752Y011171D01*
-X004750Y011194D01*
-X004750Y011614D02*
-X004777Y011616D01*
-X004804Y011621D01*
-X004830Y011631D01*
-X004854Y011643D01*
-X004876Y011659D01*
-X004896Y011677D01*
-X004913Y011699D01*
-X004928Y011722D01*
-X004938Y011747D01*
-X004946Y011773D01*
-X004950Y011800D01*
-X004950Y011828D01*
-X004946Y011855D01*
-X004938Y011881D01*
-X004928Y011906D01*
-X004913Y011929D01*
-X004896Y011951D01*
-X004876Y011969D01*
-X004854Y011985D01*
-X004830Y011997D01*
-X004804Y012007D01*
-X004777Y012012D01*
-X004750Y012014D01*
-X001000Y005114D02*
-X001002Y005187D01*
-X001008Y005260D01*
-X001018Y005332D01*
-X001032Y005404D01*
-X001049Y005475D01*
-X001071Y005545D01*
-X001096Y005614D01*
-X001125Y005681D01*
-X001157Y005746D01*
-X001193Y005810D01*
-X001233Y005872D01*
-X001275Y005931D01*
-X001321Y005988D01*
-X001370Y006042D01*
-X001422Y006094D01*
-X001476Y006143D01*
-X001533Y006189D01*
-X001592Y006231D01*
-X001654Y006271D01*
-X001718Y006307D01*
-X001783Y006339D01*
-X001850Y006368D01*
-X001919Y006393D01*
-X001989Y006415D01*
-X002060Y006432D01*
-X002132Y006446D01*
-X002204Y006456D01*
-X002277Y006462D01*
-X002350Y006464D01*
-X002423Y006462D01*
-X002496Y006456D01*
-X002568Y006446D01*
-X002640Y006432D01*
-X002711Y006415D01*
-X002781Y006393D01*
-X002850Y006368D01*
-X002917Y006339D01*
-X002982Y006307D01*
-X003046Y006271D01*
-X003108Y006231D01*
-X003167Y006189D01*
-X003224Y006143D01*
-X003278Y006094D01*
-X003330Y006042D01*
-X003379Y005988D01*
-X003425Y005931D01*
-X003467Y005872D01*
-X003507Y005810D01*
-X003543Y005746D01*
-X003575Y005681D01*
-X003604Y005614D01*
-X003629Y005545D01*
-X003651Y005475D01*
-X003668Y005404D01*
-X003682Y005332D01*
-X003692Y005260D01*
-X003698Y005187D01*
-X003700Y005114D01*
-X003698Y005041D01*
-X003692Y004968D01*
-X003682Y004896D01*
-X003668Y004824D01*
-X003651Y004753D01*
-X003629Y004683D01*
-X003604Y004614D01*
-X003575Y004547D01*
-X003543Y004482D01*
-X003507Y004418D01*
-X003467Y004356D01*
-X003425Y004297D01*
-X003379Y004240D01*
-X003330Y004186D01*
-X003278Y004134D01*
-X003224Y004085D01*
-X003167Y004039D01*
-X003108Y003997D01*
-X003046Y003957D01*
-X002982Y003921D01*
-X002917Y003889D01*
-X002850Y003860D01*
-X002781Y003835D01*
-X002711Y003813D01*
-X002640Y003796D01*
-X002568Y003782D01*
-X002496Y003772D01*
-X002423Y003766D01*
-X002350Y003764D01*
-X002277Y003766D01*
-X002204Y003772D01*
-X002132Y003782D01*
-X002060Y003796D01*
-X001989Y003813D01*
-X001919Y003835D01*
-X001850Y003860D01*
-X001783Y003889D01*
-X001718Y003921D01*
-X001654Y003957D01*
-X001592Y003997D01*
-X001533Y004039D01*
-X001476Y004085D01*
-X001422Y004134D01*
-X001370Y004186D01*
-X001321Y004240D01*
-X001275Y004297D01*
-X001233Y004356D01*
-X001193Y004418D01*
-X001157Y004482D01*
-X001125Y004547D01*
-X001096Y004614D01*
-X001071Y004683D01*
-X001049Y004753D01*
-X001032Y004824D01*
-X001018Y004896D01*
-X001008Y004968D01*
-X001002Y005041D01*
-X001000Y005114D01*
-D12*
-X004750Y011184D02*
-X006750Y011184D01*
-D13*
-X006929Y012889D02*
-X007079Y012889D01*
-X007154Y012964D01*
-X007154Y013340D01*
-X007315Y013265D02*
-X007390Y013340D01*
-X007540Y013340D01*
-X007615Y013265D01*
-X007615Y013190D01*
-X007540Y013115D01*
-X007615Y013039D01*
-X007615Y012964D01*
-X007540Y012889D01*
-X007390Y012889D01*
-X007315Y012964D01*
-X007465Y013115D02*
-X007540Y013115D01*
-X006929Y012889D02*
-X006854Y012964D01*
-X006854Y013340D01*
-X006216Y015659D02*
-X005916Y016110D01*
-X005756Y016110D02*
-X005756Y015659D01*
-X005916Y015659D02*
-X006216Y016110D01*
-X005756Y016110D02*
-X005606Y015960D01*
-X005455Y016110D01*
-X005455Y015659D01*
-X005295Y015734D02*
-X005295Y016035D01*
-X005220Y016110D01*
-X004995Y016110D01*
-X004995Y015659D01*
-X005220Y015659D01*
-X005295Y015734D01*
-X002695Y012963D02*
-X002695Y012812D01*
-X002695Y012887D02*
-X002245Y012887D01*
-X002245Y012812D02*
-X002245Y012963D01*
-X002320Y012652D02*
-X002245Y012577D01*
-X002245Y012352D01*
-X002695Y012352D01*
-X002695Y012577D01*
-X002620Y012652D01*
-X002320Y012652D01*
-X002245Y012195D02*
-X002245Y012045D01*
-X002245Y012120D02*
-X002695Y012120D01*
-X002695Y012045D02*
-X002695Y012195D01*
-X002695Y011885D02*
-X002245Y011885D01*
-X002395Y011735D01*
-X002245Y011585D01*
-X002695Y011585D01*
-X016845Y017559D02*
-X016845Y018010D01*
-X017070Y018010D01*
-X017145Y017935D01*
-X017145Y017785D01*
-X017070Y017709D01*
-X016845Y017709D01*
-X017305Y017559D02*
-X017305Y018010D01*
-X017606Y018010D02*
-X017606Y017559D01*
-X017456Y017709D01*
-X017305Y017559D01*
-X017766Y017559D02*
-X017766Y018010D01*
-X017991Y018010D01*
-X018066Y017935D01*
-X018066Y017785D01*
-X017991Y017709D01*
-X017766Y017709D01*
-X017916Y017709D02*
-X018066Y017559D01*
-D14*
-X020131Y016064D02*
-X020133Y016115D01*
-X020139Y016166D01*
-X020149Y016216D01*
-X020162Y016266D01*
-X020180Y016314D01*
-X020200Y016361D01*
-X020225Y016406D01*
-X020253Y016449D01*
-X020284Y016490D01*
-X020318Y016528D01*
-X020355Y016563D01*
-X020394Y016596D01*
-X020436Y016626D01*
-X020480Y016652D01*
-X020526Y016674D01*
-X020574Y016694D01*
-X020623Y016709D01*
-X020673Y016721D01*
-X020723Y016729D01*
-X020774Y016733D01*
-X020826Y016733D01*
-X020877Y016729D01*
-X020927Y016721D01*
-X020977Y016709D01*
-X021026Y016694D01*
-X021074Y016674D01*
-X021120Y016652D01*
-X021164Y016626D01*
-X021206Y016596D01*
-X021245Y016563D01*
-X021282Y016528D01*
-X021316Y016490D01*
-X021347Y016449D01*
-X021375Y016406D01*
-X021400Y016361D01*
-X021420Y016314D01*
-X021438Y016266D01*
-X021451Y016216D01*
-X021461Y016166D01*
-X021467Y016115D01*
-X021469Y016064D01*
-X021467Y016013D01*
-X021461Y015962D01*
-X021451Y015912D01*
-X021438Y015862D01*
-X021420Y015814D01*
-X021400Y015767D01*
-X021375Y015722D01*
-X021347Y015679D01*
-X021316Y015638D01*
-X021282Y015600D01*
-X021245Y015565D01*
-X021206Y015532D01*
-X021164Y015502D01*
-X021120Y015476D01*
-X021074Y015454D01*
-X021026Y015434D01*
-X020977Y015419D01*
-X020927Y015407D01*
-X020877Y015399D01*
-X020826Y015395D01*
-X020774Y015395D01*
-X020723Y015399D01*
-X020673Y015407D01*
-X020623Y015419D01*
-X020574Y015434D01*
-X020526Y015454D01*
-X020480Y015476D01*
-X020436Y015502D01*
-X020394Y015532D01*
-X020355Y015565D01*
-X020318Y015600D01*
-X020284Y015638D01*
-X020253Y015679D01*
-X020225Y015722D01*
-X020200Y015767D01*
-X020180Y015814D01*
-X020162Y015862D01*
-X020149Y015912D01*
-X020139Y015962D01*
-X020133Y016013D01*
-X020131Y016064D01*
-X023764Y013422D02*
-X016441Y013422D01*
-X016441Y008007D01*
-X023764Y008007D01*
-X023764Y013422D01*
-X013874Y007472D02*
-X013874Y003456D01*
-X011826Y003456D01*
-X011826Y007472D01*
-X011484Y008109D02*
-X011484Y012120D01*
-X008060Y007206D02*
-X005640Y007206D01*
-X005640Y003522D01*
-X008060Y003522D01*
-X008060Y007206D01*
-X001681Y005114D02*
-X001683Y005165D01*
-X001689Y005216D01*
-X001699Y005266D01*
-X001712Y005316D01*
-X001730Y005364D01*
-X001750Y005411D01*
-X001775Y005456D01*
-X001803Y005499D01*
-X001834Y005540D01*
-X001868Y005578D01*
-X001905Y005613D01*
-X001944Y005646D01*
-X001986Y005676D01*
-X002030Y005702D01*
-X002076Y005724D01*
-X002124Y005744D01*
-X002173Y005759D01*
-X002223Y005771D01*
-X002273Y005779D01*
-X002324Y005783D01*
-X002376Y005783D01*
-X002427Y005779D01*
-X002477Y005771D01*
-X002527Y005759D01*
-X002576Y005744D01*
-X002624Y005724D01*
-X002670Y005702D01*
-X002714Y005676D01*
-X002756Y005646D01*
-X002795Y005613D01*
-X002832Y005578D01*
-X002866Y005540D01*
-X002897Y005499D01*
-X002925Y005456D01*
-X002950Y005411D01*
-X002970Y005364D01*
-X002988Y005316D01*
-X003001Y005266D01*
-X003011Y005216D01*
-X003017Y005165D01*
-X003019Y005114D01*
-X003017Y005063D01*
-X003011Y005012D01*
-X003001Y004962D01*
-X002988Y004912D01*
-X002970Y004864D01*
-X002950Y004817D01*
-X002925Y004772D01*
-X002897Y004729D01*
-X002866Y004688D01*
-X002832Y004650D01*
-X002795Y004615D01*
-X002756Y004582D01*
-X002714Y004552D01*
-X002670Y004526D01*
-X002624Y004504D01*
-X002576Y004484D01*
-X002527Y004469D01*
-X002477Y004457D01*
-X002427Y004449D01*
-X002376Y004445D01*
-X002324Y004445D01*
-X002273Y004449D01*
-X002223Y004457D01*
-X002173Y004469D01*
-X002124Y004484D01*
-X002076Y004504D01*
-X002030Y004526D01*
-X001986Y004552D01*
-X001944Y004582D01*
-X001905Y004615D01*
-X001868Y004650D01*
-X001834Y004688D01*
-X001803Y004729D01*
-X001775Y004772D01*
-X001750Y004817D01*
-X001730Y004864D01*
-X001712Y004912D01*
-X001699Y004962D01*
-X001689Y005012D01*
-X001683Y005063D01*
-X001681Y005114D01*
-X001631Y016064D02*
-X001633Y016115D01*
-X001639Y016166D01*
-X001649Y016216D01*
-X001662Y016266D01*
-X001680Y016314D01*
-X001700Y016361D01*
-X001725Y016406D01*
-X001753Y016449D01*
-X001784Y016490D01*
-X001818Y016528D01*
-X001855Y016563D01*
-X001894Y016596D01*
-X001936Y016626D01*
-X001980Y016652D01*
-X002026Y016674D01*
-X002074Y016694D01*
-X002123Y016709D01*
-X002173Y016721D01*
-X002223Y016729D01*
-X002274Y016733D01*
-X002326Y016733D01*
-X002377Y016729D01*
-X002427Y016721D01*
-X002477Y016709D01*
-X002526Y016694D01*
-X002574Y016674D01*
-X002620Y016652D01*
-X002664Y016626D01*
-X002706Y016596D01*
-X002745Y016563D01*
-X002782Y016528D01*
-X002816Y016490D01*
-X002847Y016449D01*
-X002875Y016406D01*
-X002900Y016361D01*
-X002920Y016314D01*
-X002938Y016266D01*
-X002951Y016216D01*
-X002961Y016166D01*
-X002967Y016115D01*
-X002969Y016064D01*
-X002967Y016013D01*
-X002961Y015962D01*
-X002951Y015912D01*
-X002938Y015862D01*
-X002920Y015814D01*
-X002900Y015767D01*
-X002875Y015722D01*
-X002847Y015679D01*
-X002816Y015638D01*
-X002782Y015600D01*
-X002745Y015565D01*
-X002706Y015532D01*
-X002664Y015502D01*
-X002620Y015476D01*
-X002574Y015454D01*
-X002526Y015434D01*
-X002477Y015419D01*
-X002427Y015407D01*
-X002377Y015399D01*
-X002326Y015395D01*
-X002274Y015395D01*
-X002223Y015399D01*
-X002173Y015407D01*
-X002123Y015419D01*
-X002074Y015434D01*
-X002026Y015454D01*
-X001980Y015476D01*
-X001936Y015502D01*
-X001894Y015532D01*
-X001855Y015565D01*
-X001818Y015600D01*
-X001784Y015638D01*
-X001753Y015679D01*
-X001725Y015722D01*
-X001700Y015767D01*
-X001680Y015814D01*
-X001662Y015862D01*
-X001649Y015912D01*
-X001639Y015962D01*
-X001633Y016013D01*
-X001631Y016064D01*
-X020131Y005064D02*
-X020133Y005115D01*
-X020139Y005166D01*
-X020149Y005216D01*
-X020162Y005266D01*
-X020180Y005314D01*
-X020200Y005361D01*
-X020225Y005406D01*
-X020253Y005449D01*
-X020284Y005490D01*
-X020318Y005528D01*
-X020355Y005563D01*
-X020394Y005596D01*
-X020436Y005626D01*
-X020480Y005652D01*
-X020526Y005674D01*
-X020574Y005694D01*
-X020623Y005709D01*
-X020673Y005721D01*
-X020723Y005729D01*
-X020774Y005733D01*
-X020826Y005733D01*
-X020877Y005729D01*
-X020927Y005721D01*
-X020977Y005709D01*
-X021026Y005694D01*
-X021074Y005674D01*
-X021120Y005652D01*
-X021164Y005626D01*
-X021206Y005596D01*
-X021245Y005563D01*
-X021282Y005528D01*
-X021316Y005490D01*
-X021347Y005449D01*
-X021375Y005406D01*
-X021400Y005361D01*
-X021420Y005314D01*
-X021438Y005266D01*
-X021451Y005216D01*
-X021461Y005166D01*
-X021467Y005115D01*
-X021469Y005064D01*
-X021467Y005013D01*
-X021461Y004962D01*
-X021451Y004912D01*
-X021438Y004862D01*
-X021420Y004814D01*
-X021400Y004767D01*
-X021375Y004722D01*
-X021347Y004679D01*
-X021316Y004638D01*
-X021282Y004600D01*
-X021245Y004565D01*
-X021206Y004532D01*
-X021164Y004502D01*
-X021120Y004476D01*
-X021074Y004454D01*
-X021026Y004434D01*
-X020977Y004419D01*
-X020927Y004407D01*
-X020877Y004399D01*
-X020826Y004395D01*
-X020774Y004395D01*
-X020723Y004399D01*
-X020673Y004407D01*
-X020623Y004419D01*
-X020574Y004434D01*
-X020526Y004454D01*
-X020480Y004476D01*
-X020436Y004502D01*
-X020394Y004532D01*
-X020355Y004565D01*
-X020318Y004600D01*
-X020284Y004638D01*
-X020253Y004679D01*
-X020225Y004722D01*
-X020200Y004767D01*
-X020180Y004814D01*
-X020162Y004862D01*
-X020149Y004912D01*
-X020139Y004962D01*
-X020133Y005013D01*
-X020131Y005064D01*
-D15*
-X018017Y003995D02*
-X017710Y003995D01*
-X017710Y003765D01*
-X017863Y003841D01*
-X017940Y003841D01*
-X018017Y003765D01*
-X018017Y003611D01*
-X017940Y003534D01*
-X017786Y003534D01*
-X017710Y003611D01*
-X017556Y003534D02*
-X017403Y003688D01*
-X017479Y003688D02*
-X017249Y003688D01*
-X017249Y003534D02*
-X017249Y003995D01*
-X017479Y003995D01*
-X017556Y003918D01*
-X017556Y003765D01*
-X017479Y003688D01*
-X016918Y003628D02*
-X016611Y003628D01*
-X016764Y003628D02*
-X016764Y004088D01*
-X016611Y003935D01*
-X016457Y004012D02*
-X016457Y003705D01*
-X016380Y003628D01*
-X016150Y003628D01*
-X016150Y004088D01*
-X016380Y004088D01*
-X016457Y004012D01*
-X015997Y004088D02*
-X015690Y004088D01*
-X015690Y003628D01*
-X015997Y003628D01*
-X015843Y003858D02*
-X015690Y003858D01*
-X015536Y003628D02*
-X015229Y003628D01*
-X015229Y004088D01*
-X015596Y006214D02*
-X015903Y006214D01*
-X015980Y006290D01*
-X015980Y006444D01*
-X015903Y006520D01*
-X015903Y006674D02*
-X015980Y006751D01*
-X015980Y006904D01*
-X015903Y006981D01*
-X015750Y006981D01*
-X015673Y006904D01*
-X015673Y006827D01*
-X015750Y006674D01*
-X015520Y006674D01*
-X015520Y006981D01*
-X015596Y006520D02*
-X015520Y006444D01*
-X015520Y006290D01*
-X015596Y006214D01*
-X012602Y007640D02*
-X012295Y007640D01*
-X012602Y007947D01*
-X012602Y008024D01*
-X012525Y008101D01*
-X012372Y008101D01*
-X012295Y008024D01*
-X012142Y008101D02*
-X012142Y007717D01*
-X012065Y007640D01*
-X011911Y007640D01*
-X011835Y007717D01*
-X011835Y008101D01*
-X010261Y006645D02*
-X010030Y006415D01*
-X010337Y006415D01*
-X010261Y006645D02*
-X010261Y006184D01*
-X009877Y006184D02*
-X009723Y006338D01*
-X009800Y006338D02*
-X009570Y006338D01*
-X009570Y006184D02*
-X009570Y006645D01*
-X009800Y006645D01*
-X009877Y006568D01*
-X009877Y006415D01*
-X009800Y006338D01*
-X009847Y003695D02*
-X009770Y003618D01*
-X009770Y003311D01*
-X009847Y003234D01*
-X010000Y003234D01*
-X010077Y003311D01*
-X010230Y003465D02*
-X010537Y003465D01*
-X010461Y003695D02*
-X010461Y003234D01*
-X010230Y003465D02*
-X010461Y003695D01*
-X010077Y003618D02*
-X010000Y003695D01*
-X009847Y003695D01*
-X006311Y007384D02*
-X006311Y007845D01*
-X006080Y007615D01*
-X006387Y007615D01*
-X005927Y007461D02*
-X005927Y007845D01*
-X005620Y007845D02*
-X005620Y007461D01*
-X005697Y007384D01*
-X005850Y007384D01*
-X005927Y007461D01*
-X004261Y010084D02*
-X004107Y010084D01*
-X004030Y010161D01*
-X003877Y010084D02*
-X003723Y010238D01*
-X003800Y010238D02*
-X003570Y010238D01*
-X003570Y010084D02*
-X003570Y010545D01*
-X003800Y010545D01*
-X003877Y010468D01*
-X003877Y010315D01*
-X003800Y010238D01*
-X004030Y010468D02*
-X004107Y010545D01*
-X004261Y010545D01*
-X004337Y010468D01*
-X004337Y010391D01*
-X004261Y010315D01*
-X004337Y010238D01*
-X004337Y010161D01*
-X004261Y010084D01*
-X004261Y010315D02*
-X004184Y010315D01*
-X004207Y013484D02*
-X004130Y013561D01*
-X004207Y013484D02*
-X004361Y013484D01*
-X004437Y013561D01*
-X004437Y013638D01*
-X004361Y013715D01*
-X004284Y013715D01*
-X004361Y013715D02*
-X004437Y013791D01*
-X004437Y013868D01*
-X004361Y013945D01*
-X004207Y013945D01*
-X004130Y013868D01*
-X003977Y013868D02*
-X003900Y013945D01*
-X003747Y013945D01*
-X003670Y013868D01*
-X003670Y013561D01*
-X003747Y013484D01*
-X003900Y013484D01*
-X003977Y013561D01*
-X006649Y014334D02*
-X006649Y014795D01*
-X006879Y014795D01*
-X006956Y014718D01*
-X006956Y014565D01*
-X006879Y014488D01*
-X006649Y014488D01*
-X006803Y014488D02*
-X006956Y014334D01*
-X007110Y014334D02*
-X007417Y014334D01*
-X007263Y014334D02*
-X007263Y014795D01*
-X007110Y014641D01*
-X008386Y014156D02*
-X008386Y016479D01*
-X009606Y016479D01*
-X010394Y016479D02*
-X011614Y016479D01*
-X011614Y014156D01*
-X010709Y013250D01*
-X010394Y013250D01*
-X009606Y013250D02*
-X009291Y013250D01*
-X008386Y014156D01*
-X009646Y013348D02*
-X009569Y013368D01*
-X009494Y013391D01*
-X009420Y013419D01*
-X009348Y013450D01*
-X009277Y013485D01*
-X009208Y013523D01*
-X009142Y013565D01*
-X009077Y013610D01*
-X009015Y013658D01*
-X008955Y013710D01*
-X008898Y013764D01*
-X008844Y013821D01*
-X008792Y013881D01*
-X008744Y013943D01*
-X008699Y014008D01*
-X008658Y014075D01*
-X008620Y014144D01*
-X008585Y014215D01*
-X008554Y014287D01*
-X008526Y014361D01*
-X008503Y014436D01*
-X008483Y014512D01*
-X008467Y014590D01*
-X008455Y014668D01*
-X008447Y014746D01*
-X008443Y014825D01*
-X008443Y014903D01*
-X008447Y014982D01*
-X008455Y015060D01*
-X008467Y015138D01*
-X008483Y015216D01*
-X008503Y015292D01*
-X008526Y015367D01*
-X008554Y015441D01*
-X008585Y015513D01*
-X008620Y015584D01*
-X008658Y015653D01*
-X008699Y015720D01*
-X008744Y015785D01*
-X008792Y015847D01*
-X008844Y015907D01*
-X008898Y015964D01*
-X008955Y016018D01*
-X009015Y016070D01*
-X009077Y016118D01*
-X009142Y016163D01*
-X009208Y016205D01*
-X009277Y016243D01*
-X009348Y016278D01*
-X009420Y016309D01*
-X009494Y016337D01*
-X009569Y016360D01*
-X009646Y016380D01*
-X010354Y016380D02*
-X010431Y016360D01*
-X010506Y016337D01*
-X010580Y016309D01*
-X010652Y016278D01*
-X010723Y016243D01*
-X010792Y016205D01*
-X010858Y016163D01*
-X010923Y016118D01*
-X010985Y016070D01*
-X011045Y016018D01*
-X011102Y015964D01*
-X011156Y015907D01*
-X011208Y015847D01*
-X011256Y015785D01*
-X011301Y015720D01*
-X011342Y015653D01*
-X011380Y015584D01*
-X011415Y015513D01*
-X011446Y015441D01*
-X011474Y015367D01*
-X011497Y015292D01*
-X011517Y015216D01*
-X011533Y015138D01*
-X011545Y015060D01*
-X011553Y014982D01*
-X011557Y014903D01*
-X011557Y014825D01*
-X011553Y014746D01*
-X011545Y014668D01*
-X011533Y014590D01*
-X011517Y014512D01*
-X011497Y014436D01*
-X011474Y014361D01*
-X011446Y014287D01*
-X011415Y014215D01*
-X011380Y014144D01*
-X011342Y014075D01*
-X011301Y014008D01*
-X011256Y013943D01*
-X011208Y013881D01*
-X011156Y013821D01*
-X011102Y013764D01*
-X011045Y013710D01*
-X010985Y013658D01*
-X010923Y013610D01*
-X010858Y013565D01*
-X010792Y013523D01*
-X010723Y013485D01*
-X010652Y013450D01*
-X010580Y013419D01*
-X010506Y013391D01*
-X010431Y013368D01*
-X010354Y013348D01*
-X011749Y012395D02*
-X011749Y012011D01*
-X011826Y011934D01*
-X011979Y011934D01*
-X012056Y012011D01*
-X012056Y012395D01*
-X012210Y012241D02*
-X012363Y012395D01*
-X012363Y011934D01*
-X012210Y011934D02*
-X012517Y011934D01*
-X013148Y012406D02*
-X012242Y013312D01*
-X012242Y016422D01*
-X013856Y016422D01*
-X014644Y016422D02*
-X016258Y016422D01*
-X016258Y013312D01*
-X015352Y012406D01*
-X014644Y012406D01*
-X013856Y012406D02*
-X013148Y012406D01*
-X014849Y010645D02*
-X014849Y010184D01*
-X015156Y010184D01*
-X015310Y010184D02*
-X015617Y010184D01*
-X015463Y010184D02*
-X015463Y010645D01*
-X015310Y010491D01*
-X015320Y009295D02*
-X015550Y009295D01*
-X015627Y009218D01*
-X015627Y009065D01*
-X015550Y008988D01*
-X015320Y008988D01*
-X015473Y008988D02*
-X015627Y008834D01*
-X015780Y008834D02*
-X016087Y009141D01*
-X016087Y009218D01*
-X016011Y009295D01*
-X015857Y009295D01*
-X015780Y009218D01*
-X015780Y008834D02*
-X016087Y008834D01*
-X015320Y008834D02*
-X015320Y009295D01*
-X014644Y012504D02*
-X014729Y012524D01*
-X014813Y012547D01*
-X014896Y012574D01*
-X014978Y012605D01*
-X015058Y012639D01*
-X015137Y012678D01*
-X015214Y012719D01*
-X015289Y012764D01*
-X015362Y012812D01*
-X015433Y012864D01*
-X015501Y012918D01*
-X015567Y012976D01*
-X015630Y013036D01*
-X015690Y013099D01*
-X015748Y013165D01*
-X015802Y013234D01*
-X015854Y013304D01*
-X015902Y013377D01*
-X015946Y013453D01*
-X015988Y013530D01*
-X016026Y013608D01*
-X016060Y013689D01*
-X016091Y013771D01*
-X016118Y013854D01*
-X016141Y013938D01*
-X016160Y014023D01*
-X016176Y014109D01*
-X016188Y014196D01*
-X016196Y014283D01*
-X016200Y014370D01*
-X016200Y014458D01*
-X016196Y014545D01*
-X016188Y014632D01*
-X016176Y014719D01*
-X016160Y014805D01*
-X016141Y014890D01*
-X016118Y014974D01*
-X016091Y015057D01*
-X016060Y015139D01*
-X016026Y015220D01*
-X015988Y015298D01*
-X015946Y015375D01*
-X015902Y015451D01*
-X015854Y015524D01*
-X015802Y015594D01*
-X015748Y015663D01*
-X015690Y015729D01*
-X015630Y015792D01*
-X015567Y015852D01*
-X015501Y015910D01*
-X015433Y015964D01*
-X015362Y016016D01*
-X015289Y016064D01*
-X015214Y016109D01*
-X015137Y016150D01*
-X015058Y016189D01*
-X014978Y016223D01*
-X014896Y016254D01*
-X014813Y016281D01*
-X014729Y016304D01*
-X014644Y016324D01*
-X013856Y016324D02*
-X013771Y016304D01*
-X013687Y016281D01*
-X013604Y016254D01*
-X013522Y016223D01*
-X013442Y016189D01*
-X013363Y016150D01*
-X013286Y016109D01*
-X013211Y016064D01*
-X013138Y016016D01*
-X013067Y015964D01*
-X012999Y015910D01*
-X012933Y015852D01*
-X012870Y015792D01*
-X012810Y015729D01*
-X012752Y015663D01*
-X012698Y015594D01*
-X012646Y015524D01*
-X012598Y015451D01*
-X012554Y015375D01*
-X012512Y015298D01*
-X012474Y015220D01*
-X012440Y015139D01*
-X012409Y015057D01*
-X012382Y014974D01*
-X012359Y014890D01*
-X012340Y014805D01*
-X012324Y014719D01*
-X012312Y014632D01*
-X012304Y014545D01*
-X012300Y014458D01*
-X012300Y014370D01*
-X012304Y014283D01*
-X012312Y014196D01*
-X012324Y014109D01*
-X012340Y014023D01*
-X012359Y013938D01*
-X012382Y013854D01*
-X012409Y013771D01*
-X012440Y013689D01*
-X012474Y013608D01*
-X012512Y013530D01*
-X012554Y013453D01*
-X012598Y013377D01*
-X012646Y013304D01*
-X012698Y013234D01*
-X012752Y013165D01*
-X012810Y013099D01*
-X012870Y013036D01*
-X012933Y012976D01*
-X012999Y012918D01*
-X013067Y012864D01*
-X013138Y012812D01*
-X013211Y012764D01*
-X013286Y012719D01*
-X013363Y012678D01*
-X013442Y012639D01*
-X013522Y012605D01*
-X013604Y012574D01*
-X013687Y012547D01*
-X013771Y012524D01*
-X013856Y012504D01*
-D16*
-X011780Y011454D03*
-X011780Y010784D03*
-X011780Y010114D03*
-X011780Y009444D03*
-X011780Y008774D03*
-D17*
-X015534Y016610D02*
-X015657Y016610D01*
-X015719Y016672D01*
-X015841Y016610D02*
-X016088Y016857D01*
-X016088Y016919D01*
-X016026Y016981D01*
-X015902Y016981D01*
-X015841Y016919D01*
-X015719Y016919D02*
-X015657Y016981D01*
-X015534Y016981D01*
-X015472Y016919D01*
-X015472Y016672D01*
-X015534Y016610D01*
-X015841Y016610D02*
-X016088Y016610D01*
-X011491Y016701D02*
-X011244Y016701D01*
-X011368Y016701D02*
-X011368Y017071D01*
-X011244Y016948D01*
-X011123Y017010D02*
-X011061Y017071D01*
-X010938Y017071D01*
-X010876Y017010D01*
-X010876Y016763D01*
-X010938Y016701D01*
-X011061Y016701D01*
-X011123Y016763D01*
-D18*
-X022869Y013789D02*
-X022869Y007639D01*
-D19*
-X022634Y007796D03*
-X022634Y013633D03*
-D20*
-X016200Y004573D02*
-X016259Y004514D01*
-X016190Y004445D01*
-X016131Y004504D01*
-X016200Y004573D01*
-D21*
-X016092Y004672D03*
-M02*
diff --git a/gerber/tests/test_am_statements.py b/gerber/tests/test_am_statements.py
deleted file mode 100644
index 0d100b5..0000000
--- a/gerber/tests/test_am_statements.py
+++ /dev/null
@@ -1,395 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Author: Hamilton Kibbe <ham@hamiltonkib.be>
-
-import pytest
-
-from ..am_statements import *
-from ..am_statements import inch, metric
-
-
-def test_AMPrimitive_ctor():
- for exposure in ("on", "off", "ON", "OFF"):
- for code in (0, 1, 2, 4, 5, 6, 7, 20, 21, 22):
- p = AMPrimitive(code, exposure)
- assert p.code == code
- assert p.exposure == exposure.lower()
-
-
-def test_AMPrimitive_validation():
- pytest.raises(TypeError, AMPrimitive, "1", "off")
- pytest.raises(ValueError, AMPrimitive, 0, "exposed")
- pytest.raises(ValueError, AMPrimitive, 3, "off")
-
-
-def test_AMPrimitive_conversion():
- p = AMPrimitive(4, "on")
- pytest.raises(NotImplementedError, p.to_inch)
- pytest.raises(NotImplementedError, p.to_metric)
-
-
-def test_AMCommentPrimitive_ctor():
- c = AMCommentPrimitive(0, " This is a comment *")
- assert c.code == 0
- assert c.comment == "This is a comment"
-
-
-def test_AMCommentPrimitive_validation():
- pytest.raises(ValueError, AMCommentPrimitive, 1, "This is a comment")
-
-
-def test_AMCommentPrimitive_factory():
- c = AMCommentPrimitive.from_gerber("0 Rectangle with rounded corners. *")
- assert c.code == 0
- assert c.comment == "Rectangle with rounded corners."
-
-
-def test_AMCommentPrimitive_dump():
- c = AMCommentPrimitive(0, "Rectangle with rounded corners.")
- assert c.to_gerber() == "0 Rectangle with rounded corners. *"
-
-
-def test_AMCommentPrimitive_conversion():
- c = AMCommentPrimitive(0, "Rectangle with rounded corners.")
- ci = c
- cm = c
- ci.to_inch()
- cm.to_metric()
- assert c == ci
- assert c == cm
-
-
-def test_AMCommentPrimitive_string():
- c = AMCommentPrimitive(0, "Test Comment")
- assert str(c) == "<Aperture Macro Comment: Test Comment>"
-
-
-def test_AMCirclePrimitive_ctor():
- test_cases = (
- (1, "on", 0, (0, 0)),
- (1, "off", 1, (0, 1)),
- (1, "on", 2.5, (0, 2)),
- (1, "off", 5.0, (3, 3)),
- )
- for code, exposure, diameter, position in test_cases:
- c = AMCirclePrimitive(code, exposure, diameter, position)
- assert c.code == code
- assert c.exposure == exposure
- assert c.diameter == diameter
- assert c.position == position
-
-
-def test_AMCirclePrimitive_validation():
- pytest.raises(ValueError, AMCirclePrimitive, 2, "on", 0, (0, 0))
-
-
-def test_AMCirclePrimitive_factory():
- c = AMCirclePrimitive.from_gerber("1,0,5,0,0*")
- assert c.code == 1
- assert c.exposure == "off"
- assert c.diameter == 5
- assert c.position == (0, 0)
-
-
-def test_AMCirclePrimitive_dump():
- c = AMCirclePrimitive(1, "off", 5, (0, 0))
- assert c.to_gerber() == "1,0,5,0,0*"
- c = AMCirclePrimitive(1, "on", 5, (0, 0))
- assert c.to_gerber() == "1,1,5,0,0*"
-
-
-def test_AMCirclePrimitive_conversion():
- c = AMCirclePrimitive(1, "off", 25.4, (25.4, 0))
- c.to_inch()
- assert c.diameter == 1
- assert c.position == (1, 0)
-
- c = AMCirclePrimitive(1, "off", 1, (1, 0))
- c.to_metric()
- assert c.diameter == 25.4
- assert c.position == (25.4, 0)
-
-
-def test_AMVectorLinePrimitive_validation():
- pytest.raises(
- ValueError, AMVectorLinePrimitive, 3, "on", 0.1, (0, 0), (3.3, 5.4), 0
- )
-
-
-def test_AMVectorLinePrimitive_factory():
- l = AMVectorLinePrimitive.from_gerber("20,1,0.9,0,0.45,12,0.45,0*")
- assert l.code == 20
- assert l.exposure == "on"
- assert l.width == 0.9
- assert l.start == (0, 0.45)
- assert l.end == (12, 0.45)
- assert l.rotation == 0
-
-
-def test_AMVectorLinePrimitive_dump():
- l = AMVectorLinePrimitive.from_gerber("20,1,0.9,0,0.45,12,0.45,0*")
- assert l.to_gerber() == "20,1,0.9,0.0,0.45,12.0,0.45,0.0*"
-
-
-def test_AMVectorLinePrimtive_conversion():
- l = AMVectorLinePrimitive(20, "on", 25.4, (0, 0), (25.4, 25.4), 0)
- l.to_inch()
- assert l.width == 1
- assert l.start == (0, 0)
- assert l.end == (1, 1)
-
- l = AMVectorLinePrimitive(20, "on", 1, (0, 0), (1, 1), 0)
- l.to_metric()
- assert l.width == 25.4
- assert l.start == (0, 0)
- assert l.end == (25.4, 25.4)
-
-
-def test_AMOutlinePrimitive_validation():
- pytest.raises(
- ValueError,
- AMOutlinePrimitive,
- 7,
- "on",
- (0, 0),
- [(3.3, 5.4), (4.0, 5.4), (0, 0)],
- 0,
- )
- pytest.raises(
- ValueError,
- AMOutlinePrimitive,
- 4,
- "on",
- (0, 0),
- [(3.3, 5.4), (4.0, 5.4), (0, 1)],
- 0,
- )
-
-
-def test_AMOutlinePrimitive_factory():
- o = AMOutlinePrimitive.from_gerber("4,1,3,0,0,3,3,3,0,0,0,0*")
- assert o.code == 4
- assert o.exposure == "on"
- assert o.start_point == (0, 0)
- assert o.points == [(3, 3), (3, 0), (0, 0)]
- assert o.rotation == 0
-
-
-def test_AMOUtlinePrimitive_dump():
- o = AMOutlinePrimitive(4, "on", (0, 0), [(3, 3), (3, 0), (0, 0)], 0)
- # New lines don't matter for Gerber, but we insert them to make it easier to remove
- # For test purposes we can ignore them
- assert o.to_gerber().replace("\n", "") == "4,1,3,0,0,3,3,3,0,0,0,0*"
-
-
-def test_AMOutlinePrimitive_conversion():
- o = AMOutlinePrimitive(4, "on", (0, 0), [(25.4, 25.4), (25.4, 0), (0, 0)], 0)
- o.to_inch()
- assert o.start_point == (0, 0)
- assert o.points == ((1.0, 1.0), (1.0, 0.0), (0.0, 0.0))
-
- o = AMOutlinePrimitive(4, "on", (0, 0), [(1, 1), (1, 0), (0, 0)], 0)
- o.to_metric()
- assert o.start_point == (0, 0)
- assert o.points == ((25.4, 25.4), (25.4, 0), (0, 0))
-
-
-def test_AMPolygonPrimitive_validation():
- pytest.raises(ValueError, AMPolygonPrimitive, 6, "on", 3, (3.3, 5.4), 3, 0)
- pytest.raises(ValueError, AMPolygonPrimitive, 5, "on", 2, (3.3, 5.4), 3, 0)
- pytest.raises(ValueError, AMPolygonPrimitive, 5, "on", 13, (3.3, 5.4), 3, 0)
-
-
-def test_AMPolygonPrimitive_factory():
- p = AMPolygonPrimitive.from_gerber("5,1,3,3.3,5.4,3,0")
- assert p.code == 5
- assert p.exposure == "on"
- assert p.vertices == 3
- assert p.position == (3.3, 5.4)
- assert p.diameter == 3
- assert p.rotation == 0
-
-
-def test_AMPolygonPrimitive_dump():
- p = AMPolygonPrimitive(5, "on", 3, (3.3, 5.4), 3, 0)
- assert p.to_gerber() == "5,1,3,3.3,5.4,3,0*"
-
-
-def test_AMPolygonPrimitive_conversion():
- p = AMPolygonPrimitive(5, "off", 3, (25.4, 0), 25.4, 0)
- p.to_inch()
- assert p.diameter == 1
- assert p.position == (1, 0)
-
- p = AMPolygonPrimitive(5, "off", 3, (1, 0), 1, 0)
- p.to_metric()
- assert p.diameter == 25.4
- assert p.position == (25.4, 0)
-
-
-def test_AMMoirePrimitive_validation():
- pytest.raises(
- ValueError, AMMoirePrimitive, 7, (0, 0), 5.1, 0.2, 0.4, 6, 0.1, 6.1, 0
- )
-
-
-def test_AMMoirePrimitive_factory():
- m = AMMoirePrimitive.from_gerber("6,0,0,5,0.5,0.5,2,0.1,6,0*")
- assert m.code == 6
- assert m.position == (0, 0)
- assert m.diameter == 5
- assert m.ring_thickness == 0.5
- assert m.gap == 0.5
- assert m.max_rings == 2
- assert m.crosshair_thickness == 0.1
- assert m.crosshair_length == 6
- assert m.rotation == 0
-
-
-def test_AMMoirePrimitive_dump():
- m = AMMoirePrimitive.from_gerber("6,0,0,5,0.5,0.5,2,0.1,6,0*")
- assert m.to_gerber() == "6,0,0,5.0,0.5,0.5,2,0.1,6.0,0.0*"
-
-
-def test_AMMoirePrimitive_conversion():
- m = AMMoirePrimitive(6, (25.4, 25.4), 25.4, 25.4, 25.4, 6, 25.4, 25.4, 0)
- m.to_inch()
- assert m.position == (1.0, 1.0)
- assert m.diameter == 1.0
- assert m.ring_thickness == 1.0
- assert m.gap == 1.0
- assert m.crosshair_thickness == 1.0
- assert m.crosshair_length == 1.0
-
- m = AMMoirePrimitive(6, (1, 1), 1, 1, 1, 6, 1, 1, 0)
- m.to_metric()
- assert m.position == (25.4, 25.4)
- assert m.diameter == 25.4
- assert m.ring_thickness == 25.4
- assert m.gap == 25.4
- assert m.crosshair_thickness == 25.4
- assert m.crosshair_length == 25.4
-
-
-def test_AMThermalPrimitive_validation():
- pytest.raises(ValueError, AMThermalPrimitive, 8, (0.0, 0.0), 7, 5, 0.2, 0.0)
- pytest.raises(TypeError, AMThermalPrimitive, 7, (0.0, "0"), 7, 5, 0.2, 0.0)
-
-
-def test_AMThermalPrimitive_factory():
- t = AMThermalPrimitive.from_gerber("7,0,0,7,6,0.2,45*")
- assert t.code == 7
- assert t.position == (0, 0)
- assert t.outer_diameter == 7
- assert t.inner_diameter == 6
- assert t.gap == 0.2
- assert t.rotation == 45
-
-
-def test_AMThermalPrimitive_dump():
- t = AMThermalPrimitive.from_gerber("7,0,0,7,6,0.2,30*")
- assert t.to_gerber() == "7,0,0,7.0,6.0,0.2,30.0*"
-
-
-def test_AMThermalPrimitive_conversion():
- t = AMThermalPrimitive(7, (25.4, 25.4), 25.4, 25.4, 25.4, 0.0)
- t.to_inch()
- assert t.position == (1.0, 1.0)
- assert t.outer_diameter == 1.0
- assert t.inner_diameter == 1.0
- assert t.gap == 1.0
-
- t = AMThermalPrimitive(7, (1, 1), 1, 1, 1, 0)
- t.to_metric()
- assert t.position == (25.4, 25.4)
- assert t.outer_diameter == 25.4
- assert t.inner_diameter == 25.4
- assert t.gap == 25.4
-
-
-def test_AMCenterLinePrimitive_validation():
- pytest.raises(ValueError, AMCenterLinePrimitive, 22, 1, 0.2, 0.5, (0, 0), 0)
-
-
-def test_AMCenterLinePrimtive_factory():
- l = AMCenterLinePrimitive.from_gerber("21,1,6.8,1.2,3.4,0.6,0*")
- assert l.code == 21
- assert l.exposure == "on"
- assert l.width == 6.8
- assert l.height == 1.2
- assert l.center == (3.4, 0.6)
- assert l.rotation == 0
-
-
-def test_AMCenterLinePrimitive_dump():
- l = AMCenterLinePrimitive.from_gerber("21,1,6.8,1.2,3.4,0.6,0*")
- assert l.to_gerber() == "21,1,6.8,1.2,3.4,0.6,0.0*"
-
-
-def test_AMCenterLinePrimitive_conversion():
- l = AMCenterLinePrimitive(21, "on", 25.4, 25.4, (25.4, 25.4), 0)
- l.to_inch()
- assert l.width == 1.0
- assert l.height == 1.0
- assert l.center == (1.0, 1.0)
-
- l = AMCenterLinePrimitive(21, "on", 1, 1, (1, 1), 0)
- l.to_metric()
- assert l.width == 25.4
- assert l.height == 25.4
- assert l.center == (25.4, 25.4)
-
-
-def test_AMLowerLeftLinePrimitive_validation():
- pytest.raises(ValueError, AMLowerLeftLinePrimitive, 23, 1, 0.2, 0.5, (0, 0), 0)
-
-
-def test_AMLowerLeftLinePrimtive_factory():
- l = AMLowerLeftLinePrimitive.from_gerber("22,1,6.8,1.2,3.4,0.6,0*")
- assert l.code == 22
- assert l.exposure == "on"
- assert l.width == 6.8
- assert l.height == 1.2
- assert l.lower_left == (3.4, 0.6)
- assert l.rotation == 0
-
-
-def test_AMLowerLeftLinePrimitive_dump():
- l = AMLowerLeftLinePrimitive.from_gerber("22,1,6.8,1.2,3.4,0.6,0*")
- assert l.to_gerber() == "22,1,6.8,1.2,3.4,0.6,0.0*"
-
-
-def test_AMLowerLeftLinePrimitive_conversion():
- l = AMLowerLeftLinePrimitive(22, "on", 25.4, 25.4, (25.4, 25.4), 0)
- l.to_inch()
- assert l.width == 1.0
- assert l.height == 1.0
- assert l.lower_left == (1.0, 1.0)
-
- l = AMLowerLeftLinePrimitive(22, "on", 1, 1, (1, 1), 0)
- l.to_metric()
- assert l.width == 25.4
- assert l.height == 25.4
- assert l.lower_left == (25.4, 25.4)
-
-
-def test_AMUnsupportPrimitive():
- u = AMUnsupportPrimitive.from_gerber("Test")
- assert u.primitive == "Test"
- u = AMUnsupportPrimitive("Test")
- assert u.to_gerber() == "Test"
-
-
-def test_AMUnsupportPrimitive_smoketest():
- u = AMUnsupportPrimitive.from_gerber("Test")
- u.to_inch()
- u.to_metric()
-
-
-def test_inch():
- assert inch(25.4) == 1
-
-
-def test_metric():
- assert metric(1) == 25.4
diff --git a/gerber/tests/test_cairo_backend.py b/gerber/tests/test_cairo_backend.py
deleted file mode 100644
index 51007a9..0000000
--- a/gerber/tests/test_cairo_backend.py
+++ /dev/null
@@ -1,279 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Author: Garret Fick <garret@ficksworkshop.com>
-import os
-import shutil
-import tempfile
-
-from ..render.cairo_backend import GerberCairoContext
-from ..rs274x import read
-
-
-def _DISABLED_test_render_two_boxes():
- """Umaco exapmle of two boxes"""
- _test_render(
- "resources/example_two_square_boxes.gbr", "golden/example_two_square_boxes.png"
- )
-
-
-def _DISABLED_test_render_single_quadrant():
- """Umaco exapmle of a single quadrant arc"""
- _test_render(
- "resources/example_single_quadrant.gbr", "golden/example_single_quadrant.png"
- )
-
-
-def _DISABLED_test_render_simple_contour():
- """Umaco exapmle of a simple arrow-shaped contour"""
- gerber = _test_render(
- "resources/example_simple_contour.gbr", "golden/example_simple_contour.png"
- )
-
- # Check the resulting dimensions
- assert ((2.0, 11.0), (1.0, 9.0)) == gerber.bounding_box
-
-
-def _DISABLED_test_render_single_contour_1():
- """Umaco example of a single contour
-
- The resulting image for this test is used by other tests because they must generate the same output."""
- _test_render(
- "resources/example_single_contour_1.gbr", "golden/example_single_contour.png"
- )
-
-
-def _DISABLED_test_render_single_contour_2():
- """Umaco exapmle of a single contour, alternate contour end order
-
- The resulting image for this test is used by other tests because they must generate the same output."""
- _test_render(
- "resources/example_single_contour_2.gbr", "golden/example_single_contour.png"
- )
-
-
-def _DISABLED_test_render_single_contour_3():
- """Umaco exapmle of a single contour with extra line"""
- _test_render(
- "resources/example_single_contour_3.gbr", "golden/example_single_contour_3.png"
- )
-
-
-def _DISABLED_test_render_not_overlapping_contour():
- """Umaco example of D02 staring a second contour"""
- _test_render(
- "resources/example_not_overlapping_contour.gbr",
- "golden/example_not_overlapping_contour.png",
- )
-
-
-def _DISABLED_test_render_not_overlapping_touching():
- """Umaco example of D02 staring a second contour"""
- _test_render(
- "resources/example_not_overlapping_touching.gbr",
- "golden/example_not_overlapping_touching.png",
- )
-
-
-def test_render_overlapping_touching():
- """Umaco example of D02 staring a second contour"""
- _test_render(
- "resources/example_overlapping_touching.gbr",
- "golden/example_overlapping_touching.png",
- )
-
-
-def test_render_overlapping_contour():
- """Umaco example of D02 staring a second contour"""
- _test_render(
- "resources/example_overlapping_contour.gbr",
- "golden/example_overlapping_contour.png",
- )
-
-
-def _DISABLED_test_render_level_holes():
- """Umaco example of using multiple levels to create multiple holes"""
-
- # TODO This is clearly rendering wrong. I'm temporarily checking this in because there are more
- # rendering fixes in the related repository that may resolve these.
- _test_render(
- "resources/example_level_holes.gbr", "golden/example_overlapping_contour.png"
- )
-
-
-def _DISABLED_test_render_cutin():
- """Umaco example of using a cutin"""
-
- # TODO This is clearly rendering wrong.
- _test_render(
- "resources/example_cutin.gbr",
- "golden/example_cutin.png",
- "/Users/ham/Desktop/cutin.png",
- )
-
-
-def _DISABLED_test_render_fully_coincident():
- """Umaco example of coincident lines rendering two contours"""
-
- _test_render(
- "resources/example_fully_coincident.gbr", "golden/example_fully_coincident.png"
- )
-
-
-def _DISABLED_test_render_coincident_hole():
- """Umaco example of coincident lines rendering a hole in the contour"""
-
- _test_render(
- "resources/example_coincident_hole.gbr", "golden/example_coincident_hole.png"
- )
-
-
-def test_render_cutin_multiple():
- """Umaco example of a region with multiple cutins"""
-
- _test_render(
- "resources/example_cutin_multiple.gbr", "golden/example_cutin_multiple.png"
- )
-
-
-def _DISABLED_test_flash_circle():
- """Umaco example a simple circular flash with and without a hole"""
-
- _test_render(
- "resources/example_flash_circle.gbr",
- "golden/example_flash_circle.png",
- "/Users/ham/Desktop/flashcircle.png",
- )
-
-
-def _DISABLED_test_flash_rectangle():
- """Umaco example a simple rectangular flash with and without a hole"""
-
- _test_render(
- "resources/example_flash_rectangle.gbr", "golden/example_flash_rectangle.png"
- )
-
-
-def _DISABLED_test_flash_obround():
- """Umaco example a simple obround flash with and without a hole"""
-
- _test_render(
- "resources/example_flash_obround.gbr", "golden/example_flash_obround.png"
- )
-
-
-def _DISABLED_test_flash_polygon():
- """Umaco example a simple polygon flash with and without a hole"""
-
- _test_render(
- "resources/example_flash_polygon.gbr", "golden/example_flash_polygon.png"
- )
-
-
-def _DISABLED_test_holes_dont_clear():
- """Umaco example that an aperture with a hole does not clear the area"""
-
- _test_render(
- "resources/example_holes_dont_clear.gbr", "golden/example_holes_dont_clear.png"
- )
-
-
-def _DISABLED_test_render_am_exposure_modifier():
- """Umaco example that an aperture macro with a hole does not clear the area"""
-
- _test_render(
- "resources/example_am_exposure_modifier.gbr",
- "golden/example_am_exposure_modifier.png",
- )
-
-
-def test_render_svg_simple_contour():
- """Example of rendering to an SVG file"""
- _test_simple_render_svg("resources/example_simple_contour.gbr")
-
-
-def _resolve_path(path):
- return os.path.join(os.path.dirname(__file__), path)
-
-
-def _test_render(gerber_path, png_expected_path, create_output_path=None):
- """Render the gerber file and compare to the expected PNG output.
-
- Parameters
- ----------
- gerber_path : string
- Path to Gerber file to open
- png_expected_path : string
- Path to the PNG file to compare to
- create_output : string|None
- If not None, write the generated PNG to the specified path.
- This is primarily to help with
- """
-
- gerber_path = _resolve_path(gerber_path)
- png_expected_path = _resolve_path(png_expected_path)
- if create_output_path:
- create_output_path = _resolve_path(create_output_path)
-
- gerber = read(gerber_path)
-
- # Create PNG image to the memory stream
- ctx = GerberCairoContext()
- gerber.render(ctx)
-
- actual_bytes = ctx.dump(None)
-
- # If we want to write the file bytes, do it now. This happens
- if create_output_path:
- with open(create_output_path, "wb") as out_file:
- out_file.write(actual_bytes)
- # Creating the output is dangerous - it could overwrite the expected result.
- # So if we are creating the output, we make the test fail on purpose so you
- # won't forget to disable this
- assert not True, (
- "Test created the output %s. This needs to be disabled to make sure the test behaves correctly"
- % (create_output_path,)
- )
-
- # Read the expected PNG file
-
- with open(png_expected_path, "rb") as expected_file:
- expected_bytes = expected_file.read()
-
- # Don't directly use assert_equal otherwise any failure pollutes the test results
- equal = expected_bytes == actual_bytes
- assert equal
-
- return gerber
-
-
-def _test_simple_render_svg(gerber_path):
- """Render the gerber file as SVG
-
- Note: verifies only the header, not the full content.
-
- Parameters
- ----------
- gerber_path : string
- Path to Gerber file to open
- """
-
- gerber_path = _resolve_path(gerber_path)
- gerber = read(gerber_path)
-
- # Create SVG image to the memory stream
- ctx = GerberCairoContext()
- gerber.render(ctx)
-
- temp_dir = tempfile.mkdtemp()
- svg_temp_path = os.path.join(temp_dir, "output.svg")
-
- assert not os.path.exists(svg_temp_path)
- ctx.dump(svg_temp_path)
- assert os.path.exists(svg_temp_path)
-
- with open(svg_temp_path, "r") as expected_file:
- expected_bytes = expected_file.read()
- assert expected_bytes[:38] == '<?xml version="1.0" encoding="UTF-8"?>'
-
- shutil.rmtree(temp_dir)
diff --git a/gerber/tests/test_cam.py b/gerber/tests/test_cam.py
deleted file mode 100644
index 8a71a32..0000000
--- a/gerber/tests/test_cam.py
+++ /dev/null
@@ -1,151 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Author: Hamilton Kibbe <ham@hamiltonkib.be>
-
-import pytest
-
-from ..cam import CamFile, FileSettings
-
-
-def test_filesettings_defaults():
- """ Test FileSettings default values
- """
- fs = FileSettings()
- assert fs.format == (2, 5)
- assert fs.notation == "absolute"
- assert fs.zero_suppression == "trailing"
- assert fs.units == "inch"
-
-
-def test_filesettings_dict():
- """ Test FileSettings Dict
- """
- fs = FileSettings()
- assert fs["format"] == (2, 5)
- assert fs["notation"] == "absolute"
- assert fs["zero_suppression"] == "trailing"
- assert fs["units"] == "inch"
-
-
-def test_filesettings_assign():
- """ Test FileSettings attribute assignment
- """
- fs = FileSettings()
- fs.units = "test1"
- fs.notation = "test2"
- fs.zero_suppression = "test3"
- fs.format = "test4"
- assert fs.units == "test1"
- assert fs.notation == "test2"
- assert fs.zero_suppression == "test3"
- assert fs.format == "test4"
-
-
-def test_filesettings_dict_assign():
- """ Test FileSettings dict-style attribute assignment
- """
- fs = FileSettings()
- fs["units"] = "metric"
- fs["notation"] = "incremental"
- fs["zero_suppression"] = "leading"
- fs["format"] = (1, 2)
- assert fs.units == "metric"
- assert fs.notation == "incremental"
- assert fs.zero_suppression == "leading"
- assert fs.format == (1, 2)
-
-
-def test_camfile_init():
- """ Smoke test CamFile test
- """
- cf = CamFile()
-
-
-def test_camfile_settings():
- """ Test CamFile Default Settings
- """
- cf = CamFile()
- assert cf.settings == FileSettings()
-
-
-def test_bounds_override_smoketest():
- cf = CamFile()
- cf.bounds
-
-
-def test_zeros():
- """ Test zero/zero_suppression interaction
- """
- fs = FileSettings()
- assert fs.zero_suppression == "trailing"
- assert fs.zeros == "leading"
-
- fs["zero_suppression"] = "leading"
- assert fs.zero_suppression == "leading"
- assert fs.zeros == "trailing"
-
- fs.zero_suppression = "trailing"
- assert fs.zero_suppression == "trailing"
- assert fs.zeros == "leading"
-
- fs["zeros"] = "trailing"
- assert fs.zeros == "trailing"
- assert fs.zero_suppression == "leading"
-
- fs.zeros = "leading"
- assert fs.zeros == "leading"
- assert fs.zero_suppression == "trailing"
-
- fs = FileSettings(zeros="leading")
- assert fs.zeros == "leading"
- assert fs.zero_suppression == "trailing"
-
- fs = FileSettings(zero_suppression="leading")
- assert fs.zeros == "trailing"
- assert fs.zero_suppression == "leading"
-
- fs = FileSettings(zeros="leading", zero_suppression="trailing")
- assert fs.zeros == "leading"
- assert fs.zero_suppression == "trailing"
-
- fs = FileSettings(zeros="trailing", zero_suppression="leading")
- assert fs.zeros == "trailing"
- assert fs.zero_suppression == "leading"
-
-
-def test_filesettings_validation():
- """ Test FileSettings constructor argument validation
- """
- # absolute-ish is not a valid notation
- pytest.raises(ValueError, FileSettings, "absolute-ish", "inch", None, (2, 5), None)
-
- # degrees kelvin isn't a valid unit for a CAM file
- pytest.raises(
- ValueError, FileSettings, "absolute", "degrees kelvin", None, (2, 5), None
- )
-
- pytest.raises(
- ValueError, FileSettings, "absolute", "inch", "leading", (2, 5), "leading"
- )
-
- # Technnically this should be an error, but Eangle files often do this incorrectly so we
- # allow it
- # pytest.raises(ValueError, FileSettings, 'absolute',
- # 'inch', 'following', (2, 5), None)
-
- pytest.raises(
- ValueError, FileSettings, "absolute", "inch", None, (2, 5), "following"
- )
- pytest.raises(ValueError, FileSettings, "absolute", "inch", None, (2, 5, 6), None)
-
-
-def test_key_validation():
- fs = FileSettings()
- pytest.raises(KeyError, fs.__getitem__, "octopus")
- pytest.raises(KeyError, fs.__setitem__, "octopus", "do not care")
- pytest.raises(ValueError, fs.__setitem__, "notation", "absolute-ish")
- pytest.raises(ValueError, fs.__setitem__, "units", "degrees kelvin")
- pytest.raises(ValueError, fs.__setitem__, "zero_suppression", "following")
- pytest.raises(ValueError, fs.__setitem__, "zeros", "following")
- pytest.raises(ValueError, fs.__setitem__, "format", (2, 5, 6))
diff --git a/gerber/tests/test_common.py b/gerber/tests/test_common.py
deleted file mode 100644
index a6b1264..0000000
--- a/gerber/tests/test_common.py
+++ /dev/null
@@ -1,38 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Author: Hamilton Kibbe <ham@hamiltonkib.be>
-from ..exceptions import ParseError
-from ..common import read, loads
-from ..excellon import ExcellonFile
-from ..rs274x import GerberFile
-import os
-import pytest
-
-
-NCDRILL_FILE = os.path.join(os.path.dirname(__file__), "resources/ncdrill.DRD")
-TOP_COPPER_FILE = os.path.join(os.path.dirname(__file__), "resources/top_copper.GTL")
-
-
-def test_file_type_detection():
- """ Test file type detection
- """
- ncdrill = read(NCDRILL_FILE)
- top_copper = read(TOP_COPPER_FILE)
- assert isinstance(ncdrill, ExcellonFile)
- assert isinstance(top_copper, GerberFile)
-
-
-def test_load_from_string():
- with open(NCDRILL_FILE, "rU") as f:
- ncdrill = loads(f.read())
- with open(TOP_COPPER_FILE, "rU") as f:
- top_copper = loads(f.read())
- assert isinstance(ncdrill, ExcellonFile)
- assert isinstance(top_copper, GerberFile)
-
-
-def test_file_type_validation():
- """ Test file format validation
- """
- pytest.raises(ParseError, read, __file__)
diff --git a/gerber/tests/test_excellon.py b/gerber/tests/test_excellon.py
deleted file mode 100644
index d6e83cc..0000000
--- a/gerber/tests/test_excellon.py
+++ /dev/null
@@ -1,366 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Author: Hamilton Kibbe <ham@hamiltonkib.be>
-import os
-import pytest
-
-from ..cam import FileSettings
-from ..excellon import read, detect_excellon_format, ExcellonFile, ExcellonParser
-from ..excellon import DrillHit, DrillSlot
-from ..excellon_statements import ExcellonTool, RouteModeStmt
-
-
-NCDRILL_FILE = os.path.join(os.path.dirname(__file__), "resources/ncdrill.DRD")
-
-
-def test_format_detection():
- """ Test file type detection
- """
- with open(NCDRILL_FILE, "rU") as f:
- data = f.read()
- settings = detect_excellon_format(data)
- assert settings["format"] == (2, 4)
- assert settings["zeros"] == "trailing"
-
- settings = detect_excellon_format(filename=NCDRILL_FILE)
- assert settings["format"] == (2, 4)
- assert settings["zeros"] == "trailing"
-
-
-def test_read():
- ncdrill = read(NCDRILL_FILE)
- assert isinstance(ncdrill, ExcellonFile)
-
-
-def test_write():
- ncdrill = read(NCDRILL_FILE)
- ncdrill.write("test.ncd")
- with open(NCDRILL_FILE, "rU") as src:
- srclines = src.readlines()
- with open("test.ncd", "rU") as res:
- for idx, line in enumerate(res):
- assert line.strip() == srclines[idx].strip()
- os.remove("test.ncd")
-
-
-def test_read_settings():
- ncdrill = read(NCDRILL_FILE)
- assert ncdrill.settings["format"] == (2, 4)
- assert ncdrill.settings["zeros"] == "trailing"
-
-
-def test_bounding_box():
- ncdrill = read(NCDRILL_FILE)
- xbound, ybound = ncdrill.bounding_box
- pytest.approx(xbound, (0.1300, 2.1430))
- pytest.approx(ybound, (0.3946, 1.7164))
-
-
-def test_report():
- ncdrill = read(NCDRILL_FILE)
- rprt = ncdrill.report()
-
-
-def test_conversion():
- import copy
-
- ncdrill = read(NCDRILL_FILE)
- assert ncdrill.settings.units == "inch"
- ncdrill_inch = copy.deepcopy(ncdrill)
-
- ncdrill.to_metric()
- assert ncdrill.settings.units == "metric"
- for tool in iter(ncdrill_inch.tools.values()):
- tool.to_metric()
-
- for statement in ncdrill_inch.statements:
- statement.to_metric()
-
- for m_tool, i_tool in zip(
- iter(ncdrill.tools.values()), iter(ncdrill_inch.tools.values())
- ):
- assert i_tool == m_tool
-
- for m, i in zip(ncdrill.primitives, ncdrill_inch.primitives):
-
- assert m.position == i.position, "%s not equal to %s" % (m, i)
- assert m.diameter == i.diameter, "%s not equal to %s" % (m, i)
-
-
-def test_parser_hole_count():
- settings = FileSettings(**detect_excellon_format(NCDRILL_FILE))
- p = ExcellonParser(settings)
- p.parse(NCDRILL_FILE)
- assert p.hole_count == 36
-
-
-def test_parser_hole_sizes():
- settings = FileSettings(**detect_excellon_format(NCDRILL_FILE))
- p = ExcellonParser(settings)
- p.parse(NCDRILL_FILE)
- assert p.hole_sizes == [0.0236, 0.0354, 0.04, 0.126, 0.128]
-
-
-def test_parse_whitespace():
- p = ExcellonParser(FileSettings())
- assert p._parse_line(" ") == None
-
-
-def test_parse_comment():
- p = ExcellonParser(FileSettings())
- p._parse_line(";A comment")
- assert p.statements[0].comment == "A comment"
-
-
-def test_parse_format_comment():
- p = ExcellonParser(FileSettings())
- p._parse_line("; FILE_FORMAT=9:9 ")
- assert p.format == (9, 9)
-
-
-def test_parse_header():
- p = ExcellonParser(FileSettings())
- p._parse_line("M48 ")
- assert p.state == "HEADER"
- p._parse_line("M95 ")
- assert p.state == "DRILL"
-
-
-def test_parse_rout():
- p = ExcellonParser(FileSettings())
- p._parse_line("G00X040944Y019842")
- assert p.state == "ROUT"
- p._parse_line("G05 ")
- assert p.state == "DRILL"
-
-
-def test_parse_version():
- p = ExcellonParser(FileSettings())
- p._parse_line("VER,1 ")
- assert p.statements[0].version == 1
- p._parse_line("VER,2 ")
- assert p.statements[1].version == 2
-
-
-def test_parse_format():
- p = ExcellonParser(FileSettings())
- p._parse_line("FMAT,1 ")
- assert p.statements[0].format == 1
- p._parse_line("FMAT,2 ")
- assert p.statements[1].format == 2
-
-
-def test_parse_units():
- settings = FileSettings(units="inch", zeros="trailing")
- p = ExcellonParser(settings)
- p._parse_line(";METRIC,LZ")
- assert p.units == "inch"
- assert p.zeros == "trailing"
- p._parse_line("METRIC,LZ")
- assert p.units == "metric"
- assert p.zeros == "leading"
-
-
-def test_parse_incremental_mode():
- settings = FileSettings(units="inch", zeros="trailing")
- p = ExcellonParser(settings)
- assert p.notation == "absolute"
- p._parse_line("ICI,ON ")
- assert p.notation == "incremental"
- p._parse_line("ICI,OFF ")
- assert p.notation == "absolute"
-
-
-def test_parse_absolute_mode():
- settings = FileSettings(units="inch", zeros="trailing")
- p = ExcellonParser(settings)
- assert p.notation == "absolute"
- p._parse_line("ICI,ON ")
- assert p.notation == "incremental"
- p._parse_line("G90 ")
- assert p.notation == "absolute"
-
-
-def test_parse_repeat_hole():
- p = ExcellonParser(FileSettings())
- p.active_tool = ExcellonTool(FileSettings(), number=8)
- p._parse_line("R03X1.5Y1.5")
- assert p.statements[0].count == 3
-
-
-def test_parse_incremental_position():
- p = ExcellonParser(FileSettings(notation="incremental"))
- p._parse_line("X01Y01")
- p._parse_line("X01Y01")
- assert p.pos == [2.0, 2.0]
-
-
-def test_parse_unknown():
- p = ExcellonParser(FileSettings())
- p._parse_line("Not A Valid Statement")
- assert p.statements[0].stmt == "Not A Valid Statement"
-
-
-def test_drill_hit_units_conversion():
- """ Test unit conversion for drill hits
- """
- # Inch hit
- settings = FileSettings(units="inch")
- tool = ExcellonTool(settings, diameter=1.0)
- hit = DrillHit(tool, (1.0, 1.0))
-
- assert hit.tool.settings.units == "inch"
- assert hit.tool.diameter == 1.0
- assert hit.position == (1.0, 1.0)
-
- # No Effect
- hit.to_inch()
-
- assert hit.tool.settings.units == "inch"
- assert hit.tool.diameter == 1.0
- assert hit.position == (1.0, 1.0)
-
- # Should convert
- hit.to_metric()
-
- assert hit.tool.settings.units == "metric"
- assert hit.tool.diameter == 25.4
- assert hit.position == (25.4, 25.4)
-
- # No Effect
- hit.to_metric()
-
- assert hit.tool.settings.units == "metric"
- assert hit.tool.diameter == 25.4
- assert hit.position == (25.4, 25.4)
-
- # Convert back to inch
- hit.to_inch()
-
- assert hit.tool.settings.units == "inch"
- assert hit.tool.diameter == 1.0
- assert hit.position == (1.0, 1.0)
-
-
-def test_drill_hit_offset():
- TEST_VECTORS = [
- ((0.0, 0.0), (0.0, 1.0), (0.0, 1.0)),
- ((0.0, 0.0), (1.0, 1.0), (1.0, 1.0)),
- ((1.0, 1.0), (0.0, -1.0), (1.0, 0.0)),
- ((1.0, 1.0), (-1.0, -1.0), (0.0, 0.0)),
- ]
- for position, offset, expected in TEST_VECTORS:
- settings = FileSettings(units="inch")
- tool = ExcellonTool(settings, diameter=1.0)
- hit = DrillHit(tool, position)
-
- assert hit.position == position
-
- hit.offset(offset[0], offset[1])
-
- assert hit.position == expected
-
-
-def test_drill_slot_units_conversion():
- """ Test unit conversion for drill hits
- """
- # Inch hit
- settings = FileSettings(units="inch")
- tool = ExcellonTool(settings, diameter=1.0)
- hit = DrillSlot(tool, (1.0, 1.0), (10.0, 10.0), DrillSlot.TYPE_ROUT)
-
- assert hit.tool.settings.units == "inch"
- assert hit.tool.diameter == 1.0
- assert hit.start == (1.0, 1.0)
- assert hit.end == (10.0, 10.0)
-
- # No Effect
- hit.to_inch()
-
- assert hit.tool.settings.units == "inch"
- assert hit.tool.diameter == 1.0
- assert hit.start == (1.0, 1.0)
- assert hit.end == (10.0, 10.0)
-
- # Should convert
- hit.to_metric()
-
- assert hit.tool.settings.units == "metric"
- assert hit.tool.diameter == 25.4
- assert hit.start == (25.4, 25.4)
- assert hit.end == (254.0, 254.0)
-
- # No Effect
- hit.to_metric()
-
- assert hit.tool.settings.units == "metric"
- assert hit.tool.diameter == 25.4
- assert hit.start == (25.4, 25.4)
- assert hit.end == (254.0, 254.0)
-
- # Convert back to inch
- hit.to_inch()
-
- assert hit.tool.settings.units == "inch"
- assert hit.tool.diameter == 1.0
- assert hit.start == (1.0, 1.0)
- assert hit.end == (10.0, 10.0)
-
-
-def test_drill_slot_offset():
- TEST_VECTORS = [
- ((0.0, 0.0), (1.0, 1.0), (0.0, 0.0), (0.0, 0.0), (1.0, 1.0)),
- ((0.0, 0.0), (1.0, 1.0), (1.0, 0.0), (1.0, 0.0), (2.0, 1.0)),
- ((0.0, 0.0), (1.0, 1.0), (1.0, 1.0), (1.0, 1.0), (2.0, 2.0)),
- ((0.0, 0.0), (1.0, 1.0), (-1.0, 1.0), (-1.0, 1.0), (0.0, 2.0)),
- ]
- for start, end, offset, expected_start, expected_end in TEST_VECTORS:
- settings = FileSettings(units="inch")
- tool = ExcellonTool(settings, diameter=1.0)
- slot = DrillSlot(tool, start, end, DrillSlot.TYPE_ROUT)
-
- assert slot.start == start
- assert slot.end == end
-
- slot.offset(offset[0], offset[1])
-
- assert slot.start == expected_start
- assert slot.end == expected_end
-
-
-def test_drill_slot_bounds():
- TEST_VECTORS = [
- ((0.0, 0.0), (1.0, 1.0), 1.0, ((-0.5, 1.5), (-0.5, 1.5))),
- ((0.0, 0.0), (1.0, 1.0), 0.5, ((-0.25, 1.25), (-0.25, 1.25))),
- ]
- for start, end, diameter, expected in TEST_VECTORS:
- settings = FileSettings(units="inch")
- tool = ExcellonTool(settings, diameter=diameter)
- slot = DrillSlot(tool, start, end, DrillSlot.TYPE_ROUT)
-
- assert slot.bounding_box == expected
-
-
-def test_handling_multi_line_g00_and_g1():
- """Route Mode statements with coordinates on separate line are handled
- """
- test_data = """
-%
-M48
-M72
-T01C0.0236
-%
-T01
-G00
-X040944Y019842
-M15
-G01
-X040944Y020708
-M16
-"""
- uut = ExcellonParser()
- uut.parse_raw(test_data)
- assert (
- len([stmt for stmt in uut.statements if isinstance(stmt, RouteModeStmt)]) == 2
- )
diff --git a/gerber/tests/test_excellon_statements.py b/gerber/tests/test_excellon_statements.py
deleted file mode 100644
index 41fe294..0000000
--- a/gerber/tests/test_excellon_statements.py
+++ /dev/null
@@ -1,734 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Author: Hamilton Kibbe <ham@hamiltonkib.be>
-
-import pytest
-from ..excellon_statements import *
-from ..cam import FileSettings
-
-
-def test_excellon_statement_implementation():
- stmt = ExcellonStatement()
- pytest.raises(NotImplementedError, stmt.from_excellon, None)
- pytest.raises(NotImplementedError, stmt.to_excellon)
-
-
-def test_excellontstmt():
- """ Smoke test ExcellonStatement
- """
- stmt = ExcellonStatement()
- stmt.to_inch()
- stmt.to_metric()
- stmt.offset()
-
-
-def test_excellontool_factory():
- """ Test ExcellonTool factory methods
- """
- exc_line = "T8F01B02S00003H04Z05C0.12500"
- settings = FileSettings(
- format=(2, 5), zero_suppression="trailing", units="inch", notation="absolute"
- )
- tool = ExcellonTool.from_excellon(exc_line, settings)
- assert tool.number == 8
- assert tool.diameter == 0.125
- assert tool.feed_rate == 1
- assert tool.retract_rate == 2
- assert tool.rpm == 3
- assert tool.max_hit_count == 4
- assert tool.depth_offset == 5
-
- stmt = {
- "number": 8,
- "feed_rate": 1,
- "retract_rate": 2,
- "rpm": 3,
- "diameter": 0.125,
- "max_hit_count": 4,
- "depth_offset": 5,
- }
- tool = ExcellonTool.from_dict(settings, stmt)
- assert tool.number == 8
- assert tool.diameter == 0.125
- assert tool.feed_rate == 1
- assert tool.retract_rate == 2
- assert tool.rpm == 3
- assert tool.max_hit_count == 4
- assert tool.depth_offset == 5
-
-
-def test_excellontool_dump():
- """ Test ExcellonTool to_excellon()
- """
- exc_lines = [
- "T01F0S0C0.01200",
- "T02F0S0C0.01500",
- "T03F0S0C0.01968",
- "T04F0S0C0.02800",
- "T05F0S0C0.03300",
- "T06F0S0C0.03800",
- "T07F0S0C0.04300",
- "T08F0S0C0.12500",
- "T09F0S0C0.13000",
- "T08B01F02H03S00003C0.12500Z04",
- "T01F0S300.999C0.01200",
- ]
- settings = FileSettings(
- format=(2, 5), zero_suppression="trailing", units="inch", notation="absolute"
- )
- for line in exc_lines:
- tool = ExcellonTool.from_excellon(line, settings)
- assert tool.to_excellon() == line
-
-
-def test_excellontool_order():
- settings = FileSettings(
- format=(2, 5), zero_suppression="trailing", units="inch", notation="absolute"
- )
- line = "T8F00S00C0.12500"
- tool1 = ExcellonTool.from_excellon(line, settings)
- line = "T8C0.12500F00S00"
- tool2 = ExcellonTool.from_excellon(line, settings)
- assert tool1.diameter == tool2.diameter
- assert tool1.feed_rate == tool2.feed_rate
- assert tool1.rpm == tool2.rpm
-
-
-def test_excellontool_conversion():
- tool = ExcellonTool.from_dict(
- FileSettings(units="metric"), {"number": 8, "diameter": 25.4}
- )
- tool.to_inch()
- assert tool.diameter == 1.0
- tool = ExcellonTool.from_dict(
- FileSettings(units="inch"), {"number": 8, "diameter": 1.0}
- )
- tool.to_metric()
- assert tool.diameter == 25.4
-
- # Shouldn't change units if we're already using target units
- tool = ExcellonTool.from_dict(
- FileSettings(units="inch"), {"number": 8, "diameter": 25.4}
- )
- tool.to_inch()
- assert tool.diameter == 25.4
- tool = ExcellonTool.from_dict(
- FileSettings(units="metric"), {"number": 8, "diameter": 1.0}
- )
- tool.to_metric()
- assert tool.diameter == 1.0
-
-
-def test_excellontool_repr():
- tool = ExcellonTool.from_dict(FileSettings(), {"number": 8, "diameter": 0.125})
- assert str(tool) == "<ExcellonTool 08: 0.125in. dia.>"
- tool = ExcellonTool.from_dict(
- FileSettings(units="metric"), {"number": 8, "diameter": 0.125}
- )
- assert str(tool) == "<ExcellonTool 08: 0.125mm dia.>"
-
-
-def test_excellontool_equality():
- t = ExcellonTool.from_dict(FileSettings(), {"number": 8, "diameter": 0.125})
- t1 = ExcellonTool.from_dict(FileSettings(), {"number": 8, "diameter": 0.125})
- assert t == t1
- t1 = ExcellonTool.from_dict(
- FileSettings(units="metric"), {"number": 8, "diameter": 0.125}
- )
- assert t != t1
-
-
-def test_toolselection_factory():
- """ Test ToolSelectionStmt factory method
- """
- stmt = ToolSelectionStmt.from_excellon("T01")
- assert stmt.tool == 1
- assert stmt.compensation_index == None
- stmt = ToolSelectionStmt.from_excellon("T0223")
- assert stmt.tool == 2
- assert stmt.compensation_index == 23
- stmt = ToolSelectionStmt.from_excellon("T042")
- assert stmt.tool == 42
- assert stmt.compensation_index == None
-
-
-def test_toolselection_dump():
- """ Test ToolSelectionStmt to_excellon()
- """
- lines = ["T01", "T0223", "T10", "T09", "T0000"]
- for line in lines:
- stmt = ToolSelectionStmt.from_excellon(line)
- assert stmt.to_excellon() == line
-
-
-def test_z_axis_infeed_rate_factory():
- """ Test ZAxisInfeedRateStmt factory method
- """
- stmt = ZAxisInfeedRateStmt.from_excellon("F01")
- assert stmt.rate == 1
- stmt = ZAxisInfeedRateStmt.from_excellon("F2")
- assert stmt.rate == 2
- stmt = ZAxisInfeedRateStmt.from_excellon("F03")
- assert stmt.rate == 3
-
-
-def test_z_axis_infeed_rate_dump():
- """ Test ZAxisInfeedRateStmt to_excellon()
- """
- inputs = [("F01", "F01"), ("F2", "F02"), ("F00003", "F03")]
- for input_rate, expected_output in inputs:
- stmt = ZAxisInfeedRateStmt.from_excellon(input_rate)
- assert stmt.to_excellon() == expected_output
-
-
-def test_coordinatestmt_factory():
- """ Test CoordinateStmt factory method
- """
- settings = FileSettings(
- format=(2, 5), zero_suppression="trailing", units="inch", notation="absolute"
- )
-
- line = "X0278207Y0065293"
- stmt = CoordinateStmt.from_excellon(line, settings)
- assert stmt.x == 2.78207
- assert stmt.y == 0.65293
-
- # line = 'X02945'
- # stmt = CoordinateStmt.from_excellon(line)
- # assert_equal(stmt.x, 2.945)
-
- # line = 'Y00575'
- # stmt = CoordinateStmt.from_excellon(line)
- # assert_equal(stmt.y, 0.575)
-
- settings = FileSettings(
- format=(2, 4), zero_suppression="leading", units="inch", notation="absolute"
- )
-
- line = "X9660Y4639"
- stmt = CoordinateStmt.from_excellon(line, settings)
- assert stmt.x == 0.9660
- assert stmt.y == 0.4639
- assert stmt.to_excellon(settings) == "X9660Y4639"
- assert stmt.units == "inch"
-
- settings.units = "metric"
- stmt = CoordinateStmt.from_excellon(line, settings)
- assert stmt.units == "metric"
-
-
-def test_coordinatestmt_dump():
- """ Test CoordinateStmt to_excellon()
- """
- lines = [
- "X278207Y65293",
- "X243795",
- "Y82528",
- "Y86028",
- "X251295Y81528",
- "X2525Y78",
- "X255Y575",
- "Y52",
- "X2675",
- "Y575",
- "X2425",
- "Y52",
- "X23",
- ]
- settings = FileSettings(
- format=(2, 4), zero_suppression="leading", units="inch", notation="absolute"
- )
- for line in lines:
- stmt = CoordinateStmt.from_excellon(line, settings)
- assert stmt.to_excellon(settings) == line
-
-
-def test_coordinatestmt_conversion():
-
- settings = FileSettings()
- settings.units = "metric"
- stmt = CoordinateStmt.from_excellon("X254Y254", settings)
-
- # No effect
- stmt.to_metric()
- assert stmt.x == 25.4
- assert stmt.y == 25.4
-
- stmt.to_inch()
- assert stmt.units == "inch"
- assert stmt.x == 1.0
- assert stmt.y == 1.0
-
- # No effect
- stmt.to_inch()
- assert stmt.x == 1.0
- assert stmt.y == 1.0
-
- settings.units = "inch"
- stmt = CoordinateStmt.from_excellon("X01Y01", settings)
-
- # No effect
- stmt.to_inch()
- assert stmt.x == 1.0
- assert stmt.y == 1.0
-
- stmt.to_metric()
- assert stmt.units == "metric"
- assert stmt.x == 25.4
- assert stmt.y == 25.4
-
- # No effect
- stmt.to_metric()
- assert stmt.x == 25.4
- assert stmt.y == 25.4
-
-
-def test_coordinatestmt_offset():
- stmt = CoordinateStmt.from_excellon("X01Y01", FileSettings())
- stmt.offset()
- assert stmt.x == 1
- assert stmt.y == 1
- stmt.offset(1, 0)
- assert stmt.x == 2.0
- assert stmt.y == 1.0
- stmt.offset(0, 1)
- assert stmt.x == 2.0
- assert stmt.y == 2.0
-
-
-def test_coordinatestmt_string():
- settings = FileSettings(
- format=(2, 4), zero_suppression="leading", units="inch", notation="absolute"
- )
- stmt = CoordinateStmt.from_excellon("X9660Y4639", settings)
- assert str(stmt) == "<Coordinate Statement: X: 0.966 Y: 0.4639 >"
-
-
-def test_repeathole_stmt_factory():
- stmt = RepeatHoleStmt.from_excellon(
- "R0004X015Y32", FileSettings(zeros="leading", units="inch")
- )
- assert stmt.count == 4
- assert stmt.xdelta == 1.5
- assert stmt.ydelta == 32
- assert stmt.units == "inch"
-
- stmt = RepeatHoleStmt.from_excellon(
- "R0004X015Y32", FileSettings(zeros="leading", units="metric")
- )
- assert stmt.units == "metric"
-
-
-def test_repeatholestmt_dump():
- line = "R4X015Y32"
- stmt = RepeatHoleStmt.from_excellon(line, FileSettings())
- assert stmt.to_excellon(FileSettings()) == line
-
-
-def test_repeatholestmt_conversion():
- line = "R4X0254Y254"
- settings = FileSettings()
- settings.units = "metric"
- stmt = RepeatHoleStmt.from_excellon(line, settings)
-
- # No effect
- stmt.to_metric()
- assert stmt.xdelta == 2.54
- assert stmt.ydelta == 25.4
-
- stmt.to_inch()
- assert stmt.units == "inch"
- assert stmt.xdelta == 0.1
- assert stmt.ydelta == 1.0
-
- # no effect
- stmt.to_inch()
- assert stmt.xdelta == 0.1
- assert stmt.ydelta == 1.0
-
- line = "R4X01Y1"
- settings.units = "inch"
- stmt = RepeatHoleStmt.from_excellon(line, settings)
-
- # no effect
- stmt.to_inch()
- assert stmt.xdelta == 1.0
- assert stmt.ydelta == 10.0
-
- stmt.to_metric()
- assert stmt.units == "metric"
- assert stmt.xdelta == 25.4
- assert stmt.ydelta == 254.0
-
- # No effect
- stmt.to_metric()
- assert stmt.xdelta == 25.4
- assert stmt.ydelta == 254.0
-
-
-def test_repeathole_str():
- stmt = RepeatHoleStmt.from_excellon("R4X015Y32", FileSettings())
- assert str(stmt) == "<Repeat Hole: 4 times, offset X: 1.5 Y: 32>"
-
-
-def test_commentstmt_factory():
- """ Test CommentStmt factory method
- """
- line = ";Layer_Color=9474304"
- stmt = CommentStmt.from_excellon(line)
- assert stmt.comment == line[1:]
-
- line = ";FILE_FORMAT=2:5"
- stmt = CommentStmt.from_excellon(line)
- assert stmt.comment == line[1:]
-
- line = ";TYPE=PLATED"
- stmt = CommentStmt.from_excellon(line)
- assert stmt.comment == line[1:]
-
-
-def test_commentstmt_dump():
- """ Test CommentStmt to_excellon()
- """
- lines = [";Layer_Color=9474304", ";FILE_FORMAT=2:5", ";TYPE=PLATED"]
- for line in lines:
- stmt = CommentStmt.from_excellon(line)
- assert stmt.to_excellon() == line
-
-
-def test_header_begin_stmt():
- stmt = HeaderBeginStmt()
- assert stmt.to_excellon(None) == "M48"
-
-
-def test_header_end_stmt():
- stmt = HeaderEndStmt()
- assert stmt.to_excellon(None) == "M95"
-
-
-def test_rewindstop_stmt():
- stmt = RewindStopStmt()
- assert stmt.to_excellon(None) == "%"
-
-
-def test_z_axis_rout_position_stmt():
- stmt = ZAxisRoutPositionStmt()
- assert stmt.to_excellon(None) == "M15"
-
-
-def test_retract_with_clamping_stmt():
- stmt = RetractWithClampingStmt()
- assert stmt.to_excellon(None) == "M16"
-
-
-def test_retract_without_clamping_stmt():
- stmt = RetractWithoutClampingStmt()
- assert stmt.to_excellon(None) == "M17"
-
-
-def test_cutter_compensation_off_stmt():
- stmt = CutterCompensationOffStmt()
- assert stmt.to_excellon(None) == "G40"
-
-
-def test_cutter_compensation_left_stmt():
- stmt = CutterCompensationLeftStmt()
- assert stmt.to_excellon(None) == "G41"
-
-
-def test_cutter_compensation_right_stmt():
- stmt = CutterCompensationRightStmt()
- assert stmt.to_excellon(None) == "G42"
-
-
-def test_endofprogramstmt_factory():
- settings = FileSettings(units="inch")
- stmt = EndOfProgramStmt.from_excellon("M30X01Y02", settings)
- assert stmt.x == 1.0
- assert stmt.y == 2.0
- assert stmt.units == "inch"
- settings.units = "metric"
- stmt = EndOfProgramStmt.from_excellon("M30X01", settings)
- assert stmt.x == 1.0
- assert stmt.y == None
- assert stmt.units == "metric"
- stmt = EndOfProgramStmt.from_excellon("M30Y02", FileSettings())
- assert stmt.x == None
- assert stmt.y == 2.0
-
-
-def test_endofprogramStmt_dump():
- lines = ["M30X01Y02"]
- for line in lines:
- stmt = EndOfProgramStmt.from_excellon(line, FileSettings())
- assert stmt.to_excellon(FileSettings()) == line
-
-
-def test_endofprogramstmt_conversion():
- settings = FileSettings()
- settings.units = "metric"
- stmt = EndOfProgramStmt.from_excellon("M30X0254Y254", settings)
- # No effect
- stmt.to_metric()
- assert stmt.x == 2.54
- assert stmt.y == 25.4
-
- stmt.to_inch()
- assert stmt.units == "inch"
- assert stmt.x == 0.1
- assert stmt.y == 1.0
-
- # No effect
- stmt.to_inch()
- assert stmt.x == 0.1
- assert stmt.y == 1.0
-
- settings.units = "inch"
- stmt = EndOfProgramStmt.from_excellon("M30X01Y1", settings)
-
- # No effect
- stmt.to_inch()
- assert stmt.x == 1.0
- assert stmt.y == 10.0
-
- stmt.to_metric()
- assert stmt.units == "metric"
- assert stmt.x == 25.4
- assert stmt.y == 254.0
-
- # No effect
- stmt.to_metric()
- assert stmt.x == 25.4
- assert stmt.y == 254.0
-
-
-def test_endofprogramstmt_offset():
- stmt = EndOfProgramStmt(1, 1)
- stmt.offset()
- assert stmt.x == 1
- assert stmt.y == 1
- stmt.offset(1, 0)
- assert stmt.x == 2.0
- assert stmt.y == 1.0
- stmt.offset(0, 1)
- assert stmt.x == 2.0
- assert stmt.y == 2.0
-
-
-def test_unitstmt_factory():
- """ Test UnitStmt factory method
- """
- line = "INCH,LZ"
- stmt = UnitStmt.from_excellon(line)
- assert stmt.units == "inch"
- assert stmt.zeros == "leading"
-
- line = "INCH,TZ"
- stmt = UnitStmt.from_excellon(line)
- assert stmt.units == "inch"
- assert stmt.zeros == "trailing"
-
- line = "METRIC,LZ"
- stmt = UnitStmt.from_excellon(line)
- assert stmt.units == "metric"
- assert stmt.zeros == "leading"
-
- line = "METRIC,TZ"
- stmt = UnitStmt.from_excellon(line)
- assert stmt.units == "metric"
- assert stmt.zeros == "trailing"
-
-
-def test_unitstmt_dump():
- """ Test UnitStmt to_excellon()
- """
- lines = ["INCH,LZ", "INCH,TZ", "METRIC,LZ", "METRIC,TZ"]
- for line in lines:
- stmt = UnitStmt.from_excellon(line)
- assert stmt.to_excellon() == line
-
-
-def test_unitstmt_conversion():
- stmt = UnitStmt.from_excellon("METRIC,TZ")
- stmt.to_inch()
- assert stmt.units == "inch"
-
- stmt = UnitStmt.from_excellon("INCH,TZ")
- stmt.to_metric()
- assert stmt.units == "metric"
-
-
-def test_incrementalmode_factory():
- """ Test IncrementalModeStmt factory method
- """
- line = "ICI,ON"
- stmt = IncrementalModeStmt.from_excellon(line)
- assert stmt.mode == "on"
-
- line = "ICI,OFF"
- stmt = IncrementalModeStmt.from_excellon(line)
- assert stmt.mode == "off"
-
-
-def test_incrementalmode_dump():
- """ Test IncrementalModeStmt to_excellon()
- """
- lines = ["ICI,ON", "ICI,OFF"]
- for line in lines:
- stmt = IncrementalModeStmt.from_excellon(line)
- assert stmt.to_excellon() == line
-
-
-def test_incrementalmode_validation():
- """ Test IncrementalModeStmt input validation
- """
- pytest.raises(ValueError, IncrementalModeStmt, "OFF-ISH")
-
-
-def test_versionstmt_factory():
- """ Test VersionStmt factory method
- """
- line = "VER,1"
- stmt = VersionStmt.from_excellon(line)
- assert stmt.version == 1
-
- line = "VER,2"
- stmt = VersionStmt.from_excellon(line)
- assert stmt.version == 2
-
-
-def test_versionstmt_dump():
- """ Test VersionStmt to_excellon()
- """
- lines = ["VER,1", "VER,2"]
- for line in lines:
- stmt = VersionStmt.from_excellon(line)
- assert stmt.to_excellon() == line
-
-
-def test_versionstmt_validation():
- """ Test VersionStmt input validation
- """
- pytest.raises(ValueError, VersionStmt, 3)
-
-
-def test_formatstmt_factory():
- """ Test FormatStmt factory method
- """
- line = "FMAT,1"
- stmt = FormatStmt.from_excellon(line)
- assert stmt.format == 1
-
- line = "FMAT,2"
- stmt = FormatStmt.from_excellon(line)
- assert stmt.format == 2
-
-
-def test_formatstmt_dump():
- """ Test FormatStmt to_excellon()
- """
- lines = ["FMAT,1", "FMAT,2"]
- for line in lines:
- stmt = FormatStmt.from_excellon(line)
- assert stmt.to_excellon() == line
-
-
-def test_formatstmt_validation():
- """ Test FormatStmt input validation
- """
- pytest.raises(ValueError, FormatStmt, 3)
-
-
-def test_linktoolstmt_factory():
- """ Test LinkToolStmt factory method
- """
- line = "1/2/3/4"
- stmt = LinkToolStmt.from_excellon(line)
- assert stmt.linked_tools == [1, 2, 3, 4]
-
- line = "01/02/03/04"
- stmt = LinkToolStmt.from_excellon(line)
- assert stmt.linked_tools == [1, 2, 3, 4]
-
-
-def test_linktoolstmt_dump():
- """ Test LinkToolStmt to_excellon()
- """
- lines = ["1/2/3/4", "5/6/7"]
- for line in lines:
- stmt = LinkToolStmt.from_excellon(line)
- assert stmt.to_excellon() == line
-
-
-def test_measmodestmt_factory():
- """ Test MeasuringModeStmt factory method
- """
- line = "M72"
- stmt = MeasuringModeStmt.from_excellon(line)
- assert stmt.units == "inch"
-
- line = "M71"
- stmt = MeasuringModeStmt.from_excellon(line)
- assert stmt.units == "metric"
-
-
-def test_measmodestmt_dump():
- """ Test MeasuringModeStmt to_excellon()
- """
- lines = ["M71", "M72"]
- for line in lines:
- stmt = MeasuringModeStmt.from_excellon(line)
- assert stmt.to_excellon() == line
-
-
-def test_measmodestmt_validation():
- """ Test MeasuringModeStmt input validation
- """
- pytest.raises(ValueError, MeasuringModeStmt.from_excellon, "M70")
- pytest.raises(ValueError, MeasuringModeStmt, "millimeters")
-
-
-def test_measmodestmt_conversion():
- line = "M72"
- stmt = MeasuringModeStmt.from_excellon(line)
- assert stmt.units == "inch"
- stmt.to_metric()
- assert stmt.units == "metric"
-
- line = "M71"
- stmt = MeasuringModeStmt.from_excellon(line)
- assert stmt.units == "metric"
- stmt.to_inch()
- assert stmt.units == "inch"
-
-
-def test_routemode_stmt():
- stmt = RouteModeStmt()
- assert stmt.to_excellon(FileSettings()) == "G00"
-
-
-def test_linearmode_stmt():
- stmt = LinearModeStmt()
- assert stmt.to_excellon(FileSettings()) == "G01"
-
-
-def test_drillmode_stmt():
- stmt = DrillModeStmt()
- assert stmt.to_excellon(FileSettings()) == "G05"
-
-
-def test_absolutemode_stmt():
- stmt = AbsoluteModeStmt()
- assert stmt.to_excellon(FileSettings()) == "G90"
-
-
-def test_unknownstmt():
- stmt = UnknownStmt("TEST")
- assert stmt.stmt == "TEST"
- assert str(stmt) == "<Unknown Statement: TEST>"
-
-
-def test_unknownstmt_dump():
- stmt = UnknownStmt("TEST")
- assert stmt.to_excellon(FileSettings()) == "TEST"
diff --git a/gerber/tests/test_gerber_statements.py b/gerber/tests/test_gerber_statements.py
deleted file mode 100644
index 140cbd1..0000000
--- a/gerber/tests/test_gerber_statements.py
+++ /dev/null
@@ -1,959 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Author: Hamilton Kibbe <ham@hamiltonkib.be>
-
-import pytest
-from ..gerber_statements import *
-from ..cam import FileSettings
-
-
-def test_Statement_smoketest():
- stmt = Statement("Test")
- assert stmt.type == "Test"
- stmt.to_metric()
- assert "units=metric" in str(stmt)
- stmt.to_inch()
- assert "units=inch" in str(stmt)
- stmt.to_metric()
- stmt.offset(1, 1)
- assert "type=Test" in str(stmt)
-
-
-def test_FSParamStmt_factory():
- """ Test FSParamStruct factory
- """
- stmt = {"param": "FS", "zero": "L", "notation": "A", "x": "27"}
- fs = FSParamStmt.from_dict(stmt)
- assert fs.param == "FS"
- assert fs.zero_suppression == "leading"
- assert fs.notation == "absolute"
- assert fs.format == (2, 7)
-
- stmt = {"param": "FS", "zero": "T", "notation": "I", "x": "27"}
- fs = FSParamStmt.from_dict(stmt)
- assert fs.param == "FS"
- assert fs.zero_suppression == "trailing"
- assert fs.notation == "incremental"
- assert fs.format == (2, 7)
-
-
-def test_FSParamStmt():
- """ Test FSParamStmt initialization
- """
- param = "FS"
- zeros = "trailing"
- notation = "absolute"
- fmt = (2, 5)
- stmt = FSParamStmt(param, zeros, notation, fmt)
- assert stmt.param == param
- assert stmt.zero_suppression == zeros
- assert stmt.notation == notation
- assert stmt.format == fmt
-
-
-def test_FSParamStmt_dump():
- """ Test FSParamStmt to_gerber()
- """
- stmt = {"param": "FS", "zero": "L", "notation": "A", "x": "27"}
- fs = FSParamStmt.from_dict(stmt)
- assert fs.to_gerber() == "%FSLAX27Y27*%"
-
- stmt = {"param": "FS", "zero": "T", "notation": "I", "x": "25"}
- fs = FSParamStmt.from_dict(stmt)
- assert fs.to_gerber() == "%FSTIX25Y25*%"
-
- settings = FileSettings(zero_suppression="leading", notation="absolute")
- assert fs.to_gerber(settings) == "%FSLAX25Y25*%"
-
-
-def test_FSParamStmt_string():
- """ Test FSParamStmt.__str__()
- """
- stmt = {"param": "FS", "zero": "L", "notation": "A", "x": "27"}
- fs = FSParamStmt.from_dict(stmt)
- assert str(fs) == "<Format Spec: 2:7 leading zero suppression absolute notation>"
-
- stmt = {"param": "FS", "zero": "T", "notation": "I", "x": "25"}
- fs = FSParamStmt.from_dict(stmt)
- assert (
- str(fs) == "<Format Spec: 2:5 trailing zero suppression incremental notation>"
- )
-
-
-def test_MOParamStmt_factory():
- """ Test MOParamStruct factory
- """
- stmts = [{"param": "MO", "mo": "IN"}, {"param": "MO", "mo": "in"}]
- for stmt in stmts:
- mo = MOParamStmt.from_dict(stmt)
- assert mo.param == "MO"
- assert mo.mode == "inch"
-
- stmts = [{"param": "MO", "mo": "MM"}, {"param": "MO", "mo": "mm"}]
- for stmt in stmts:
- mo = MOParamStmt.from_dict(stmt)
- assert mo.param == "MO"
- assert mo.mode == "metric"
-
- stmt = {"param": "MO"}
- mo = MOParamStmt.from_dict(stmt)
- assert mo.mode == None
- stmt = {"param": "MO", "mo": "degrees kelvin"}
- pytest.raises(ValueError, MOParamStmt.from_dict, stmt)
-
-
-def test_MOParamStmt():
- """ Test MOParamStmt initialization
- """
- param = "MO"
- mode = "inch"
- stmt = MOParamStmt(param, mode)
- assert stmt.param == param
-
- for mode in ["inch", "metric"]:
- stmt = MOParamStmt(param, mode)
- assert stmt.mode == mode
-
-
-def test_MOParamStmt_dump():
- """ Test MOParamStmt to_gerber()
- """
- stmt = {"param": "MO", "mo": "IN"}
- mo = MOParamStmt.from_dict(stmt)
- assert mo.to_gerber() == "%MOIN*%"
-
- stmt = {"param": "MO", "mo": "MM"}
- mo = MOParamStmt.from_dict(stmt)
- assert mo.to_gerber() == "%MOMM*%"
-
-
-def test_MOParamStmt_conversion():
- stmt = {"param": "MO", "mo": "MM"}
- mo = MOParamStmt.from_dict(stmt)
- mo.to_inch()
- assert mo.mode == "inch"
-
- stmt = {"param": "MO", "mo": "IN"}
- mo = MOParamStmt.from_dict(stmt)
- mo.to_metric()
- assert mo.mode == "metric"
-
-
-def test_MOParamStmt_string():
- """ Test MOParamStmt.__str__()
- """
- stmt = {"param": "MO", "mo": "IN"}
- mo = MOParamStmt.from_dict(stmt)
- assert str(mo) == "<Mode: inches>"
-
- stmt = {"param": "MO", "mo": "MM"}
- mo = MOParamStmt.from_dict(stmt)
- assert str(mo) == "<Mode: millimeters>"
-
-
-def test_IPParamStmt_factory():
- """ Test IPParamStruct factory
- """
- stmt = {"param": "IP", "ip": "POS"}
- ip = IPParamStmt.from_dict(stmt)
- assert ip.ip == "positive"
-
- stmt = {"param": "IP", "ip": "NEG"}
- ip = IPParamStmt.from_dict(stmt)
- assert ip.ip == "negative"
-
-
-def test_IPParamStmt():
- """ Test IPParamStmt initialization
- """
- param = "IP"
- for ip in ["positive", "negative"]:
- stmt = IPParamStmt(param, ip)
- assert stmt.param == param
- assert stmt.ip == ip
-
-
-def test_IPParamStmt_dump():
- """ Test IPParamStmt to_gerber()
- """
- stmt = {"param": "IP", "ip": "POS"}
- ip = IPParamStmt.from_dict(stmt)
- assert ip.to_gerber() == "%IPPOS*%"
-
- stmt = {"param": "IP", "ip": "NEG"}
- ip = IPParamStmt.from_dict(stmt)
- assert ip.to_gerber() == "%IPNEG*%"
-
-
-def test_IPParamStmt_string():
- stmt = {"param": "IP", "ip": "POS"}
- ip = IPParamStmt.from_dict(stmt)
- assert str(ip) == "<Image Polarity: positive>"
-
- stmt = {"param": "IP", "ip": "NEG"}
- ip = IPParamStmt.from_dict(stmt)
- assert str(ip) == "<Image Polarity: negative>"
-
-
-def test_IRParamStmt_factory():
- stmt = {"param": "IR", "angle": "45"}
- ir = IRParamStmt.from_dict(stmt)
- assert ir.param == "IR"
- assert ir.angle == 45
-
-
-def test_IRParamStmt_dump():
- stmt = {"param": "IR", "angle": "45"}
- ir = IRParamStmt.from_dict(stmt)
- assert ir.to_gerber() == "%IR45*%"
-
-
-def test_IRParamStmt_string():
- stmt = {"param": "IR", "angle": "45"}
- ir = IRParamStmt.from_dict(stmt)
- assert str(ir) == "<Image Angle: 45>"
-
-
-def test_OFParamStmt_factory():
- """ Test OFParamStmt factory
- """
- stmt = {"param": "OF", "a": "0.1234567", "b": "0.1234567"}
- of = OFParamStmt.from_dict(stmt)
- assert of.a == 0.1234567
- assert of.b == 0.1234567
-
-
-def test_OFParamStmt():
- """ Test IPParamStmt initialization
- """
- param = "OF"
- for val in [0.0, -3.4567]:
- stmt = OFParamStmt(param, val, val)
- assert stmt.param == param
- assert stmt.a == val
- assert stmt.b == val
-
-
-def test_OFParamStmt_dump():
- """ Test OFParamStmt to_gerber()
- """
- stmt = {"param": "OF", "a": "0.123456", "b": "0.123456"}
- of = OFParamStmt.from_dict(stmt)
- assert of.to_gerber() == "%OFA0.12345B0.12345*%"
-
-
-def test_OFParamStmt_conversion():
- stmt = {"param": "OF", "a": "2.54", "b": "25.4"}
- of = OFParamStmt.from_dict(stmt)
- of.units = "metric"
-
- # No effect
- of.to_metric()
- assert of.a == 2.54
- assert of.b == 25.4
-
- of.to_inch()
- assert of.units == "inch"
- assert of.a == 0.1
- assert of.b == 1.0
-
- # No effect
- of.to_inch()
- assert of.a == 0.1
- assert of.b == 1.0
-
- stmt = {"param": "OF", "a": "0.1", "b": "1.0"}
- of = OFParamStmt.from_dict(stmt)
- of.units = "inch"
-
- # No effect
- of.to_inch()
- assert of.a == 0.1
- assert of.b == 1.0
-
- of.to_metric()
- assert of.units == "metric"
- assert of.a == 2.54
- assert of.b == 25.4
-
- # No effect
- of.to_metric()
- assert of.a == 2.54
- assert of.b == 25.4
-
-
-def test_OFParamStmt_offset():
- s = OFParamStmt("OF", 0, 0)
- s.offset(1, 0)
- assert s.a == 1.0
- assert s.b == 0.0
- s.offset(0, 1)
- assert s.a == 1.0
- assert s.b == 1.0
-
-
-def test_OFParamStmt_string():
- """ Test OFParamStmt __str__
- """
- stmt = {"param": "OF", "a": "0.123456", "b": "0.123456"}
- of = OFParamStmt.from_dict(stmt)
- assert str(of) == "<Offset: X: 0.123456 Y: 0.123456 >"
-
-
-def test_SFParamStmt_factory():
- stmt = {"param": "SF", "a": "1.4", "b": "0.9"}
- sf = SFParamStmt.from_dict(stmt)
- assert sf.param == "SF"
- assert sf.a == 1.4
- assert sf.b == 0.9
-
-
-def test_SFParamStmt_dump():
- stmt = {"param": "SF", "a": "1.4", "b": "0.9"}
- sf = SFParamStmt.from_dict(stmt)
- assert sf.to_gerber() == "%SFA1.4B0.9*%"
-
-
-def test_SFParamStmt_conversion():
- stmt = {"param": "OF", "a": "2.54", "b": "25.4"}
- of = SFParamStmt.from_dict(stmt)
- of.units = "metric"
- of.to_metric()
-
- # No effect
- assert of.a == 2.54
- assert of.b == 25.4
-
- of.to_inch()
- assert of.units == "inch"
- assert of.a == 0.1
- assert of.b == 1.0
-
- # No effect
- of.to_inch()
- assert of.a == 0.1
- assert of.b == 1.0
-
- stmt = {"param": "OF", "a": "0.1", "b": "1.0"}
- of = SFParamStmt.from_dict(stmt)
- of.units = "inch"
-
- # No effect
- of.to_inch()
- assert of.a == 0.1
- assert of.b == 1.0
-
- of.to_metric()
- assert of.units == "metric"
- assert of.a == 2.54
- assert of.b == 25.4
-
- # No effect
- of.to_metric()
- assert of.a == 2.54
- assert of.b == 25.4
-
-
-def test_SFParamStmt_offset():
- s = SFParamStmt("OF", 0, 0)
- s.offset(1, 0)
- assert s.a == 1.0
- assert s.b == 0.0
- s.offset(0, 1)
- assert s.a == 1.0
- assert s.b == 1.0
-
-
-def test_SFParamStmt_string():
- stmt = {"param": "SF", "a": "1.4", "b": "0.9"}
- sf = SFParamStmt.from_dict(stmt)
- assert str(sf) == "<Scale Factor: X: 1.4 Y: 0.9>"
-
-
-def test_LPParamStmt_factory():
- """ Test LPParamStmt factory
- """
- stmt = {"param": "LP", "lp": "C"}
- lp = LPParamStmt.from_dict(stmt)
- assert lp.lp == "clear"
-
- stmt = {"param": "LP", "lp": "D"}
- lp = LPParamStmt.from_dict(stmt)
- assert lp.lp == "dark"
-
-
-def test_LPParamStmt_dump():
- """ Test LPParamStmt to_gerber()
- """
- stmt = {"param": "LP", "lp": "C"}
- lp = LPParamStmt.from_dict(stmt)
- assert lp.to_gerber() == "%LPC*%"
-
- stmt = {"param": "LP", "lp": "D"}
- lp = LPParamStmt.from_dict(stmt)
- assert lp.to_gerber() == "%LPD*%"
-
-
-def test_LPParamStmt_string():
- """ Test LPParamStmt.__str__()
- """
- stmt = {"param": "LP", "lp": "D"}
- lp = LPParamStmt.from_dict(stmt)
- assert str(lp) == "<Level Polarity: dark>"
-
- stmt = {"param": "LP", "lp": "C"}
- lp = LPParamStmt.from_dict(stmt)
- assert str(lp) == "<Level Polarity: clear>"
-
-
-def test_AMParamStmt_factory():
- name = "DONUTVAR"
- macro = """0 Test Macro. *
-1,1,1.5,0,0*
-20,1,0.9,0,0.45,12,0.45,0*
-21,1,6.8,1.2,3.4,0.6,0*
-22,1,6.8,1.2,0,0,0*
-4,1,4,0.1,0.1,0.5,0.1,0.5,0.5,0.1,0.5,0.1,0.1,0*
-5,1,8,0,0,8,0*
-6,0,0,5,0.5,0.5,2,0.1,6,0*
-7,0,0,7,6,0.2,0*
-8,THIS IS AN UNSUPPORTED PRIMITIVE*
-"""
- s = AMParamStmt.from_dict({"param": "AM", "name": name, "macro": macro})
- s.build()
- assert len(s.primitives) == 10
- assert isinstance(s.primitives[0], AMCommentPrimitive)
- assert isinstance(s.primitives[1], AMCirclePrimitive)
- assert isinstance(s.primitives[2], AMVectorLinePrimitive)
- assert isinstance(s.primitives[3], AMCenterLinePrimitive)
- assert isinstance(s.primitives[4], AMLowerLeftLinePrimitive)
- assert isinstance(s.primitives[5], AMOutlinePrimitive)
- assert isinstance(s.primitives[6], AMPolygonPrimitive)
- assert isinstance(s.primitives[7], AMMoirePrimitive)
- assert isinstance(s.primitives[8], AMThermalPrimitive)
- assert isinstance(s.primitives[9], AMUnsupportPrimitive)
-
-
-def testAMParamStmt_conversion():
- name = "POLYGON"
- macro = "5,1,8,25.4,25.4,25.4,0*"
- s = AMParamStmt.from_dict({"param": "AM", "name": name, "macro": macro})
-
- s.build()
- s.units = "metric"
-
- # No effect
- s.to_metric()
- assert s.primitives[0].position == (25.4, 25.4)
- assert s.primitives[0].diameter == 25.4
-
- s.to_inch()
- assert s.units == "inch"
- assert s.primitives[0].position == (1.0, 1.0)
- assert s.primitives[0].diameter == 1.0
-
- # No effect
- s.to_inch()
- assert s.primitives[0].position == (1.0, 1.0)
- assert s.primitives[0].diameter == 1.0
-
- macro = "5,1,8,1,1,1,0*"
- s = AMParamStmt.from_dict({"param": "AM", "name": name, "macro": macro})
- s.build()
- s.units = "inch"
-
- # No effect
- s.to_inch()
- assert s.primitives[0].position == (1.0, 1.0)
- assert s.primitives[0].diameter == 1.0
-
- s.to_metric()
- assert s.units == "metric"
- assert s.primitives[0].position == (25.4, 25.4)
- assert s.primitives[0].diameter == 25.4
-
- # No effect
- s.to_metric()
- assert s.primitives[0].position == (25.4, 25.4)
- assert s.primitives[0].diameter == 25.4
-
-
-def test_AMParamStmt_dump():
- name = "POLYGON"
- macro = "5,1,8,25.4,25.4,25.4,0.0"
- s = AMParamStmt.from_dict({"param": "AM", "name": name, "macro": macro})
- s.build()
- assert s.to_gerber() == "%AMPOLYGON*5,1,8,25.4,25.4,25.4,0.0*%"
-
- # TODO - Store Equations and update on unit change...
- s = AMParamStmt.from_dict(
- {"param": "AM", "name": "OC8", "macro": "5,1,8,0,0,1.08239X$1,22.5"}
- )
- s.build()
- # assert_equal(s.to_gerber(), '%AMOC8*5,1,8,0,0,1.08239X$1,22.5*%')
- assert s.to_gerber() == "%AMOC8*5,1,8,0,0,0,22.5*%"
-
-
-def test_AMParamStmt_string():
- name = "POLYGON"
- macro = "5,1,8,25.4,25.4,25.4,0*"
- s = AMParamStmt.from_dict({"param": "AM", "name": name, "macro": macro})
- s.build()
- assert str(s) == "<Aperture Macro POLYGON: 5,1,8,25.4,25.4,25.4,0*>"
-
-
-def test_ASParamStmt_factory():
- stmt = {"param": "AS", "mode": "AXBY"}
- s = ASParamStmt.from_dict(stmt)
- assert s.param == "AS"
- assert s.mode == "AXBY"
-
-
-def test_ASParamStmt_dump():
- stmt = {"param": "AS", "mode": "AXBY"}
- s = ASParamStmt.from_dict(stmt)
- assert s.to_gerber() == "%ASAXBY*%"
-
-
-def test_ASParamStmt_string():
- stmt = {"param": "AS", "mode": "AXBY"}
- s = ASParamStmt.from_dict(stmt)
- assert str(s) == "<Axis Select: AXBY>"
-
-
-def test_INParamStmt_factory():
- """ Test INParamStmt factory
- """
- stmt = {"param": "IN", "name": "test"}
- inp = INParamStmt.from_dict(stmt)
- assert inp.name == "test"
-
-
-def test_INParamStmt_dump():
- """ Test INParamStmt to_gerber()
- """
- stmt = {"param": "IN", "name": "test"}
- inp = INParamStmt.from_dict(stmt)
- assert inp.to_gerber() == "%INtest*%"
-
-
-def test_INParamStmt_string():
- stmt = {"param": "IN", "name": "test"}
- inp = INParamStmt.from_dict(stmt)
- assert str(inp) == "<Image Name: test>"
-
-
-def test_LNParamStmt_factory():
- """ Test LNParamStmt factory
- """
- stmt = {"param": "LN", "name": "test"}
- lnp = LNParamStmt.from_dict(stmt)
- assert lnp.name == "test"
-
-
-def test_LNParamStmt_dump():
- """ Test LNParamStmt to_gerber()
- """
- stmt = {"param": "LN", "name": "test"}
- lnp = LNParamStmt.from_dict(stmt)
- assert lnp.to_gerber() == "%LNtest*%"
-
-
-def test_LNParamStmt_string():
- stmt = {"param": "LN", "name": "test"}
- lnp = LNParamStmt.from_dict(stmt)
- assert str(lnp) == "<Level Name: test>"
-
-
-def test_comment_stmt():
- """ Test comment statement
- """
- stmt = CommentStmt("A comment")
- assert stmt.type == "COMMENT"
- assert stmt.comment == "A comment"
-
-
-def test_comment_stmt_dump():
- """ Test CommentStmt to_gerber()
- """
- stmt = CommentStmt("A comment")
- assert stmt.to_gerber() == "G04A comment*"
-
-
-def test_comment_stmt_string():
- stmt = CommentStmt("A comment")
- assert str(stmt) == "<Comment: A comment>"
-
-
-def test_eofstmt():
- """ Test EofStmt
- """
- stmt = EofStmt()
- assert stmt.type == "EOF"
-
-
-def test_eofstmt_dump():
- """ Test EofStmt to_gerber()
- """
- stmt = EofStmt()
- assert stmt.to_gerber() == "M02*"
-
-
-def test_eofstmt_string():
- assert str(EofStmt()) == "<EOF Statement>"
-
-
-def test_quadmodestmt_factory():
- """ Test QuadrantModeStmt.from_gerber()
- """
- line = "G74*"
- stmt = QuadrantModeStmt.from_gerber(line)
- assert stmt.type == "QuadrantMode"
- assert stmt.mode == "single-quadrant"
-
- line = "G75*"
- stmt = QuadrantModeStmt.from_gerber(line)
- assert stmt.mode == "multi-quadrant"
-
-
-def test_quadmodestmt_validation():
- """ Test QuadrantModeStmt input validation
- """
- line = "G76*"
- pytest.raises(ValueError, QuadrantModeStmt.from_gerber, line)
- pytest.raises(ValueError, QuadrantModeStmt, "quadrant-ful")
-
-
-def test_quadmodestmt_dump():
- """ Test QuadrantModeStmt.to_gerber()
- """
- for line in ("G74*", "G75*"):
- stmt = QuadrantModeStmt.from_gerber(line)
- assert stmt.to_gerber() == line
-
-
-def test_regionmodestmt_factory():
- """ Test RegionModeStmt.from_gerber()
- """
- line = "G36*"
- stmt = RegionModeStmt.from_gerber(line)
- assert stmt.type == "RegionMode"
- assert stmt.mode == "on"
-
- line = "G37*"
- stmt = RegionModeStmt.from_gerber(line)
- assert stmt.mode == "off"
-
-
-def test_regionmodestmt_validation():
- """ Test RegionModeStmt input validation
- """
- line = "G38*"
- pytest.raises(ValueError, RegionModeStmt.from_gerber, line)
- pytest.raises(ValueError, RegionModeStmt, "off-ish")
-
-
-def test_regionmodestmt_dump():
- """ Test RegionModeStmt.to_gerber()
- """
- for line in ("G36*", "G37*"):
- stmt = RegionModeStmt.from_gerber(line)
- assert stmt.to_gerber() == line
-
-
-def test_unknownstmt():
- """ Test UnknownStmt
- """
- line = "G696969*"
- stmt = UnknownStmt(line)
- assert stmt.type == "UNKNOWN"
- assert stmt.line == line
-
-
-def test_unknownstmt_dump():
- """ Test UnknownStmt.to_gerber()
- """
- lines = ("G696969*", "M03*")
- for line in lines:
- stmt = UnknownStmt(line)
- assert stmt.to_gerber() == line
-
-
-def test_statement_string():
- """ Test Statement.__str__()
- """
- stmt = Statement("PARAM")
- assert "type=PARAM" in str(stmt)
- stmt.test = "PASS"
- assert "test=PASS" in str(stmt)
- assert "type=PARAM" in str(stmt)
-
-
-def test_ADParamStmt_factory():
- """ Test ADParamStmt factory
- """
- stmt = {"param": "AD", "d": 0, "shape": "C"}
- ad = ADParamStmt.from_dict(stmt)
- assert ad.d == 0
- assert ad.shape == "C"
-
- stmt = {"param": "AD", "d": 1, "shape": "R"}
- ad = ADParamStmt.from_dict(stmt)
- assert ad.d == 1
- assert ad.shape == "R"
-
- stmt = {"param": "AD", "d": 1, "shape": "C", "modifiers": "1.42"}
- ad = ADParamStmt.from_dict(stmt)
- assert ad.d == 1
- assert ad.shape == "C"
- assert ad.modifiers == [(1.42,)]
-
- stmt = {"param": "AD", "d": 1, "shape": "C", "modifiers": "1.42X"}
- ad = ADParamStmt.from_dict(stmt)
- assert ad.d == 1
- assert ad.shape == "C"
- assert ad.modifiers == [(1.42,)]
-
- stmt = {"param": "AD", "d": 1, "shape": "R", "modifiers": "1.42X1.24"}
- ad = ADParamStmt.from_dict(stmt)
- assert ad.d == 1
- assert ad.shape == "R"
- assert ad.modifiers == [(1.42, 1.24)]
-
-
-def test_ADParamStmt_conversion():
- stmt = {"param": "AD", "d": 0, "shape": "C", "modifiers": "25.4X25.4,25.4X25.4"}
- ad = ADParamStmt.from_dict(stmt)
- ad.units = "metric"
-
- # No effect
- ad.to_metric()
- assert ad.modifiers[0] == (25.4, 25.4)
- assert ad.modifiers[1] == (25.4, 25.4)
-
- ad.to_inch()
- assert ad.units == "inch"
- assert ad.modifiers[0] == (1.0, 1.0)
- assert ad.modifiers[1] == (1.0, 1.0)
-
- # No effect
- ad.to_inch()
- assert ad.modifiers[0] == (1.0, 1.0)
- assert ad.modifiers[1] == (1.0, 1.0)
-
- stmt = {"param": "AD", "d": 0, "shape": "C", "modifiers": "1X1,1X1"}
- ad = ADParamStmt.from_dict(stmt)
- ad.units = "inch"
-
- # No effect
- ad.to_inch()
- assert ad.modifiers[0] == (1.0, 1.0)
- assert ad.modifiers[1] == (1.0, 1.0)
-
- ad.to_metric()
- assert ad.modifiers[0] == (25.4, 25.4)
- assert ad.modifiers[1] == (25.4, 25.4)
-
- # No effect
- ad.to_metric()
- assert ad.modifiers[0] == (25.4, 25.4)
- assert ad.modifiers[1] == (25.4, 25.4)
-
-
-def test_ADParamStmt_dump():
- stmt = {"param": "AD", "d": 0, "shape": "C"}
- ad = ADParamStmt.from_dict(stmt)
- assert ad.to_gerber() == "%ADD0C*%"
- stmt = {"param": "AD", "d": 0, "shape": "C", "modifiers": "1X1,1X1"}
- ad = ADParamStmt.from_dict(stmt)
- assert ad.to_gerber() == "%ADD0C,1X1,1X1*%"
-
-
-def test_ADPamramStmt_string():
- stmt = {"param": "AD", "d": 0, "shape": "C"}
- ad = ADParamStmt.from_dict(stmt)
- assert str(ad) == "<Aperture Definition: 0: circle>"
-
- stmt = {"param": "AD", "d": 0, "shape": "R"}
- ad = ADParamStmt.from_dict(stmt)
- assert str(ad) == "<Aperture Definition: 0: rectangle>"
-
- stmt = {"param": "AD", "d": 0, "shape": "O"}
- ad = ADParamStmt.from_dict(stmt)
- assert str(ad) == "<Aperture Definition: 0: obround>"
-
- stmt = {"param": "AD", "d": 0, "shape": "test"}
- ad = ADParamStmt.from_dict(stmt)
- assert str(ad) == "<Aperture Definition: 0: test>"
-
-
-def test_MIParamStmt_factory():
- stmt = {"param": "MI", "a": 1, "b": 1}
- mi = MIParamStmt.from_dict(stmt)
- assert mi.a == 1
- assert mi.b == 1
-
-
-def test_MIParamStmt_dump():
- stmt = {"param": "MI", "a": 1, "b": 1}
- mi = MIParamStmt.from_dict(stmt)
- assert mi.to_gerber() == "%MIA1B1*%"
- stmt = {"param": "MI", "a": 1}
- mi = MIParamStmt.from_dict(stmt)
- assert mi.to_gerber() == "%MIA1B0*%"
- stmt = {"param": "MI", "b": 1}
- mi = MIParamStmt.from_dict(stmt)
- assert mi.to_gerber() == "%MIA0B1*%"
-
-
-def test_MIParamStmt_string():
- stmt = {"param": "MI", "a": 1, "b": 1}
- mi = MIParamStmt.from_dict(stmt)
- assert str(mi) == "<Image Mirror: A=1 B=1>"
-
- stmt = {"param": "MI", "b": 1}
- mi = MIParamStmt.from_dict(stmt)
- assert str(mi) == "<Image Mirror: A=0 B=1>"
-
- stmt = {"param": "MI", "a": 1}
- mi = MIParamStmt.from_dict(stmt)
- assert str(mi) == "<Image Mirror: A=1 B=0>"
-
-
-def test_coordstmt_ctor():
- cs = CoordStmt("G04", 0.0, 0.1, 0.2, 0.3, "D01", FileSettings())
- assert cs.function == "G04"
- assert cs.x == 0.0
- assert cs.y == 0.1
- assert cs.i == 0.2
- assert cs.j == 0.3
- assert cs.op == "D01"
-
-
-def test_coordstmt_factory():
- stmt = {
- "function": "G04",
- "x": "0",
- "y": "001",
- "i": "002",
- "j": "003",
- "op": "D01",
- }
- cs = CoordStmt.from_dict(stmt, FileSettings())
- assert cs.function == "G04"
- assert cs.x == 0.0
- assert cs.y == 0.1
- assert cs.i == 0.2
- assert cs.j == 0.3
- assert cs.op == "D01"
-
-
-def test_coordstmt_dump():
- cs = CoordStmt("G04", 0.0, 0.1, 0.2, 0.3, "D01", FileSettings())
- assert cs.to_gerber(FileSettings()) == "G04X0Y001I002J003D01*"
-
-
-def test_coordstmt_conversion():
- cs = CoordStmt("G71", 25.4, 25.4, 25.4, 25.4, "D01", FileSettings())
- cs.units = "metric"
-
- # No effect
- cs.to_metric()
- assert cs.x == 25.4
- assert cs.y == 25.4
- assert cs.i == 25.4
- assert cs.j == 25.4
- assert cs.function == "G71"
-
- cs.to_inch()
- assert cs.units == "inch"
- assert cs.x == 1.0
- assert cs.y == 1.0
- assert cs.i == 1.0
- assert cs.j == 1.0
- assert cs.function == "G70"
-
- # No effect
- cs.to_inch()
- assert cs.x == 1.0
- assert cs.y == 1.0
- assert cs.i == 1.0
- assert cs.j == 1.0
- assert cs.function == "G70"
-
- cs = CoordStmt("G70", 1.0, 1.0, 1.0, 1.0, "D01", FileSettings())
- cs.units = "inch"
-
- # No effect
- cs.to_inch()
- assert cs.x == 1.0
- assert cs.y == 1.0
- assert cs.i == 1.0
- assert cs.j == 1.0
- assert cs.function == "G70"
-
- cs.to_metric()
- assert cs.x == 25.4
- assert cs.y == 25.4
- assert cs.i == 25.4
- assert cs.j == 25.4
- assert cs.function == "G71"
-
- # No effect
- cs.to_metric()
- assert cs.x == 25.4
- assert cs.y == 25.4
- assert cs.i == 25.4
- assert cs.j == 25.4
- assert cs.function == "G71"
-
-
-def test_coordstmt_offset():
- c = CoordStmt("G71", 0, 0, 0, 0, "D01", FileSettings())
- c.offset(1, 0)
- assert c.x == 1.0
- assert c.y == 0.0
- assert c.i == 1.0
- assert c.j == 0.0
- c.offset(0, 1)
- assert c.x == 1.0
- assert c.y == 1.0
- assert c.i == 1.0
- assert c.j == 1.0
-
-
-def test_coordstmt_string():
- cs = CoordStmt("G04", 0, 1, 2, 3, "D01", FileSettings())
- assert (
- str(cs) == "<Coordinate Statement: Fn: G04 X: 0 Y: 1 I: 2 J: 3 Op: Lights On>"
- )
- cs = CoordStmt("G04", None, None, None, None, "D02", FileSettings())
- assert str(cs) == "<Coordinate Statement: Fn: G04 Op: Lights Off>"
- cs = CoordStmt("G04", None, None, None, None, "D03", FileSettings())
- assert str(cs) == "<Coordinate Statement: Fn: G04 Op: Flash>"
- cs = CoordStmt("G04", None, None, None, None, "TEST", FileSettings())
- assert str(cs) == "<Coordinate Statement: Fn: G04 Op: TEST>"
-
-
-def test_aperturestmt_ctor():
- ast = ApertureStmt(3, False)
- assert ast.d == 3
- assert ast.deprecated == False
- ast = ApertureStmt(4, True)
- assert ast.d == 4
- assert ast.deprecated == True
- ast = ApertureStmt(4, 1)
- assert ast.d == 4
- assert ast.deprecated == True
- ast = ApertureStmt(3)
- assert ast.d == 3
- assert ast.deprecated == False
-
-
-def test_aperturestmt_dump():
- ast = ApertureStmt(3, False)
- assert ast.to_gerber() == "D3*"
- ast = ApertureStmt(3, True)
- assert ast.to_gerber() == "G54D3*"
- assert str(ast) == "<Aperture: 3>"
diff --git a/gerber/tests/test_ipc356.py b/gerber/tests/test_ipc356.py
deleted file mode 100644
index 77f0782..0000000
--- a/gerber/tests/test_ipc356.py
+++ /dev/null
@@ -1,148 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Author: Hamilton Kibbe <ham@hamiltonkib.be>
-import pytest
-from ..ipc356 import *
-from ..cam import FileSettings
-
-import os
-
-IPC_D_356_FILE = os.path.join(os.path.dirname(__file__), "resources/ipc-d-356.ipc")
-
-
-def test_read():
- ipcfile = read(IPC_D_356_FILE)
- assert isinstance(ipcfile, IPCNetlist)
-
-
-def test_parser():
- ipcfile = read(IPC_D_356_FILE)
- assert ipcfile.settings.units == "inch"
- assert ipcfile.settings.angle_units == "degrees"
- assert len(ipcfile.comments) == 3
- assert len(ipcfile.parameters) == 4
- assert len(ipcfile.test_records) == 105
- assert len(ipcfile.components) == 21
- assert len(ipcfile.vias) == 14
- assert ipcfile.test_records[-1].net_name == "A_REALLY_LONG_NET_NAME"
- assert ipcfile.outlines[0].type == "BOARD_EDGE"
- assert set(ipcfile.outlines[0].points) == {
- (0.0, 0.0),
- (2.25, 0.0),
- (2.25, 1.5),
- (0.0, 1.5),
- (0.13, 0.024),
- }
-
-
-def test_comment():
- c = IPC356_Comment("Layer Stackup:")
- assert c.comment == "Layer Stackup:"
- c = IPC356_Comment.from_line("C Layer Stackup: ")
- assert c.comment == "Layer Stackup:"
- pytest.raises(ValueError, IPC356_Comment.from_line, "P JOB")
- assert str(c) == "<IPC-D-356 Comment: Layer Stackup:>"
-
-
-def test_parameter():
- p = IPC356_Parameter("VER", "IPC-D-356A")
- assert p.parameter == "VER"
- assert p.value == "IPC-D-356A"
- p = IPC356_Parameter.from_line("P VER IPC-D-356A ")
- assert p.parameter == "VER"
- assert p.value == "IPC-D-356A"
- pytest.raises(ValueError, IPC356_Parameter.from_line, "C Layer Stackup: ")
- assert str(p) == "<IPC-D-356 Parameter: VER=IPC-D-356A>"
-
-
-def test_eof():
- e = IPC356_EndOfFile()
- assert e.to_netlist() == "999"
- assert str(e) == "<IPC-D-356 EOF>"
-
-
-def test_outline():
- type = "BOARD_EDGE"
- points = [(0.01, 0.01), (2.0, 2.0), (4.0, 2.0), (4.0, 6.0)]
- b = IPC356_Outline(type, points)
- assert b.type == type
- assert b.points == points
- b = IPC356_Outline.from_line(
- "389BOARD_EDGE X100Y100 X20000Y20000 X40000 Y60000",
- FileSettings(units="inch"),
- )
- assert b.type == "BOARD_EDGE"
- assert b.points == points
-
-
-def test_test_record():
- pytest.raises(ValueError, IPC356_TestRecord.from_line, "P JOB", FileSettings())
- record_string = (
- "317+5VDC VIA - D0150PA00X 006647Y 012900X0000 S3"
- )
- r = IPC356_TestRecord.from_line(record_string, FileSettings(units="inch"))
- assert r.feature_type == "through-hole"
- assert r.net_name == "+5VDC"
- assert r.id == "VIA"
- pytest.approx(r.hole_diameter, 0.015)
- assert r.plated
- assert r.access == "both"
- pytest.approx(r.x_coord, 0.6647)
- pytest.approx(r.y_coord, 1.29)
- assert r.rect_x == 0.0
- assert r.soldermask_info == "both"
- r = IPC356_TestRecord.from_line(record_string, FileSettings(units="metric"))
- pytest.approx(r.hole_diameter, 0.15)
- pytest.approx(r.x_coord, 6.647)
- pytest.approx(r.y_coord, 12.9)
- assert r.rect_x == 0.0
- assert str(r) == "<IPC-D-356 +5VDC Test Record: through-hole>"
-
- record_string = (
- "327+3.3VDC R40 -1 PA01X 032100Y 007124X0236Y0315R180 S0"
- )
- r = IPC356_TestRecord.from_line(record_string, FileSettings(units="inch"))
- assert r.feature_type == "smt"
- assert r.net_name == "+3.3VDC"
- assert r.id == "R40"
- assert r.pin == "1"
- assert r.plated
- assert r.access == "top"
- pytest.approx(r.x_coord, 3.21)
- pytest.approx(r.y_coord, 0.7124)
- pytest.approx(r.rect_x, 0.0236)
- pytest.approx(r.rect_y, 0.0315)
- assert r.rect_rotation == 180
- assert r.soldermask_info == "none"
- r = IPC356_TestRecord.from_line(record_string, FileSettings(units="metric"))
- pytest.approx(r.x_coord, 32.1)
- pytest.approx(r.y_coord, 7.124)
- pytest.approx(r.rect_x, 0.236)
- pytest.approx(r.rect_y, 0.315)
-
- record_string = (
- "317 J4 -M2 D0330PA00X 012447Y 008030X0000 S1"
- )
- r = IPC356_TestRecord.from_line(record_string, FileSettings(units="inch"))
- assert r.feature_type == "through-hole"
- assert r.id == "J4"
- assert r.pin == "M2"
- pytest.approx(r.hole_diameter, 0.033)
- assert r.plated
- assert r.access == "both"
- pytest.approx(r.x_coord, 1.2447)
- pytest.approx(r.y_coord, 0.8030)
- pytest.approx(r.rect_x, 0.0)
- assert r.soldermask_info == "primary side"
-
- record_string = "317SCL COMMUNICATION-1 D 40PA00X 34000Y 20000X 600Y1200R270 "
- r = IPC356_TestRecord.from_line(record_string, FileSettings(units="inch"))
- assert r.feature_type == "through-hole"
- assert r.net_name == "SCL"
- assert r.id == "COMMUNICATION"
- assert r.pin == "1"
- pytest.approx(r.hole_diameter, 0.004)
- assert r.plated
- pytest.approx(r.x_coord, 3.4)
- pytest.approx(r.y_coord, 2.0)
diff --git a/gerber/tests/test_layers.py b/gerber/tests/test_layers.py
deleted file mode 100644
index 2178787..0000000
--- a/gerber/tests/test_layers.py
+++ /dev/null
@@ -1,158 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# copyright 2016 Hamilton Kibbe <ham@hamiltonkib.be>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import os
-
-from ..layers import *
-from ..common import read
-
-NCDRILL_FILE = os.path.join(os.path.dirname(__file__), "resources/ncdrill.DRD")
-NETLIST_FILE = os.path.join(os.path.dirname(__file__), "resources/ipc-d-356.ipc")
-COPPER_FILE = os.path.join(os.path.dirname(__file__), "resources/top_copper.GTL")
-
-
-def test_guess_layer_class():
- """ Test layer type inferred correctly from filename
- """
-
- # Add any specific test cases here (filename, layer_class)
- test_vectors = [
- (None, "unknown"),
- ("NCDRILL.TXT", "unknown"),
- ("example_board.gtl", "top"),
- ("exampmle_board.sst", "topsilk"),
- ("ipc-d-356.ipc", "ipc_netlist"),
- ]
-
- for hint in hints:
- for ext in hint.ext:
- assert hint.layer == guess_layer_class("board.{}".format(ext))
- for name in hint.name:
- assert hint.layer == guess_layer_class("{}.pho".format(name))
-
- for filename, layer_class in test_vectors:
- assert layer_class == guess_layer_class(filename)
-
-
-def test_guess_layer_class_regex():
- """ Test regular expressions for layer matching
- """
-
- # Add any specific test case (filename, layer_class)
- test_vectors = [("test - top copper.gbr", "top"), ("test - copper top.gbr", "top")]
-
- # Add custom regular expressions
- layer_hints = [
- Hint(
- layer="top",
- ext=[],
- name=[],
- regex=r"(.*)(\scopper top|\stop copper).gbr",
- content=[],
- )
- ]
- hints.extend(layer_hints)
-
- for filename, layer_class in test_vectors:
- assert layer_class == guess_layer_class(filename)
-
-
-def test_guess_layer_class_by_content():
- """ Test layer class by checking content
- """
-
- expected_layer_class = "bottom"
- filename = os.path.join(
- os.path.dirname(__file__), "resources/example_guess_by_content.g0"
- )
-
- layer_hints = [
- Hint(
- layer="bottom",
- ext=[],
- name=[],
- regex="",
- content=["G04 Layer name: Bottom"],
- )
- ]
- hints.extend(layer_hints)
-
- assert expected_layer_class == guess_layer_class_by_content(filename)
-
-
-def test_sort_layers():
- """ Test layer ordering
- """
- layers = [
- PCBLayer(layer_class="drawing"),
- PCBLayer(layer_class="drill"),
- PCBLayer(layer_class="bottompaste"),
- PCBLayer(layer_class="bottomsilk"),
- PCBLayer(layer_class="bottommask"),
- PCBLayer(layer_class="bottom"),
- PCBLayer(layer_class="internal"),
- PCBLayer(layer_class="top"),
- PCBLayer(layer_class="topmask"),
- PCBLayer(layer_class="topsilk"),
- PCBLayer(layer_class="toppaste"),
- PCBLayer(layer_class="outline"),
- ]
-
- layer_order = [
- "outline",
- "toppaste",
- "topsilk",
- "topmask",
- "top",
- "internal",
- "bottom",
- "bottommask",
- "bottomsilk",
- "bottompaste",
- "drill",
- "drawing",
- ]
- bottom_order = list(reversed(layer_order[:10])) + layer_order[10:]
- assert [l.layer_class for l in sort_layers(layers)] == layer_order
- assert [l.layer_class for l in sort_layers(layers, from_top=False)] == bottom_order
-
-
-def test_PCBLayer_from_file():
- layer = PCBLayer.from_cam(read(COPPER_FILE))
- assert isinstance(layer, PCBLayer)
- layer = PCBLayer.from_cam(read(NCDRILL_FILE))
- assert isinstance(layer, DrillLayer)
- layer = PCBLayer.from_cam(read(NETLIST_FILE))
- assert isinstance(layer, PCBLayer)
- assert layer.layer_class == "ipc_netlist"
-
-
-def test_PCBLayer_bounds():
- source = read(COPPER_FILE)
- layer = PCBLayer.from_cam(source)
- assert source.bounds == layer.bounds
-
-
-def test_DrillLayer_from_cam():
- no_exceptions = True
- try:
- layer = DrillLayer.from_cam(read(NCDRILL_FILE))
- assert isinstance(layer, DrillLayer)
- except:
- no_exceptions = False
- assert no_exceptions
diff --git a/gerber/tests/test_primitives.py b/gerber/tests/test_primitives.py
deleted file mode 100644
index ad5b34f..0000000
--- a/gerber/tests/test_primitives.py
+++ /dev/null
@@ -1,1429 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Author: Hamilton Kibbe <ham@hamiltonkib.be>
-
-import pytest
-from operator import add
-from ..primitives import *
-
-
-def test_primitive_smoketest():
- p = Primitive()
- try:
- p.bounding_box
- assert not True, "should have thrown the exception"
- except NotImplementedError:
- pass
- # pytest.raises(NotImplementedError, p.bounding_box)
-
- p.to_metric()
- p.to_inch()
- # try:
- # p.offset(1, 1)
- # assert_false(True, 'should have thrown the exception')
- # except NotImplementedError:
- # pass
-
-
-def test_line_angle():
- """ Test Line primitive angle calculation
- """
- cases = [
- ((0, 0), (1, 0), math.radians(0)),
- ((0, 0), (1, 1), math.radians(45)),
- ((0, 0), (0, 1), math.radians(90)),
- ((0, 0), (-1, 1), math.radians(135)),
- ((0, 0), (-1, 0), math.radians(180)),
- ((0, 0), (-1, -1), math.radians(225)),
- ((0, 0), (0, -1), math.radians(270)),
- ((0, 0), (1, -1), math.radians(315)),
- ]
- for start, end, expected in cases:
- l = Line(start, end, 0)
- line_angle = (l.angle + 2 * math.pi) % (2 * math.pi)
- pytest.approx(line_angle, expected)
-
-
-def test_line_bounds():
- """ Test Line primitive bounding box calculation
- """
- cases = [
- ((0, 0), (1, 1), ((-1, 2), (-1, 2))),
- ((-1, -1), (1, 1), ((-2, 2), (-2, 2))),
- ((1, 1), (-1, -1), ((-2, 2), (-2, 2))),
- ((-1, 1), (1, -1), ((-2, 2), (-2, 2))),
- ]
-
- c = Circle((0, 0), 2)
- r = Rectangle((0, 0), 2, 2)
- for shape in (c, r):
- for start, end, expected in cases:
- l = Line(start, end, shape)
- assert l.bounding_box == expected
- # Test a non-square rectangle
- r = Rectangle((0, 0), 3, 2)
- cases = [
- ((0, 0), (1, 1), ((-1.5, 2.5), (-1, 2))),
- ((-1, -1), (1, 1), ((-2.5, 2.5), (-2, 2))),
- ((1, 1), (-1, -1), ((-2.5, 2.5), (-2, 2))),
- ((-1, 1), (1, -1), ((-2.5, 2.5), (-2, 2))),
- ]
- for start, end, expected in cases:
- l = Line(start, end, r)
- assert l.bounding_box == expected
-
-
-def test_line_vertices():
- c = Circle((0, 0), 2)
- l = Line((0, 0), (1, 1), c)
- assert l.vertices == None
-
- # All 4 compass points, all 4 quadrants and the case where start == end
- test_cases = [
- ((0, 0), (1, 0), ((-1, -1), (-1, 1), (2, 1), (2, -1))),
- ((0, 0), (1, 1), ((-1, -1), (-1, 1), (0, 2), (2, 2), (2, 0), (1, -1))),
- ((0, 0), (0, 1), ((-1, -1), (-1, 2), (1, 2), (1, -1))),
- ((0, 0), (-1, 1), ((-1, -1), (-2, 0), (-2, 2), (0, 2), (1, 1), (1, -1))),
- ((0, 0), (-1, 0), ((-2, -1), (-2, 1), (1, 1), (1, -1))),
- ((0, 0), (-1, -1), ((-2, -2), (1, -1), (1, 1), (-1, 1), (-2, 0), (0, -2))),
- ((0, 0), (0, -1), ((-1, -2), (-1, 1), (1, 1), (1, -2))),
- ((0, 0), (1, -1), ((-1, -1), (0, -2), (2, -2), (2, 0), (1, 1), (-1, 1))),
- ((0, 0), (0, 0), ((-1, -1), (-1, 1), (1, 1), (1, -1))),
- ]
- r = Rectangle((0, 0), 2, 2)
-
- for start, end, vertices in test_cases:
- l = Line(start, end, r)
- assert set(vertices) == set(l.vertices)
-
-
-def test_line_conversion():
- c = Circle((0, 0), 25.4, units="metric")
- l = Line((2.54, 25.4), (254.0, 2540.0), c, units="metric")
-
- # No effect
- l.to_metric()
- assert l.start == (2.54, 25.4)
- assert l.end == (254.0, 2540.0)
- assert l.aperture.diameter == 25.4
-
- l.to_inch()
- assert l.start == (0.1, 1.0)
- assert l.end == (10.0, 100.0)
- assert l.aperture.diameter == 1.0
-
- # No effect
- l.to_inch()
- assert l.start == (0.1, 1.0)
- assert l.end == (10.0, 100.0)
- assert l.aperture.diameter == 1.0
-
- c = Circle((0, 0), 1.0, units="inch")
- l = Line((0.1, 1.0), (10.0, 100.0), c, units="inch")
-
- # No effect
- l.to_inch()
- assert l.start == (0.1, 1.0)
- assert l.end == (10.0, 100.0)
- assert l.aperture.diameter == 1.0
-
- l.to_metric()
- assert l.start == (2.54, 25.4)
- assert l.end == (254.0, 2540.0)
- assert l.aperture.diameter == 25.4
-
- # No effect
- l.to_metric()
- assert l.start == (2.54, 25.4)
- assert l.end == (254.0, 2540.0)
- assert l.aperture.diameter == 25.4
-
- r = Rectangle((0, 0), 25.4, 254.0, units="metric")
- l = Line((2.54, 25.4), (254.0, 2540.0), r, units="metric")
- l.to_inch()
- assert l.start == (0.1, 1.0)
- assert l.end == (10.0, 100.0)
- assert l.aperture.width == 1.0
- assert l.aperture.height == 10.0
-
- r = Rectangle((0, 0), 1.0, 10.0, units="inch")
- l = Line((0.1, 1.0), (10.0, 100.0), r, units="inch")
- l.to_metric()
- assert l.start == (2.54, 25.4)
- assert l.end == (254.0, 2540.0)
- assert l.aperture.width == 25.4
- assert l.aperture.height == 254.0
-
-
-def test_line_offset():
- c = Circle((0, 0), 1)
- l = Line((0, 0), (1, 1), c)
- l.offset(1, 0)
- assert l.start == (1.0, 0.0)
- assert l.end == (2.0, 1.0)
- l.offset(0, 1)
- assert l.start == (1.0, 1.0)
- assert l.end == (2.0, 2.0)
-
-
-def test_arc_radius():
- """ Test Arc primitive radius calculation
- """
- cases = [((-3, 4), (5, 0), (0, 0), 5), ((0, 1), (1, 0), (0, 0), 1)]
-
- for start, end, center, radius in cases:
- a = Arc(start, end, center, "clockwise", 0, "single-quadrant")
- assert a.radius == radius
-
-
-def test_arc_sweep_angle():
- """ Test Arc primitive sweep angle calculation
- """
- cases = [
- ((1, 0), (0, 1), (0, 0), "counterclockwise", math.radians(90)),
- ((1, 0), (0, 1), (0, 0), "clockwise", math.radians(270)),
- ((1, 0), (-1, 0), (0, 0), "clockwise", math.radians(180)),
- ((1, 0), (-1, 0), (0, 0), "counterclockwise", math.radians(180)),
- ]
-
- for start, end, center, direction, sweep in cases:
- c = Circle((0, 0), 1)
- a = Arc(start, end, center, direction, c, "single-quadrant")
- assert a.sweep_angle == sweep
-
-
-def test_arc_bounds():
- """ Test Arc primitive bounding box calculation
- """
- cases = [
- ((1, 0), (0, 1), (0, 0), "clockwise", ((-1.5, 1.5), (-1.5, 1.5))),
- ((1, 0), (0, 1), (0, 0), "counterclockwise", ((-0.5, 1.5), (-0.5, 1.5))),
- ((0, 1), (-1, 0), (0, 0), "clockwise", ((-1.5, 1.5), (-1.5, 1.5))),
- ((0, 1), (-1, 0), (0, 0), "counterclockwise", ((-1.5, 0.5), (-0.5, 1.5))),
- ((-1, 0), (0, -1), (0, 0), "clockwise", ((-1.5, 1.5), (-1.5, 1.5))),
- ((-1, 0), (0, -1), (0, 0), "counterclockwise", ((-1.5, 0.5), (-1.5, 0.5))),
- ((0, -1), (1, 0), (0, 0), "clockwise", ((-1.5, 1.5), (-1.5, 1.5))),
- ((0, -1), (1, 0), (0, 0), "counterclockwise", ((-0.5, 1.5), (-1.5, 0.5))),
- # Arcs with the same start and end point render a full circle
- ((1, 0), (1, 0), (0, 0), "clockwise", ((-1.5, 1.5), (-1.5, 1.5))),
- ((1, 0), (1, 0), (0, 0), "counterclockwise", ((-1.5, 1.5), (-1.5, 1.5))),
- ]
- for start, end, center, direction, bounds in cases:
- c = Circle((0, 0), 1)
- a = Arc(start, end, center, direction, c, "multi-quadrant")
- assert a.bounding_box == bounds
-
-
-def test_arc_bounds_no_aperture():
- """ Test Arc primitive bounding box calculation ignoring aperture
- """
- cases = [
- ((1, 0), (0, 1), (0, 0), "clockwise", ((-1.0, 1.0), (-1.0, 1.0))),
- ((1, 0), (0, 1), (0, 0), "counterclockwise", ((0.0, 1.0), (0.0, 1.0))),
- ((0, 1), (-1, 0), (0, 0), "clockwise", ((-1.0, 1.0), (-1.0, 1.0))),
- ((0, 1), (-1, 0), (0, 0), "counterclockwise", ((-1.0, 0.0), (0.0, 1.0))),
- ((-1, 0), (0, -1), (0, 0), "clockwise", ((-1.0, 1.0), (-1.0, 1.0))),
- ((-1, 0), (0, -1), (0, 0), "counterclockwise", ((-1.0, 0.0), (-1.0, 0.0))),
- ((0, -1), (1, 0), (0, 0), "clockwise", ((-1.0, 1.0), (-1.0, 1.0))),
- ((0, -1), (1, 0), (0, 0), "counterclockwise", ((-0.0, 1.0), (-1.0, 0.0))),
- # Arcs with the same start and end point render a full circle
- ((1, 0), (1, 0), (0, 0), "clockwise", ((-1.0, 1.0), (-1.0, 1.0))),
- ((1, 0), (1, 0), (0, 0), "counterclockwise", ((-1.0, 1.0), (-1.0, 1.0))),
- ]
- for start, end, center, direction, bounds in cases:
- c = Circle((0, 0), 1)
- a = Arc(start, end, center, direction, c, "multi-quadrant")
- assert a.bounding_box_no_aperture == bounds
-
-
-def test_arc_conversion():
- c = Circle((0, 0), 25.4, units="metric")
- a = Arc(
- (2.54, 25.4),
- (254.0, 2540.0),
- (25400.0, 254000.0),
- "clockwise",
- c,
- "single-quadrant",
- units="metric",
- )
-
- # No effect
- a.to_metric()
- assert a.start == (2.54, 25.4)
- assert a.end == (254.0, 2540.0)
- assert a.center == (25400.0, 254000.0)
- assert a.aperture.diameter == 25.4
-
- a.to_inch()
- assert a.start == (0.1, 1.0)
- assert a.end == (10.0, 100.0)
- assert a.center == (1000.0, 10000.0)
- assert a.aperture.diameter == 1.0
-
- # no effect
- a.to_inch()
- assert a.start == (0.1, 1.0)
- assert a.end == (10.0, 100.0)
- assert a.center == (1000.0, 10000.0)
- assert a.aperture.diameter == 1.0
-
- c = Circle((0, 0), 1.0, units="inch")
- a = Arc(
- (0.1, 1.0),
- (10.0, 100.0),
- (1000.0, 10000.0),
- "clockwise",
- c,
- "single-quadrant",
- units="inch",
- )
- a.to_metric()
- assert a.start == (2.54, 25.4)
- assert a.end == (254.0, 2540.0)
- assert a.center == (25400.0, 254000.0)
- assert a.aperture.diameter == 25.4
-
-
-def test_arc_offset():
- c = Circle((0, 0), 1)
- a = Arc((0, 0), (1, 1), (2, 2), "clockwise", c, "single-quadrant")
- a.offset(1, 0)
- assert a.start == (1.0, 0.0)
- assert a.end == (2.0, 1.0)
- assert a.center == (3.0, 2.0)
- a.offset(0, 1)
- assert a.start == (1.0, 1.0)
- assert a.end == (2.0, 2.0)
- assert a.center == (3.0, 3.0)
-
-
-def test_circle_radius():
- """ Test Circle primitive radius calculation
- """
- c = Circle((1, 1), 2)
- assert c.radius == 1
-
-
-def test_circle_hole_radius():
- """ Test Circle primitive hole radius calculation
- """
- c = Circle((1, 1), 4, 2)
- assert c.hole_radius == 1
-
-
-def test_circle_bounds():
- """ Test Circle bounding box calculation
- """
- c = Circle((1, 1), 2)
- assert c.bounding_box == ((0, 2), (0, 2))
-
-
-def test_circle_conversion():
- """Circle conversion of units"""
- # Circle initially metric, no hole
- c = Circle((2.54, 25.4), 254.0, units="metric")
-
- c.to_metric() # shouldn't do antyhing
- assert c.position == (2.54, 25.4)
- assert c.diameter == 254.0
- assert c.hole_diameter == None
-
- c.to_inch()
- assert c.position == (0.1, 1.0)
- assert c.diameter == 10.0
- assert c.hole_diameter == None
-
- # no effect
- c.to_inch()
- assert c.position == (0.1, 1.0)
- assert c.diameter == 10.0
- assert c.hole_diameter == None
-
- # Circle initially metric, with hole
- c = Circle((2.54, 25.4), 254.0, 127.0, units="metric")
-
- c.to_metric() # shouldn't do antyhing
- assert c.position == (2.54, 25.4)
- assert c.diameter == 254.0
- assert c.hole_diameter == 127.0
-
- c.to_inch()
- assert c.position == (0.1, 1.0)
- assert c.diameter == 10.0
- assert c.hole_diameter == 5.0
-
- # no effect
- c.to_inch()
- assert c.position == (0.1, 1.0)
- assert c.diameter == 10.0
- assert c.hole_diameter == 5.0
-
- # Circle initially inch, no hole
- c = Circle((0.1, 1.0), 10.0, units="inch")
- # No effect
- c.to_inch()
- assert c.position == (0.1, 1.0)
- assert c.diameter == 10.0
- assert c.hole_diameter == None
-
- c.to_metric()
- assert c.position == (2.54, 25.4)
- assert c.diameter == 254.0
- assert c.hole_diameter == None
-
- # no effect
- c.to_metric()
- assert c.position == (2.54, 25.4)
- assert c.diameter == 254.0
- assert c.hole_diameter == None
-
- c = Circle((0.1, 1.0), 10.0, 5.0, units="inch")
- # No effect
- c.to_inch()
- assert c.position == (0.1, 1.0)
- assert c.diameter == 10.0
- assert c.hole_diameter == 5.0
-
- c.to_metric()
- assert c.position == (2.54, 25.4)
- assert c.diameter == 254.0
- assert c.hole_diameter == 127.0
-
- # no effect
- c.to_metric()
- assert c.position == (2.54, 25.4)
- assert c.diameter == 254.0
- assert c.hole_diameter == 127.0
-
-
-def test_circle_offset():
- c = Circle((0, 0), 1)
- c.offset(1, 0)
- assert c.position == (1.0, 0.0)
- c.offset(0, 1)
- assert c.position == (1.0, 1.0)
-
-
-def test_ellipse_ctor():
- """ Test ellipse creation
- """
- e = Ellipse((2, 2), 3, 2)
- assert e.position == (2, 2)
- assert e.width == 3
- assert e.height == 2
-
-
-def test_ellipse_bounds():
- """ Test ellipse bounding box calculation
- """
- e = Ellipse((2, 2), 4, 2)
- assert e.bounding_box == ((0, 4), (1, 3))
- e = Ellipse((2, 2), 4, 2, rotation=90)
- assert e.bounding_box == ((1, 3), (0, 4))
- e = Ellipse((2, 2), 4, 2, rotation=180)
- assert e.bounding_box == ((0, 4), (1, 3))
- e = Ellipse((2, 2), 4, 2, rotation=270)
- assert e.bounding_box == ((1, 3), (0, 4))
-
-
-def test_ellipse_conversion():
- e = Ellipse((2.54, 25.4), 254.0, 2540.0, units="metric")
-
- # No effect
- e.to_metric()
- assert e.position == (2.54, 25.4)
- assert e.width == 254.0
- assert e.height == 2540.0
-
- e.to_inch()
- assert e.position == (0.1, 1.0)
- assert e.width == 10.0
- assert e.height == 100.0
-
- # No effect
- e.to_inch()
- assert e.position == (0.1, 1.0)
- assert e.width == 10.0
- assert e.height == 100.0
-
- e = Ellipse((0.1, 1.0), 10.0, 100.0, units="inch")
-
- # no effect
- e.to_inch()
- assert e.position == (0.1, 1.0)
- assert e.width == 10.0
- assert e.height == 100.0
-
- e.to_metric()
- assert e.position == (2.54, 25.4)
- assert e.width == 254.0
- assert e.height == 2540.0
-
- # No effect
- e.to_metric()
- assert e.position == (2.54, 25.4)
- assert e.width == 254.0
- assert e.height == 2540.0
-
-
-def test_ellipse_offset():
- e = Ellipse((0, 0), 1, 2)
- e.offset(1, 0)
- assert e.position == (1.0, 0.0)
- e.offset(0, 1)
- assert e.position == (1.0, 1.0)
-
-
-def test_rectangle_ctor():
- """ Test rectangle creation
- """
- test_cases = (((0, 0), 1, 1), ((0, 0), 1, 2), ((1, 1), 1, 2))
- for pos, width, height in test_cases:
- r = Rectangle(pos, width, height)
- assert r.position == pos
- assert r.width == width
- assert r.height == height
-
-
-def test_rectangle_hole_radius():
- """ Test rectangle hole diameter calculation
- """
- r = Rectangle((0, 0), 2, 2)
- assert 0 == r.hole_radius
-
- r = Rectangle((0, 0), 2, 2, 1)
- assert 0.5 == r.hole_radius
-
-
-def test_rectangle_bounds():
- """ Test rectangle bounding box calculation
- """
- r = Rectangle((0, 0), 2, 2)
- xbounds, ybounds = r.bounding_box
- pytest.approx(xbounds, (-1, 1))
- pytest.approx(ybounds, (-1, 1))
- r = Rectangle((0, 0), 2, 2, rotation=45)
- xbounds, ybounds = r.bounding_box
- pytest.approx(xbounds, (-math.sqrt(2), math.sqrt(2)))
- pytest.approx(ybounds, (-math.sqrt(2), math.sqrt(2)))
-
-
-def test_rectangle_vertices():
- sqrt2 = math.sqrt(2.0)
- TEST_VECTORS = [
- ((0, 0), 2.0, 2.0, 0.0, ((-1.0, -1.0), (-1.0, 1.0), (1.0, 1.0), (1.0, -1.0))),
- ((0, 0), 2.0, 3.0, 0.0, ((-1.0, -1.5), (-1.0, 1.5), (1.0, 1.5), (1.0, -1.5))),
- ((0, 0), 2.0, 2.0, 90.0, ((-1.0, -1.0), (-1.0, 1.0), (1.0, 1.0), (1.0, -1.0))),
- ((0, 0), 3.0, 2.0, 90.0, ((-1.0, -1.5), (-1.0, 1.5), (1.0, 1.5), (1.0, -1.5))),
- (
- (0, 0),
- 2.0,
- 2.0,
- 45.0,
- ((-sqrt2, 0.0), (0.0, sqrt2), (sqrt2, 0), (0, -sqrt2)),
- ),
- ]
- for pos, width, height, rotation, expected in TEST_VECTORS:
- r = Rectangle(pos, width, height, rotation=rotation)
- for test, expect in zip(sorted(r.vertices), sorted(expected)):
- pytest.approx(test, expect)
-
- r = Rectangle((0, 0), 2.0, 2.0, rotation=0.0)
- r.rotation = 45.0
- for test, expect in zip(
- sorted(r.vertices),
- sorted(((-sqrt2, 0.0), (0.0, sqrt2), (sqrt2, 0), (0, -sqrt2))),
- ):
- pytest.approx(test, expect)
-
-
-def test_rectangle_segments():
-
- r = Rectangle((0, 0), 2.0, 2.0)
- expected = [vtx for segment in r.segments for vtx in segment]
- for vertex in r.vertices:
- assert vertex in expected
-
-
-def test_rectangle_conversion():
- """Test converting rectangles between units"""
-
- # Initially metric no hole
- r = Rectangle((2.54, 25.4), 254.0, 2540.0, units="metric")
-
- r.to_metric()
- assert r.position == (2.54, 25.4)
- assert r.width == 254.0
- assert r.height == 2540.0
-
- r.to_inch()
- assert r.position == (0.1, 1.0)
- assert r.width == 10.0
- assert r.height == 100.0
-
- r.to_inch()
- assert r.position == (0.1, 1.0)
- assert r.width == 10.0
- assert r.height == 100.0
-
- # Initially metric with hole
- r = Rectangle((2.54, 25.4), 254.0, 2540.0, 127.0, units="metric")
-
- r.to_metric()
- assert r.position == (2.54, 25.4)
- assert r.width == 254.0
- assert r.height == 2540.0
- assert r.hole_diameter == 127.0
-
- r.to_inch()
- assert r.position == (0.1, 1.0)
- assert r.width == 10.0
- assert r.height == 100.0
- assert r.hole_diameter == 5.0
-
- r.to_inch()
- assert r.position == (0.1, 1.0)
- assert r.width == 10.0
- assert r.height == 100.0
- assert r.hole_diameter == 5.0
-
- # Initially inch, no hole
- r = Rectangle((0.1, 1.0), 10.0, 100.0, units="inch")
- r.to_inch()
- assert r.position == (0.1, 1.0)
- assert r.width == 10.0
- assert r.height == 100.0
-
- r.to_metric()
- assert r.position == (2.54, 25.4)
- assert r.width == 254.0
- assert r.height == 2540.0
-
- r.to_metric()
- assert r.position == (2.54, 25.4)
- assert r.width == 254.0
- assert r.height == 2540.0
-
- # Initially inch with hole
- r = Rectangle((0.1, 1.0), 10.0, 100.0, 5.0, units="inch")
- r.to_inch()
- assert r.position == (0.1, 1.0)
- assert r.width == 10.0
- assert r.height == 100.0
- assert r.hole_diameter == 5.0
-
- r.to_metric()
- assert r.position == (2.54, 25.4)
- assert r.width == 254.0
- assert r.height == 2540.0
- assert r.hole_diameter == 127.0
-
- r.to_metric()
- assert r.position == (2.54, 25.4)
- assert r.width == 254.0
- assert r.height == 2540.0
- assert r.hole_diameter == 127.0
-
-
-def test_rectangle_offset():
- r = Rectangle((0, 0), 1, 2)
- r.offset(1, 0)
- assert r.position == (1.0, 0.0)
- r.offset(0, 1)
- assert r.position == (1.0, 1.0)
-
-
-def test_diamond_ctor():
- """ Test diamond creation
- """
- test_cases = (((0, 0), 1, 1), ((0, 0), 1, 2), ((1, 1), 1, 2))
- for pos, width, height in test_cases:
- d = Diamond(pos, width, height)
- assert d.position == pos
- assert d.width == width
- assert d.height == height
-
-
-def test_diamond_bounds():
- """ Test diamond bounding box calculation
- """
- d = Diamond((0, 0), 2, 2)
- xbounds, ybounds = d.bounding_box
- pytest.approx(xbounds, (-1, 1))
- pytest.approx(ybounds, (-1, 1))
- d = Diamond((0, 0), math.sqrt(2), math.sqrt(2), rotation=45)
- xbounds, ybounds = d.bounding_box
- pytest.approx(xbounds, (-1, 1))
- pytest.approx(ybounds, (-1, 1))
-
-
-def test_diamond_conversion():
- d = Diamond((2.54, 25.4), 254.0, 2540.0, units="metric")
-
- d.to_metric()
- assert d.position == (2.54, 25.4)
- assert d.width == 254.0
- assert d.height == 2540.0
-
- d.to_inch()
- assert d.position == (0.1, 1.0)
- assert d.width == 10.0
- assert d.height == 100.0
-
- d.to_inch()
- assert d.position == (0.1, 1.0)
- assert d.width == 10.0
- assert d.height == 100.0
-
- d = Diamond((0.1, 1.0), 10.0, 100.0, units="inch")
-
- d.to_inch()
- assert d.position == (0.1, 1.0)
- assert d.width == 10.0
- assert d.height == 100.0
-
- d.to_metric()
- assert d.position == (2.54, 25.4)
- assert d.width == 254.0
- assert d.height == 2540.0
-
- d.to_metric()
- assert d.position == (2.54, 25.4)
- assert d.width == 254.0
- assert d.height == 2540.0
-
-
-def test_diamond_offset():
- d = Diamond((0, 0), 1, 2)
- d.offset(1, 0)
- assert d.position == (1.0, 0.0)
- d.offset(0, 1)
- assert d.position == (1.0, 1.0)
-
-
-def test_chamfer_rectangle_ctor():
- """ Test chamfer rectangle creation
- """
- test_cases = (
- ((0, 0), 1, 1, 0.2, (True, True, False, False)),
- ((0, 0), 1, 2, 0.3, (True, True, True, True)),
- ((1, 1), 1, 2, 0.4, (False, False, False, False)),
- )
- for pos, width, height, chamfer, corners in test_cases:
- r = ChamferRectangle(pos, width, height, chamfer, corners)
- assert r.position == pos
- assert r.width == width
- assert r.height == height
- assert r.chamfer == chamfer
- pytest.approx(r.corners, corners)
-
-
-def test_chamfer_rectangle_bounds():
- """ Test chamfer rectangle bounding box calculation
- """
- r = ChamferRectangle((0, 0), 2, 2, 0.2, (True, True, False, False))
- xbounds, ybounds = r.bounding_box
- pytest.approx(xbounds, (-1, 1))
- pytest.approx(ybounds, (-1, 1))
- r = ChamferRectangle((0, 0), 2, 2, 0.2, (True, True, False, False), rotation=45)
- xbounds, ybounds = r.bounding_box
- pytest.approx(xbounds, (-math.sqrt(2), math.sqrt(2)))
- pytest.approx(ybounds, (-math.sqrt(2), math.sqrt(2)))
-
-
-def test_chamfer_rectangle_conversion():
- r = ChamferRectangle(
- (2.54, 25.4), 254.0, 2540.0, 0.254, (True, True, False, False), units="metric"
- )
-
- r.to_metric()
- assert r.position == (2.54, 25.4)
- assert r.width == 254.0
- assert r.height == 2540.0
- assert r.chamfer == 0.254
-
- r.to_inch()
- assert r.position == (0.1, 1.0)
- assert r.width == 10.0
- assert r.height == 100.0
- assert r.chamfer == 0.01
-
- r.to_inch()
- assert r.position == (0.1, 1.0)
- assert r.width == 10.0
- assert r.height == 100.0
- assert r.chamfer == 0.01
-
- r = ChamferRectangle(
- (0.1, 1.0), 10.0, 100.0, 0.01, (True, True, False, False), units="inch"
- )
- r.to_inch()
- assert r.position == (0.1, 1.0)
- assert r.width == 10.0
- assert r.height == 100.0
- assert r.chamfer == 0.01
-
- r.to_metric()
- assert r.position == (2.54, 25.4)
- assert r.width == 254.0
- assert r.height == 2540.0
- assert r.chamfer == 0.254
-
- r.to_metric()
- assert r.position == (2.54, 25.4)
- assert r.width == 254.0
- assert r.height == 2540.0
- assert r.chamfer == 0.254
-
-
-def test_chamfer_rectangle_offset():
- r = ChamferRectangle((0, 0), 1, 2, 0.01, (True, True, False, False))
- r.offset(1, 0)
- assert r.position == (1.0, 0.0)
- r.offset(0, 1)
- assert r.position == (1.0, 1.0)
-
-
-def test_chamfer_rectangle_vertices():
- TEST_VECTORS = [
- (
- 1.0,
- (True, True, True, True),
- (
- (-2.5, -1.5),
- (-2.5, 1.5),
- (-1.5, 2.5),
- (1.5, 2.5),
- (2.5, 1.5),
- (2.5, -1.5),
- (1.5, -2.5),
- (-1.5, -2.5),
- ),
- ),
- (
- 1.0,
- (True, False, False, False),
- ((-2.5, -2.5), (-2.5, 2.5), (1.5, 2.5), (2.5, 1.5), (2.5, -2.5)),
- ),
- (
- 1.0,
- (False, True, False, False),
- ((-2.5, -2.5), (-2.5, 1.5), (-1.5, 2.5), (2.5, 2.5), (2.5, -2.5)),
- ),
- (
- 1.0,
- (False, False, True, False),
- ((-2.5, -1.5), (-2.5, 2.5), (2.5, 2.5), (2.5, -2.5), (-1.5, -2.5)),
- ),
- (
- 1.0,
- (False, False, False, True),
- ((-2.5, -2.5), (-2.5, 2.5), (2.5, 2.5), (2.5, -1.5), (1.5, -2.5)),
- ),
- ]
- for chamfer, corners, expected in TEST_VECTORS:
- r = ChamferRectangle((0, 0), 5, 5, chamfer, corners)
- assert set(r.vertices) == set(expected)
-
-
-def test_round_rectangle_ctor():
- """ Test round rectangle creation
- """
- test_cases = (
- ((0, 0), 1, 1, 0.2, (True, True, False, False)),
- ((0, 0), 1, 2, 0.3, (True, True, True, True)),
- ((1, 1), 1, 2, 0.4, (False, False, False, False)),
- )
- for pos, width, height, radius, corners in test_cases:
- r = RoundRectangle(pos, width, height, radius, corners)
- assert r.position == pos
- assert r.width == width
- assert r.height == height
- assert r.radius == radius
- pytest.approx(r.corners, corners)
-
-
-def test_round_rectangle_bounds():
- """ Test round rectangle bounding box calculation
- """
- r = RoundRectangle((0, 0), 2, 2, 0.2, (True, True, False, False))
- xbounds, ybounds = r.bounding_box
- pytest.approx(xbounds, (-1, 1))
- pytest.approx(ybounds, (-1, 1))
- r = RoundRectangle((0, 0), 2, 2, 0.2, (True, True, False, False), rotation=45)
- xbounds, ybounds = r.bounding_box
- pytest.approx(xbounds, (-math.sqrt(2), math.sqrt(2)))
- pytest.approx(ybounds, (-math.sqrt(2), math.sqrt(2)))
-
-
-def test_round_rectangle_conversion():
- r = RoundRectangle(
- (2.54, 25.4), 254.0, 2540.0, 0.254, (True, True, False, False), units="metric"
- )
-
- r.to_metric()
- assert r.position == (2.54, 25.4)
- assert r.width == 254.0
- assert r.height == 2540.0
- assert r.radius == 0.254
-
- r.to_inch()
- assert r.position == (0.1, 1.0)
- assert r.width == 10.0
- assert r.height == 100.0
- assert r.radius == 0.01
-
- r.to_inch()
- assert r.position == (0.1, 1.0)
- assert r.width == 10.0
- assert r.height == 100.0
- assert r.radius == 0.01
-
- r = RoundRectangle(
- (0.1, 1.0), 10.0, 100.0, 0.01, (True, True, False, False), units="inch"
- )
-
- r.to_inch()
- assert r.position == (0.1, 1.0)
- assert r.width == 10.0
- assert r.height == 100.0
- assert r.radius == 0.01
-
- r.to_metric()
- assert r.position == (2.54, 25.4)
- assert r.width == 254.0
- assert r.height == 2540.0
- assert r.radius == 0.254
-
- r.to_metric()
- assert r.position == (2.54, 25.4)
- assert r.width == 254.0
- assert r.height == 2540.0
- assert r.radius == 0.254
-
-
-def test_round_rectangle_offset():
- r = RoundRectangle((0, 0), 1, 2, 0.01, (True, True, False, False))
- r.offset(1, 0)
- assert r.position == (1.0, 0.0)
- r.offset(0, 1)
- assert r.position == (1.0, 1.0)
-
-
-def test_obround_ctor():
- """ Test obround creation
- """
- test_cases = (((0, 0), 1, 1), ((0, 0), 1, 2), ((1, 1), 1, 2))
- for pos, width, height in test_cases:
- o = Obround(pos, width, height)
- assert o.position == pos
- assert o.width == width
- assert o.height == height
-
-
-def test_obround_bounds():
- """ Test obround bounding box calculation
- """
- o = Obround((2, 2), 2, 4)
- xbounds, ybounds = o.bounding_box
- pytest.approx(xbounds, (1, 3))
- pytest.approx(ybounds, (0, 4))
- o = Obround((2, 2), 4, 2)
- xbounds, ybounds = o.bounding_box
- pytest.approx(xbounds, (0, 4))
- pytest.approx(ybounds, (1, 3))
-
-
-def test_obround_orientation():
- o = Obround((0, 0), 2, 1)
- assert o.orientation == "horizontal"
- o = Obround((0, 0), 1, 2)
- assert o.orientation == "vertical"
-
-
-def test_obround_subshapes():
- o = Obround((0, 0), 1, 4)
- ss = o.subshapes
- pytest.approx(ss["rectangle"].position, (0, 0))
- pytest.approx(ss["circle1"].position, (0, 1.5))
- pytest.approx(ss["circle2"].position, (0, -1.5))
- o = Obround((0, 0), 4, 1)
- ss = o.subshapes
- pytest.approx(ss["rectangle"].position, (0, 0))
- pytest.approx(ss["circle1"].position, (1.5, 0))
- pytest.approx(ss["circle2"].position, (-1.5, 0))
-
-
-def test_obround_conversion():
- o = Obround((2.54, 25.4), 254.0, 2540.0, units="metric")
-
- # No effect
- o.to_metric()
- assert o.position == (2.54, 25.4)
- assert o.width == 254.0
- assert o.height == 2540.0
-
- o.to_inch()
- assert o.position == (0.1, 1.0)
- assert o.width == 10.0
- assert o.height == 100.0
-
- # No effect
- o.to_inch()
- assert o.position == (0.1, 1.0)
- assert o.width == 10.0
- assert o.height == 100.0
-
- o = Obround((0.1, 1.0), 10.0, 100.0, units="inch")
-
- # No effect
- o.to_inch()
- assert o.position == (0.1, 1.0)
- assert o.width == 10.0
- assert o.height == 100.0
-
- o.to_metric()
- assert o.position == (2.54, 25.4)
- assert o.width == 254.0
- assert o.height == 2540.0
-
- # No effect
- o.to_metric()
- assert o.position == (2.54, 25.4)
- assert o.width == 254.0
- assert o.height == 2540.0
-
-
-def test_obround_offset():
- o = Obround((0, 0), 1, 2)
- o.offset(1, 0)
- assert o.position == (1.0, 0.0)
- o.offset(0, 1)
- assert o.position == (1.0, 1.0)
-
-
-def test_polygon_ctor():
- """ Test polygon creation
- """
- test_cases = (((0, 0), 3, 5, 0), ((0, 0), 5, 6, 0), ((1, 1), 7, 7, 45))
- for pos, sides, radius, hole_diameter in test_cases:
- p = Polygon(pos, sides, radius, hole_diameter)
- assert p.position == pos
- assert p.sides == sides
- assert p.radius == radius
- assert p.hole_diameter == hole_diameter
-
-
-def test_polygon_bounds():
- """ Test polygon bounding box calculation
- """
- p = Polygon((2, 2), 3, 2, 0)
- xbounds, ybounds = p.bounding_box
- pytest.approx(xbounds, (0, 4))
- pytest.approx(ybounds, (0, 4))
- p = Polygon((2, 2), 3, 4, 0)
- xbounds, ybounds = p.bounding_box
- pytest.approx(xbounds, (-2, 6))
- pytest.approx(ybounds, (-2, 6))
-
-
-def test_polygon_conversion():
- p = Polygon((2.54, 25.4), 3, 254.0, 0, units="metric")
-
- # No effect
- p.to_metric()
- assert p.position == (2.54, 25.4)
- assert p.radius == 254.0
-
- p.to_inch()
- assert p.position == (0.1, 1.0)
- assert p.radius == 10.0
-
- # No effect
- p.to_inch()
- assert p.position == (0.1, 1.0)
- assert p.radius == 10.0
-
- p = Polygon((0.1, 1.0), 3, 10.0, 0, units="inch")
-
- # No effect
- p.to_inch()
- assert p.position == (0.1, 1.0)
- assert p.radius == 10.0
-
- p.to_metric()
- assert p.position == (2.54, 25.4)
- assert p.radius == 254.0
-
- # No effect
- p.to_metric()
- assert p.position == (2.54, 25.4)
- assert p.radius == 254.0
-
-
-def test_polygon_offset():
- p = Polygon((0, 0), 5, 10, 0)
- p.offset(1, 0)
- assert p.position == (1.0, 0.0)
- p.offset(0, 1)
- assert p.position == (1.0, 1.0)
-
-
-def test_region_ctor():
- """ Test Region creation
- """
- apt = Circle((0, 0), 0)
- lines = (
- Line((0, 0), (1, 0), apt),
- Line((1, 0), (1, 1), apt),
- Line((1, 1), (0, 1), apt),
- Line((0, 1), (0, 0), apt),
- )
- points = ((0, 0), (1, 0), (1, 1), (0, 1))
- r = Region(lines)
- for i, p in enumerate(lines):
- assert r.primitives[i] == p
-
-
-def test_region_bounds():
- """ Test region bounding box calculation
- """
- apt = Circle((0, 0), 0)
- lines = (
- Line((0, 0), (1, 0), apt),
- Line((1, 0), (1, 1), apt),
- Line((1, 1), (0, 1), apt),
- Line((0, 1), (0, 0), apt),
- )
- r = Region(lines)
- xbounds, ybounds = r.bounding_box
- pytest.approx(xbounds, (0, 1))
- pytest.approx(ybounds, (0, 1))
-
-
-def test_region_offset():
- apt = Circle((0, 0), 0)
- lines = (
- Line((0, 0), (1, 0), apt),
- Line((1, 0), (1, 1), apt),
- Line((1, 1), (0, 1), apt),
- Line((0, 1), (0, 0), apt),
- )
- r = Region(lines)
- xlim, ylim = r.bounding_box
- r.offset(0, 1)
- new_xlim, new_ylim = r.bounding_box
- pytest.approx(new_xlim, xlim)
- pytest.approx(new_ylim, tuple([y + 1 for y in ylim]))
-
-
-def test_round_butterfly_ctor():
- """ Test round butterfly creation
- """
- test_cases = (((0, 0), 3), ((0, 0), 5), ((1, 1), 7))
- for pos, diameter in test_cases:
- b = RoundButterfly(pos, diameter)
- assert b.position == pos
- assert b.diameter == diameter
- assert b.radius == diameter / 2.0
-
-
-def test_round_butterfly_ctor_validation():
- """ Test RoundButterfly argument validation
- """
- pytest.raises(TypeError, RoundButterfly, 3, 5)
- pytest.raises(TypeError, RoundButterfly, (3, 4, 5), 5)
-
-
-def test_round_butterfly_conversion():
- b = RoundButterfly((2.54, 25.4), 254.0, units="metric")
-
- # No Effect
- b.to_metric()
- assert b.position == (2.54, 25.4)
- assert b.diameter == (254.0)
-
- b.to_inch()
- assert b.position == (0.1, 1.0)
- assert b.diameter == 10.0
-
- # No effect
- b.to_inch()
- assert b.position == (0.1, 1.0)
- assert b.diameter == 10.0
-
- b = RoundButterfly((0.1, 1.0), 10.0, units="inch")
-
- # No effect
- b.to_inch()
- assert b.position == (0.1, 1.0)
- assert b.diameter == 10.0
-
- b.to_metric()
- assert b.position == (2.54, 25.4)
- assert b.diameter == (254.0)
-
- # No Effect
- b.to_metric()
- assert b.position == (2.54, 25.4)
- assert b.diameter == (254.0)
-
-
-def test_round_butterfly_offset():
- b = RoundButterfly((0, 0), 1)
- b.offset(1, 0)
- assert b.position == (1.0, 0.0)
- b.offset(0, 1)
- assert b.position == (1.0, 1.0)
-
-
-def test_round_butterfly_bounds():
- """ Test RoundButterfly bounding box calculation
- """
- b = RoundButterfly((0, 0), 2)
- xbounds, ybounds = b.bounding_box
- pytest.approx(xbounds, (-1, 1))
- pytest.approx(ybounds, (-1, 1))
-
-
-def test_square_butterfly_ctor():
- """ Test SquareButterfly creation
- """
- test_cases = (((0, 0), 3), ((0, 0), 5), ((1, 1), 7))
- for pos, side in test_cases:
- b = SquareButterfly(pos, side)
- assert b.position == pos
- assert b.side == side
-
-
-def test_square_butterfly_ctor_validation():
- """ Test SquareButterfly argument validation
- """
- pytest.raises(TypeError, SquareButterfly, 3, 5)
- pytest.raises(TypeError, SquareButterfly, (3, 4, 5), 5)
-
-
-def test_square_butterfly_bounds():
- """ Test SquareButterfly bounding box calculation
- """
- b = SquareButterfly((0, 0), 2)
- xbounds, ybounds = b.bounding_box
- pytest.approx(xbounds, (-1, 1))
- pytest.approx(ybounds, (-1, 1))
-
-
-def test_squarebutterfly_conversion():
- b = SquareButterfly((2.54, 25.4), 254.0, units="metric")
-
- # No effect
- b.to_metric()
- assert b.position == (2.54, 25.4)
- assert b.side == (254.0)
-
- b.to_inch()
- assert b.position == (0.1, 1.0)
- assert b.side == 10.0
-
- # No effect
- b.to_inch()
- assert b.position == (0.1, 1.0)
- assert b.side == 10.0
-
- b = SquareButterfly((0.1, 1.0), 10.0, units="inch")
-
- # No effect
- b.to_inch()
- assert b.position == (0.1, 1.0)
- assert b.side == 10.0
-
- b.to_metric()
- assert b.position == (2.54, 25.4)
- assert b.side == (254.0)
-
- # No effect
- b.to_metric()
- assert b.position == (2.54, 25.4)
- assert b.side == (254.0)
-
-
-def test_square_butterfly_offset():
- b = SquareButterfly((0, 0), 1)
- b.offset(1, 0)
- assert b.position == (1.0, 0.0)
- b.offset(0, 1)
- assert b.position == (1.0, 1.0)
-
-
-def test_donut_ctor():
- """ Test Donut primitive creation
- """
- test_cases = (
- ((0, 0), "round", 3, 5),
- ((0, 0), "square", 5, 7),
- ((1, 1), "hexagon", 7, 9),
- ((2, 2), "octagon", 9, 11),
- )
- for pos, shape, in_d, out_d in test_cases:
- d = Donut(pos, shape, in_d, out_d)
- assert d.position == pos
- assert d.shape == shape
- assert d.inner_diameter == in_d
- assert d.outer_diameter == out_d
-
-
-def test_donut_ctor_validation():
- pytest.raises(TypeError, Donut, 3, "round", 5, 7)
- pytest.raises(TypeError, Donut, (3, 4, 5), "round", 5, 7)
- pytest.raises(ValueError, Donut, (0, 0), "triangle", 3, 5)
- pytest.raises(ValueError, Donut, (0, 0), "round", 5, 3)
-
-
-def test_donut_bounds():
- d = Donut((0, 0), "round", 0.0, 2.0)
- xbounds, ybounds = d.bounding_box
- assert xbounds == (-1.0, 1.0)
- assert ybounds == (-1.0, 1.0)
-
-
-def test_donut_conversion():
- d = Donut((2.54, 25.4), "round", 254.0, 2540.0, units="metric")
-
- # No effect
- d.to_metric()
- assert d.position == (2.54, 25.4)
- assert d.inner_diameter == 254.0
- assert d.outer_diameter == 2540.0
-
- d.to_inch()
- assert d.position == (0.1, 1.0)
- assert d.inner_diameter == 10.0
- assert d.outer_diameter == 100.0
-
- # No effect
- d.to_inch()
- assert d.position == (0.1, 1.0)
- assert d.inner_diameter == 10.0
- assert d.outer_diameter == 100.0
-
- d = Donut((0.1, 1.0), "round", 10.0, 100.0, units="inch")
-
- # No effect
- d.to_inch()
- assert d.position == (0.1, 1.0)
- assert d.inner_diameter == 10.0
- assert d.outer_diameter == 100.0
-
- d.to_metric()
- assert d.position == (2.54, 25.4)
- assert d.inner_diameter == 254.0
- assert d.outer_diameter == 2540.0
-
- # No effect
- d.to_metric()
- assert d.position == (2.54, 25.4)
- assert d.inner_diameter == 254.0
- assert d.outer_diameter == 2540.0
-
-
-def test_donut_offset():
- d = Donut((0, 0), "round", 1, 10)
- d.offset(1, 0)
- assert d.position == (1.0, 0.0)
- d.offset(0, 1)
- assert d.position == (1.0, 1.0)
-
-
-def test_drill_ctor():
- """ Test drill primitive creation
- """
- test_cases = (((0, 0), 2), ((1, 1), 3), ((2, 2), 5))
- for position, diameter in test_cases:
- d = Drill(position, diameter)
- assert d.position == position
- assert d.diameter == diameter
- assert d.radius == diameter / 2.0
-
-
-def test_drill_ctor_validation():
- """ Test drill argument validation
- """
- pytest.raises(TypeError, Drill, 3, 5)
- pytest.raises(TypeError, Drill, (3, 4, 5), 5)
-
-
-def test_drill_bounds():
- d = Drill((0, 0), 2)
- xbounds, ybounds = d.bounding_box
- pytest.approx(xbounds, (-1, 1))
- pytest.approx(ybounds, (-1, 1))
- d = Drill((1, 2), 2)
- xbounds, ybounds = d.bounding_box
- pytest.approx(xbounds, (0, 2))
- pytest.approx(ybounds, (1, 3))
-
-
-def test_drill_conversion():
- d = Drill((2.54, 25.4), 254.0, units="metric")
-
- # No effect
- d.to_metric()
- assert d.position == (2.54, 25.4)
- assert d.diameter == 254.0
-
- d.to_inch()
- assert d.position == (0.1, 1.0)
- assert d.diameter == 10.0
-
- # No effect
- d.to_inch()
- assert d.position == (0.1, 1.0)
- assert d.diameter == 10.0
-
- d = Drill((0.1, 1.0), 10.0, units="inch")
-
- # No effect
- d.to_inch()
- assert d.position == (0.1, 1.0)
- assert d.diameter == 10.0
-
- d.to_metric()
- assert d.position == (2.54, 25.4)
- assert d.diameter == 254.0
-
- # No effect
- d.to_metric()
- assert d.position == (2.54, 25.4)
- assert d.diameter == 254.0
-
-
-def test_drill_offset():
- d = Drill((0, 0), 1.0)
- d.offset(1, 0)
- assert d.position == (1.0, 0.0)
- d.offset(0, 1)
- assert d.position == (1.0, 1.0)
-
-
-def test_drill_equality():
- d = Drill((2.54, 25.4), 254.0)
- d1 = Drill((2.54, 25.4), 254.0)
- assert d == d1
- d1 = Drill((2.54, 25.4), 254.2)
- assert d != d1
-
-
-def test_slot_bounds():
- """ Test Slot primitive bounding box calculation
- """
- cases = [
- ((0, 0), (1, 1), ((-1, 2), (-1, 2))),
- ((-1, -1), (1, 1), ((-2, 2), (-2, 2))),
- ((1, 1), (-1, -1), ((-2, 2), (-2, 2))),
- ((-1, 1), (1, -1), ((-2, 2), (-2, 2))),
- ]
-
- for start, end, expected in cases:
- s = Slot(start, end, 2.0)
- assert s.bounding_box == expected
diff --git a/gerber/tests/test_rs274x.py b/gerber/tests/test_rs274x.py
deleted file mode 100644
index e7baf11..0000000
--- a/gerber/tests/test_rs274x.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Author: Hamilton Kibbe <ham@hamiltonkib.be>
-import os
-import pytest
-
-from ..rs274x import read, GerberFile
-
-
-TOP_COPPER_FILE = os.path.join(os.path.dirname(__file__), "resources/top_copper.GTL")
-
-MULTILINE_READ_FILE = os.path.join(
- os.path.dirname(__file__), "resources/multiline_read.ger"
-)
-
-
-def test_read():
- top_copper = read(TOP_COPPER_FILE)
- assert isinstance(top_copper, GerberFile)
-
-
-def test_multiline_read():
- multiline = read(MULTILINE_READ_FILE)
- assert isinstance(multiline, GerberFile)
- assert 10 == len(multiline.statements)
-
-
-def test_comments_parameter():
- top_copper = read(TOP_COPPER_FILE)
- assert top_copper.comments[0] == "This is a comment,:"
-
-
-def test_size_parameter():
- top_copper = read(TOP_COPPER_FILE)
- size = top_copper.size
- pytest.approx(size[0], 2.256900, 6)
- pytest.approx(size[1], 1.500000, 6)
-
-
-def test_conversion():
- top_copper = read(TOP_COPPER_FILE)
- assert top_copper.units == "inch"
- top_copper_inch = read(TOP_COPPER_FILE)
- top_copper.to_metric()
- for statement in top_copper_inch.statements:
- statement.to_metric()
- for primitive in top_copper_inch.primitives:
- primitive.to_metric()
- assert top_copper.units == "metric"
- for i, m in zip(top_copper.statements, top_copper_inch.statements):
- assert i == m
-
- for i, m in zip(top_copper.primitives, top_copper_inch.primitives):
- assert i == m
diff --git a/gerber/tests/test_rs274x_backend.py b/gerber/tests/test_rs274x_backend.py
deleted file mode 100644
index 13347c5..0000000
--- a/gerber/tests/test_rs274x_backend.py
+++ /dev/null
@@ -1,232 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Author: Garret Fick <garret@ficksworkshop.com>
-
-import os
-
-from ..render.rs274x_backend import Rs274xContext
-from ..rs274x import read
-
-
-def test_render_two_boxes():
- """Umaco exapmle of two boxes"""
- _test_render(
- "resources/example_two_square_boxes.gbr", "golden/example_two_square_boxes.gbr"
- )
-
-
-def _test_render_single_quadrant():
- """Umaco exapmle of a single quadrant arc"""
-
- # TODO there is probably a bug here
- _test_render(
- "resources/example_single_quadrant.gbr", "golden/example_single_quadrant.gbr"
- )
-
-
-def _test_render_simple_contour():
- """Umaco exapmle of a simple arrow-shaped contour"""
- _test_render(
- "resources/example_simple_contour.gbr", "golden/example_simple_contour.gbr"
- )
-
-
-def _test_render_single_contour_1():
- """Umaco example of a single contour
-
- The resulting image for this test is used by other tests because they must generate the same output."""
- _test_render(
- "resources/example_single_contour_1.gbr", "golden/example_single_contour.gbr"
- )
-
-
-def _test_render_single_contour_2():
- """Umaco exapmle of a single contour, alternate contour end order
-
- The resulting image for this test is used by other tests because they must generate the same output."""
- _test_render(
- "resources/example_single_contour_2.gbr", "golden/example_single_contour.gbr"
- )
-
-
-def _test_render_single_contour_3():
- """Umaco exapmle of a single contour with extra line"""
- _test_render(
- "resources/example_single_contour_3.gbr", "golden/example_single_contour_3.gbr"
- )
-
-
-def _test_render_not_overlapping_contour():
- """Umaco example of D02 staring a second contour"""
- _test_render(
- "resources/example_not_overlapping_contour.gbr",
- "golden/example_not_overlapping_contour.gbr",
- )
-
-
-def _test_render_not_overlapping_touching():
- """Umaco example of D02 staring a second contour"""
- _test_render(
- "resources/example_not_overlapping_touching.gbr",
- "golden/example_not_overlapping_touching.gbr",
- )
-
-
-def _test_render_overlapping_touching():
- """Umaco example of D02 staring a second contour"""
- _test_render(
- "resources/example_overlapping_touching.gbr",
- "golden/example_overlapping_touching.gbr",
- )
-
-
-def _test_render_overlapping_contour():
- """Umaco example of D02 staring a second contour"""
- _test_render(
- "resources/example_overlapping_contour.gbr",
- "golden/example_overlapping_contour.gbr",
- )
-
-
-def _DISABLED_test_render_level_holes():
- """Umaco example of using multiple levels to create multiple holes"""
-
- # TODO This is clearly rendering wrong. I'm temporarily checking this in because there are more
- # rendering fixes in the related repository that may resolve these.
- _test_render(
- "resources/example_level_holes.gbr", "golden/example_overlapping_contour.gbr"
- )
-
-
-def _DISABLED_test_render_cutin():
- """Umaco example of using a cutin"""
-
- # TODO This is clearly rendering wrong.
- _test_render("resources/example_cutin.gbr", "golden/example_cutin.gbr")
-
-
-def _test_render_fully_coincident():
- """Umaco example of coincident lines rendering two contours"""
-
- _test_render(
- "resources/example_fully_coincident.gbr", "golden/example_fully_coincident.gbr"
- )
-
-
-def _test_render_coincident_hole():
- """Umaco example of coincident lines rendering a hole in the contour"""
-
- _test_render(
- "resources/example_coincident_hole.gbr", "golden/example_coincident_hole.gbr"
- )
-
-
-def _test_render_cutin_multiple():
- """Umaco example of a region with multiple cutins"""
-
- _test_render(
- "resources/example_cutin_multiple.gbr", "golden/example_cutin_multiple.gbr"
- )
-
-
-def _test_flash_circle():
- """Umaco example a simple circular flash with and without a hole"""
-
- _test_render(
- "resources/example_flash_circle.gbr", "golden/example_flash_circle.gbr"
- )
-
-
-def _test_flash_rectangle():
- """Umaco example a simple rectangular flash with and without a hole"""
-
- _test_render(
- "resources/example_flash_rectangle.gbr", "golden/example_flash_rectangle.gbr"
- )
-
-
-def _test_flash_obround():
- """Umaco example a simple obround flash with and without a hole"""
-
- _test_render(
- "resources/example_flash_obround.gbr", "golden/example_flash_obround.gbr"
- )
-
-
-def _test_flash_polygon():
- """Umaco example a simple polygon flash with and without a hole"""
-
- _test_render(
- "resources/example_flash_polygon.gbr", "golden/example_flash_polygon.gbr"
- )
-
-
-def _test_holes_dont_clear():
- """Umaco example that an aperture with a hole does not clear the area"""
-
- _test_render(
- "resources/example_holes_dont_clear.gbr", "golden/example_holes_dont_clear.gbr"
- )
-
-
-def _test_render_am_exposure_modifier():
- """Umaco example that an aperture macro with a hole does not clear the area"""
-
- _test_render(
- "resources/example_am_exposure_modifier.gbr",
- "golden/example_am_exposure_modifier.gbr",
- )
-
-
-def _resolve_path(path):
- return os.path.join(os.path.dirname(__file__), path)
-
-
-def _test_render(gerber_path, png_expected_path, create_output_path=None):
- """Render the gerber file and compare to the expected PNG output.
-
- Parameters
- ----------
- gerber_path : string
- Path to Gerber file to open
- png_expected_path : string
- Path to the PNG file to compare to
- create_output : string|None
- If not None, write the generated PNG to the specified path.
- This is primarily to help with
- """
-
- gerber_path = _resolve_path(gerber_path)
- png_expected_path = _resolve_path(png_expected_path)
- if create_output_path:
- create_output_path = _resolve_path(create_output_path)
-
- gerber = read(gerber_path)
-
- # Create GBR output from the input file
- ctx = Rs274xContext(gerber.settings)
- gerber.render(ctx)
-
- actual_contents = ctx.dump()
-
- # If we want to write the file bytes, do it now. This happens
- if create_output_path:
- with open(create_output_path, "wb") as out_file:
- out_file.write(actual_contents.getvalue())
- # Creating the output is dangerous - it could overwrite the expected result.
- # So if we are creating the output, we make the test fail on purpose so you
- # won't forget to disable this
- assert not True, (
- "Test created the output %s. This needs to be disabled to make sure the test behaves correctly"
- % (create_output_path,)
- )
-
- # Read the expected PNG file
-
- with open(png_expected_path, "r") as expected_file:
- expected_contents = expected_file.read()
-
- assert expected_contents == actual_contents.getvalue()
-
- return gerber
diff --git a/gerber/tests/test_utils.py b/gerber/tests/test_utils.py
deleted file mode 100644
index 68484d1..0000000
--- a/gerber/tests/test_utils.py
+++ /dev/null
@@ -1,167 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Author: Hamilton Kibbe <ham@hamiltonkib.be>
-
-import pytest
-from ..utils import *
-
-
-def test_zero_suppression():
- """ Test gerber value parser and writer handle zero suppression correctly.
- """
- # Default format
- fmt = (2, 5)
-
- # Test leading zero suppression
- zero_suppression = "leading"
- test_cases = [
- ("1", 0.00001),
- ("10", 0.0001),
- ("100", 0.001),
- ("1000", 0.01),
- ("10000", 0.1),
- ("100000", 1.0),
- ("1000000", 10.0),
- ("-1", -0.00001),
- ("-10", -0.0001),
- ("-100", -0.001),
- ("-1000", -0.01),
- ("-10000", -0.1),
- ("-100000", -1.0),
- ("-1000000", -10.0),
- ("0", 0.0),
- ]
- for string, value in test_cases:
- assert value == parse_gerber_value(string, fmt, zero_suppression)
- assert string == write_gerber_value(value, fmt, zero_suppression)
-
- # Test trailing zero suppression
- zero_suppression = "trailing"
- test_cases = [
- ("1", 10.0),
- ("01", 1.0),
- ("001", 0.1),
- ("0001", 0.01),
- ("00001", 0.001),
- ("000001", 0.0001),
- ("0000001", 0.00001),
- ("-1", -10.0),
- ("-01", -1.0),
- ("-001", -0.1),
- ("-0001", -0.01),
- ("-00001", -0.001),
- ("-000001", -0.0001),
- ("-0000001", -0.00001),
- ("0", 0.0),
- ]
- for string, value in test_cases:
- assert value == parse_gerber_value(string, fmt, zero_suppression)
- assert string == write_gerber_value(value, fmt, zero_suppression)
-
- assert write_gerber_value(0.000000001, fmt, "leading") == "0"
- assert write_gerber_value(0.000000001, fmt, "trailing") == "0"
-
-
-def test_format():
- """ Test gerber value parser and writer handle format correctly
- """
- zero_suppression = "leading"
- test_cases = [
- ((2, 7), "1", 0.0000001),
- ((2, 6), "1", 0.000001),
- ((2, 5), "1", 0.00001),
- ((2, 4), "1", 0.0001),
- ((2, 3), "1", 0.001),
- ((2, 2), "1", 0.01),
- ((2, 1), "1", 0.1),
- ((2, 7), "-1", -0.0000001),
- ((2, 6), "-1", -0.000001),
- ((2, 5), "-1", -0.00001),
- ((2, 4), "-1", -0.0001),
- ((2, 3), "-1", -0.001),
- ((2, 2), "-1", -0.01),
- ((2, 1), "-1", -0.1),
- ((2, 6), "0", 0),
- ]
- for fmt, string, value in test_cases:
- assert value == parse_gerber_value(string, fmt, zero_suppression)
- assert string == write_gerber_value(value, fmt, zero_suppression)
-
- zero_suppression = "trailing"
- test_cases = [
- ((6, 5), "1", 100000.0),
- ((5, 5), "1", 10000.0),
- ((4, 5), "1", 1000.0),
- ((3, 5), "1", 100.0),
- ((2, 5), "1", 10.0),
- ((1, 5), "1", 1.0),
- ((6, 5), "-1", -100000.0),
- ((5, 5), "-1", -10000.0),
- ((4, 5), "-1", -1000.0),
- ((3, 5), "-1", -100.0),
- ((2, 5), "-1", -10.0),
- ((1, 5), "-1", -1.0),
- ((2, 5), "0", 0),
- ]
- for fmt, string, value in test_cases:
- assert value == parse_gerber_value(string, fmt, zero_suppression)
- assert string == write_gerber_value(value, fmt, zero_suppression)
-
-
-def test_decimal_truncation():
- """ Test decimal_string truncates value to the correct precision
- """
- value = 1.123456789
- for x in range(10):
- result = decimal_string(value, precision=x)
- calculated = "1." + "".join(str(y) for y in range(1, x + 1))
- assert result == calculated
-
-
-def test_decimal_padding():
- """ Test decimal_string padding
- """
- value = 1.123
- assert decimal_string(value, precision=3, padding=True) == "1.123"
- assert decimal_string(value, precision=4, padding=True) == "1.1230"
- assert decimal_string(value, precision=5, padding=True) == "1.12300"
- assert decimal_string(value, precision=6, padding=True) == "1.123000"
- assert decimal_string(0, precision=6, padding=True) == "0.000000"
-
-
-def test_parse_format_validation():
- """ Test parse_gerber_value() format validation
- """
- pytest.raises(ValueError, parse_gerber_value, "00001111", (7, 5))
- pytest.raises(ValueError, parse_gerber_value, "00001111", (5, 8))
- pytest.raises(ValueError, parse_gerber_value, "00001111", (13, 1))
-
-
-def test_write_format_validation():
- """ Test write_gerber_value() format validation
- """
- pytest.raises(ValueError, write_gerber_value, 69.0, (7, 5))
- pytest.raises(ValueError, write_gerber_value, 69.0, (5, 8))
- pytest.raises(ValueError, write_gerber_value, 69.0, (13, 1))
-
-
-def test_detect_format_with_short_file():
- """ Verify file format detection works with short files
- """
- assert "unknown" == detect_file_format("gerber/tests/__init__.py")
-
-
-def test_validate_coordinates():
- pytest.raises(TypeError, validate_coordinates, 3)
- pytest.raises(TypeError, validate_coordinates, 3.1)
- pytest.raises(TypeError, validate_coordinates, "14")
- pytest.raises(TypeError, validate_coordinates, (0,))
- pytest.raises(TypeError, validate_coordinates, (0, 1, 2))
- pytest.raises(TypeError, validate_coordinates, (0, "string"))
-
-
-def test_convex_hull():
- points = [(0, 0), (1, 0), (1, 1), (0.5, 0.5), (0, 1), (0, 0)]
- expected = [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]
- assert set(convex_hull(points)) == set(expected)
diff --git a/gerber/utils.py b/gerber/utils.py
deleted file mode 100644
index 3d39df9..0000000
--- a/gerber/utils.py
+++ /dev/null
@@ -1,458 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-gerber.utils
-============
-**Gerber and Excellon file handling utilities**
-
-This module provides utility functions for working with Gerber and Excellon
-files.
-"""
-
-import os
-from math import radians, sin, cos, sqrt, atan2, pi
-
-MILLIMETERS_PER_INCH = 25.4
-
-
-def parse_gerber_value(value, format=(2, 5), zero_suppression='trailing'):
- """ Convert gerber/excellon formatted string to floating-point number
-
- .. note::
- Format and zero suppression are configurable. Note that the Excellon
- and Gerber formats use opposite terminology with respect to leading
- and trailing zeros. The Gerber format specifies which zeros are
- suppressed, while the Excellon format specifies which zeros are
- included. This function uses the Gerber-file convention, so an
- Excellon file in LZ (leading zeros) mode would use
- `zero_suppression='trailing'`
-
-
- Parameters
- ----------
- value : string
- A Gerber/Excellon-formatted string representing a numerical value.
-
- format : tuple (int,int)
- Gerber/Excellon precision format expressed as a tuple containing:
- (number of integer-part digits, number of decimal-part digits)
-
- zero_suppression : string
- Zero-suppression mode. May be 'leading', 'trailing' or 'none'
-
- Returns
- -------
- value : float
- The specified value as a floating-point number.
-
- """
- # Handle excellon edge case with explicit decimal. "That was easy!"
- if '.' in value:
- return float(value)
-
- # Format precision
- integer_digits, decimal_digits = format
- MAX_DIGITS = integer_digits + decimal_digits
-
- # Absolute maximum number of digits supported. This will handle up to
- # 6:7 format, which is somewhat supported, even though the gerber spec
- # only allows up to 6:6
- if MAX_DIGITS > 13 or integer_digits > 6 or decimal_digits > 7:
- raise ValueError('Parser only supports precision up to 6:7 format')
-
- # Remove extraneous information
- value = value.lstrip('+')
- negative = '-' in value
- if negative:
- value = value.lstrip('-')
-
- missing_digits = MAX_DIGITS - len(value)
-
- if zero_suppression == 'trailing':
- digits = list(value + ('0' * missing_digits))
- elif zero_suppression == 'leading':
- digits = list(('0' * missing_digits) + value)
- else:
- digits = list(value)
-
- result = float(
- ''.join(digits[:integer_digits] + ['.'] + digits[integer_digits:]))
- return -result if negative else result
-
-
-def write_gerber_value(value, format=(2, 5), zero_suppression='trailing'):
- """ Convert a floating point number to a Gerber/Excellon-formatted string.
-
- .. note::
- Format and zero suppression are configurable. Note that the Excellon
- and Gerber formats use opposite terminology with respect to leading
- and trailing zeros. The Gerber format specifies which zeros are
- suppressed, while the Excellon format specifies which zeros are
- included. This function uses the Gerber-file convention, so an
- Excellon file in LZ (leading zeros) mode would use
- `zero_suppression='trailing'`
-
- Parameters
- ----------
- value : float
- A floating point value.
-
- format : tuple (n=2)
- Gerber/Excellon precision format expressed as a tuple containing:
- (number of integer-part digits, number of decimal-part digits)
-
- zero_suppression : string
- Zero-suppression mode. May be 'leading', 'trailing' or 'none'
-
- Returns
- -------
- value : string
- The specified value as a Gerber/Excellon-formatted string.
- """
-
- if format[0] == float:
- return "%f" %value
-
- # Format precision
- integer_digits, decimal_digits = format
- MAX_DIGITS = integer_digits + decimal_digits
-
- if MAX_DIGITS > 13 or integer_digits > 6 or decimal_digits > 7:
- raise ValueError('Parser only supports precision up to 6:7 format')
-
- # Edge case... (per Gerber spec we should return 0 in all cases, see page
- # 77)
- if value == 0:
- return '0'
-
- # negative sign affects padding, so deal with it at the end...
- negative = value < 0.0
- if negative:
- value = -1.0 * value
-
- # Format string for padding out in both directions
- fmtstring = '%%0%d.0%df' % (MAX_DIGITS + 1, decimal_digits)
- digits = [val for val in fmtstring % value if val != '.']
-
- # If all the digits are 0, return '0'.
- digit_sum = sum([int(digit) for digit in digits])
- if digit_sum == 0:
- return '0'
-
- # Suppression...
- if zero_suppression == 'trailing':
- while digits and digits[-1] == '0':
- digits.pop()
- elif zero_suppression == 'leading':
- while digits and digits[0] == '0':
- digits.pop(0)
-
- if not digits:
- return '0'
-
- return ''.join(digits) if not negative else ''.join(['-'] + digits)
-
-
-def decimal_string(value, precision=6, padding=False):
- """ Convert float to string with limited precision
-
- Parameters
- ----------
- value : float
- A floating point value.
-
- precision :
- Maximum number of decimal places to print
-
- Returns
- -------
- value : string
- The specified value as a string.
-
- """
- floatstr = '%0.10g' % value
- integer = None
- decimal = None
- if '.' in floatstr:
- integer, decimal = floatstr.split('.')
- elif ',' in floatstr:
- integer, decimal = floatstr.split(',')
- else:
- integer, decimal = floatstr, "0"
-
- if len(decimal) > precision:
- decimal = decimal[:precision]
- elif padding:
- decimal = decimal + (precision - len(decimal)) * '0'
-
- if integer or decimal:
- return ''.join([integer, '.', decimal])
- else:
- return int(floatstr)
-
-
-def detect_file_format(data):
- """ Determine format of a file
-
- Parameters
- ----------
- data : string
- string containing file data.
-
- Returns
- -------
- format : string
- File format. 'excellon' or 'rs274x' or 'unknown'
- """
- lines = data.split('\n')
- for line in lines:
- if 'M48' in line:
- return 'excellon'
- elif '%FS' in line:
- return 'rs274x'
- elif ((len(line.split()) >= 2) and
- (line.split()[0] == 'P') and (line.split()[1] == 'JOB')):
- return 'ipc_d_356'
- return 'unknown'
-
-
-def validate_coordinates(position):
- if position is not None:
- if len(position) != 2:
- raise TypeError('Position must be a tuple (n=2) of coordinates')
- else:
- for coord in position:
- if not (isinstance(coord, int) or isinstance(coord, float)):
- raise TypeError('Coordinates must be integers or floats')
-
-
-def metric(value):
- """ Convert inch value to millimeters
-
- Parameters
- ----------
- value : float
- A value in inches.
-
- Returns
- -------
- value : float
- The equivalent value expressed in millimeters.
- """
- return value * MILLIMETERS_PER_INCH
-
-
-def inch(value):
- """ Convert millimeter value to inches
-
- Parameters
- ----------
- value : float
- A value in millimeters.
-
- Returns
- -------
- value : float
- The equivalent value expressed in inches.
- """
- return value / MILLIMETERS_PER_INCH
-
-
-def rotate_point(point, angle, center=(0.0, 0.0)):
- """ Rotate a point about another point.
-
- Parameters
- -----------
- point : tuple(<float>, <float>)
- Point to rotate about origin or center point
-
- angle : float
- Angle to rotate the point [degrees]
-
- center : tuple(<float>, <float>)
- Coordinates about which the point is rotated. Defaults to the origin.
-
- Returns
- -------
- rotated_point : tuple(<float>, <float>)
- `point` rotated about `center` by `angle` degrees.
- """
- angle = radians(angle)
-
- cos_angle = cos(angle)
- sin_angle = sin(angle)
-
- return (
- cos_angle * (point[0] - center[0]) - sin_angle * (point[1] - center[1]) + center[0],
- sin_angle * (point[0] - center[0]) + cos_angle * (point[1] - center[1]) + center[1])
-
-def nearly_equal(point1, point2, ndigits = 6):
- '''Are the points nearly equal'''
-
- return round(point1[0] - point2[0], ndigits) == 0 and round(point1[1] - point2[1], ndigits) == 0
-
-
-def sq_distance(point1, point2):
-
- diff1 = point1[0] - point2[0]
- diff2 = point1[1] - point2[1]
- return diff1 * diff1 + diff2 * diff2
-
-
-def listdir(directory, ignore_hidden=True, ignore_os=True):
- """ List files in given directory.
- Differs from os.listdir() in that hidden and OS-generated files are ignored
- by default.
-
- Parameters
- ----------
- directory : str
- path to the directory for which to list files.
-
- ignore_hidden : bool
- If True, ignore files beginning with a leading '.'
-
- ignore_os : bool
- If True, ignore OS-generated files, e.g. Thumbs.db
-
- Returns
- -------
- files : list
- list of files in specified directory
- """
- os_files = ('.DS_Store', 'Thumbs.db', 'ethumbs.db')
- files = os.listdir(directory)
- if ignore_hidden:
- files = [f for f in files if not f.startswith('.')]
- if ignore_os:
- files = [f for f in files if not f in os_files]
- return files
-
-def ConvexHull_qh(points):
- #a hull must be a planar shape with nonzero area, so there must be at least 3 points
- if(len(points)<3):
- raise Exception("not a planar shape")
- #find points with lowest and highest X coordinates
- minxp=0;
- maxxp=0;
- for i in range(len(points)):
- if(points[i][0]<points[minxp][0]):
- minxp=i;
- if(points[i][0]>points[maxxp][0]):
- maxxp=i;
- if minxp==maxxp:
- #all points are collinear
- raise Exception("not a planar shape")
- #separate points into those above and those below the minxp-maxxp line
- lpoints=[]
- rpoints=[]
- #to detemine if point X is on the left or right of dividing line A-B, compare slope of A-B to slope of A-X
- #slope is (By-Ay)/(Bx-Ax)
- a=points[minxp]
- b=points[maxxp]
- slopeab=atan2(b[1]-a[1],b[0]-a[0])
- for i in range(len(points)):
- p=points[i]
- if i == minxp or i == maxxp:
- continue
- slopep=atan2(p[1]-a[1],p[0]-a[0])
- sdiff=slopep-slopeab
- if(sdiff<pi):sdiff+=2*pi
- if(sdiff>pi):sdiff-=2*pi
- if(sdiff>0):
- lpoints+=[i]
- if(sdiff<0):
- rpoints+=[i]
- hull=[minxp]+_findhull(rpoints, maxxp, minxp, points)+[maxxp]+_findhull(lpoints, minxp, maxxp, points)
- hullo=_optimize(hull,points)
- return hullo
-
-def _optimize(hull,points):
- #find triplets that are collinear and remove middle point
- toremove=[]
- newhull=hull[:]
- l=len(hull)
- for i in range(l):
- p1=hull[i]
- p2=hull[(i+1)%l]
- p3=hull[(i+2)%l]
- #(p1.y-p2.y)*(p1.x-p3.x)==(p1.y-p3.y)*(p1.x-p2.x)
- if (points[p1][1]-points[p2][1])*(points[p1][0]-points[p3][0])==(points[p1][1]-points[p3][1])*(points[p1][0]-points[p2][0]):
- toremove+=[p2]
- for i in toremove:
- newhull.remove(i)
- return newhull
-
-def _distance(a, b, x):
- #find the distance between point x and line a-b
- return abs((b[1]-a[1])*x[0]-(b[0]-a[0])*x[1]+b[0]*a[1]-a[0]*b[1])/sqrt((b[1]-a[1])**2 + (b[0]-a[0])**2 );
-
-def _findhull(idxp, a_i, b_i, points):
- #if no points in input, return no points in output
- if(len(idxp)==0):
- return [];
- #find point c furthest away from line a-b
- farpoint=-1
- fdist=-1.0;
- for i in idxp:
- d=_distance(points[a_i], points[b_i], points[i])
- if(d>fdist):
- fdist=d;
- farpoint=i
- if(fdist<=0):
- #none of the points have a positive distance from line, bad things have happened
- return []
- #separate points into those inside triangle, those outside triangle left of far point, and those outside triangle right of far point
- a=points[a_i]
- b=points[b_i]
- c=points[farpoint]
- slopeac=atan2(c[1]-a[1],c[0]-a[0])
- slopecb=atan2(b[1]-c[1],b[0]-c[0])
- lpoints=[]
- rpoints=[]
- for i in idxp:
- if i==farpoint:
- #ignore triangle vertex
- continue
- x=points[i]
- #if point x is left of line a-c it's in left set
- slopeax=atan2(x[1]-a[1],x[0]-a[0])
- if slopeac==slopeax:
- continue
- sdiff=slopeac-slopeax
- if(sdiff<-pi):sdiff+=2*pi
- if(sdiff>pi):sdiff-=2*pi
- if(sdiff<0):
- lpoints+=[i]
- else:
- #if point x is right of line b-c it's in right set, otherwise it's inside triangle and can be ignored
- slopecx=atan2(x[1]-c[1],x[0]-c[0])
- if slopecx==slopecb:
- continue
- sdiff=slopecx-slopecb
- if(sdiff<-pi):sdiff+=2*pi
- if(sdiff>pi):sdiff-=2*pi
- if(sdiff>0):
- rpoints+=[i]
- #the hull segment between points a and b consists of the hull segment between a and c, the point c, and the hull segment between c and b
- ret=_findhull(rpoints, farpoint, b_i, points)+[farpoint]+_findhull(lpoints, a_i, farpoint, points)
- return ret
-
-
-def convex_hull(points):
- vertices = ConvexHull_qh(points)
- return [points[idx] for idx in vertices]