summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md39
-rw-r--r--gerberex/composition.py22
-rw-r--r--gerberex/dxf.py205
-rw-r--r--test/data/base.txt370
-rw-r--r--test/data/fill3.dxf552
-rw-r--r--test/data/mousebite.dxf344
-rw-r--r--test/data/mousebites.dxf652
-rw-r--r--test/outline.GML626
-rwxr-xr-xtest/test.py25
9 files changed, 2786 insertions, 49 deletions
diff --git a/README.md b/README.md
index d44593e..8f90efa 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
-PCB tools extension
+pcb-tools-extension
===
-PCB tools extension is a Python library to panelize gerber files.
-This library is designed based on [PCB tools](https://github.com/curtacircuitos/pcb-tools) which provides cool functionality to handle PCB such as generationg PCB image from gerber files.
+pcb-tools-extension is a Python library to panelize gerber files.
+This library is designed based on [pcb-tools](https://github.com/curtacircuitos/pcb-tools) which provides cool functionality to handle PCB such as generationg PCB image from gerber files.
-PCB tools extension adds following function to PCB tools.
+pcb-tools-extension adds following function to pcb-tools.
- Rotate PCB data
- Write back loded PCB data (original PCB tools does not work completely)
@@ -13,7 +13,7 @@ PCB tools extension adds following function to PCB tools.
Only RS-274x format and Excellon drill format data can be handled by current version of this library.
## Install
-PCB tools extension is not registerd to PyPI repository.<br>
+pcb-tools-extension is not registerd to PyPI repository.<br>
Please install from GitHub repository as below.
```shell
$ pip install git+https://github.com/opiopan/pcb-tools-extension
@@ -62,6 +62,8 @@ ctx.dump('panelized-board.txt')
```
## DXF file translation
+
+### PCB Outline
You can also load a dxf file and handle that as same as RX-274x gerber file.<br>
This function is useful to generate outline data of pnanelized PCB boad.
@@ -96,8 +98,33 @@ dxf.draw_mode = gerberex.DxfFile.DM_FILL
dxf.write('outline.gml')
```
+### Mouse bites
+
+<img alt="mouse bites" src="https://raw.githubusercontent.com/wiki/opiopan/pcb-tools-extension/images/mousebites.png" width=200 align="right">
+
+
+If ```DM_MOUSE_BITES``` is specified for ```drawing_mode```, filled circles will arranged along a DXF line object at equal intervals. <br>
+DXF file object in this state can be merged to excellon file also. That means you can arrange mouse bites easily.
+
+```python
+import gerberex
+
+ctx = gerberex.DrillComposition()
+drill = gerberex.read('drill.txt')
+ctx.merge(drill)
+
+dxf = gerberex.read('mousebites.dxf')
+dxf.draw_mode = gerberex.DxfFile.DM_MOUSE_BITES
+dxf.to_metric()
+dxf.width = 0.5
+dxf.pitch = 1
+ctx.merge(dxf)
+
+ctx.dump('merged_drill.txt')
+```
+
## Panelized board image Example
-This image is generated by original [PCB tools](https://github.com/curtacircuitos/pcb-tools) fucntion.
+This image is generated by original [pcb-tools](https://github.com/curtacircuitos/pcb-tools) fucntion.
<p align="center">
<img alt="description" src="https://raw.githubusercontent.com/wiki/opiopan/pcb-tools-extension/images/panelized.jpg" width=750>
diff --git a/gerberex/composition.py b/gerberex/composition.py
index afcaf97..73f5702 100644
--- a/gerberex/composition.py
+++ b/gerberex/composition.py
@@ -127,10 +127,13 @@ class DrillComposition(Composition):
self.header2_statements = []
self.tools = []
self.hits = []
+ self.dxf_statements = []
def merge(self, file):
if isinstance(file, gerberex.excellon.ExcellonFileEx):
self._merge_excellon(file)
+ elif isinstance(file, gerberex.DxfFile):
+ self._merge_dxf(file)
else:
raise Exception('unsupported file type')
@@ -147,6 +150,9 @@ class DrillComposition(Composition):
for h in self.hits:
if h.tool.number == t.number:
yield CoordinateStmt(*h.position).to_excellon(self.settings)
+ for num, statement in self.dxf_statements:
+ if num == t.number:
+ yield statement.to_excellon(self.settings)
yield EndOfProgramStmt().to_excellon()
with open(path, 'w') as f:
@@ -186,6 +192,22 @@ class DrillComposition(Composition):
hit.tool = tool_map[hit.tool.number]
self.hits.append(hit)
+ def _merge_dxf(self, file):
+ if not self.settings:
+ self.settings = file.settings
+ else:
+ if self.settings.units == 'metric':
+ file.to_metric()
+ else:
+ file.to_inch()
+
+ if not self.header1_statements:
+ self.header1_statements = file.header
+ self.header2_statements = file.header2
+
+ tool = self._register_tool(ExcellonTool(self.settings, number=1, diameter=file.width))
+ self.dxf_statements.append((tool.number, file.statements))
+
def _register_tool(self, tool):
for existing in self.tools:
if existing.equivalent(tool):
diff --git a/gerberex/dxf.py b/gerberex/dxf.py
index 11072b5..a2c26d0 100644
--- a/gerberex/dxf.py
+++ b/gerberex/dxf.py
@@ -8,13 +8,18 @@ from math import pi, cos, sin, tan, atan, atan2, acos, asin, sqrt
from gerber.cam import CamFile, FileSettings
from gerber.utils import inch, metric, write_gerber_value
from gerber.gerber_statements import ADParamStmt
+from gerber.excellon_statements import ExcellonTool
+from gerber.excellon_statements import CoordinateStmt
import dxfgrabber
class DxfStatement(object):
def __init__(self, entity):
self.entity = entity
- def to_gerber(self, settings=None):
+ def to_gerber(self, settings=None, pitch=0, width=0):
+ pass
+
+ def to_excellon(self, settings=None, pitch=0, width=0):
pass
def to_inch(self):
@@ -27,39 +32,77 @@ class DxfLineStatement(DxfStatement):
def __init__(self, entity):
super(DxfLineStatement, self).__init__(entity)
- def to_gerber(self, settings=FileSettings):
- x0 = self.entity.start[0]
- y0 = self.entity.start[1]
- x1 = self.entity.end[0]
- y1 = self.entity.end[1]
- return 'G01*\nX{0}Y{1}D02*\nX{2}Y{3}D01*'.format(
- write_gerber_value(x0, settings.format,
- settings.zero_suppression),
- write_gerber_value(y0, settings.format,
- settings.zero_suppression),
- write_gerber_value(x1, settings.format,
- settings.zero_suppression),
- write_gerber_value(y1, settings.format,
- settings.zero_suppression)
- )
+ def to_gerber(self, settings=FileSettings(), pitch=0, width=0):
+ if pitch == 0:
+ x0 = self.entity.start[0]
+ y0 = self.entity.start[1]
+ x1 = self.entity.end[0]
+ y1 = self.entity.end[1]
+ return 'G01*\nX{0}Y{1}D02*\nX{2}Y{3}D01*'.format(
+ write_gerber_value(x0, settings.format,
+ settings.zero_suppression),
+ write_gerber_value(y0, settings.format,
+ settings.zero_suppression),
+ write_gerber_value(x1, settings.format,
+ settings.zero_suppression),
+ write_gerber_value(y1, settings.format,
+ settings.zero_suppression)
+ )
+ else:
+ gstr = ""
+ for p in self._dots(pitch, width):
+ gstr += 'X{0}Y{1}D03*\n'.format(
+ write_gerber_value(p[0], settings.format,
+ settings.zero_suppression),
+ write_gerber_value(p[1], settings.format,
+ settings.zero_suppression))
+ return gstr
+
+ def to_excellon(self, settings=FileSettings(), pitch=0, width=0):
+ if not pitch:
+ return
+ gstr = ""
+ for p in self._dots(pitch, width):
+ gstr += CoordinateStmt(x=p[0], y=p[1]).to_excellon(settings) + '\n'
+ return gstr
def to_inch(self):
- self.entity.start[idx] = (
- inch(self.entity.start[idx][0]), inch(self.entity.start[idx][1]))
- self.entity.end[idx] = (
- inch(self.entity.end[idx][0]), inch(self.entity.end[idx][1]))
+ self.entity.start = (
+ inch(self.entity.start[0]), inch(self.entity.start[1]))
+ self.entity.end = (
+ inch(self.entity.end[0]), inch(self.entity.end[1]))
def to_metric(self):
- self.entity.start[idx] = (
- metric(self.entity.start[idx][0]), inch(self.entity.start[idx][1]))
- self.entity.end[idx] = (
- metric(self.entity.end[idx][0]), inch(self.entity.end[idx][1]))
+ self.entity.start = (
+ metric(self.entity.start[0]), inch(self.entity.start[1]))
+ self.entity.end = (
+ metric(self.entity.end[0]), inch(self.entity.end[1]))
+
+ def _dots(self, pitch, width):
+ x0 = self.entity.start[0]
+ y0 = self.entity.start[1]
+ x1 = self.entity.end[0]
+ y1 = self.entity.end[1]
+ xp = x1 - x0
+ yp = y1 - y0
+ l = sqrt(xp * xp + yp * yp)
+ xd = xp * pitch / l
+ yd = yp * pitch / l
+
+ d = 0;
+ while d < l + width / 2:
+ yield (x0, y0)
+ x0 += xd
+ y0 += yd
+ d += pitch
class DxfCircleStatement(DxfStatement):
def __init__(self, entity):
super(DxfCircleStatement, self).__init__(entity)
- def to_gerber(self, settings=FileSettings):
+ def to_gerber(self, settings=FileSettings(), pitch=0, width=0):
+ if pitch:
+ return
r = self.entity.radius
x0 = self.entity.center[0]
y0 = self.entity.center[1]
@@ -82,19 +125,21 @@ class DxfCircleStatement(DxfStatement):
def to_inch(self):
self.entity.radius = inch(self.entity.radius)
- self.entity.center[idx] = (
- inch(self.entity.center[idx][0]), inch(self.entity.center[idx][1]))
+ self.entity.center = (
+ inch(self.entity.center[0]), inch(self.entity.center[1]))
def to_metric(self):
self.entity.radius = metric(self.entity.radius)
- self.entity.center[idx] = (
- metric(self.entity.center[idx][0]), metric(self.entity.center[idx][1]))
+ self.entity.center = (
+ metric(self.entity.center[0]), metric(self.entity.center[1]))
class DxfArcStatement(DxfStatement):
def __init__(self, entity):
super(DxfArcStatement, self).__init__(entity)
- def to_gerber(self, settings=FileSettings):
+ def to_gerber(self, settings=FileSettings(), pitch=0, width=0):
+ if pitch:
+ return
deg0 = self.entity.start_angle
deg1 = self.entity.end_angle
r = self.entity.radius
@@ -126,21 +171,23 @@ class DxfArcStatement(DxfStatement):
self.entity.start_angle = inch(self.entity.start_angle)
self.entity.end_angle = inch(self.entity.end_angle)
self.entity.radius = inch(self.entity.radius)
- self.entity.center[idx] = (
- inch(self.entity.center[idx][0]), inch(self.entity.center[idx][1]))
+ self.entity.center = (
+ inch(self.entity.center[0]), inch(self.entity.center[1]))
def to_metric(self):
self.entity.start_angle = metric(self.entity.start_angle)
self.entity.end_angle = metric(self.entity.end_angle)
self.entity.radius = metric(self.entity.radius)
- self.entity.center[idx] = (
- metric(self.entity.center[idx][0]), metric(self.entity.center[idx][1]))
+ self.entity.center = (
+ metric(self.entity.center[0]), metric(self.entity.center[1]))
class DxfPolylineStatement(DxfStatement):
def __init__(self, entity):
super(DxfPolylineStatement, self).__init__(entity)
- def to_gerber(self, settings=FileSettings()):
+ def to_gerber(self, settings=FileSettings(), pitch=0, width=0):
+ if pitch:
+ return
x0 = self.entity.points[0][0]
y0 = self.entity.points[0][1]
b = self.entity.bulge[0]
@@ -214,6 +261,8 @@ class DxfStatements(object):
self._units = units
self.dcode = dcode
self.draw_mode = draw_mode
+ self.pitch = inch(1) if self._units == 'unit' else 1
+ self.width = 0
self.statements = []
for entity in entities:
if entity.dxftype == 'LWPOLYLINE':
@@ -241,19 +290,33 @@ class DxfStatements(object):
yield 'G37*'
else:
for statement in self.statements:
- yield statement.to_gerber(settings)
+ yield statement.to_gerber(
+ settings,
+ pitch=self.pitch if self.draw_mode == DxfFile.DM_MOUSE_BITES else 0,
+ width=self.width)
return '\n'.join(gerbers())
+ def to_excellon(self, settings=FileSettings()):
+ if not self.draw_mode == DxfFile.DM_MOUSE_BITES:
+ return
+ def drills():
+ for statement in self.statements:
+ if isinstance(statement, DxfLineStatement):
+ yield statement.to_excellon(settings, pitch=self.pitch, width=self.width)
+ return '\n'.join(drills())
+
def to_inch(self):
if self._units == 'metric':
self._units = 'inch'
+ self.pitch = inch(self.pitch)
for statement in self.statements:
statement.to_inch()
def to_metric(self):
if self._units == 'inch':
self._units = 'metric'
+ self.pitch = metric(self.pitch)
for statement in self.statements:
statement.to_metric()
@@ -270,6 +333,32 @@ class DxfHeaderStatement(object):
settings.format[0], settings.format[1],
settings.format[0], settings.format[1]
)
+
+ def to_excellon(self, settings):
+ return 'M48\n'\
+ 'FMAT,2\n'\
+ 'ICI,{0}\n'\
+ '{1},{2},{3}.{4}\n'\
+ '{5}'.format(
+ 'ON' if settings.notation == 'incremental' else 'OFF',
+ 'INCH' if settings.units == 'inch' else 'METRIC',
+ 'TZ' if settings.zero_suppression == 'leading' else 'LZ',
+ '0' * settings.format[0], '0' * settings.format[1],
+ 'M72' if settings.units == 'inch' else 'M71'
+ )
+
+ def to_inch(self):
+ pass
+
+ def to_metric(self):
+ pass
+
+class DxfHeader2Statement(object):
+ def to_gerber(self, settings):
+ pass
+
+ def to_excellon(self, settings):
+ return '%'
def to_inch(self):
pass
@@ -280,8 +369,15 @@ class DxfHeaderStatement(object):
class DxfFile(CamFile):
DM_LINE = 0
DM_FILL = 1
+ DM_MOUSE_BITES = 2
+
+ FT_RX274X = 0
+ FT_EXCELLON = 1
+
+ def __init__(self, dxf, settings=None, draw_mode=None, filename=None):
+ if not settings:
+ settings = FileSettings(zero_suppression='leading')
- def __init__(self, dxf, settings=FileSettings(), draw_mode=None, filename=None):
if draw_mode == None:
draw_mode = self.DM_LINE
if dxf.header['$INSUNITS'] == 1:
@@ -294,6 +390,8 @@ class DxfFile(CamFile):
super(DxfFile, self).__init__(settings=settings, filename=filename)
self._draw_mode = draw_mode
self.header = DxfHeaderStatement()
+
+ self.header2 = DxfHeader2Statement()
self.aperture = ADParamStmt.circle(dcode=10, diameter=0.0)
self.statements = DxfStatements(dxf.entities, self.units, dcode=self.aperture.d, draw_mode=self.draw_mode)
@@ -313,6 +411,7 @@ class DxfFile(CamFile):
@width.setter
def width(self, value):
self.aperture.modifiers = ([float(value),],)
+ self.statements.width = value
@property
def draw_mode(self):
@@ -322,23 +421,42 @@ class DxfFile(CamFile):
def draw_mode(self, value):
self._draw_mode = value
self.statements.draw_mode = value
+
+ @property
+ def pitch(self):
+ return self.statements.pitch
- def write(self, filename=None):
+ @pitch.setter
+ def pitch(self, value):
+ self.statements.pitch = value
+
+ def write(self, filename=None, filetype=FT_RX274X):
if self.settings.notation != 'absolute':
raise Exception('DXF file\'s notation must be absolute ')
-
+
filename = filename if filename is not None else self.filename
with open(filename, 'w') as f:
- f.write(self.header.to_gerber(self.settings) + '\n')
- f.write(self.aperture.to_gerber(self.settings) + '\n')
- f.write(self.statements.to_gerber(self.settings) + '\n')
- f.write('M02*\n')
+ if filetype == self.FT_RX274X:
+ f.write(self.header.to_gerber(self.settings) + '\n')
+ f.write(self.aperture.to_gerber(self.settings) + '\n')
+ f.write(self.statements.to_gerber(self.settings) + '\n')
+ f.write('M02*\n')
+ else:
+ tool = ExcellonTool(self.settings, number=1, diameter=self.width)
+ f.write(self.header.to_excellon(self.settings) + '\n')
+ f.write(tool.to_excellon(self.settings) + '\n')
+ f.write(self.header2.to_excellon(self.settings) + '\n')
+ f.write('T01\n')
+ f.write(self.statements.to_excellon(self.settings) + '\n')
+ f.write('M30\n')
+
def to_inch(self):
if self.units == 'metric':
self.header.to_inch()
self.aperture.to_inch()
self.statements.to_inch()
+ self.pitch = inch(self.pitch)
self.units = 'inch'
def to_metric(self):
@@ -346,6 +464,7 @@ class DxfFile(CamFile):
self.header.to_metric()
self.aperture.to_metric()
self.statements.to_metric()
+ self.pitch = metric(self.pitch)
self.units = 'metric'
def offset(self, ofset_x, offset_y):
diff --git a/test/data/base.txt b/test/data/base.txt
new file mode 100644
index 0000000..726db40
--- /dev/null
+++ b/test/data/base.txt
@@ -0,0 +1,370 @@
+M48
+;GenerationSoftware,Autodesk,EAGLE,9.3.0*%
+;CreationDate,2019-03-17T12:32:03Z*%
+FMAT,2
+ICI,OFF
+METRIC,TZ,000.000
+T01C0.350
+T02C0.850
+T03C1.000
+T04C1.150
+T05C2.750
+T06C0.508
+T07C0.800
+T08C0.930
+T09C1.200
+T10C0.400
+T11C0.500
+%
+G90
+M71
+T01
+X40640Y8509
+X39243Y8509
+X35306Y8509
+X36576Y8509
+X37846Y8509
+X15748Y12700
+X17018Y12192
+X17018Y13462
+X19558Y12192
+X19558Y13462
+X18288Y12700
+X22098Y12192
+X22098Y13462
+X20828Y12700
+X23368Y12700
+X29718Y7620
+X29083Y8890
+X28448Y7620
+X27178Y7620
+X27813Y8890
+X28448Y10160
+X27178Y10160
+X27178Y11430
+X42164Y8509
+X34036Y8509
+X40640Y31009
+X39243Y31009
+X35306Y31009
+X36576Y31009
+X37846Y31009
+X15748Y35200
+X17018Y34692
+X17018Y35962
+X19558Y34692
+X19558Y35962
+X18288Y35200
+X22098Y34692
+X22098Y35962
+X20828Y35200
+X23368Y35200
+X29718Y30120
+X29083Y31390
+X28448Y30120
+X27178Y30120
+X27813Y31390
+X28448Y32660
+X27178Y32660
+X27178Y33930
+X42164Y31009
+X34036Y31009
+X4191Y75113
+X11557Y75494
+X7747Y77018
+X7747Y69017
+X4064Y87178
+X7112Y87178
+X8890Y86035
+X1651Y84892
+X6096Y82479
+X12700Y78796
+X13335Y78161
+X7747Y82225
+X7112Y83907
+X14450Y94250
+X24191Y75113
+X31557Y75494
+X27747Y77018
+X27747Y69017
+X24064Y87178
+X27112Y87178
+X28890Y86035
+X21651Y84892
+X26096Y82479
+X32700Y78796
+X33335Y78161
+X27747Y82225
+X27112Y83907
+X34450Y94250
+X44191Y75113
+X51557Y75494
+X47747Y77018
+X47747Y69017
+X44064Y87178
+X47112Y87178
+X48890Y86035
+X41651Y84892
+X46096Y82479
+X52700Y78796
+X53335Y78161
+X47747Y82225
+X47112Y83907
+X54450Y94250
+X85979Y27680
+X91725Y27275
+X88194Y31940
+X93028Y30858
+X87811Y26069
+X83184Y31830
+X69618Y77521
+X65808Y80442
+X65173Y79680
+X68602Y93904
+X69618Y93904
+X70761Y77394
+X69999Y76505
+X91618Y77521
+X87808Y80442
+X87173Y79680
+X90602Y93904
+X91618Y93904
+X92761Y77394
+X91999Y76505
+T02
+X11095Y5667
+X11095Y8207
+X11095Y10747
+X11095Y28167
+X11095Y30707
+X11095Y33247
+T03
+X56630Y18070
+X15990Y15530
+X56630Y15530
+X54090Y18070
+X54090Y15530
+X51550Y18070
+X51550Y15530
+X49010Y18070
+X49010Y15530
+X46470Y18070
+X46470Y15530
+X43930Y18070
+X43930Y15530
+X41390Y18070
+X41390Y15530
+X38850Y18070
+X38850Y15530
+X36310Y18070
+X36310Y15530
+X33770Y18070
+X33770Y15530
+X31230Y18070
+X31230Y15530
+X26150Y15530
+X23610Y18070
+X23610Y15530
+X28690Y18070
+X13450Y15530
+X8370Y15530
+X8370Y18070
+X10910Y15530
+X10910Y18070
+X28690Y15530
+X13450Y18070
+X26150Y18070
+X15990Y18070
+X18530Y15530
+X18530Y18070
+X21070Y15530
+X21070Y18070
+X56630Y40570
+X15990Y38030
+X56630Y38030
+X54090Y40570
+X54090Y38030
+X51550Y40570
+X51550Y38030
+X49010Y40570
+X49010Y38030
+X46470Y40570
+X46470Y38030
+X43930Y40570
+X43930Y38030
+X41390Y40570
+X41390Y38030
+X38850Y40570
+X38850Y38030
+X36310Y40570
+X36310Y38030
+X33770Y40570
+X33770Y38030
+X31230Y40570
+X31230Y38030
+X26150Y38030
+X23610Y40570
+X23610Y38030
+X28690Y40570
+X13450Y38030
+X8370Y38030
+X8370Y40570
+X10910Y38030
+X10910Y40570
+X28690Y38030
+X13450Y40570
+X26150Y40570
+X15990Y40570
+X18530Y38030
+X18530Y40570
+X21070Y38030
+X21070Y40570
+X90676Y52237
+X91118Y49736
+X91559Y47234
+X92000Y44733
+X2286Y50985
+X4826Y50985
+X7366Y50985
+X9906Y50985
+X72920Y60630
+X61490Y98730
+X70380Y60630
+X67840Y60630
+X65300Y60630
+X76730Y96190
+X61490Y60630
+X76730Y60630
+X76730Y63170
+X76730Y65710
+X76730Y68250
+X76730Y70790
+X76730Y73330
+X76730Y75870
+X76730Y78410
+X76730Y80950
+X76730Y83490
+X76730Y86030
+X76730Y88570
+X76730Y91110
+X76730Y93650
+X61490Y63170
+X76730Y98730
+X61490Y65710
+X61490Y96190
+X61490Y93650
+X61490Y91110
+X61490Y88570
+X61490Y86030
+X61490Y83490
+X61490Y80950
+X61490Y78410
+X61490Y75870
+X61490Y73330
+X61490Y70790
+X61490Y68250
+X94920Y60630
+X83490Y98730
+X92380Y60630
+X89840Y60630
+X87300Y60630
+X98730Y96190
+X83490Y60630
+X98730Y60630
+X98730Y63170
+X98730Y65710
+X98730Y68250
+X98730Y70790
+X98730Y73330
+X98730Y75870
+X98730Y78410
+X98730Y80950
+X98730Y83490
+X98730Y86030
+X98730Y88570
+X98730Y91110
+X98730Y93650
+X83490Y63170
+X98730Y98730
+X83490Y65710
+X83490Y96190
+X83490Y93650
+X83490Y91110
+X83490Y88570
+X83490Y86030
+X83490Y83490
+X83490Y80950
+X83490Y78410
+X83490Y75870
+X83490Y73330
+X83490Y70790
+X83490Y68250
+T04
+X8465Y5667
+X8465Y10747
+X8465Y28167
+X8465Y33247
+T05
+X61500Y16800
+X3500Y16800
+X61500Y39300
+X3500Y39300
+T06
+X2062Y66253
+X2062Y67503
+X2062Y68753
+X2062Y70003
+X22062Y66253
+X22062Y67503
+X22062Y68753
+X22062Y70003
+X42062Y66253
+X42062Y67503
+X42062Y68753
+X42062Y70003
+T07
+X5588Y85527
+X25588Y85527
+X45588Y85527
+X89268Y28776
+T08
+X11200Y64785
+X8700Y64785
+X6700Y64785
+X4200Y64785
+X31200Y64785
+X28700Y64785
+X26700Y64785
+X24200Y64785
+X51200Y64785
+X48700Y64785
+X46700Y64785
+X44200Y64785
+X76332Y46397
+X78794Y46831
+X80763Y47178
+X83225Y47612
+T09
+X5450Y62685
+X9950Y62685
+X25450Y62685
+X29950Y62685
+X45450Y62685
+X49950Y62685
+X81630Y49463
+X77198Y48682
+X67910Y98476
+X70310Y98476
+X89910Y98476
+X92310Y98476
+T10
+X71900Y43897
+X80287Y38025
+X83906Y37245
+T11
+X85936Y30123
+X66570Y88570
+X65173Y68377
+X88570Y88570
+X87173Y68377
+M30
diff --git a/test/data/fill3.dxf b/test/data/fill3.dxf
new file mode 100644
index 0000000..8e40c5a
--- /dev/null
+++ b/test/data/fill3.dxf
@@ -0,0 +1,552 @@
+0
+SECTION
+2
+HEADER
+9
+$INSUNITS
+70
+4
+9
+$ACADVER
+1
+AC1014
+9
+$HANDSEED
+5
+FFFF
+0
+ENDSEC
+0
+SECTION
+2
+TABLES
+0
+TABLE
+2
+VPORT
+5
+8
+100
+AcDbSymbolTable
+0
+ENDTAB
+0
+TABLE
+2
+LTYPE
+5
+5
+100
+AcDbSymbolTable
+0
+LTYPE
+5
+14
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+2
+BYBLOCK
+70
+0
+0
+LTYPE
+5
+15
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+2
+BYLAYER
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+LAYER
+5
+2
+100
+AcDbSymbolTable
+70
+2
+0
+LAYER
+5
+50
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+0
+70
+0
+6
+CONTINUOUS
+0
+ENDTAB
+0
+TABLE
+2
+STYLE
+5
+3
+100
+AcDbSymbolTable
+70
+1
+0
+STYLE
+5
+11
+100
+AcDbSymbolTableRecord
+100
+AcDbTextStyleTableRecord
+2
+STANDARD
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+VIEW
+5
+6
+100
+AcDbSymbolTable
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+UCS
+5
+7
+100
+AcDbSymbolTable
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+APPID
+5
+9
+100
+AcDbSymbolTable
+70
+2
+0
+APPID
+5
+12
+100
+AcDbSymbolTableRecord
+100
+AcDbRegAppTableRecord
+2
+ACAD
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+DIMSTYLE
+5
+A
+100
+AcDbSymbolTable
+70
+1
+0
+ENDTAB
+0
+TABLE
+2
+BLOCK_RECORD
+5
+1
+100
+AcDbSymbolTable
+70
+1
+0
+BLOCK_RECORD
+5
+1F
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+2
+*MODEL_SPACE
+0
+BLOCK_RECORD
+5
+1B
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+2
+*PAPER_SPACE
+0
+ENDTAB
+0
+ENDSEC
+0
+SECTION
+2
+BLOCKS
+0
+BLOCK
+5
+20
+100
+AcDbEntity
+100
+AcDbBlockBegin
+2
+*MODEL_SPACE
+0
+ENDBLK
+5
+21
+100
+AcDbEntity
+100
+AcDbBlockEnd
+0
+BLOCK
+5
+1C
+100
+AcDbEntity
+100
+AcDbBlockBegin
+2
+*PAPER_SPACE
+0
+ENDBLK
+5
+1D
+100
+AcDbEntity
+100
+AcDbBlockEnd
+0
+ENDSEC
+0
+SECTION
+2
+ENTITIES
+0
+ARC
+5
+100
+100
+AcDbEntity
+8
+0
+100
+AcDbCircle
+10
+73.465697644393032
+20
+48.416408867916559
+30
+0
+40
+0.5999999999999982
+100
+AcDbArc
+50
+-169.99999999999946
+51
+10.000000000000746
+0
+ARC
+5
+101
+100
+AcDbEntity
+8
+0
+100
+AcDbCircle
+10
+84.987948354635876
+20
+50.448092546619641
+30
+0
+40
+0.5999999999999982
+100
+AcDbArc
+50
+-169.99999999999923
+51
+10.000000000000965
+0
+ARC
+5
+102
+100
+AcDbEntity
+8
+0
+100
+AcDbCircle
+10
+73.239955013426027
+20
+49.696658946832429
+30
+0
+40
+0.59999999999999665
+100
+AcDbArc
+50
+10.000000000000087
+51
+190.00000000000051
+0
+ARC
+5
+103
+100
+AcDbEntity
+8
+0
+100
+AcDbCircle
+10
+3.0000000000000004
+20
+3.0000000000000004
+30
+0
+40
+3.0000000000000004
+100
+AcDbArc
+50
+180
+51
+270
+0
+LINE
+5
+104
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+72.874812992585703
+20
+48.312219961316394
+30
+0
+11
+72.649070361618698
+21
+49.592470040232264
+31
+0
+0
+ARC
+5
+105
+100
+AcDbEntity
+8
+0
+100
+AcDbCircle
+10
+84.76220572366887
+20
+51.728342625535511
+30
+0
+40
+0.59999999999996168
+100
+AcDbArc
+50
+10.000000000000307
+51
+190.00000000000006
+0
+LINE
+5
+106
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+85.353090375476171
+20
+51.832531532135668
+30
+0
+11
+85.578833006443176
+21
+50.552281453219805
+31
+0
+0
+LINE
+5
+107
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+0
+20
+0
+30
+0
+11
+0
+21
+3.0000000000000009
+31
+0
+0
+LINE
+5
+108
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+73.830839665233341
+20
+49.800847853432586
+30
+0
+11
+74.056582296200347
+21
+48.520597774516723
+31
+0
+0
+LINE
+5
+109
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+3.0000000000000004
+20
+0
+30
+0
+11
+0
+21
+0
+31
+0
+0
+LINE
+5
+110
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+84.397063702828561
+20
+50.343903640019477
+30
+0
+11
+84.171321071861513
+21
+51.624153718935347
+31
+0
+0
+ENDSEC
+0
+SECTION
+2
+OBJECTS
+0
+DICTIONARY
+5
+C
+100
+AcDbDictionary
+3
+ACAD_GROUP
+350
+D
+3
+ACAD_MLINESTYLE
+350
+17
+0
+DICTIONARY
+5
+D
+100
+AcDbDictionary
+0
+DICTIONARY
+5
+1A
+330
+C
+100
+AcDbDictionary
+0
+DICTIONARY
+5
+17
+100
+AcDbDictionary
+0
+ENDSEC
+0
+EOF
diff --git a/test/data/mousebite.dxf b/test/data/mousebite.dxf
new file mode 100644
index 0000000..f68a1d5
--- /dev/null
+++ b/test/data/mousebite.dxf
@@ -0,0 +1,344 @@
+0
+SECTION
+2
+HEADER
+9
+$INSUNITS
+70
+4
+9
+$ACADVER
+1
+AC1014
+9
+$HANDSEED
+5
+FFFF
+0
+ENDSEC
+0
+SECTION
+2
+TABLES
+0
+TABLE
+2
+VPORT
+5
+8
+100
+AcDbSymbolTable
+0
+ENDTAB
+0
+TABLE
+2
+LTYPE
+5
+5
+100
+AcDbSymbolTable
+0
+LTYPE
+5
+14
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+2
+BYBLOCK
+70
+0
+0
+LTYPE
+5
+15
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+2
+BYLAYER
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+LAYER
+5
+2
+100
+AcDbSymbolTable
+70
+2
+0
+LAYER
+5
+50
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+0
+70
+0
+6
+CONTINUOUS
+0
+ENDTAB
+0
+TABLE
+2
+STYLE
+5
+3
+100
+AcDbSymbolTable
+70
+1
+0
+STYLE
+5
+11
+100
+AcDbSymbolTableRecord
+100
+AcDbTextStyleTableRecord
+2
+STANDARD
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+VIEW
+5
+6
+100
+AcDbSymbolTable
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+UCS
+5
+7
+100
+AcDbSymbolTable
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+APPID
+5
+9
+100
+AcDbSymbolTable
+70
+2
+0
+APPID
+5
+12
+100
+AcDbSymbolTableRecord
+100
+AcDbRegAppTableRecord
+2
+ACAD
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+DIMSTYLE
+5
+A
+100
+AcDbSymbolTable
+70
+1
+0
+ENDTAB
+0
+TABLE
+2
+BLOCK_RECORD
+5
+1
+100
+AcDbSymbolTable
+70
+1
+0
+BLOCK_RECORD
+5
+1F
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+2
+*MODEL_SPACE
+0
+BLOCK_RECORD
+5
+1B
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+2
+*PAPER_SPACE
+0
+ENDTAB
+0
+ENDSEC
+0
+SECTION
+2
+BLOCKS
+0
+BLOCK
+5
+20
+100
+AcDbEntity
+100
+AcDbBlockBegin
+2
+*MODEL_SPACE
+0
+ENDBLK
+5
+21
+100
+AcDbEntity
+100
+AcDbBlockEnd
+0
+BLOCK
+5
+1C
+100
+AcDbEntity
+100
+AcDbBlockBegin
+2
+*PAPER_SPACE
+0
+ENDBLK
+5
+1D
+100
+AcDbEntity
+100
+AcDbBlockEnd
+0
+ENDSEC
+0
+SECTION
+2
+ENTITIES
+0
+LINE
+5
+100
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+0
+20
+0
+30
+0
+11
+10
+21
+0
+31
+0
+0
+LINE
+5
+101
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+0
+20
+5.0798803480694392
+30
+0
+11
+6.9518511056354715
+21
+12.268187951746041
+31
+0
+0
+ENDSEC
+0
+SECTION
+2
+OBJECTS
+0
+DICTIONARY
+5
+C
+100
+AcDbDictionary
+3
+ACAD_GROUP
+350
+D
+3
+ACAD_MLINESTYLE
+350
+17
+0
+DICTIONARY
+5
+D
+100
+AcDbDictionary
+0
+DICTIONARY
+5
+1A
+330
+C
+100
+AcDbDictionary
+0
+DICTIONARY
+5
+17
+100
+AcDbDictionary
+0
+ENDSEC
+0
+EOF
diff --git a/test/data/mousebites.dxf b/test/data/mousebites.dxf
new file mode 100644
index 0000000..7fb69b8
--- /dev/null
+++ b/test/data/mousebites.dxf
@@ -0,0 +1,652 @@
+0
+SECTION
+2
+HEADER
+9
+$INSUNITS
+70
+4
+9
+$ACADVER
+1
+AC1014
+9
+$HANDSEED
+5
+FFFF
+0
+ENDSEC
+0
+SECTION
+2
+TABLES
+0
+TABLE
+2
+VPORT
+5
+8
+100
+AcDbSymbolTable
+0
+ENDTAB
+0
+TABLE
+2
+LTYPE
+5
+5
+100
+AcDbSymbolTable
+0
+LTYPE
+5
+14
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+2
+BYBLOCK
+70
+0
+0
+LTYPE
+5
+15
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+2
+BYLAYER
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+LAYER
+5
+2
+100
+AcDbSymbolTable
+70
+2
+0
+LAYER
+5
+50
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+0
+70
+0
+6
+CONTINUOUS
+0
+ENDTAB
+0
+TABLE
+2
+STYLE
+5
+3
+100
+AcDbSymbolTable
+70
+1
+0
+STYLE
+5
+11
+100
+AcDbSymbolTableRecord
+100
+AcDbTextStyleTableRecord
+2
+STANDARD
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+VIEW
+5
+6
+100
+AcDbSymbolTable
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+UCS
+5
+7
+100
+AcDbSymbolTable
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+APPID
+5
+9
+100
+AcDbSymbolTable
+70
+2
+0
+APPID
+5
+12
+100
+AcDbSymbolTableRecord
+100
+AcDbRegAppTableRecord
+2
+ACAD
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+DIMSTYLE
+5
+A
+100
+AcDbSymbolTable
+70
+1
+0
+ENDTAB
+0
+TABLE
+2
+BLOCK_RECORD
+5
+1
+100
+AcDbSymbolTable
+70
+1
+0
+BLOCK_RECORD
+5
+1F
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+2
+*MODEL_SPACE
+0
+BLOCK_RECORD
+5
+1B
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+2
+*PAPER_SPACE
+0
+ENDTAB
+0
+ENDSEC
+0
+SECTION
+2
+BLOCKS
+0
+BLOCK
+5
+20
+100
+AcDbEntity
+100
+AcDbBlockBegin
+2
+*MODEL_SPACE
+0
+ENDBLK
+5
+21
+100
+AcDbEntity
+100
+AcDbBlockEnd
+0
+BLOCK
+5
+1C
+100
+AcDbEntity
+100
+AcDbBlockBegin
+2
+*PAPER_SPACE
+0
+ENDBLK
+5
+1D
+100
+AcDbEntity
+100
+AcDbBlockEnd
+0
+ENDSEC
+0
+SECTION
+2
+ENTITIES
+0
+LINE
+5
+100
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+22.5
+20
+21.400000000000002
+30
+0
+11
+42.5
+21
+21.400000000000002
+31
+0
+0
+LINE
+5
+101
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+21
+20
+43.050000000000004
+30
+0
+11
+44
+21
+43.050000000000004
+31
+0
+0
+LINE
+5
+102
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+1.9000000000000004
+20
+57.250000000000007
+30
+0
+11
+12.000000000000002
+21
+57.250000000000007
+31
+0
+0
+LINE
+5
+103
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+1.9000000000000004
+20
+59.75
+30
+0
+11
+13.5
+21
+59.75
+31
+0
+0
+LINE
+5
+104
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+22.200000000000003
+20
+59.75
+30
+0
+11
+33.199999999999996
+21
+59.75
+31
+0
+0
+LINE
+5
+105
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+41.5
+20
+59.75
+30
+0
+11
+53.200000000000003
+21
+59.75
+31
+0
+0
+LINE
+5
+106
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+81.970000000000027
+20
+88.5
+30
+0
+11
+81.970000000000013
+21
+71.5
+31
+0
+0
+LINE
+5
+107
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+78.250000000000043
+20
+88.499999999999972
+30
+0
+11
+78.250000000000028
+21
+71.5
+31
+0
+0
+LINE
+5
+108
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+59.970000000000006
+20
+88.499999999999972
+30
+0
+11
+59.970000000000013
+21
+71.5
+31
+0
+0
+LINE
+5
+109
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+55.650000000000013
+20
+88.499999999999972
+30
+0
+11
+55.650000000000006
+21
+71.5
+31
+0
+0
+LINE
+5
+110
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+39.749999999999993
+20
+88.499999999999972
+30
+0
+11
+39.75
+21
+71.5
+31
+0
+0
+LINE
+5
+111
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+35.650000000000006
+20
+88.499999999999972
+30
+0
+11
+35.650000000000006
+21
+71.5
+31
+0
+0
+LINE
+5
+112
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+19.749999999999993
+20
+88.499999999999972
+30
+0
+11
+19.749999999999996
+21
+71.5
+31
+0
+0
+LINE
+5
+113
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+15.650000000000009
+20
+88.499999999999972
+30
+0
+11
+15.650000000000004
+21
+71.5
+31
+0
+0
+LINE
+5
+114
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+65.25
+20
+16.500250410747213
+30
+0
+11
+65.25
+21
+3.5002504107472134
+31
+0
+0
+LINE
+5
+115
+100
+AcDbEntity
+8
+0
+100
+AcDbLine
+10
+79.752051115713613
+20
+9.0215259002820929
+30
+0
+11
+92.554551904872312
+21
+11.278952209952195
+31
+0
+0
+ENDSEC
+0
+SECTION
+2
+OBJECTS
+0
+DICTIONARY
+5
+C
+100
+AcDbDictionary
+3
+ACAD_GROUP
+350
+D
+3
+ACAD_MLINESTYLE
+350
+17
+0
+DICTIONARY
+5
+D
+100
+AcDbDictionary
+0
+DICTIONARY
+5
+1A
+330
+C
+100
+AcDbDictionary
+0
+DICTIONARY
+5
+17
+100
+AcDbDictionary
+0
+ENDSEC
+0
+EOF
diff --git a/test/outline.GML b/test/outline.GML
new file mode 100644
index 0000000..267119a
--- /dev/null
+++ b/test/outline.GML
@@ -0,0 +1,626 @@
+G75*
+%MOMM*%
+%OFA0B0*%
+%FSTAX34Y34*%
+%IPPOS*%
+%LPD*%
+%ADD10C,0*%
+D10*
+G36*
+G01*
+X08455Y002812D02*
+G75*
+G03*
+X08455Y002812I-0006J0D01*
+G01*
+X004875Y0168D02*
+G75*
+G03*
+X004875Y0168I-001375J0D01*
+G01*
+X02245Y062035D02*
+G75*
+G02*
+X02125Y062035I-0006J0D01*
+G01*
+X02125Y063335D01*
+G02*
+X02245Y063335I0006J0D01*
+G01*
+X02245Y062035D01*
+G01*
+X03295Y063335D02*
+G75*
+G02*
+X03415Y063335I0006J0D01*
+G01*
+X03415Y062035D01*
+G02*
+X03295Y062035I-0006J0D01*
+G01*
+X03295Y063335D01*
+G01*
+X00245Y062035D02*
+G75*
+G01*
+X00245Y063335D01*
+G03*
+X00125Y063335I-0006J0D01*
+G01*
+X00125Y062035D01*
+G03*
+X00245Y062035I0006J0D01*
+G01*
+X02605Y062685D02*
+G75*
+G03*
+X02605Y062685I-0006J0D01*
+G01*
+X03055Y062685D02*
+G75*
+G03*
+X03055Y062685I-0006J0D01*
+G01*
+X062875Y0393D02*
+G75*
+G03*
+X062875Y0393I-001375J0D01*
+G01*
+X062Y0428D02*
+G75*
+G02*
+X065Y0398I0J-003D01*
+G01*
+X065Y0357395D01*
+G03*
+X066Y0347395I001J0D01*
+G01*
+X0714Y0347395D01*
+G03*
+X0724Y0357395I0J001D01*
+G01*
+X0724Y0431D01*
+G01*
+X0835214Y0431D01*
+G03*
+X0845214Y0441I0J001D01*
+G01*
+X0845214Y046375D01*
+G03*
+X0835214Y047375I-001J0D01*
+G01*
+X0472842Y047375D01*
+G03*
+X0462842Y046375I0J-001D01*
+G01*
+X0462842Y0438D01*
+G03*
+X0472842Y0428I001J0D01*
+G01*
+X062Y0428D01*
+G01*
+X01415Y062035D02*
+G75*
+G01*
+X01415Y063335D01*
+G03*
+X01295Y063335I-0006J0D01*
+G01*
+X01295Y062035D01*
+G03*
+X01415Y062035I0006J0D01*
+G01*
+X04245Y062035D02*
+G75*
+G02*
+X04125Y062035I-0006J0D01*
+G01*
+X04125Y063335D01*
+G02*
+X04245Y063335I0006J0D01*
+G01*
+X04245Y062035D01*
+G01*
+X04Y1D02*
+G75*
+G01*
+X0554Y1D01*
+G01*
+X0554Y0916665D01*
+G03*
+X0564Y0906665I001J0D01*
+G01*
+X05922Y0906665D01*
+G03*
+X06022Y0916665I0J001D01*
+G01*
+X06022Y1D01*
+G01*
+X078Y1D01*
+G01*
+X078Y0916665D01*
+G03*
+X079Y0906665I001J0D01*
+G01*
+X08122Y0906665D01*
+G03*
+X08222Y0916665I0J001D01*
+G01*
+X08222Y1D01*
+G01*
+X1Y1D01*
+G01*
+X1Y05936D01*
+G01*
+X09391Y05936D01*
+G03*
+X09291Y05836I0J-001D01*
+G01*
+X09291Y0571655D01*
+G03*
+X09391Y0561655I001J0D01*
+G01*
+X1Y0561655D01*
+G01*
+X1Y047375D01*
+G01*
+X0873214Y047375D01*
+G03*
+X0863214Y046375I0J-001D01*
+G01*
+X0863214Y0441D01*
+G03*
+X0873214Y0431I001J0D01*
+G01*
+X1Y0431D01*
+G01*
+X1Y0D01*
+G01*
+X0724Y0D01*
+G01*
+X0724Y0082125D01*
+G03*
+X0714Y0092125I-001J0D01*
+G01*
+X066Y0092125D01*
+G03*
+X065Y0082125I0J-001D01*
+G01*
+X065Y003D01*
+G02*
+X062Y0I-003J0D01*
+G01*
+X003Y0D01*
+G02*
+X0Y003I0J003D01*
+G01*
+X0Y0173D01*
+G02*
+X003Y0203I003J0D01*
+G01*
+X0137888Y0203D01*
+G03*
+X0147888Y0213I0J001D01*
+G01*
+X0147888Y0215D01*
+G03*
+X0137888Y0225I-001J0D01*
+G01*
+X003Y0225D01*
+G02*
+X0Y0255I0J003D01*
+G01*
+X0Y0398D01*
+G02*
+X003Y0428I003J0D01*
+G01*
+X0139638Y0428D01*
+G03*
+X0149638Y0438I0J001D01*
+G01*
+X0149638Y047375D01*
+G01*
+X0149638Y0484437D01*
+G03*
+X0139638Y0494437I-001J0D01*
+G01*
+X013Y0494437D01*
+G03*
+X012Y0484437I0J-001D01*
+G01*
+X012Y044D01*
+G01*
+X0Y044D01*
+G01*
+X0Y057D01*
+G01*
+X005Y057D01*
+G03*
+X006Y058I0J001D01*
+G01*
+X006Y059D01*
+G03*
+X005Y06I-001J0D01*
+G01*
+X0Y06D01*
+G01*
+X0Y1D01*
+G01*
+X0154Y1D01*
+G01*
+X0154Y0916665D01*
+G03*
+X0164Y0906665I001J0D01*
+G01*
+X019Y0906665D01*
+G03*
+X02Y0916665I0J001D01*
+G01*
+X02Y1D01*
+G01*
+X0354Y1D01*
+G01*
+X0354Y0916665D01*
+G03*
+X0364Y0906665I001J0D01*
+G01*
+X039Y0906665D01*
+G03*
+X04Y0916665I0J001D01*
+G01*
+X04Y1D01*
+G01*
+X05295Y063335D02*
+G75*
+G01*
+X05295Y062035D01*
+G03*
+X05415Y062035I0006J0D01*
+G01*
+X05415Y063335D01*
+G03*
+X05295Y063335I-0006J0D01*
+G01*
+X09145Y003462D02*
+G75*
+G01*
+X09145Y002162D01*
+G03*
+X09265Y002162I0006J0D01*
+G01*
+X09265Y003462D01*
+G03*
+X09145Y003462I-0006J0D01*
+G01*
+X08095Y002162D02*
+G75*
+G01*
+X08095Y003462D01*
+G03*
+X07975Y003462I-0006J0D01*
+G01*
+X07975Y002162D01*
+G03*
+X08095Y002162I0006J0D01*
+G01*
+X062Y0203D02*
+G75*
+G02*
+X065Y0173I0J-003D01*
+G01*
+X065Y0120125D01*
+G03*
+X066Y0110125I001J0D01*
+G01*
+X0714Y0110125D01*
+G03*
+X0724Y0120125I0J001D01*
+G01*
+X0724Y0319395D01*
+G03*
+X0714Y0329395I-001J0D01*
+G01*
+X066Y0329395D01*
+G03*
+X065Y0319395I0J-001D01*
+G01*
+X065Y0255D01*
+G02*
+X062Y0225I-003J0D01*
+G01*
+X0472842Y0225D01*
+G03*
+X0462842Y0215I0J-001D01*
+G01*
+X0462842Y0213D01*
+G03*
+X0472842Y0203I001J0D01*
+G01*
+X062Y0203D01*
+G01*
+X00605Y062685D02*
+G75*
+G03*
+X00605Y062685I-0006J0D01*
+G01*
+X01055Y062685D02*
+G75*
+G03*
+X01055Y062685I-0006J0D01*
+G01*
+X04605Y062685D02*
+G75*
+G03*
+X04605Y062685I-0006J0D01*
+G01*
+X05055Y062685D02*
+G75*
+G03*
+X05055Y062685I-0006J0D01*
+G01*
+X062875Y0168D02*
+G75*
+G03*
+X062875Y0168I-001375J0D01*
+G01*
+X004875Y0393D02*
+G75*
+G03*
+X004875Y0393I-001375J0D01*
+G01*
+X08905Y002812D02*
+G75*
+G03*
+X08905Y002812I-0006J0D01*
+G01*
+X0165888Y0213D02*
+G75*
+G01*
+X0165888Y0215D01*
+G02*
+X0175888Y0225I001J0D01*
+G01*
+X0434842Y0225D01*
+G02*
+X0444842Y0215I0J-001D01*
+G01*
+X0444842Y0213D01*
+G02*
+X0434842Y0203I-001J0D01*
+G01*
+X0175888Y0203D01*
+G02*
+X0165888Y0213I0J001D01*
+G01*
+X0167638Y0438D02*
+G75*
+G01*
+X0167638Y046375D01*
+G02*
+X0177638Y047375I001J0D01*
+G01*
+X0434842Y047375D01*
+G02*
+X0444842Y046375I0J-001D01*
+G01*
+X0444842Y0438D01*
+G02*
+X0434842Y0428I-001J0D01*
+G01*
+X0177638Y0428D01*
+G02*
+X0167638Y0438I0J001D01*
+G01*
+X0078Y059D02*
+G75*
+G01*
+X0078Y058D01*
+G03*
+X0088Y057I001J0D01*
+G01*
+X012Y057D01*
+G01*
+X012Y0522437D01*
+G03*
+X013Y0512437I001J0D01*
+G01*
+X0139638Y0512437D01*
+G03*
+X0149638Y0522437I0J001D01*
+G01*
+X0149638Y0561655D01*
+G01*
+X025598Y0561655D01*
+G03*
+X026598Y0571655I0J001D01*
+G01*
+X026598Y059D01*
+G03*
+X025598Y06I-001J0D01*
+G01*
+X02Y06D01*
+G01*
+X02Y0659907D01*
+G03*
+X019Y0669907I-001J0D01*
+G01*
+X0164Y0669907D01*
+G03*
+X0154Y0659907I0J-001D01*
+G01*
+X0154Y06D01*
+G01*
+X0088Y06D01*
+G03*
+X0078Y059I0J-001D01*
+G01*
+X028398Y0571655D02*
+G75*
+G01*
+X028398Y059D01*
+G02*
+X029398Y06I001J0D01*
+G01*
+X0354Y06D01*
+G01*
+X0354Y0662142D01*
+G02*
+X0364Y0672142I001J0D01*
+G01*
+X039Y0672142D01*
+G02*
+X04Y0662142I0J-001D01*
+G01*
+X04Y06D01*
+G01*
+X0456478Y06D01*
+G02*
+X0466478Y059I0J-001D01*
+G01*
+X0466478Y0571655D01*
+G02*
+X0456478Y0561655I-001J0D01*
+G01*
+X029398Y0561655D01*
+G02*
+X028398Y0571655I0J001D01*
+G01*
+X0484478Y0571655D02*
+G75*
+G01*
+X0484478Y059D01*
+G02*
+X0494478Y06I001J0D01*
+G01*
+X0554Y06D01*
+G01*
+X0554Y0662142D01*
+G02*
+X0564Y0672142I001J0D01*
+G01*
+X05922Y0672142D01*
+G02*
+X06022Y0662142I0J-001D01*
+G01*
+X06022Y05936D01*
+G01*
+X0668994Y05936D01*
+G02*
+X0678994Y05836I0J-001D01*
+G01*
+X0678994Y0571655D01*
+G02*
+X0668994Y0561655I-001J0D01*
+G01*
+X0494478Y0561655D01*
+G02*
+X0484478Y0571655I0J001D01*
+G01*
+X0696994Y0571655D02*
+G75*
+G01*
+X0696994Y05836D01*
+G02*
+X0706994Y05936I001J0D01*
+G01*
+X078Y05936D01*
+G01*
+X078Y0662142D01*
+G02*
+X079Y0672142I001J0D01*
+G01*
+X08122Y0672142D01*
+G02*
+X08222Y0662142I0J-001D01*
+G01*
+X08222Y05936D01*
+G01*
+X09011Y05936D01*
+G02*
+X09111Y05836I0J-001D01*
+G01*
+X09111Y0571655D01*
+G02*
+X09011Y0561655I-001J0D01*
+G01*
+X0706994Y0561655D01*
+G02*
+X0696994Y0571655I0J001D01*
+G01*
+X0164Y0690142D02*
+G75*
+G01*
+X019Y0690142D01*
+G03*
+X02Y0700142I0J001D01*
+G01*
+X02Y0878665D01*
+G03*
+X019Y0888665I-001J0D01*
+G01*
+X0164Y0888665D01*
+G03*
+X0154Y0878665I0J-001D01*
+G01*
+X0154Y0700142D01*
+G03*
+X0164Y0690142I001J0D01*
+G01*
+X0364Y0888665D02*
+G75*
+G01*
+X039Y0888665D01*
+G02*
+X04Y0878665I0J-001D01*
+G01*
+X04Y0700142D01*
+G02*
+X039Y0690142I-001J0D01*
+G01*
+X0364Y0690142D01*
+G02*
+X0354Y0700142I0J001D01*
+G01*
+X0354Y0878665D01*
+G02*
+X0364Y0888665I001J0D01*
+G01*
+X0564Y0888665D02*
+G75*
+G01*
+X05922Y0888665D01*
+G02*
+X06022Y0878665I0J-001D01*
+G01*
+X06022Y0700142D01*
+G02*
+X05922Y0690142I-001J0D01*
+G01*
+X0564Y0690142D01*
+G02*
+X0554Y0700142I0J001D01*
+G01*
+X0554Y0878665D01*
+G02*
+X0564Y0888665I001J0D01*
+G01*
+X079Y0888665D02*
+G75*
+G01*
+X08122Y0888665D01*
+G02*
+X08222Y0878665I0J-001D01*
+G01*
+X08222Y0700142D01*
+G02*
+X08122Y0690142I-001J0D01*
+G01*
+X079Y0690142D01*
+G02*
+X078Y0700142I0J001D01*
+G01*
+X078Y0878665D01*
+G02*
+X079Y0888665I001J0D01*
+G37*
+M02*
diff --git a/test/test.py b/test/test.py
index ced478b..98da22f 100755
--- a/test/test.py
+++ b/test/test.py
@@ -49,6 +49,31 @@ os.chdir(os.path.dirname(__file__))
#merge2()
+ctx = gerberex.DrillComposition()
+base = gerberex.read('data/base.txt')
+dxf = gerberex.read('data/mousebites.dxf')
+dxf.draw_mode = DxfFile.DM_MOUSE_BITES
+dxf.to_metric()
+dxf.width = 0.5
+ctx.merge(base)
+ctx.merge(dxf)
+ctx.dump('outputs/merged.txt')
+
+dxf = gerberex.read('data/mousebite.dxf')
+dxf.zero_suppression = 'leading'
+dxf.write('outputs/a.gtl')
+dxf.draw_mode = DxfFile.DM_MOUSE_BITES
+dxf.width = 0.5
+dxf.write('outputs/b.gml')
+dxf.format = (3,3)
+dxf.write('outputs/b.txt', filetype=DxfFile.FT_EXCELLON)
+top = gerber.load_layer('outputs/a.gtl')
+drill = gerber.load_layer('outputs/b.txt')
+ctx = GerberCairoContext(scale=50)
+ctx.render_layer(top)
+ctx.render_layer(drill)
+ctx.dump('outputs/b.png')
+
file = gerberex.read('data/test.GTL')
file.rotate(45)
file.write('outputs/test_changed.GTL')