From fc3f1a23b87d9c4e51967abb0ed4107daa2be5cf Mon Sep 17 00:00:00 2001 From: Hiroshi Murayama Date: Sat, 28 Sep 2019 17:40:09 +0900 Subject: 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 --- README.md | 27 ++- gerberex/dxf.py | 327 +++++++++++++-------------------- gerberex/dxf_path.py | 174 +++++++++++++----- tests/expects/dxf_offset.gtl | 10 +- tests/expects/dxf_rectangle_inch.gtl | 11 +- tests/expects/dxf_rectangle_metric.gtl | 11 +- tests/expects/dxf_rotate.gtl | 10 +- tests/expects/dxf_save_fill.gtl | 10 +- tests/expects/dxf_save_line.gtl | 10 +- tests/expects/dxf_to_inch.gtl | 10 +- 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.
+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') mouse bites -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)) - - 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* -- cgit