diff --git a/README.md b/README.md
index 8deccd0..6f84cb7 100644
--- a/README.md
+++ b/README.md
@@ -70,19 +70,21 @@ 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.
+You can also load a dxf file and handle that as same as RX-274x gerber file or Excellon NC file.
This function is useful to generate outline data of pnanelized PCB boad.
```python
import gerberex
-ctx = gerberex.GerberComposition()
dxf = gerberex.read('outline.dxf')
-ctx.merge(dxf)
+ctx1 = gerberex.GerberComposition()
+ctx1.merge(dxf)
+ctx2 = gerberex.DrillComposition()
+ctx2.merge(dxf)
```
Circle object, Arc object, Line object and Polyline object are supported. Other kind of objects in DXF file are ignored when translating to gerber data.
-You can specify line width (default 0). PCB tools extension will translate DXF primitive shape to RX-274x line or arc sequense using circle aperture with diamater as same as specified line width.
+You can specify line width (default 0). PCB tools extension will translate DXF primitive shape to RX-274x line or arc sequense using circle aperture with diamater as same as specified line width.
```python
import gerberex
@@ -93,6 +95,19 @@ dxf.width = 0.004
dxf.write('outline.gml')
```
+If ```FT_EXCELLON``` is specified for ```filetype``` argument of ```write()```, Excellon NC data is generated. In this case, Excellon file consists of routing commands using a specified width drill.
+
+
+```python
+import gerberex
+
+dxf = gerberex.read('outline.dxf')
+dxf.to_metric()
+dxf.width = 0.3
+dxf.write('outline.txt', filetype=dxf.FT_EXCELLON)
+```
+
+
You can also translate DXF closed shape such as circle to RX-274x polygon fill sequence.
In order to fill closed shape, ```DM_FILL``` has to be set to ```draw_mode``` property. In this mode, All object except closed shapes listed below are ignored.
@@ -100,6 +115,8 @@ In order to fill closed shape, ```DM_FILL``` has to be set to ```draw_mode``` pr
- closed polyline
- closed path which consists of lines and arcs
+NOTE: ```DM_FILL``` can be used only to generate RX-274x data, it cannot be used to generate Excellon data.
+
```python
import gerberex
@@ -122,7 +139,7 @@ outline.write('outline.gml')
-If ```DM_MOUSE_BITES``` is specified for ```draw_mode```, filled circles are arranged along a DXF line object at equal intervals.
+If ```DM_MOUSE_BITES``` is specified for ```draw_mode```, filled circles are arranged at equal intervals along a paths consisted of DXF line, arc, circle, and plyline objects.
DXF file object in this state can be merged to excellon file also. That means you can arrange mouse bites easily.
```python
diff --git a/gerberex/dxf.py b/gerberex/dxf.py
index ae543ae..8f0b984 100644
--- a/gerberex/dxf.py
+++ b/gerberex/dxf.py
@@ -12,7 +12,7 @@ from gerber.gerber_statements import ADParamStmt
from gerber.excellon_statements import ExcellonTool
from gerber.excellon_statements import CoordinateStmt
from gerberex.utility import is_equal_point, is_equal_value
-from gerberex.dxf_path import generate_closed_paths
+from gerberex.dxf_path import generate_paths
from gerberex.excellon import write_excellon_header
from gerberex.rs274x import write_gerber_header
@@ -25,12 +25,6 @@ class DxfStatement(object):
self.end = None
self.is_closed = False
- 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):
pass
@@ -61,38 +55,6 @@ class DxfLineStatement(DxfStatement):
super(DxfLineStatement, self).__init__(entity)
self.start = start
self.end = end
-
- def to_gerber(self, settings=FileSettings(), pitch=0, width=0):
- if pitch == 0:
- x0, y0 = self.start
- x1, y1 = self.end
- 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.start = (
@@ -119,7 +81,7 @@ class DxfLineStatement(DxfStatement):
self.start = self.end
self.end = pt
- def _dots(self, pitch, width):
+ def dots(self, pitch, width, offset=0):
x0, y0 = self.start
x1, y1 = self.end
y1 = self.end[1]
@@ -128,13 +90,18 @@ class DxfLineStatement(DxfStatement):
l = sqrt(xp * xp + yp * yp)
xd = xp * pitch / l
yd = yp * pitch / l
+ x0 += xp * offset / l
+ y0 += yp * offset / l
- d = 0;
- while d < l + width / 2:
- yield (x0, y0)
- x0 += xd
- y0 += yd
- d += pitch
+ if offset > l + width / 2:
+ return (None, offset - l)
+ else:
+ d = offset;
+ while d < l + width / 2:
+ yield ((x0, y0), d - l)
+ x0 += xd
+ y0 += yd
+ d += pitch
def offset(self, offset_x, offset_y):
self.start = (self.start[0] + offset_x, self.start[1] + offset_y)
@@ -144,104 +111,34 @@ class DxfLineStatement(DxfStatement):
self.start = rotate_point(self.start, angle, center)
self.end = rotate_point(self.end, angle, center)
-class DxfCircleStatement(DxfStatement):
- def __init__(self, entity):
- super(DxfCircleStatement, self).__init__(entity)
- self.radius = self.entity.radius
- self.center = (self.entity.center[0], self.entity.center[1])
- self.start = (self.center[0] + self.radius, self.center[1])
- self.end = self.start
- self.is_closed = True
-
- def to_gerber(self, settings=FileSettings(), pitch=0, width=0):
- if pitch:
- return
- r = self.radius
- x0, y0 = self.center
- return 'G01*\nX{0}Y{1}D02*\n' \
- 'G75*\nG03*\nX{2}Y{3}I{4}J{5}D01*'.format(
- write_gerber_value(x0 + r, settings.format,
- settings.zero_suppression),
- write_gerber_value(y0, settings.format,
- settings.zero_suppression),
-
- write_gerber_value(x0 + r, settings.format,
- settings.zero_suppression),
- write_gerber_value(y0, settings.format,
- settings.zero_suppression),
- write_gerber_value(-r, settings.format,
- settings.zero_suppression),
- write_gerber_value(0, settings.format,
- settings.zero_suppression)
- )
-
- def to_inch(self):
- self.radius = inch(self.radius)
- self.center = (
- inch(self.center[0]), inch(self.center[1]))
-
- def to_metric(self):
- self.radius = metric(self.radius)
- self.center = (
- metric(self.center[0]), metric(self.center[1]))
-
- def is_equal_to(self, target, error_range=0):
- if not isinstance(target, DxfCircleStatement):
- return False
- return is_equal_point(self.center, target.enter, error_range) and \
- is_equal_value(self.radius, target.radius)
-
- def reverse(self):
- pass
-
- def offset(self, offset_x, offset_y):
- self.center = (self.center[0] + offset_x, self.center[1] + offset_y)
-
- def rotate(self, angle, center=(0, 0)):
- self.center = rotate_point(self.center, angle, center)
-
class DxfArcStatement(DxfStatement):
def __init__(self, entity):
super(DxfArcStatement, self).__init__(entity)
- self.start_angle = self.entity.start_angle
- self.end_angle = self.entity.end_angle
- self.radius = self.entity.radius
- self.center = (self.entity.center[0], self.entity.center[1])
- self.start = (
- self.center[0] + self.radius * cos(self.start_angle / 180. * pi),
- self.center[1] + self.radius * sin(self.start_angle / 180. * pi),
- )
- self.end = (
- self.center[0] + self.radius * cos(self.end_angle / 180. * pi),
- self.center[1] + self.radius * sin(self.end_angle / 180. * pi),
- )
- angle = self.end_angle - self.start_angle
- self.is_closed = angle >= 360 or angle <= -360
-
- def to_gerber(self, settings=FileSettings(), pitch=0, width=0):
- if pitch:
- return
- x0 = self.center[0]
- y0 = self.center[1]
- start_x, start_y = self.start
- end_x, end_y = self.end
-
- return 'G01*\nX{0}Y{1}D02*\n' \
- 'G75*\nG{2}*\nX{3}Y{4}I{5}J{6}D01*'.format(
- write_gerber_value(start_x, settings.format,
- settings.zero_suppression),
- write_gerber_value(start_y, settings.format,
- settings.zero_suppression),
- '02' if self.start_angle > self.end_angle else '03',
- write_gerber_value(end_x, settings.format,
- settings.zero_suppression),
- write_gerber_value(end_y, settings.format,
- settings.zero_suppression),
- write_gerber_value(x0 - start_x, settings.format,
- settings.zero_suppression),
- write_gerber_value(y0 - start_y, settings.format,
- settings.zero_suppression)
- )
+ if entity.dxftype == 'CIRCLE':
+ self.radius = self.entity.radius
+ self.center = (self.entity.center[0], self.entity.center[1])
+ self.start = (self.center[0] + self.radius, self.center[1])
+ self.end = self.start
+ self.start_angle = 0
+ self.end_angle = -360
+ self.is_closed = True
+ elif entity.dxftype == 'ARC':
+ self.start_angle = self.entity.start_angle
+ self.end_angle = self.entity.end_angle
+ self.radius = self.entity.radius
+ self.center = (self.entity.center[0], self.entity.center[1])
+ self.start = (
+ self.center[0] + self.radius * cos(self.start_angle / 180. * pi),
+ self.center[1] + self.radius * sin(self.start_angle / 180. * pi),
+ )
+ self.end = (
+ self.center[0] + self.radius * cos(self.end_angle / 180. * pi),
+ self.center[1] + self.radius * sin(self.end_angle / 180. * pi),
+ )
+ angle = self.end_angle - self.start_angle
+ self.is_closed = angle >= 360 or angle <= -360
+ else:
+ raise Exception('invalid DXF type was specified')
def to_inch(self):
self.radius = inch(self.radius)
@@ -274,6 +171,28 @@ class DxfArcStatement(DxfStatement):
self.start = self.end
self.end = tmp
+ def dots(self, pitch, width, offset=0):
+ angle = self.end_angle - self.start_angle
+ afactor = 1 if angle > 0 else -1
+ aangle = angle * afactor
+ L = 2 * pi * self.radius
+ l = L * aangle / 360
+ pangle = pitch / L * 360
+ wangle = width / L * 360
+ oangle = offset / L * 360
+
+ if offset > l + width / 2:
+ yield (None, offset - l)
+ else:
+ da = oangle
+ while da < aangle + wangle / 2:
+ cangle = self.start_angle + da * afactor
+ x = self.radius * cos(cangle / 180 * pi) + self.center[0]
+ y = self.radius * sin(cangle / 180 * pi) + self.center[1]
+ remain = (da - aangle) / 360 * L
+ yield((x, y), remain)
+ da += pangle
+
def offset(self, offset_x, offset_y):
self.center = (self.center[0] + offset_x, self.center[1] + offset_y)
self.start = (self.start[0] + offset_x, self.start[1] + offset_y)
@@ -296,36 +215,30 @@ class DxfPolylineStatement(DxfStatement):
else:
self.end = (self.entity.points[-1][0], self.entity.points[-1][1])
- 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]
- gerber = 'G01*\nX{0}Y{1}D02*\nG75*'.format(
- write_gerber_value(x0, settings.format,
- settings.zero_suppression),
- write_gerber_value(y0, settings.format,
- settings.zero_suppression),
- )
-
+ def disassemble(self):
+ class Item:
+ pass
+
def ptseq():
for i in range(1, len(self.entity.points)):
yield i
if self.entity.is_closed:
yield 0
-
+
+ x0 = self.entity.points[0][0]
+ y0 = self.entity.points[0][1]
+ b = self.entity.bulge[0]
for idx in ptseq():
pt = self.entity.points[idx]
x1 = pt[0]
y1 = pt[1]
if b == 0:
- gerber += '\nG01*\nX{0}Y{1}D01*'.format(
- write_gerber_value(x1, settings.format,
- settings.zero_suppression),
- write_gerber_value(y1, settings.format,
- settings.zero_suppression),
- )
+ item = Item()
+ item.dxftype = 'LINE'
+ item.start = (x0, y0)
+ item.end = (x1, y1)
+ item.is_closed = False
+ yield DxfLineStatement.from_entity(item)
else:
ang = 4 * atan(b)
xm = x0 + x1
@@ -334,24 +247,27 @@ class DxfPolylineStatement(DxfStatement):
xc = (xm - t * (y1 - y0)) / 2
yc = (ym + t * (x1 - x0)) / 2
r = sqrt((x0 - xc)*(x0 - xc) + (y0 - yc)*(y0 - yc))
+ rx0 = x0 - xc
+ ry0 = y0 - yc
+ rc = max(min(rx0 / r, 1.0), -1.0)
+ start_angle = acos(rc) if ry0 > 0 else 2 * pi - acos(rc)
+ start_angle *= 180 / pi
+ end_angle = start_angle + ang * 180 / pi
- gerber += '\nG{0}*\nX{1}Y{2}I{3}J{4}D01*'.format(
- '03' if ang > 0 else '02',
- write_gerber_value(x1, settings.format,
- settings.zero_suppression),
- write_gerber_value(y1, settings.format,
- settings.zero_suppression),
- write_gerber_value(xc - x0, settings.format,
- settings.zero_suppression),
- write_gerber_value(yc - y0, settings.format,
- settings.zero_suppression)
- )
+ item = Item()
+ item.dxftype = 'ARC'
+ item.start = (x0, y0)
+ item.end = (x1, y1)
+ item.start_angle = start_angle
+ item.end_angle = end_angle
+ item.radius = r
+ item.center = (xc, yc)
+ item.is_closed = end_angle - start_angle >= 360
+ yield DxfArcStatement(item)
x0 = x1
y0 = y1
b = self.entity.bulge[idx]
-
- return gerber
def to_inch(self):
self.start = (inch(self.start[0]), inch(self.start[1]))
@@ -376,7 +292,6 @@ class DxfPolylineStatement(DxfStatement):
for idx in range(len(self.entity.points)):
self.entity.points[idx] = rotate_point(self.entity.points[idx], angle, center)
-
class DxfStatements(object):
def __init__(self, statements, units, dcode=10, draw_mode=None):
if draw_mode == None:
@@ -388,7 +303,7 @@ class DxfStatements(object):
self.width = 0
self.error_range = inch(ACCEPTABLE_ERROR) if self._units == 'inch' else ACCEPTABLE_ERROR
self.statements = statements
- self.paths = generate_closed_paths(self.statements, self.error_range)
+ self.close_paths, self.open_paths = generate_paths(self.statements, self.error_range)
@property
def units(self):
@@ -401,58 +316,62 @@ class DxfStatements(object):
yield 'D{0}*'.format(self.dcode)
if self.draw_mode == DxfFile.DM_FILL:
yield 'G36*'
- for statement in self.statements:
- if isinstance(statement, DxfCircleStatement) or \
- (isinstance(statement, DxfPolylineStatement) and statement.entity.is_closed):
- yield statement.to_gerber(settings)
- for path in self.paths:
+ for path in self.close_paths:
yield path.to_gerber(settings)
yield 'G37*'
else:
- for statement in self.statements:
- yield statement.to_gerber(
- settings,
- pitch=self.pitch if self.draw_mode == DxfFile.DM_MOUSE_BITES else 0,
- width=self.width)
+ pitch = self.pitch if self.draw_mode == DxfFile.DM_MOUSE_BITES else 0
+ for path in self.open_paths:
+ yield path.to_gerber(settings, pitch=pitch, width=self.width)
+ for path in self.close_paths:
+ yield path.to_gerber(settings, pitch=pitch, width=self.width)
return '\n'.join(gerbers())
def to_excellon(self, settings=FileSettings()):
- if not self.draw_mode == DxfFile.DM_MOUSE_BITES:
+ if self.draw_mode == DxfFile.DM_FILL:
return
def drills():
- for statement in self.statements:
- if isinstance(statement, DxfLineStatement):
- yield statement.to_excellon(settings, pitch=self.pitch, width=self.width)
+ pitch = self.pitch if self.draw_mode == DxfFile.DM_MOUSE_BITES else 0
+ for path in self.open_paths:
+ yield path.to_excellon(settings, pitch=pitch, width=self.width)
+ for path in self.close_paths:
+ yield path.to_excellon(settings, pitch=pitch, width=self.width)
return '\n'.join(drills())
def to_inch(self):
if self._units == 'metric':
self._units = 'inch'
self.pitch = inch(self.pitch)
+ self.width = inch(self.width)
self.error_range = inch(self.error_range)
- for statement in self.statements:
- statement.to_inch()
- for path in self.paths:
+ for path in self.open_paths:
+ path.to_inch()
+ for path in self.close_paths:
path.to_inch()
def to_metric(self):
if self._units == 'inch':
self._units = 'metric'
self.pitch = metric(self.pitch)
+ self.width = metric(self.width)
self.error_range = metric(self.error_range)
- for statement in self.statements:
- statement.to_metric()
- for path in self.paths:
+ for path in self.open_paths:
+ path.to_metric()
+ for path in self.close_paths:
path.to_metric()
def offset(self, offset_x, offset_y):
- for statement in self.statements:
- statement.offset(offset_x, offset_y)
+ for path in self.open_paths:
+ path.offset(offset_x, offset_y)
+ for path in self.close_paths:
+ path.offset(offset_x, offset_y)
def rotate(self, angle, center=(0, 0)):
- for statement in self.statements:
- statement.rotate(angle, center)
+ for path in self.open_paths:
+ path.rotate(angle, center)
+ for path in self.close_paths:
+ path.rotate(angle, center)
class DxfFile(CamFile):
DM_LINE = 0
@@ -483,7 +402,7 @@ class DxfFile(CamFile):
elif entity.dxftype == 'LINE':
statements.append(DxfLineStatement.from_entity(entity))
elif entity.dxftype == 'CIRCLE':
- statements.append(DxfCircleStatement(entity))
+ statements.append(DxfArcStatement(entity))
elif entity.dxftype == 'ARC':
statements.append(DxfArcStatement(entity))
@@ -513,6 +432,10 @@ class DxfFile(CamFile):
self._draw_mode = draw_mode
self.aperture = ADParamStmt.circle(dcode=10, diameter=0.0)
+ if settings.units == 'inch':
+ self.aperture.to_inch()
+ else:
+ self.aperture.to_metric()
self.statements = DxfStatements(
statements, self.units, dcode=self.aperture.d, draw_mode=self.draw_mode)
diff --git a/gerberex/dxf_path.py b/gerberex/dxf_path.py
index ca48d00..0a92287 100644
--- a/gerberex/dxf_path.py
+++ b/gerberex/dxf_path.py
@@ -6,10 +6,11 @@
from gerber.utils import inch, metric, write_gerber_value
from gerber.cam import FileSettings
from gerberex.utility import is_equal_point, is_equal_value
+from gerberex.excellon import CoordinateStmtEx
class DxfPath(object):
- def __init__(self, statement, error_range=0):
- self.statements = [statement]
+ def __init__(self, statements, error_range=0):
+ self.statements = statements
self.error_range = error_range
@property
@@ -22,8 +23,10 @@ class DxfPath(object):
@property
def is_closed(self):
- return len(self.statements) > 1 and \
- is_equal_point(self.start, self.end, self.error_range)
+ if len(self.statements) == 1:
+ return self.statements[0].is_closed
+ else:
+ return is_equal_point(self.start, self.end, self.error_range)
def is_equal_to(self, target, error_range=0):
if not isinstance(target, DxfPath):
@@ -43,12 +46,31 @@ class DxfPath(object):
return False
return True
return False
+
+ def contain(self, target, error_range=0):
+ for statement in self.statements:
+ if statement.is_equal_to(target, error_range):
+ return True
+ else:
+ return False
def to_inch(self):
self.error_range = inch(self.error_range)
+ for statement in self.statements:
+ statement.to_inch()
def to_metric(self):
self.error_range = metric(self.error_range)
+ for statement in self.statements:
+ statement.to_metric()
+
+ def offset(self, offset_x, offset_y):
+ for statement in self.statements:
+ statement.offset(offset_x, offset_y)
+
+ def rotate(self, angle, center=(0, 0)):
+ for statement in self.statements:
+ statement.rotate(angle, center)
def reverse(self):
rlist = []
@@ -133,60 +155,118 @@ class DxfPath(object):
def to_gerber(self, settings=FileSettings(), pitch=0, width=0):
from gerberex.dxf import DxfArcStatement
- if pitch:
- return
+ if pitch == 0:
+ x0, y0 = self.statements[0].start
+ gerber = 'G01*\nX{0}Y{1}D02*\nG75*'.format(
+ write_gerber_value(x0, settings.format,
+ settings.zero_suppression),
+ write_gerber_value(y0, settings.format,
+ settings.zero_suppression),
+ )
- x0, y0 = self.statements[0].start
- gerber = 'G01*\nX{0}Y{1}D02*\nG75*'.format(
- write_gerber_value(x0, settings.format,
- settings.zero_suppression),
- write_gerber_value(y0, settings.format,
- settings.zero_suppression),
- )
-
- for statement in self.statements:
- x0, y0 = statement.start
- x1, y1 = statement.end
- if isinstance(statement, DxfArcStatement):
- xc, yc = statement.center
- gerber += '\nG{0}*\nX{1}Y{2}I{3}J{4}D01*'.format(
- '03' if statement.end_angle > statement.start_angle else '02',
- write_gerber_value(x1, settings.format,
- settings.zero_suppression),
- write_gerber_value(y1, settings.format,
- settings.zero_suppression),
- write_gerber_value(xc - x0, settings.format,
- settings.zero_suppression),
- write_gerber_value(yc - y0, settings.format,
- settings.zero_suppression)
- )
- else:
- gerber += '\nG01*\nX{0}Y{1}D01*'.format(
- write_gerber_value(x1, settings.format,
- settings.zero_suppression),
- write_gerber_value(y1, settings.format,
+ for statement in self.statements:
+ x0, y0 = statement.start
+ x1, y1 = statement.end
+ if isinstance(statement, DxfArcStatement):
+ xc, yc = statement.center
+ gerber += '\nG{0}*\nX{1}Y{2}I{3}J{4}D01*'.format(
+ '03' if statement.end_angle > statement.start_angle else '02',
+ write_gerber_value(x1, settings.format,
+ settings.zero_suppression),
+ write_gerber_value(y1, settings.format,
+ settings.zero_suppression),
+ write_gerber_value(xc - x0, settings.format,
+ settings.zero_suppression),
+ write_gerber_value(yc - y0, settings.format,
+ settings.zero_suppression)
+ )
+ else:
+ gerber += '\nG01*\nX{0}Y{1}D01*'.format(
+ write_gerber_value(x1, settings.format,
+ settings.zero_suppression),
+ write_gerber_value(y1, settings.format,
+ settings.zero_suppression),
+ )
+ else:
+ def ploter(x, y):
+ return 'X{0}Y{1}D03*\n'.format(
+ write_gerber_value(x, settings.format,
settings.zero_suppression),
+ write_gerber_value(y, settings.format,
+ settings.zero_suppression),
)
+ gerber = self._plot_dots(pitch, width, ploter)
return gerber
-def generate_closed_paths(statements, error_range=0):
- from gerberex.dxf import DxfLineStatement, DxfArcStatement
+ def to_excellon(self, settings=FileSettings(), pitch=0, width=0):
+ from gerberex.dxf import DxfArcStatement
+ if pitch == 0:
+ x, y = self.statements[0].start
+ excellon = 'G00{0}\nM15\n'.format(
+ CoordinateStmtEx(x=x, y=y).to_excellon(settings))
+
+ for statement in self.statements:
+ x, y = statement.end
+ if isinstance(statement, DxfArcStatement):
+ r = statement.radius
+ excellon += '{0}{1}\n'.format(
+ 'G03' if statement.end_angle > statement.start_angle else 'G02',
+ CoordinateStmtEx(x=x, y=y, radius=r).to_excellon(settings))
+ else:
+ excellon += 'G01{0}\n'.format(
+ CoordinateStmtEx(x=x, y=y).to_excellon(settings))
+
+ excellon += 'M16\nG05\n'
+ else:
+ def ploter(x, y):
+ return CoordinateStmtEx(x=x, y=y).to_excellon(settings) + '\n'
+ excellon = self._plot_dots(pitch, width, ploter)
+
+ return excellon
+
+ def _plot_dots(self, pitch, width, ploter):
+ out = ''
+ offset = 0
+ for idx in range(0, len(self.statements)):
+ statement = self.statements[idx]
+ if offset < 0:
+ offset += pitch
+ for dot, offset in statement.dots(pitch, width, offset):
+ if dot is None:
+ break
+ if offset > 0 and (statement.is_closed or idx != len(self.statements) - 1):
+ break
+ #if idx == len(self.statements) - 1 and statement.is_closed and offset > -pitch:
+ # break
+ out += ploter(dot[0], dot[1])
+ return out
+
+
+def generate_paths(statements, error_range=0):
+ from gerberex.dxf import DxfPolylineStatement
+
+ paths = []
+ for statement in filter(lambda s: isinstance(s, DxfPolylineStatement), statements):
+ units = [unit for unit in statement.disassemble()]
+ paths.append(DxfPath(units, error_range))
unique_statements = []
redundant = 0
- for statement in statements:
- for target in unique_statements:
- if not isinstance(statement, DxfLineStatement) and \
- not isinstance(statement, DxfArcStatement):
- break
- if statement.is_equal_to(target, error_range):
+ for statement in filter(lambda s: not isinstance(s, DxfPolylineStatement), statements):
+ for path in paths:
+ if path.contain(statement):
redundant += 1
break
else:
- unique_statements.append(statement)
+ for target in unique_statements:
+ if statement.is_equal_to(target, error_range):
+ redundant += 1
+ break
+ else:
+ unique_statements.append(statement)
- paths = [DxfPath(s, error_range) for s in unique_statements]
+ paths.extend([DxfPath([s], error_range) for s in unique_statements])
prev_paths_num = 0
while prev_paths_num != len(paths):
@@ -201,5 +281,7 @@ def generate_closed_paths(statements, error_range=0):
working.append(mergee)
prev_paths_num = len(paths)
paths = working
- return list(filter(lambda p: p.is_closed, paths))
+ closed_path = list(filter(lambda p: p.is_closed, paths))
+ open_path = list(filter(lambda p: not p.is_closed, paths))
+ return (closed_path, open_path)
diff --git a/tests/expects/dxf_offset.gtl b/tests/expects/dxf_offset.gtl
index 3d2646d..634664e 100644
--- a/tests/expects/dxf_offset.gtl
+++ b/tests/expects/dxf_offset.gtl
@@ -25,11 +25,6 @@ X210000Y60000D01*
G02*
X200000Y50000I-10000J0D01*
G01*
-X119171Y100000D02*
-G75*
-G03*
-X119171Y100000I-3000J0D01*
-G01*
X119171Y125107D02*
G75*
G02*
@@ -38,4 +33,9 @@ G01*
X116171Y125107D01*
G01*
X119171Y125107D01*
+G01*
+X119171Y100000D02*
+G75*
+G02*
+X119171Y100000I-3000J0D01*
M02*
diff --git a/tests/expects/dxf_rectangle_inch.gtl b/tests/expects/dxf_rectangle_inch.gtl
index 44bb5ed..c196f92 100644
--- a/tests/expects/dxf_rectangle_inch.gtl
+++ b/tests/expects/dxf_rectangle_inch.gtl
@@ -6,15 +6,14 @@ G75*
%LPD*%
D10*
G01*
-X0Y0D02*
+X0Y39370D02*
+G75*
+G01*
+X0Y0D01*
+G01*
X39370Y0D01*
G01*
-X39370Y0D02*
X39370Y39370D01*
G01*
-X39370Y39370D02*
X0Y39370D01*
-G01*
-X0Y39370D02*
-X0Y0D01*
M02*
diff --git a/tests/expects/dxf_rectangle_metric.gtl b/tests/expects/dxf_rectangle_metric.gtl
index fed828a..092471f 100644
--- a/tests/expects/dxf_rectangle_metric.gtl
+++ b/tests/expects/dxf_rectangle_metric.gtl
@@ -6,15 +6,14 @@ G75*
%LPD*%
D10*
G01*
-X0Y0D02*
+X0Y100000D02*
+G75*
+G01*
+X0Y0D01*
+G01*
X100000Y0D01*
G01*
-X100000Y0D02*
X100000Y100000D01*
G01*
-X100000Y100000D02*
X0Y100000D01*
-G01*
-X0Y100000D02*
-X0Y0D01*
M02*
diff --git a/tests/expects/dxf_rotate.gtl b/tests/expects/dxf_rotate.gtl
index 71f9f83..71e1647 100644
--- a/tests/expects/dxf_rotate.gtl
+++ b/tests/expects/dxf_rotate.gtl
@@ -25,11 +25,6 @@ X130782Y15428D01*
G02*
X124805Y2611I-9397J-3420D01*
G01*
-X31930Y20924D02*
-G75*
-G03*
-X31930Y20924I-3000J0D01*
-G01*
X23162Y45543D02*
G75*
G02*
@@ -38,4 +33,9 @@ G01*
X20343Y44517D01*
G01*
X23162Y45543D01*
+G01*
+X31749Y21950D02*
+G75*
+G02*
+X31749Y21950I-2819J-1026D01*
M02*
diff --git a/tests/expects/dxf_save_fill.gtl b/tests/expects/dxf_save_fill.gtl
index 54624b8..3cd2572 100644
--- a/tests/expects/dxf_save_fill.gtl
+++ b/tests/expects/dxf_save_fill.gtl
@@ -26,11 +26,6 @@ X100000Y10000D01*
G02*
X90000Y0I-10000J0D01*
G01*
-X9171Y50000D02*
-G75*
-G03*
-X9171Y50000I-3000J0D01*
-G01*
X9171Y75107D02*
G75*
G02*
@@ -39,5 +34,10 @@ G01*
X6171Y75107D01*
G01*
X9171Y75107D01*
+G01*
+X9171Y50000D02*
+G75*
+G02*
+X9171Y50000I-3000J0D01*
G37*
M02*
diff --git a/tests/expects/dxf_save_line.gtl b/tests/expects/dxf_save_line.gtl
index 6a15313..eb993f4 100644
--- a/tests/expects/dxf_save_line.gtl
+++ b/tests/expects/dxf_save_line.gtl
@@ -25,11 +25,6 @@ X100000Y10000D01*
G02*
X90000Y0I-10000J0D01*
G01*
-X9171Y50000D02*
-G75*
-G03*
-X9171Y50000I-3000J0D01*
-G01*
X9171Y75107D02*
G75*
G02*
@@ -38,4 +33,9 @@ G01*
X6171Y75107D01*
G01*
X9171Y75107D01*
+G01*
+X9171Y50000D02*
+G75*
+G02*
+X9171Y50000I-3000J0D01*
M02*
diff --git a/tests/expects/dxf_to_inch.gtl b/tests/expects/dxf_to_inch.gtl
index 74a4118..16a94cf 100644
--- a/tests/expects/dxf_to_inch.gtl
+++ b/tests/expects/dxf_to_inch.gtl
@@ -25,11 +25,6 @@ X39370Y3937D01*
G02*
X35433Y0I-3937J0D01*
G01*
-X3610Y19685D02*
-G75*
-G03*
-X3610Y19685I-1181J0D01*
-G01*
X3610Y29570D02*
G75*
G02*
@@ -38,4 +33,9 @@ G01*
X2429Y29570D01*
G01*
X3610Y29570D01*
+G01*
+X3610Y19685D02*
+G75*
+G02*
+X3610Y19685I-1181J0D01*
M02*