aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiroshi Murayama <opiopan@gmail.com>2019-09-28 17:40:09 +0900
committerHiroshi Murayama <opiopan@gmail.com>2019-09-28 17:40:09 +0900
commitfc3f1a23b87d9c4e51967abb0ed4107daa2be5cf (patch)
treecee39181631ee4ce72c1eb62c709c88222fbf237
parent882bf14a8df67e7a3d703bf4acb37650cefbe9f8 (diff)
downloadpcb-tools-extension-fc3f1a23b87d9c4e51967abb0ed4107daa2be5cf.tar.gz
pcb-tools-extension-fc3f1a23b87d9c4e51967abb0ed4107daa2be5cf.tar.bz2
pcb-tools-extension-fc3f1a23b87d9c4e51967abb0ed4107daa2be5cf.zip
improve DXF file handling functions:
- DM_LINE mode support to generate Excellon routing sequence - DM_MOUSE_BITES mode support to generate mouse bites along all path also, not only line object
-rw-r--r--README.md27
-rw-r--r--gerberex/dxf.py327
-rw-r--r--gerberex/dxf_path.py174
-rw-r--r--tests/expects/dxf_offset.gtl10
-rw-r--r--tests/expects/dxf_rectangle_inch.gtl11
-rw-r--r--tests/expects/dxf_rectangle_metric.gtl11
-rw-r--r--tests/expects/dxf_rotate.gtl10
-rw-r--r--tests/expects/dxf_save_fill.gtl10
-rw-r--r--tests/expects/dxf_save_line.gtl10
-rw-r--r--tests/expects/dxf_to_inch.gtl10
10 files changed, 310 insertions, 290 deletions
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.<br>
+You can also load a dxf file and handle that as same as RX-274x gerber file or Excellon NC file.<br>
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.<br>
+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. <br>
```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.<br>
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')
<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 ```draw_mode```, filled circles are arranged along a DXF line object at equal intervals. <br>
+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. <br>
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))
-
- 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)
- )
+ 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
+
+ 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*