From 9febca7da6a730b3b3ca3a54129a9f88e5c44d14 Mon Sep 17 00:00:00 2001 From: opiopan Date: Thu, 21 Mar 2019 22:00:32 +0900 Subject: initial commit --- gerberex/dxf.py | 357 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 357 insertions(+) create mode 100644 gerberex/dxf.py (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py new file mode 100644 index 0000000..b641924 --- /dev/null +++ b/gerberex/dxf.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright 2019 Hiroshi Murayama + +import io +from math import pi, cos, sin, tan, atan, atan2, acos, asin, sqrt +from gerber.cam import CamFile, FileSettings +from gerber.utils import inch, metric, write_gerber_value +from gerber.gerber_statements import ADParamStmt +import dxfgrabber + +class DxfStatement(object): + def __init__(self, entity): + self.entity = entity + + def to_gerber(self, settings=None): + pass + + def to_inch(self): + pass + + def to_metric(self): + pass + +class DxfLineStatement(DxfStatement): + def __init__(self, entity): + super(DxfLineStatement, self).__init__(entity) + + def to_gerber(self, settings=FileSettings): + x0 = self.entity.start[0] + y0 = self.entity.start[1] + x1 = self.entity.end[0] + y1 = self.entity.end[1] + return 'G01*\nX{0}Y{1}D02*\nX{2}Y{3}D01*'.format( + write_gerber_value(x0, settings.format, + settings.zero_suppression), + write_gerber_value(y0, settings.format, + settings.zero_suppression), + write_gerber_value(x1, settings.format, + settings.zero_suppression), + write_gerber_value(y1, settings.format, + settings.zero_suppression) + ) + + def to_inch(self): + self.entity.start[idx] = ( + inch(self.entity.start[idx][0]), inch(self.entity.start[idx][1])) + self.entity.end[idx] = ( + inch(self.entity.end[idx][0]), inch(self.entity.end[idx][1])) + + def to_metric(self): + self.entity.start[idx] = ( + metric(self.entity.start[idx][0]), inch(self.entity.start[idx][1])) + self.entity.end[idx] = ( + metric(self.entity.end[idx][0]), inch(self.entity.end[idx][1])) + +class DxfCircleStatement(DxfStatement): + def __init__(self, entity): + super(DxfCircleStatement, self).__init__(entity) + + def to_gerber(self, settings=FileSettings): + r = self.entity.radius + x0 = self.entity.center[0] + y0 = self.entity.center[1] + 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.entity.radius = inch(self.entity.radius) + self.entity.center[idx] = ( + inch(self.entity.center[idx][0]), inch(self.entity.center[idx][1])) + + def to_metric(self): + self.entity.radius = metric(self.entity.radius) + self.entity.center[idx] = ( + metric(self.entity.center[idx][0]), metric(self.entity.center[idx][1])) + +class DxfArcStatement(DxfStatement): + def __init__(self, entity): + super(DxfArcStatement, self).__init__(entity) + + def to_gerber(self, settings=FileSettings): + deg0 = self.entity.start_angle + deg1 = self.entity.end_angle + r = self.entity.radius + x0 = self.entity.center[0] + y0 = self.entity.center[1] + begin_x = x0 + r * cos(deg0 / 180. * pi) + begin_y = y0 + r * sin(deg0 / 180. * pi) + end_x = x0 + r * cos(deg1 / 180. * pi) + end_y = y0 + r * sin(deg1 / 180. * pi) + + return 'G01*\nX{0}Y{1}D02*\n' \ + 'G75*\nG{2}*\nX{3}Y{4}I{5}J{6}D01*'.format( + write_gerber_value(begin_x, settings.format, + settings.zero_suppression), + write_gerber_value(begin_y, settings.format, + settings.zero_suppression), + '03' if deg0 > deg1 else '02', + write_gerber_value(end_x, settings.format, + settings.zero_suppression), + write_gerber_value(end_y, settings.format, + settings.zero_suppression), + write_gerber_value(x0 - begin_x, settings.format, + settings.zero_suppression), + write_gerber_value(y0 - begin_y, settings.format, + settings.zero_suppression) + ) + + def to_inch(self): + self.entity.start_angle = inch(self.entity.start_angle) + self.entity.end_angle = inch(self.entity.end_angle) + self.entity.radius = inch(self.entity.radius) + self.entity.center[idx] = ( + inch(self.entity.center[idx][0]), inch(self.entity.center[idx][1])) + + def to_metric(self): + self.entity.start_angle = metric(self.entity.start_angle) + self.entity.end_angle = metric(self.entity.end_angle) + self.entity.radius = metric(self.entity.radius) + self.entity.center[idx] = ( + metric(self.entity.center[idx][0]), metric(self.entity.center[idx][1])) + +class DxfPolylineStatement(DxfStatement): + def __init__(self, entity): + super(DxfPolylineStatement, self).__init__(entity) + + def to_gerber(self, settings=FileSettings()): + 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 ptseq(): + for i in range(1, len(self.entity.points)): + yield i + if self.entity.is_closed: + yield 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), + ) + else: + ang = 4 * atan(b) + xm = x0 + x1 + ym = y0 + y1 + t = 1 / tan(ang / 2) + 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) + ) + + x0 = x1 + y0 = y1 + b = self.entity.bulge[idx] + + return gerber + + def to_inch(self): + for idx in range(0, len(self.entity.points)): + self.entity.points[idx] = ( + inch(self.entity.points[idx][0]), inch(self.entity.points[idx][1])) + self.entity.bulge[idx] = inch(self.entity.bulge[idx]) + + def to_metric(self): + for idx in range(0, len(self.entity.points)): + self.entity.points[idx] = ( + metric(self.entity.points[idx][0]), metric(self.entity.points[idx][1])) + self.entity.bulge[idx] = metric(self.entity.bulge[idx]) + +class DxfStatements(object): + def __init__(self, entities, units, dcode=10, draw_mode=None): + if draw_mode == None: + draw_mode = DxfFile.DM_LINE + self._units = units + self.dcode = dcode + self.draw_mode = draw_mode + self.statements = [] + for entity in entities: + if entity.dxftype == 'LWPOLYLINE': + self.statements.append(DxfPolylineStatement(entity)) + elif entity.dxftype == 'LINE': + self.statements.append(DxfLineStatement(entity)) + elif entity.dxftype == 'CIRCLE': + self.statements.append(DxfCircleStatement(entity)) + elif entity.dxftype == 'ARC': + self.statements.append(DxfArcStatement(entity)) + + @property + def units(self): + return _units + + def to_gerber(self, settings=FileSettings()): + def gerbers(): + 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) + yield 'G37*' + else: + for statement in self.statements: + yield statement.to_gerber(settings) + + return '\n'.join(gerbers()) + + def to_inch(self): + if self._units == 'metric': + self._units = 'inch' + for statement in self.statements: + statement.to_inch() + + def to_metric(self): + if self._units == 'inch': + self._units = 'metric' + for statement in self.statements: + statement.to_metric() + +class DxfHeaderStatement(object): + def to_gerber(self, settings): + return 'G75*\n'\ + '%MO{0}*%\n'\ + '%OFA0B0*%\n'\ + '%FS{1}AX{2}{3}Y{4}{5}*%\n'\ + '%IPPOS*%\n'\ + '%LPD*%'.format( + 'IN' if settings.units == 'inch' else 'MM', + 'L' if settings.zero_suppression == 'leading' else 'T', + settings.format[0], settings.format[1], + settings.format[0], settings.format[1] + ) + + def to_inch(self): + pass + + def to_metric(self): + pass + +class DxfFile(CamFile): + DM_LINE = 0 + DM_FILL = 1 + + def __init__(self, dxf, settings=FileSettings(), draw_mode=None, filename=None): + if draw_mode == None: + draw_mode = self.DM_LINE + if dxf.header['$INSUNITS'] == 1: + settings.units = 'inch' + settings.format = (2, 5) + else: + settings.units = 'metric' + settings.format = (3, 4) + + super(DxfFile, self).__init__(settings=settings, filename=filename) + self._draw_mode = draw_mode + self.header = DxfHeaderStatement() + self.aperture = ADParamStmt.circle(dcode=10, diameter=0.0) + self.statements = DxfStatements(dxf.entities, self.units, dcode=self.aperture.d, draw_mode=self.draw_mode) + + @property + def dcode(self): + return self.aperture.dcode + + @dcode.setter + def dcode(self, value): + self.aperture.d = value + self.statements.dcode = value + + @property + def width(self): + return self.aperture.modifiers[0][0] + + @width.setter + def width(self, value): + self.aperture.modifiers = ([float(value),],) + + @property + def draw_mode(self): + return self._draw_mode + + @draw_mode.setter + def draw_mode(self, value): + self._draw_mode = value + self.statements.draw_mode = value + + def write(self, filename=None): + if self.settings.notation != 'absolute': + raise Exception('DXF file\'s notation must be absolute ') + + filename = filename if filename is not None else self.filename + with open(filename, 'w') as f: + f.write(self.header.to_gerber(self.settings) + '\n') + f.write(self.aperture.to_gerber(self.settings) + '\n') + f.write(self.statements.to_gerber(self.settings) + '\n') + f.write('M02*\n') + + def to_inch(self): + if self.units == 'metric': + self.header.to_inch() + self.aperture.to_inch() + self.statements.to_inch() + self.units = 'inch' + + def to_metric(self): + if self.units == 'inch': + self.header.to_metric() + self.aperture.to_metric() + self.statements.to_metric() + self.units = 'metric' + + def offset(self, ofset_x, offset_y): + raise Exception('Not supported') + +def loads(data, filename=None): + stream = io.StringIO(data) + dxf = dxfgrabber.read(stream) + return DxfFile(dxf) -- cgit From 690df56bb71020901167605a87ec451081fa18d7 Mon Sep 17 00:00:00 2001 From: opiopan Date: Sat, 23 Mar 2019 21:59:13 +0900 Subject: add rotation fuction --- gerberex/dxf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index b641924..11072b5 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -111,7 +111,7 @@ class DxfArcStatement(DxfStatement): settings.zero_suppression), write_gerber_value(begin_y, settings.format, settings.zero_suppression), - '03' if deg0 > deg1 else '02', + '03', write_gerber_value(end_x, settings.format, settings.zero_suppression), write_gerber_value(end_y, settings.format, -- cgit From fcd704e1eef9034e2000f55b2918d7df41379408 Mon Sep 17 00:00:00 2001 From: opiopan Date: Sat, 30 Mar 2019 11:16:13 +0900 Subject: add mouse bites generator function --- gerberex/dxf.py | 205 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 162 insertions(+), 43 deletions(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index 11072b5..a2c26d0 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -8,13 +8,18 @@ from math import pi, cos, sin, tan, atan, atan2, acos, asin, sqrt from gerber.cam import CamFile, FileSettings from gerber.utils import inch, metric, write_gerber_value from gerber.gerber_statements import ADParamStmt +from gerber.excellon_statements import ExcellonTool +from gerber.excellon_statements import CoordinateStmt import dxfgrabber class DxfStatement(object): def __init__(self, entity): self.entity = entity - def to_gerber(self, settings=None): + def to_gerber(self, settings=None, pitch=0, width=0): + pass + + def to_excellon(self, settings=None, pitch=0, width=0): pass def to_inch(self): @@ -27,39 +32,77 @@ class DxfLineStatement(DxfStatement): def __init__(self, entity): super(DxfLineStatement, self).__init__(entity) - def to_gerber(self, settings=FileSettings): - x0 = self.entity.start[0] - y0 = self.entity.start[1] - x1 = self.entity.end[0] - y1 = self.entity.end[1] - return 'G01*\nX{0}Y{1}D02*\nX{2}Y{3}D01*'.format( - write_gerber_value(x0, settings.format, - settings.zero_suppression), - write_gerber_value(y0, settings.format, - settings.zero_suppression), - write_gerber_value(x1, settings.format, - settings.zero_suppression), - write_gerber_value(y1, settings.format, - settings.zero_suppression) - ) + def to_gerber(self, settings=FileSettings(), pitch=0, width=0): + if pitch == 0: + x0 = self.entity.start[0] + y0 = self.entity.start[1] + x1 = self.entity.end[0] + y1 = self.entity.end[1] + return 'G01*\nX{0}Y{1}D02*\nX{2}Y{3}D01*'.format( + write_gerber_value(x0, settings.format, + settings.zero_suppression), + write_gerber_value(y0, settings.format, + settings.zero_suppression), + write_gerber_value(x1, settings.format, + settings.zero_suppression), + write_gerber_value(y1, settings.format, + settings.zero_suppression) + ) + else: + gstr = "" + for p in self._dots(pitch, width): + gstr += 'X{0}Y{1}D03*\n'.format( + write_gerber_value(p[0], settings.format, + settings.zero_suppression), + write_gerber_value(p[1], settings.format, + settings.zero_suppression)) + return gstr + + def to_excellon(self, settings=FileSettings(), pitch=0, width=0): + if not pitch: + return + gstr = "" + for p in self._dots(pitch, width): + gstr += CoordinateStmt(x=p[0], y=p[1]).to_excellon(settings) + '\n' + return gstr def to_inch(self): - self.entity.start[idx] = ( - inch(self.entity.start[idx][0]), inch(self.entity.start[idx][1])) - self.entity.end[idx] = ( - inch(self.entity.end[idx][0]), inch(self.entity.end[idx][1])) + self.entity.start = ( + inch(self.entity.start[0]), inch(self.entity.start[1])) + self.entity.end = ( + inch(self.entity.end[0]), inch(self.entity.end[1])) def to_metric(self): - self.entity.start[idx] = ( - metric(self.entity.start[idx][0]), inch(self.entity.start[idx][1])) - self.entity.end[idx] = ( - metric(self.entity.end[idx][0]), inch(self.entity.end[idx][1])) + self.entity.start = ( + metric(self.entity.start[0]), inch(self.entity.start[1])) + self.entity.end = ( + metric(self.entity.end[0]), inch(self.entity.end[1])) + + def _dots(self, pitch, width): + x0 = self.entity.start[0] + y0 = self.entity.start[1] + x1 = self.entity.end[0] + y1 = self.entity.end[1] + xp = x1 - x0 + yp = y1 - y0 + l = sqrt(xp * xp + yp * yp) + xd = xp * pitch / l + yd = yp * pitch / l + + d = 0; + while d < l + width / 2: + yield (x0, y0) + x0 += xd + y0 += yd + d += pitch class DxfCircleStatement(DxfStatement): def __init__(self, entity): super(DxfCircleStatement, self).__init__(entity) - def to_gerber(self, settings=FileSettings): + def to_gerber(self, settings=FileSettings(), pitch=0, width=0): + if pitch: + return r = self.entity.radius x0 = self.entity.center[0] y0 = self.entity.center[1] @@ -82,19 +125,21 @@ class DxfCircleStatement(DxfStatement): def to_inch(self): self.entity.radius = inch(self.entity.radius) - self.entity.center[idx] = ( - inch(self.entity.center[idx][0]), inch(self.entity.center[idx][1])) + self.entity.center = ( + inch(self.entity.center[0]), inch(self.entity.center[1])) def to_metric(self): self.entity.radius = metric(self.entity.radius) - self.entity.center[idx] = ( - metric(self.entity.center[idx][0]), metric(self.entity.center[idx][1])) + self.entity.center = ( + metric(self.entity.center[0]), metric(self.entity.center[1])) class DxfArcStatement(DxfStatement): def __init__(self, entity): super(DxfArcStatement, self).__init__(entity) - def to_gerber(self, settings=FileSettings): + def to_gerber(self, settings=FileSettings(), pitch=0, width=0): + if pitch: + return deg0 = self.entity.start_angle deg1 = self.entity.end_angle r = self.entity.radius @@ -126,21 +171,23 @@ class DxfArcStatement(DxfStatement): self.entity.start_angle = inch(self.entity.start_angle) self.entity.end_angle = inch(self.entity.end_angle) self.entity.radius = inch(self.entity.radius) - self.entity.center[idx] = ( - inch(self.entity.center[idx][0]), inch(self.entity.center[idx][1])) + self.entity.center = ( + inch(self.entity.center[0]), inch(self.entity.center[1])) def to_metric(self): self.entity.start_angle = metric(self.entity.start_angle) self.entity.end_angle = metric(self.entity.end_angle) self.entity.radius = metric(self.entity.radius) - self.entity.center[idx] = ( - metric(self.entity.center[idx][0]), metric(self.entity.center[idx][1])) + self.entity.center = ( + metric(self.entity.center[0]), metric(self.entity.center[1])) class DxfPolylineStatement(DxfStatement): def __init__(self, entity): super(DxfPolylineStatement, self).__init__(entity) - def to_gerber(self, settings=FileSettings()): + def to_gerber(self, settings=FileSettings(), pitch=0, width=0): + if pitch: + return x0 = self.entity.points[0][0] y0 = self.entity.points[0][1] b = self.entity.bulge[0] @@ -214,6 +261,8 @@ class DxfStatements(object): self._units = units self.dcode = dcode self.draw_mode = draw_mode + self.pitch = inch(1) if self._units == 'unit' else 1 + self.width = 0 self.statements = [] for entity in entities: if entity.dxftype == 'LWPOLYLINE': @@ -241,19 +290,33 @@ class DxfStatements(object): yield 'G37*' else: for statement in self.statements: - yield statement.to_gerber(settings) + yield statement.to_gerber( + settings, + pitch=self.pitch if self.draw_mode == DxfFile.DM_MOUSE_BITES else 0, + width=self.width) return '\n'.join(gerbers()) + def to_excellon(self, settings=FileSettings()): + if not self.draw_mode == DxfFile.DM_MOUSE_BITES: + return + def drills(): + for statement in self.statements: + if isinstance(statement, DxfLineStatement): + yield statement.to_excellon(settings, pitch=self.pitch, width=self.width) + return '\n'.join(drills()) + def to_inch(self): if self._units == 'metric': self._units = 'inch' + self.pitch = inch(self.pitch) for statement in self.statements: statement.to_inch() def to_metric(self): if self._units == 'inch': self._units = 'metric' + self.pitch = metric(self.pitch) for statement in self.statements: statement.to_metric() @@ -270,6 +333,32 @@ class DxfHeaderStatement(object): settings.format[0], settings.format[1], settings.format[0], settings.format[1] ) + + def to_excellon(self, settings): + return 'M48\n'\ + 'FMAT,2\n'\ + 'ICI,{0}\n'\ + '{1},{2},{3}.{4}\n'\ + '{5}'.format( + 'ON' if settings.notation == 'incremental' else 'OFF', + 'INCH' if settings.units == 'inch' else 'METRIC', + 'TZ' if settings.zero_suppression == 'leading' else 'LZ', + '0' * settings.format[0], '0' * settings.format[1], + 'M72' if settings.units == 'inch' else 'M71' + ) + + def to_inch(self): + pass + + def to_metric(self): + pass + +class DxfHeader2Statement(object): + def to_gerber(self, settings): + pass + + def to_excellon(self, settings): + return '%' def to_inch(self): pass @@ -280,8 +369,15 @@ class DxfHeaderStatement(object): class DxfFile(CamFile): DM_LINE = 0 DM_FILL = 1 + DM_MOUSE_BITES = 2 + + FT_RX274X = 0 + FT_EXCELLON = 1 + + def __init__(self, dxf, settings=None, draw_mode=None, filename=None): + if not settings: + settings = FileSettings(zero_suppression='leading') - def __init__(self, dxf, settings=FileSettings(), draw_mode=None, filename=None): if draw_mode == None: draw_mode = self.DM_LINE if dxf.header['$INSUNITS'] == 1: @@ -294,6 +390,8 @@ class DxfFile(CamFile): super(DxfFile, self).__init__(settings=settings, filename=filename) self._draw_mode = draw_mode self.header = DxfHeaderStatement() + + self.header2 = DxfHeader2Statement() self.aperture = ADParamStmt.circle(dcode=10, diameter=0.0) self.statements = DxfStatements(dxf.entities, self.units, dcode=self.aperture.d, draw_mode=self.draw_mode) @@ -313,6 +411,7 @@ class DxfFile(CamFile): @width.setter def width(self, value): self.aperture.modifiers = ([float(value),],) + self.statements.width = value @property def draw_mode(self): @@ -322,23 +421,42 @@ class DxfFile(CamFile): def draw_mode(self, value): self._draw_mode = value self.statements.draw_mode = value + + @property + def pitch(self): + return self.statements.pitch - def write(self, filename=None): + @pitch.setter + def pitch(self, value): + self.statements.pitch = value + + def write(self, filename=None, filetype=FT_RX274X): if self.settings.notation != 'absolute': raise Exception('DXF file\'s notation must be absolute ') - + filename = filename if filename is not None else self.filename with open(filename, 'w') as f: - f.write(self.header.to_gerber(self.settings) + '\n') - f.write(self.aperture.to_gerber(self.settings) + '\n') - f.write(self.statements.to_gerber(self.settings) + '\n') - f.write('M02*\n') + if filetype == self.FT_RX274X: + f.write(self.header.to_gerber(self.settings) + '\n') + f.write(self.aperture.to_gerber(self.settings) + '\n') + f.write(self.statements.to_gerber(self.settings) + '\n') + f.write('M02*\n') + else: + tool = ExcellonTool(self.settings, number=1, diameter=self.width) + f.write(self.header.to_excellon(self.settings) + '\n') + f.write(tool.to_excellon(self.settings) + '\n') + f.write(self.header2.to_excellon(self.settings) + '\n') + f.write('T01\n') + f.write(self.statements.to_excellon(self.settings) + '\n') + f.write('M30\n') + def to_inch(self): if self.units == 'metric': self.header.to_inch() self.aperture.to_inch() self.statements.to_inch() + self.pitch = inch(self.pitch) self.units = 'inch' def to_metric(self): @@ -346,6 +464,7 @@ class DxfFile(CamFile): self.header.to_metric() self.aperture.to_metric() self.statements.to_metric() + self.pitch = metric(self.pitch) self.units = 'metric' def offset(self, ofset_x, offset_y): -- cgit From 900d992fa3af05f93ac7a4cf717f28598e1a868d Mon Sep 17 00:00:00 2001 From: opiopan Date: Sun, 31 Mar 2019 13:30:15 +0900 Subject: auto detection closed paths in the collection of DXF arc object and line object, then fill these closed path --- gerberex/dxf.py | 198 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 145 insertions(+), 53 deletions(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index a2c26d0..e6eb971 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -5,16 +5,29 @@ import io from math import pi, cos, sin, tan, atan, atan2, acos, asin, sqrt +import dxfgrabber from gerber.cam import CamFile, FileSettings from gerber.utils import inch, metric, write_gerber_value from gerber.gerber_statements import ADParamStmt from gerber.excellon_statements import ExcellonTool from gerber.excellon_statements import CoordinateStmt -import dxfgrabber +from gerberex.dxf_path import generate_closed_paths + +ACCEPTABLE_ERROR = 0.001 + +def is_equal_value(a, b, error_range=0): + return a - b <= error_range and a - b >= -error_range + +def is_equal_point(a, b, error_range=0): + return is_equal_value(a[0], b[0], error_range) and \ + is_equal_value(a[1], b[1], error_range) class DxfStatement(object): def __init__(self, entity): self.entity = entity + self.start = None + self.end = None + self.is_closed = False def to_gerber(self, settings=None, pitch=0, width=0): pass @@ -28,16 +41,22 @@ class DxfStatement(object): def to_metric(self): pass + def is_equal_to(self, target, error_range=0): + return False + + def reverse(self): + raise Exception('Not implemented') + class DxfLineStatement(DxfStatement): def __init__(self, entity): super(DxfLineStatement, self).__init__(entity) + self.start = (self.entity.start[0], self.entity.start[1]) + self.end = (self.entity.end[0], self.entity.end[1]) def to_gerber(self, settings=FileSettings(), pitch=0, width=0): if pitch == 0: - x0 = self.entity.start[0] - y0 = self.entity.start[1] - x1 = self.entity.end[0] - y1 = self.entity.end[1] + 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), @@ -67,22 +86,34 @@ class DxfLineStatement(DxfStatement): return gstr def to_inch(self): - self.entity.start = ( - inch(self.entity.start[0]), inch(self.entity.start[1])) - self.entity.end = ( - inch(self.entity.end[0]), inch(self.entity.end[1])) + self.start = ( + inch(self.start[0]), inch(self.start[1])) + self.end = ( + inch(self.end[0]), inch(self.end[1])) def to_metric(self): - self.entity.start = ( - metric(self.entity.start[0]), inch(self.entity.start[1])) - self.entity.end = ( - metric(self.entity.end[0]), inch(self.entity.end[1])) + self.start = ( + metric(self.start[0]), inch(self.start[1])) + self.end = ( + metric(self.end[0]), inch(self.end[1])) + + def is_equal_to(self, target, error_range=0): + if not isinstance(target, DxfLineStatement): + return False + return (is_equal_point(self.start, target.start, error_range) and \ + is_equal_point(self.end, target.end, error_range)) or \ + (is_equal_point(self.start, target.end, error_range) and \ + is_equal_point(self.end, target.start, error_range)) + + def reverse(self): + pt = self.start + self.start = self.end + self.end = pt def _dots(self, pitch, width): - x0 = self.entity.start[0] - y0 = self.entity.start[1] - x1 = self.entity.end[0] - y1 = self.entity.end[1] + x0, y0 = self.start + x1, y1 = self.end + y1 = self.end[1] xp = x1 - x0 yp = y1 - y0 l = sqrt(xp * xp + yp * yp) @@ -99,13 +130,17 @@ class DxfLineStatement(DxfStatement): 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.entity.radius - x0 = self.entity.center[0] - y0 = self.entity.center[1] + 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, @@ -124,66 +159,107 @@ class DxfCircleStatement(DxfStatement): ) def to_inch(self): - self.entity.radius = inch(self.entity.radius) - self.entity.center = ( - inch(self.entity.center[0]), inch(self.entity.center[1])) + self.radius = inch(self.radius) + self.center = ( + inch(self.center[0]), inch(self.center[1])) def to_metric(self): - self.entity.radius = metric(self.entity.radius) - self.entity.center = ( - metric(self.entity.center[0]), metric(self.entity.center[1])) + 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 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 - deg0 = self.entity.start_angle - deg1 = self.entity.end_angle - r = self.entity.radius - x0 = self.entity.center[0] - y0 = self.entity.center[1] - begin_x = x0 + r * cos(deg0 / 180. * pi) - begin_y = y0 + r * sin(deg0 / 180. * pi) - end_x = x0 + r * cos(deg1 / 180. * pi) - end_y = y0 + r * sin(deg1 / 180. * pi) + 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(begin_x, settings.format, + write_gerber_value(start_x, settings.format, settings.zero_suppression), - write_gerber_value(begin_y, settings.format, + write_gerber_value(start_y, settings.format, settings.zero_suppression), - '03', + '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 - begin_x, settings.format, + write_gerber_value(x0 - start_x, settings.format, settings.zero_suppression), - write_gerber_value(y0 - begin_y, settings.format, + write_gerber_value(y0 - start_y, settings.format, settings.zero_suppression) ) def to_inch(self): - self.entity.start_angle = inch(self.entity.start_angle) - self.entity.end_angle = inch(self.entity.end_angle) - self.entity.radius = inch(self.entity.radius) - self.entity.center = ( - inch(self.entity.center[0]), inch(self.entity.center[1])) + self.radius = inch(self.radius) + self.center = (inch(self.center[0]), inch(self.center[1])) + self.start = (inch(self.start[0]), inch(self.start[1])) + self.end = (inch(self.end[0]), inch(self.end[1])) def to_metric(self): - self.entity.start_angle = metric(self.entity.start_angle) - self.entity.end_angle = metric(self.entity.end_angle) - self.entity.radius = metric(self.entity.radius) - self.entity.center = ( - metric(self.entity.center[0]), metric(self.entity.center[1])) + self.radius = metric(self.radius) + self.center = (metric(self.center[0]), metric(self.center[1])) + self.start = (metric(self.start[0]), metric(self.start[1])) + self.end = (metric(self.end[0]), metric(self.end[1])) + + def is_equal_to(self, target, error_range=0): + if not isinstance(target, DxfArcStatement): + return False + aerror_range = error_range / pi * self.radius * 180 + return is_equal_point(self.center, target.center, error_range) and \ + is_equal_value(self.radius, target.radius, error_range) and \ + ((is_equal_value(self.start_angle, target.start_angle, aerror_range) and + is_equal_value(self.end_angle, target.end_angle, aerror_range)) or + (is_equal_value(self.start_angle, target.end_angle, aerror_range) and + is_equal_value(self.end_angle, target.end_angle, aerror_range))) + + def reverse(self): + tmp = self.start_angle + self.start_angle = self.end_angle + self.end_angle = tmp + tmp = self.start + self.start = self.end + self.end = tmp class DxfPolylineStatement(DxfStatement): def __init__(self, entity): super(DxfPolylineStatement, self).__init__(entity) + self.start = (self.entity.points[0][0], self.entity.points[0][1]) + self.is_closed = self.entity.is_closed + if self.is_closed: + self.end = self.start + else: + self.start = (self.entity.points[-1][0], self.entity.points[-1][1]) def to_gerber(self, settings=FileSettings(), pitch=0, width=0): if pitch: @@ -243,12 +319,16 @@ class DxfPolylineStatement(DxfStatement): 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])) for idx in range(0, len(self.entity.points)): self.entity.points[idx] = ( inch(self.entity.points[idx][0]), inch(self.entity.points[idx][1])) self.entity.bulge[idx] = inch(self.entity.bulge[idx]) def to_metric(self): + self.start = (metric(self.start[0]), metric(self.start[1])) + self.end = (metric(self.end[0]), metric(self.end[1])) for idx in range(0, len(self.entity.points)): self.entity.points[idx] = ( metric(self.entity.points[idx][0]), metric(self.entity.points[idx][1])) @@ -261,8 +341,9 @@ class DxfStatements(object): self._units = units self.dcode = dcode self.draw_mode = draw_mode - self.pitch = inch(1) if self._units == 'unit' else 1 + self.pitch = inch(1) if self._units == 'inch' else 1 self.width = 0 + self.error_range = inch(ACCEPTABLE_ERROR) if self._units == 'inch' else ACCEPTABLE_ERROR self.statements = [] for entity in entities: if entity.dxftype == 'LWPOLYLINE': @@ -273,6 +354,7 @@ class DxfStatements(object): self.statements.append(DxfCircleStatement(entity)) elif entity.dxftype == 'ARC': self.statements.append(DxfArcStatement(entity)) + self.paths = generate_closed_paths(self.statements, self.error_range) @property def units(self): @@ -282,12 +364,16 @@ class DxfStatements(object): def gerbers(): yield 'D{0}*'.format(self.dcode) if self.draw_mode == DxfFile.DM_FILL: - yield 'G36*' for statement in self.statements: + yield 'G36*' if isinstance(statement, DxfCircleStatement) or \ (isinstance(statement, DxfPolylineStatement) and statement.entity.is_closed): yield statement.to_gerber(settings) - yield 'G37*' + yield 'G37*' + for path in self.paths: + yield 'G36*' + yield path.to_gerber(settings) + yield 'G37*' else: for statement in self.statements: yield statement.to_gerber( @@ -310,15 +396,21 @@ class DxfStatements(object): if self._units == 'metric': self._units = 'inch' self.pitch = inch(self.pitch) + self.error_range = inch(self.error_range) for statement in self.statements: statement.to_inch() + for path in self.paths: + path.to_inch() def to_metric(self): if self._units == 'inch': self._units = 'metric' self.pitch = metric(self.pitch) + self.error_range = metric(self.error_range) for statement in self.statements: statement.to_metric() + for path in self.paths: + path.to_metric() class DxfHeaderStatement(object): def to_gerber(self, settings): -- cgit From 53816574a986722d7af26c5597248d9c96f31bd3 Mon Sep 17 00:00:00 2001 From: opiopan Date: Sun, 31 Mar 2019 18:16:34 +0900 Subject: fix a minor issue --- gerberex/dxf.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index e6eb971..e863238 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -259,7 +259,7 @@ class DxfPolylineStatement(DxfStatement): if self.is_closed: self.end = self.start else: - self.start = (self.entity.points[-1][0], self.entity.points[-1][1]) + self.end = (self.entity.points[-1][0], self.entity.points[-1][1]) def to_gerber(self, settings=FileSettings(), pitch=0, width=0): if pitch: @@ -364,16 +364,14 @@ class DxfStatements(object): def gerbers(): yield 'D{0}*'.format(self.dcode) if self.draw_mode == DxfFile.DM_FILL: + yield 'G36*' for statement in self.statements: - yield 'G36*' if isinstance(statement, DxfCircleStatement) or \ (isinstance(statement, DxfPolylineStatement) and statement.entity.is_closed): yield statement.to_gerber(settings) - yield 'G37*' for path in self.paths: - yield 'G36*' yield path.to_gerber(settings) - yield 'G37*' + yield 'G37*' else: for statement in self.statements: yield statement.to_gerber( -- cgit From eda75275505e14439e2dcd1990d2b95217546db1 Mon Sep 17 00:00:00 2001 From: opiopan Date: Mon, 1 Apr 2019 22:07:56 +0900 Subject: compliant with Python 2.7 --- gerberex/dxf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index e863238..1b2ddf7 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -3,7 +3,7 @@ # Copyright 2019 Hiroshi Murayama -import io +import io, sys from math import pi, cos, sin, tan, atan, atan2, acos, asin, sqrt import dxfgrabber from gerber.cam import CamFile, FileSettings @@ -561,6 +561,8 @@ class DxfFile(CamFile): raise Exception('Not supported') def loads(data, filename=None): + if sys.version_info.major == 2: + data = unicode(data) stream = io.StringIO(data) dxf = dxfgrabber.read(stream) return DxfFile(dxf) -- cgit From cb420e39e278f7ab6f002600a7698d7be101eb7d Mon Sep 17 00:00:00 2001 From: opiopan Date: Wed, 3 Apr 2019 00:30:00 +0900 Subject: fix a rotaion issue --- gerberex/dxf.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index 1b2ddf7..39d256b 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -11,17 +11,11 @@ from gerber.utils import inch, metric, write_gerber_value from gerber.gerber_statements import ADParamStmt from gerber.excellon_statements import ExcellonTool from gerber.excellon_statements import CoordinateStmt +from gerberex.utility import is_equal_point, is_equal_value from gerberex.dxf_path import generate_closed_paths ACCEPTABLE_ERROR = 0.001 -def is_equal_value(a, b, error_range=0): - return a - b <= error_range and a - b >= -error_range - -def is_equal_point(a, b, error_range=0): - return is_equal_value(a[0], b[0], error_range) and \ - is_equal_value(a[1], b[1], error_range) - class DxfStatement(object): def __init__(self, entity): self.entity = entity -- cgit From 6b4603af21839c94a33d3804c364ff1b809eb341 Mon Sep 17 00:00:00 2001 From: opiopan Date: Wed, 3 Apr 2019 12:17:59 +0900 Subject: add rectangle generator --- gerberex/dxf.py | 82 +++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 25 deletions(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index 39d256b..9a2186c 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -42,10 +42,16 @@ class DxfStatement(object): raise Exception('Not implemented') class DxfLineStatement(DxfStatement): - def __init__(self, entity): + @classmethod + def from_entity(cls, entity): + start = (entity.start[0], entity.start[1]) + end = (entity.end[0], entity.end[1]) + return cls(entity, start, end) + + def __init__(self, entity, start, end): super(DxfLineStatement, self).__init__(entity) - self.start = (self.entity.start[0], self.entity.start[1]) - self.end = (self.entity.end[0], self.entity.end[1]) + self.start = start + self.end = end def to_gerber(self, settings=FileSettings(), pitch=0, width=0): if pitch == 0: @@ -329,7 +335,7 @@ class DxfPolylineStatement(DxfStatement): self.entity.bulge[idx] = metric(self.entity.bulge[idx]) class DxfStatements(object): - def __init__(self, entities, units, dcode=10, draw_mode=None): + def __init__(self, statements, units, dcode=10, draw_mode=None): if draw_mode == None: draw_mode = DxfFile.DM_LINE self._units = units @@ -338,16 +344,7 @@ class DxfStatements(object): self.pitch = inch(1) if self._units == 'inch' else 1 self.width = 0 self.error_range = inch(ACCEPTABLE_ERROR) if self._units == 'inch' else ACCEPTABLE_ERROR - self.statements = [] - for entity in entities: - if entity.dxftype == 'LWPOLYLINE': - self.statements.append(DxfPolylineStatement(entity)) - elif entity.dxftype == 'LINE': - self.statements.append(DxfLineStatement(entity)) - elif entity.dxftype == 'CIRCLE': - self.statements.append(DxfCircleStatement(entity)) - elif entity.dxftype == 'ARC': - self.statements.append(DxfArcStatement(entity)) + self.statements = statements self.paths = generate_closed_paths(self.statements, self.error_range) @property @@ -458,18 +455,52 @@ class DxfFile(CamFile): FT_RX274X = 0 FT_EXCELLON = 1 - def __init__(self, dxf, settings=None, draw_mode=None, filename=None): - if not settings: - settings = FileSettings(zero_suppression='leading') + @classmethod + def from_dxf(cls, dxf, settings=None, draw_mode=None, filename=None): + fsettings = settings if settings else \ + FileSettings(zero_suppression='leading') - if draw_mode == None: - draw_mode = self.DM_LINE if dxf.header['$INSUNITS'] == 1: - settings.units = 'inch' - settings.format = (2, 5) + fsettings.units = 'inch' + if not settings: + fsettings.format = (2, 5) + else: + fsettings.units = 'metric' + if not settings: + fsettings.format = (3, 4) + + statements = [] + for entity in dxf.entities: + if entity.dxftype == 'LWPOLYLINE': + statements.append(DxfPolylineStatement(entity)) + elif entity.dxftype == 'LINE': + statements.append(DxfLineStatement.from_entity(entity)) + elif entity.dxftype == 'CIRCLE': + statements.append(DxfCircleStatement(entity)) + elif entity.dxftype == 'ARC': + statements.append(DxfArcStatement(entity)) + + return cls(statements, fsettings, draw_mode, filename) + + @classmethod + def rectangle(cls, width, height, left=0, bottom=0, units='metric', draw_mode=None, filename=None): + if units == 'metric': + settings = FileSettings(units=units, zero_suppression='leading', format=(3,4)) else: - settings.units = 'metric' - settings.format = (3, 4) + settings = FileSettings(units=units, zero_suppression='leading', format=(2,5)) + statements = [ + DxfLineStatement(None, (left, bottom), (left + width, bottom)), + DxfLineStatement(None, (left + width, bottom), (left + width, bottom + height)), + DxfLineStatement(None, (left + width, bottom + height), (left, bottom + height)), + DxfLineStatement(None, (left, bottom + height), (left, bottom)), + ] + return cls(statements, settings, draw_mode, filename) + + def __init__(self, statements, settings=None, draw_mode=None, filename=None): + if not settings: + settings = FileSettings(units='metric', format=(3,4), zero_suppression='leading') + if draw_mode == None: + draw_mode = self.DM_LINE super(DxfFile, self).__init__(settings=settings, filename=filename) self._draw_mode = draw_mode @@ -477,7 +508,8 @@ class DxfFile(CamFile): self.header2 = DxfHeader2Statement() self.aperture = ADParamStmt.circle(dcode=10, diameter=0.0) - self.statements = DxfStatements(dxf.entities, self.units, dcode=self.aperture.d, draw_mode=self.draw_mode) + self.statements = DxfStatements( + statements, self.units, dcode=self.aperture.d, draw_mode=self.draw_mode) @property def dcode(self): @@ -559,4 +591,4 @@ def loads(data, filename=None): data = unicode(data) stream = io.StringIO(data) dxf = dxfgrabber.read(stream) - return DxfFile(dxf) + return DxfFile.from_dxf(dxf) -- cgit From d53293a609a83aa945af6864285b90d36bcbdd69 Mon Sep 17 00:00:00 2001 From: opiopan Date: Wed, 3 Apr 2019 19:05:20 +0900 Subject: add move and rotation capability to DxfFile object --- gerberex/dxf.py | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 7 deletions(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index 9a2186c..ba02f08 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -7,7 +7,7 @@ import io, sys from math import pi, cos, sin, tan, atan, atan2, acos, asin, sqrt import dxfgrabber from gerber.cam import CamFile, FileSettings -from gerber.utils import inch, metric, write_gerber_value +from gerber.utils import inch, metric, write_gerber_value, rotate_point from gerber.gerber_statements import ADParamStmt from gerber.excellon_statements import ExcellonTool from gerber.excellon_statements import CoordinateStmt @@ -41,6 +41,13 @@ class DxfStatement(object): def reverse(self): raise Exception('Not implemented') + def offset(self, offset_x, offset_y): + raise Exception('Not supported') + + def rotate(self, angle, center=(0, 0)): + raise Exception('Not supported') + + class DxfLineStatement(DxfStatement): @classmethod def from_entity(cls, entity): @@ -93,9 +100,9 @@ class DxfLineStatement(DxfStatement): def to_metric(self): self.start = ( - metric(self.start[0]), inch(self.start[1])) + metric(self.start[0]), metric(self.start[1])) self.end = ( - metric(self.end[0]), inch(self.end[1])) + metric(self.end[0]), metric(self.end[1])) def is_equal_to(self, target, error_range=0): if not isinstance(target, DxfLineStatement): @@ -127,6 +134,14 @@ class DxfLineStatement(DxfStatement): y0 += yd d += pitch + def offset(self, offset_x, offset_y): + self.start = (self.start[0] + offset_x, self.start[1] + offset_y) + self.end = (self.end[0] + offset_x, self.end[1] + offset_y) + + def rotate(self, angle, center=(0, 0)): + 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) @@ -177,6 +192,12 @@ class DxfCircleStatement(DxfStatement): 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) @@ -251,6 +272,18 @@ class DxfArcStatement(DxfStatement): self.start = self.end self.end = tmp + 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) + self.end = (self.end[0] + offset_x, self.end[1] + offset_y) + + def rotate(self, angle, center=(0, 0)): + self.start_angle += angle + self.end_angle += angle + self.center = rotate_point(self.center, angle, center) + self.start = rotate_point(self.start, angle, center) + self.end = rotate_point(self.end, angle, center) + class DxfPolylineStatement(DxfStatement): def __init__(self, entity): super(DxfPolylineStatement, self).__init__(entity) @@ -324,7 +357,6 @@ class DxfPolylineStatement(DxfStatement): for idx in range(0, len(self.entity.points)): self.entity.points[idx] = ( inch(self.entity.points[idx][0]), inch(self.entity.points[idx][1])) - self.entity.bulge[idx] = inch(self.entity.bulge[idx]) def to_metric(self): self.start = (metric(self.start[0]), metric(self.start[1])) @@ -332,7 +364,16 @@ class DxfPolylineStatement(DxfStatement): for idx in range(0, len(self.entity.points)): self.entity.points[idx] = ( metric(self.entity.points[idx][0]), metric(self.entity.points[idx][1])) - self.entity.bulge[idx] = metric(self.entity.bulge[idx]) + + def offset(self, offset_x, offset_y): + for idx in range(len(self.entity.points)): + self.entity.points[idx] = ( + self.entity.points[idx][0] + offset_x, self.entity.points[idx][1] + offset_y) + + def rotate(self, angle, center=(0, 0)): + 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): @@ -400,6 +441,14 @@ class DxfStatements(object): statement.to_metric() for path in self.paths: path.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) class DxfHeaderStatement(object): def to_gerber(self, settings): @@ -583,8 +632,11 @@ class DxfFile(CamFile): self.pitch = metric(self.pitch) self.units = 'metric' - def offset(self, ofset_x, offset_y): - raise Exception('Not supported') + def offset(self, offset_x, offset_y): + self.statements.offset(offset_x, offset_y) + + def rotate(self, angle, center=(0, 0)): + self.statements.rotate(angle, center) def loads(data, filename=None): if sys.version_info.major == 2: -- cgit From 13ab9db6e7571f3d0fcb406bfe6795eea9ce4e1c Mon Sep 17 00:00:00 2001 From: Hiroshi Murayama Date: Sun, 25 Aug 2019 20:16:53 +0900 Subject: support incremental coordinate for excellon --- gerberex/dxf.py | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index ba02f08..389cca9 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -13,6 +13,7 @@ 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.excellon import write_excellon_header ACCEPTABLE_ERROR = 0.001 @@ -465,31 +466,8 @@ class DxfHeaderStatement(object): ) def to_excellon(self, settings): - return 'M48\n'\ - 'FMAT,2\n'\ - 'ICI,{0}\n'\ - '{1},{2},{3}.{4}\n'\ - '{5}'.format( - 'ON' if settings.notation == 'incremental' else 'OFF', - 'INCH' if settings.units == 'inch' else 'METRIC', - 'TZ' if settings.zero_suppression == 'leading' else 'LZ', - '0' * settings.format[0], '0' * settings.format[1], - 'M72' if settings.units == 'inch' else 'M71' - ) - - def to_inch(self): - pass - - def to_metric(self): pass -class DxfHeader2Statement(object): - def to_gerber(self, settings): - pass - - def to_excellon(self, settings): - return '%' - def to_inch(self): pass @@ -555,7 +533,6 @@ class DxfFile(CamFile): self._draw_mode = draw_mode self.header = DxfHeaderStatement() - self.header2 = DxfHeader2Statement() self.aperture = ADParamStmt.circle(dcode=10, diameter=0.0) self.statements = DxfStatements( statements, self.units, dcode=self.aperture.d, draw_mode=self.draw_mode) @@ -607,10 +584,8 @@ class DxfFile(CamFile): f.write(self.statements.to_gerber(self.settings) + '\n') f.write('M02*\n') else: - tool = ExcellonTool(self.settings, number=1, diameter=self.width) - f.write(self.header.to_excellon(self.settings) + '\n') - f.write(tool.to_excellon(self.settings) + '\n') - f.write(self.header2.to_excellon(self.settings) + '\n') + tools = [ExcellonTool(self.settings, number=1, diameter=self.width)] + write_excellon_header(f, self.settings, tools) f.write('T01\n') f.write(self.statements.to_excellon(self.settings) + '\n') f.write('M30\n') -- cgit From 2b1c751ff76ebd6901633235ee694cc93dabce81 Mon Sep 17 00:00:00 2001 From: Hiroshi Murayama Date: Mon, 9 Sep 2019 09:07:38 +0900 Subject: improve compatibility with RS-274x specification: - can merge multiple files having different file scope modifier, such as AS, MI, OF, SF, and IR - support modal coordinate notation --- gerberex/dxf.py | 31 ++++--------------------------- 1 file changed, 4 insertions(+), 27 deletions(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index 389cca9..ae543ae 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -14,6 +14,7 @@ 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.excellon import write_excellon_header +from gerberex.rs274x import write_gerber_header ACCEPTABLE_ERROR = 0.001 @@ -395,6 +396,8 @@ class DxfStatements(object): def to_gerber(self, settings=FileSettings()): def gerbers(): + yield 'G75*' + yield '%LPD*%' yield 'D{0}*'.format(self.dcode) if self.draw_mode == DxfFile.DM_FILL: yield 'G36*' @@ -451,29 +454,6 @@ class DxfStatements(object): for statement in self.statements: statement.rotate(angle, center) -class DxfHeaderStatement(object): - def to_gerber(self, settings): - return 'G75*\n'\ - '%MO{0}*%\n'\ - '%OFA0B0*%\n'\ - '%FS{1}AX{2}{3}Y{4}{5}*%\n'\ - '%IPPOS*%\n'\ - '%LPD*%'.format( - 'IN' if settings.units == 'inch' else 'MM', - 'L' if settings.zero_suppression == 'leading' else 'T', - settings.format[0], settings.format[1], - settings.format[0], settings.format[1] - ) - - def to_excellon(self, settings): - pass - - def to_inch(self): - pass - - def to_metric(self): - pass - class DxfFile(CamFile): DM_LINE = 0 DM_FILL = 1 @@ -531,7 +511,6 @@ class DxfFile(CamFile): super(DxfFile, self).__init__(settings=settings, filename=filename) self._draw_mode = draw_mode - self.header = DxfHeaderStatement() self.aperture = ADParamStmt.circle(dcode=10, diameter=0.0) self.statements = DxfStatements( @@ -579,7 +558,7 @@ class DxfFile(CamFile): filename = filename if filename is not None else self.filename with open(filename, 'w') as f: if filetype == self.FT_RX274X: - f.write(self.header.to_gerber(self.settings) + '\n') + write_gerber_header(f, self.settings) f.write(self.aperture.to_gerber(self.settings) + '\n') f.write(self.statements.to_gerber(self.settings) + '\n') f.write('M02*\n') @@ -593,7 +572,6 @@ class DxfFile(CamFile): def to_inch(self): if self.units == 'metric': - self.header.to_inch() self.aperture.to_inch() self.statements.to_inch() self.pitch = inch(self.pitch) @@ -601,7 +579,6 @@ class DxfFile(CamFile): def to_metric(self): if self.units == 'inch': - self.header.to_metric() self.aperture.to_metric() self.statements.to_metric() self.pitch = metric(self.pitch) -- cgit 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 --- gerberex/dxf.py | 327 ++++++++++++++++++++++---------------------------------- 1 file changed, 125 insertions(+), 202 deletions(-) (limited to 'gerberex/dxf.py') 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) -- cgit From 48b35377b1cce897a70d1d037b3e73b616af9be7 Mon Sep 17 00:00:00 2001 From: Hiroshi Murayama Date: Sat, 28 Sep 2019 20:42:11 +0900 Subject: minor refactoring --- gerberex/dxf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index 8f0b984..eca246c 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -120,7 +120,7 @@ class DxfArcStatement(DxfStatement): self.start = (self.center[0] + self.radius, self.center[1]) self.end = self.start self.start_angle = 0 - self.end_angle = -360 + self.end_angle = 360 self.is_closed = True elif entity.dxftype == 'ARC': self.start_angle = self.entity.start_angle -- cgit From d7a069324222bb8f69adc9b1c815fc9f3f6a29d6 Mon Sep 17 00:00:00 2001 From: Hiroshi Murayama Date: Mon, 30 Sep 2019 18:52:17 +0900 Subject: fix a issue that coordinate normalization for excellon is imperfect --- gerberex/dxf.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index eca246c..00b7695 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -475,9 +475,8 @@ class DxfFile(CamFile): self.statements.pitch = value def write(self, filename=None, filetype=FT_RX274X): - if self.settings.notation != 'absolute': - raise Exception('DXF file\'s notation must be absolute ') - + self.settings.notation = 'absolute' + self.settings.zeros = 'trailing' filename = filename if filename is not None else self.filename with open(filename, 'w') as f: if filetype == self.FT_RX274X: -- cgit From 244fcaa5346f4fad819cc2b72857cfb2c472944a Mon Sep 17 00:00:00 2001 From: Hiroshi Murayama Date: Sat, 28 Dec 2019 23:45:33 +0900 Subject: add a function that generate filled gerberdata with representing internal shape by fliping polarity --- gerberex/dxf.py | 282 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 274 insertions(+), 8 deletions(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index 00b7695..95a3114 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -12,12 +12,88 @@ 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_paths +from gerberex.dxf_path import generate_paths, judge_containment from gerberex.excellon import write_excellon_header from gerberex.rs274x import write_gerber_header ACCEPTABLE_ERROR = 0.001 +def _normalize_angle(start_angle, end_angle): + angle = end_angle - start_angle + if angle > 0: + start = start_angle % 360 + else: + angle = -angle + start = end_angle % 360 + angle = min(angle, 360) + start = start - 360 if start > 180 else start + + regions = [] + while angle > 0: + end = start + angle + if end <= 180: + regions.append((start * pi / 180, end * pi / 180)) + angle = 0 + else: + regions.append((start * pi / 180, pi)) + angle = end - 180 + start = -180 + return regions + +def _intersections_of_line_and_circle(start, end, center, radius, error_range): + x1 = start[0] - center[0] + y1 = start[1] - center[1] + x2 = end[0] - center[0] + y2 = end[1] - center[1] + + dx = x2 - x1 + dy = y2 - y1 + dr = sqrt(dx * dx + dy * dy) + D = x1 * y2 - x2 * y1 + + D2 = D * D + dr2 = dr * dr + r2 = radius * radius + delta = r2 * dr2 - D2 + e4 = error_range * error_range * error_range * error_range * 10 + if delta > - e4 and delta < e4: + delta = 0 + if delta < 0: + return None + + sqrt_D = sqrt(delta) + E_x = -dx * sqrt_D if dy < 0 else dx * sqrt_D + E_y = abs(dy) * sqrt_D + + p1_x = (D * dy + E_x) / dr2 + p2_x = (D * dy - E_x) / dr2 + p1_y = (-D * dx + E_y) / dr2 + p2_y = (-D * dx - E_y) / dr2 + + p1_angle = atan2(p1_y, p1_x) + p2_angle = atan2(p2_y, p2_x) + if dx == 0: + p1_t = (p1_y - y1) / dy + p2_t = (p2_y - y1) / dy + else: + p1_t = (p1_x - x1) / dx + p2_t = (p2_x - x1) / dx + + if delta == 0: + return ( + (p1_x + center[0], p1_y + center[1]), + None, + p1_angle, None, + p1_t, None + ) + else: + return ( + (p1_x + center[0], p1_y + center[1]), + (p2_x + center[0], p2_y + center[1]), + p1_angle, p2_angle, + p1_t, p2_t + ) + class DxfStatement(object): def __init__(self, entity): self.entity = entity @@ -51,6 +127,13 @@ class DxfLineStatement(DxfStatement): end = (entity.end[0], entity.end[1]) return cls(entity, start, end) + @property + def bounding_box(self): + return (min(self.start[0], self.end[0]), + min(self.start[1], self.end[1]), + max(self.start[0], self.end[0]), + max(self.start[1], self.end[1])) + def __init__(self, entity, start, end): super(DxfLineStatement, self).__init__(entity) self.start = start @@ -110,6 +193,53 @@ class DxfLineStatement(DxfStatement): def rotate(self, angle, center=(0, 0)): self.start = rotate_point(self.start, angle, center) self.end = rotate_point(self.end, angle, center) + + def intersections_with_halfline(self, point_from, point_to, error_range): + denominator = (self.end[0] - self.start[0]) * (point_to[1] - point_from[1]) - \ + (self.end[1] - self.start[1]) * (point_to[0] - point_from[0]) + de = error_range * error_range + if denominator > -de and denominator < de: + return [] + from_dx = point_from[0] - self.start[0] + from_dy = point_from[1] - self.start[1] + r = ((point_to[1] - point_from[1]) * from_dx - + (point_to[0] - point_from[0]) * from_dy) / denominator + s = ((self.end[1] - self.start[1]) * from_dx - + (self.end[0] - self.start[0]) * from_dy) / denominator + dx = (self.end[0] - self.start[0]) + dy = (self.end[1] - self.start[1]) + le = error_range / sqrt(dx * dx + dy * dy) + if s < 0 or r < -le or r > 1 + le: + return [] + + pt = (self.start[0] + (self.end[0] - self.start[0]) * r, + self.start[1] + (self.end[1] - self.start[1]) * r) + if is_equal_point(pt, self.start, error_range): + return [] + else: + return [pt] + + def intersections_with_arc(self, center, radius, angle_regions, error_range): + intersection = \ + _intersections_of_line_and_circle(self.start, self.end, center, radius, error_range) + if intersection is None: + return [] + else: + p1, p2, p1_angle, p2_angle, p1_t, p2_t = intersection + + pts = [] + if p1_t >= 0 and p1_t <= 1: + for region in angle_regions: + if p1_angle >= region[0] and p1_angle <= region[1]: + pts.append(p1) + break + if p2 is not None and p2_t >= 0 and p2_t <= 1: + for region in angle_regions: + if p2_angle >= region[0] and p2_angle <= region[1]: + pts.append(p2) + break + + return pts class DxfArcStatement(DxfStatement): def __init__(self, entity): @@ -139,6 +269,12 @@ class DxfArcStatement(DxfStatement): self.is_closed = angle >= 360 or angle <= -360 else: raise Exception('invalid DXF type was specified') + self.angle_regions = _normalize_angle(self.start_angle, self.end_angle) + + @property + def bounding_box(self): + return (self.center[0] - self.radius, self.center[1] - self.radius, + self.center[0] + self.radius, self.center[1] + self.radius) def to_inch(self): self.radius = inch(self.radius) @@ -204,6 +340,82 @@ class DxfArcStatement(DxfStatement): self.center = rotate_point(self.center, angle, center) self.start = rotate_point(self.start, angle, center) self.end = rotate_point(self.end, angle, center) + self.angle_regions = _normalize_angle(self.start_angle, self.end_angle) + + def intersections_with_halfline(self, point_from, point_to, error_range): + intersection = \ + _intersections_of_line_and_circle( + point_from, point_to, self.center, self.radius, error_range) + if intersection is None: + return [] + else: + p1, p2, p1_angle, p2_angle, p1_t, p2_t = intersection + + if is_equal_point(p1, self.start, error_range): + p1 = None + elif p2 is not None and is_equal_point(p2, self.start, error_range): + p2 = None + + aerror = error_range * self.radius + pts = [] + if p1 is not None and p1_t >= 0 and not is_equal_point(p1, self.start, error_range): + for region in self.angle_regions: + if p1_angle >= region[0] - aerror and p1_angle <= region[1] + aerror: + pts.append(p1) + break + if p2 is not None and p2_t >= 0 and not is_equal_point(p2, self.start, error_range): + for region in self.angle_regions: + if p2_angle >= region[0] - aerror and p2_angle <= region[1] + aerror: + pts.append(p2) + break + + return pts + + def intersections_with_arc(self, center, radius, angle_regions, error_range): + x1 = center[0] - self.center[0] + y1 = center[1] - self.center[1] + r1 = self.radius + r2 = radius + cd_sq = x1 * x1 + y1 * y1 + cd = sqrt(cd_sq) + rd = abs(r1 - r2) + + if (cd >= 0 and cd <= rd) or cd >= r1 + r2: + return [] + + A = (cd_sq + r1 * r1 - r2 * r2) / 2 + scale = sqrt(cd_sq * r1 * r1 - A * A) / cd_sq + xl = A * x1 / cd_sq + xr = y1 * scale + yl = A * y1 / cd_sq + yr = x1 * scale + + pt1_x = xl + xr + pt1_y = yl - yr + pt2_x = xl - xr + pt2_y = yl + yr + pt1_angle1 = atan2(pt1_y, pt1_x) + pt1_angle2 = atan2(pt1_y - y1, pt1_x - x1) + pt2_angle1 = atan2(pt2_y, pt2_x) + pt2_angle2 = atan2(pt2_y - y1, pt2_x - x1) + + aerror = error_range * self.radius + pts=[] + for region in self.angle_regions: + if pt1_angle1 >= region[0] and pt1_angle1 <= region[1]: + for region in angle_regions: + if pt1_angle2 >= region[0] - aerror and pt1_angle2 <= region[1] + aerror: + pts.append((pt1_x + self.center[0], pt1_y + self.center[1])) + break + break + for region in self.angle_regions: + if pt2_angle1 >= region[0] and pt2_angle1 <= region[1]: + for region in angle_regions: + if pt2_angle2 >= region[0] - aerror and pt2_angle2 <= region[1] + aerror: + pts.append((pt2_x + self.center[0], pt2_y + self.center[1])) + break + break + return pts class DxfPolylineStatement(DxfStatement): def __init__(self, entity): @@ -293,31 +505,69 @@ class DxfPolylineStatement(DxfStatement): 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: + def __init__(self, statements, units, dcode=10, draw_mode=None, fill_mode=None): + if draw_mode is None: draw_mode = DxfFile.DM_LINE + if fill_mode is None: + fill_mode = DxfFile.FM_TURN_OVER self._units = units self.dcode = dcode self.draw_mode = draw_mode + self.fill_mode = fill_mode self.pitch = inch(1) if self._units == 'inch' else 1 self.width = 0 self.error_range = inch(ACCEPTABLE_ERROR) if self._units == 'inch' else ACCEPTABLE_ERROR - self.statements = statements + self.statements = list(filter( + lambda i: not (isinstance(i, DxfLineStatement) and \ + is_equal_point(i.start, i.end, self.error_range)), + statements + )) self.close_paths, self.open_paths = generate_paths(self.statements, self.error_range) + self.sorted_close_paths = [] + self.polarity = True # True means dark, False means clear @property def units(self): return _units + def _polarity_command(self, polarity=None): + if polarity is None: + polarity = self.polarity + return '%LPD*%' if polarity else '%LPC*%' + + def _prepare_sorted_close_paths(self): + if self.sorted_close_paths: + return + for i in range(0, len(self.close_paths)): + for j in range(i + 1, len(self.close_paths)): + containee, container = judge_containment( + self.close_paths[i], self.close_paths[j], self.error_range) + if containee is not None: + containee.containers.append(container) + self.sorted_close_paths = sorted(self.close_paths, key=lambda path: len(path.containers)) + def to_gerber(self, settings=FileSettings()): def gerbers(): yield 'G75*' - yield '%LPD*%' + yield self._polarity_command() yield 'D{0}*'.format(self.dcode) if self.draw_mode == DxfFile.DM_FILL: yield 'G36*' - for path in self.close_paths: - yield path.to_gerber(settings) + if self.fill_mode == DxfFile.FM_TURN_OVER: + self._prepare_sorted_close_paths() + polarity = self.polarity + level = 0 + for path in self.sorted_close_paths: + if len(path.containers) > level: + level = len(path.containers) + polarity = not polarity + yield 'G37*' + yield self._polarity_command(polarity) + yield 'G36*' + yield path.to_gerber(settings) + else: + for path in self.close_paths: + yield path.to_gerber(settings) yield 'G37*' else: pitch = self.pitch if self.draw_mode == DxfFile.DM_MOUSE_BITES else 0 @@ -378,6 +628,9 @@ class DxfFile(CamFile): DM_FILL = 1 DM_MOUSE_BITES = 2 + FM_SIMPLE = 0 + FM_TURN_OVER = 1 + FT_RX274X = 0 FT_EXCELLON = 1 @@ -430,6 +683,7 @@ class DxfFile(CamFile): super(DxfFile, self).__init__(settings=settings, filename=filename) self._draw_mode = draw_mode + self._fill_mode = self.FM_TURN_OVER self.aperture = ADParamStmt.circle(dcode=10, diameter=0.0) if settings.units == 'inch': @@ -437,7 +691,7 @@ class DxfFile(CamFile): else: self.aperture.to_metric() self.statements = DxfStatements( - statements, self.units, dcode=self.aperture.d, draw_mode=self.draw_mode) + statements, self.units, dcode=self.aperture.d, draw_mode=self.draw_mode, fill_mode=self.filename) @property def dcode(self): @@ -466,6 +720,15 @@ class DxfFile(CamFile): self._draw_mode = value self.statements.draw_mode = value + @property + def fill_mode(self): + return self._fill_mode + + @fill_mode.setter + def fill_mode(self, value): + self._fill_mode = value + self.statements.fill_mode = value + @property def pitch(self): return self.statements.pitch @@ -512,6 +775,9 @@ class DxfFile(CamFile): def rotate(self, angle, center=(0, 0)): self.statements.rotate(angle, center) + def negate_polarity(self): + self.statements.polarity = not self.statements.polarity + def loads(data, filename=None): if sys.version_info.major == 2: data = unicode(data) -- cgit From ca23fbd9534ab3cba3fd7b032816766c1150ebf9 Mon Sep 17 00:00:00 2001 From: Hiroshi Murayama Date: Mon, 30 Dec 2019 17:51:48 +0900 Subject: fix bugs that fail judgement of path's containment --- gerberex/dxf.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'gerberex/dxf.py') diff --git a/gerberex/dxf.py b/gerberex/dxf.py index 95a3114..2341092 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -51,12 +51,13 @@ def _intersections_of_line_and_circle(start, end, center, radius, error_range): dr = sqrt(dx * dx + dy * dy) D = x1 * y2 - x2 * y1 + distance = abs(dy * x1 - dx * y1) / dr + D2 = D * D dr2 = dr * dr r2 = radius * radius delta = r2 * dr2 - D2 - e4 = error_range * error_range * error_range * error_range * 10 - if delta > - e4 and delta < e4: + if distance > radius - error_range and distance < radius + error_range: delta = 0 if delta < 0: return None @@ -198,7 +199,7 @@ class DxfLineStatement(DxfStatement): denominator = (self.end[0] - self.start[0]) * (point_to[1] - point_from[1]) - \ (self.end[1] - self.start[1]) * (point_to[0] - point_from[0]) de = error_range * error_range - if denominator > -de and denominator < de: + if denominator >= -de and denominator <= de: return [] from_dx = point_from[0] - self.start[0] from_dy = point_from[1] - self.start[1] @@ -356,16 +357,25 @@ class DxfArcStatement(DxfStatement): elif p2 is not None and is_equal_point(p2, self.start, error_range): p2 = None + def is_contained(angle, region, error): + if angle >= region[0] - error and angle <= region[1] + error: + return True + if angle < 0 and region[1] > 0: + angle = angle + 2 * pi + elif angle > 0 and region[0] < 0: + angle = angle - 2 * pi + return angle >= region[0] - error and angle <= region[1] + error + aerror = error_range * self.radius pts = [] if p1 is not None and p1_t >= 0 and not is_equal_point(p1, self.start, error_range): for region in self.angle_regions: - if p1_angle >= region[0] - aerror and p1_angle <= region[1] + aerror: + if is_contained(p1_angle, region, aerror): pts.append(p1) break if p2 is not None and p2_t >= 0 and not is_equal_point(p2, self.start, error_range): for region in self.angle_regions: - if p2_angle >= region[0] - aerror and p2_angle <= region[1] + aerror: + if is_contained(p2_angle, region, aerror): pts.append(p2) break -- cgit