From fcd704e1eef9034e2000f55b2918d7df41379408 Mon Sep 17 00:00:00 2001
From: opiopan <opiopan@gmail.com>
Date: Sat, 30 Mar 2019 11:16:13 +0900
Subject: add mouse bites generator function

---
 gerberex/composition.py |  22 ++++++
 gerberex/dxf.py         | 205 ++++++++++++++++++++++++++++++++++++++----------
 2 files changed, 184 insertions(+), 43 deletions(-)

(limited to 'gerberex')

diff --git a/gerberex/composition.py b/gerberex/composition.py
index afcaf97..73f5702 100644
--- a/gerberex/composition.py
+++ b/gerberex/composition.py
@@ -127,10 +127,13 @@ class DrillComposition(Composition):
         self.header2_statements = []
         self.tools = []
         self.hits = []
+        self.dxf_statements = []
     
     def merge(self, file):
         if isinstance(file, gerberex.excellon.ExcellonFileEx):
             self._merge_excellon(file)
+        elif isinstance(file, gerberex.DxfFile):
+            self._merge_dxf(file)
         else:
             raise Exception('unsupported file type')
 
@@ -147,6 +150,9 @@ class DrillComposition(Composition):
                 for h in self.hits:
                     if h.tool.number == t.number:
                         yield CoordinateStmt(*h.position).to_excellon(self.settings)
+                for num, statement in self.dxf_statements:
+                    if num == t.number:
+                        yield statement.to_excellon(self.settings)
             yield EndOfProgramStmt().to_excellon()
         
         with open(path, 'w') as f:
@@ -186,6 +192,22 @@ class DrillComposition(Composition):
             hit.tool = tool_map[hit.tool.number]
             self.hits.append(hit)
     
+    def _merge_dxf(self, file):
+        if not self.settings:
+            self.settings = file.settings
+        else:
+            if self.settings.units == 'metric':
+                file.to_metric()
+            else:
+                file.to_inch()
+
+        if not self.header1_statements:
+            self.header1_statements = file.header
+            self.header2_statements = file.header2
+        
+        tool = self._register_tool(ExcellonTool(self.settings, number=1, diameter=file.width))
+        self.dxf_statements.append((tool.number, file.statements))
+
     def _register_tool(self, tool):
         for existing in self.tools:
             if existing.equivalent(tool):
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