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
This commit is contained in:
Hiroshi Murayama 2019-09-28 17:40:09 +09:00
parent 882bf14a8d
commit fc3f1a23b8
10 changed files with 309 additions and 289 deletions

View file

@ -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

View file

@ -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
@ -62,38 +56,6 @@ class DxfLineStatement(DxfStatement):
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 = (
inch(self.start[0]), inch(self.start[1]))
@ -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,10 +90,15 @@ 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;
if offset > l + width / 2:
return (None, offset - l)
else:
d = offset;
while d < l + width / 2:
yield (x0, y0)
yield ((x0, y0), d - l)
x0 += xd
y0 += yd
d += pitch
@ -144,65 +111,18 @@ class DxfLineStatement(DxfStatement):
self.start = rotate_point(self.start, angle, center)
self.end = rotate_point(self.end, angle, center)
class DxfCircleStatement(DxfStatement):
class DxfArcStatement(DxfStatement):
def __init__(self, entity):
super(DxfCircleStatement, self).__init__(entity)
super(DxfArcStatement, self).__init__(entity)
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
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)
elif entity.dxftype == 'ARC':
self.start_angle = self.entity.start_angle
self.end_angle = self.entity.end_angle
self.radius = self.entity.radius
@ -217,31 +137,8 @@ class DxfArcStatement(DxfStatement):
)
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)
)
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,18 +215,9 @@ 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)):
@ -315,17 +225,20 @@ class DxfPolylineStatement(DxfStatement):
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,25 +247,28 @@ 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]))
self.end = (inch(self.end[0]), inch(self.end[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)

View file

@ -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):
@ -44,11 +47,30 @@ class DxfPath(object):
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,9 +155,7 @@ 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,
@ -167,26 +187,86 @@ class DxfPath(object):
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):
for statement in filter(lambda s: not isinstance(s, DxfPolylineStatement), statements):
for path in paths:
if path.contain(statement):
redundant += 1
break
else:
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)

View file

@ -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*

View file

@ -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*

View file

@ -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*

View file

@ -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*

View file

@ -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*

View file

@ -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*

View file

@ -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*