aboutsummaryrefslogtreecommitdiff
path: root/gerberex
diff options
context:
space:
mode:
authoropiopan <opiopan@gmail.com>2019-03-31 13:30:15 +0900
committeropiopan <opiopan@gmail.com>2019-03-31 13:30:15 +0900
commit900d992fa3af05f93ac7a4cf717f28598e1a868d (patch)
treefdd66ac71dcfb6a4d173268f0ead73cdcfd47600 /gerberex
parentfcd704e1eef9034e2000f55b2918d7df41379408 (diff)
downloadpcb-tools-extension-900d992fa3af05f93ac7a4cf717f28598e1a868d.tar.gz
pcb-tools-extension-900d992fa3af05f93ac7a4cf717f28598e1a868d.tar.bz2
pcb-tools-extension-900d992fa3af05f93ac7a4cf717f28598e1a868d.zip
auto detection closed paths in the collection of DXF arc object and line object, then fill these closed path
Diffstat (limited to 'gerberex')
-rw-r--r--gerberex/dxf.py198
-rw-r--r--gerberex/dxf_path.py197
2 files changed, 342 insertions, 53 deletions
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):
diff --git a/gerberex/dxf_path.py b/gerberex/dxf_path.py
new file mode 100644
index 0000000..b066b08
--- /dev/null
+++ b/gerberex/dxf_path.py
@@ -0,0 +1,197 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright 2019 Hiroshi Murayama <opiopan@gmail.com>
+
+from gerber.utils import inch, metric, write_gerber_value
+from gerber.cam import FileSettings
+
+class DxfPath(object):
+ def __init__(self, statement, error_range=0):
+ self.statements = [statement]
+ self.error_range = error_range
+
+ @property
+ def start(self):
+ return self.statements[0].start
+
+ @property
+ def end(self):
+ return self.statements[-1].end
+
+ @property
+ def is_closed(self):
+ from gerberex.dxf import is_equal_point
+ return len(self.statements) > 1 and \
+ is_equal_point(self.start, self.end, self.error_range)
+
+ def is_equal_to(self, target, error_range=0):
+ from gerberex.dxf import is_equal_point
+ if not isinstance(target, DxfPath):
+ return False
+ if len(self.statements) != len(target.statements):
+ return False
+ if is_equal_point(self.start, target.start, error_range) and \
+ is_equal_point(self.end, target.end, error_range):
+ for i in range(0, len(self.statements)):
+ if not self.statements[i].is_equal_to(target.statements[i], error_range):
+ return False
+ return True
+ elif is_equal_point(self.start, target.end, error_range) and \
+ is_equal_point(self.end, target.start, error_range):
+ for i in range(0, len(self.statements)):
+ if not self.statements[i].is_equal_to(target.statements[-1 - i], error_range):
+ return False
+ return True
+ return False
+
+ def to_inch(self):
+ self.error_range = inch(self.error_range)
+
+ def to_metric(self):
+ self.error_range = metric(self.error_range)
+
+ def reverse(self):
+ rlist = []
+ for statement in reversed(self.statements):
+ statement.reverse()
+ rlist.append(statement)
+ self.statements = rlist
+
+ def merge(self, element, error_range=0):
+ from gerberex.dxf import is_equal_point
+ if self.is_closed or element.is_closed:
+ return False
+ if not error_range:
+ error_range = self.error_range
+ if is_equal_point(self.end, element.start, error_range):
+ return self._append_at_end(element, error_range)
+ elif is_equal_point(self.end, element.end, error_range):
+ element.reverse()
+ return self._append_at_end(element, error_range)
+ elif is_equal_point(self.start, element.end, error_range):
+ return self._insert_on_top(element, error_range)
+ elif is_equal_point(self.start, element.start, error_range):
+ element.reverse()
+ return self._insert_on_top(element, error_range)
+ else:
+ return False
+
+ def _append_at_end(self, element, error_range=0):
+ if isinstance(element, DxfPath):
+ if self.is_equal_to(element, error_range):
+ return False
+ for i in range(0, min(len(self.statements), len(element.statements))):
+ if not self.statements[-1 - i].is_equal_to(element.statements[i]):
+ break
+ for j in range(0, min(len(self.statements), len(element.statements))):
+ if not self.statements[j].is_equal_to(element.statements[-1 - j]):
+ break
+ if i + j >= len(element.statements):
+ return False
+ mergee = list(element.statements)
+ if i > 0:
+ del mergee[0:i]
+ del self.statements[-i]
+ if j > 0:
+ del mergee[-j]
+ del self.statements[0:j]
+ self.statements.extend(mergee)
+ return True
+ else:
+ if self.statements[-1].is_equal_to(element, error_range) or \
+ self.statements[0].is_equal_to(element, error_range):
+ return False
+ self.statements.appen(element)
+ return True
+
+ def _insert_on_top(self, element, error_range=0):
+ if isinstance(element, DxfPath):
+ if self.is_equal_to(element, error_range):
+ return False
+ for i in range(0, min(len(self.statements), len(element.statements))):
+ if not self.statements[-1 - i].is_equal_to(element.statements[i]):
+ break
+ for j in range(0, min(len(self.statements), len(element.statements))):
+ if not self.statements[j].is_equal_to(element.statements[-1 - j]):
+ break
+ if i + j >= len(element.statements):
+ return False
+ mergee = list(element.statements)
+ if i > 0:
+ del mergee[0:i]
+ del self.statements[-i]
+ if j > 0:
+ del mergee[-j]
+ del self.statements[0:j]
+ self.statements[0:0] = mergee
+ return True
+ else:
+ if self.statements[-1].is_equal_to(element, error_range) or \
+ self.statements[0].is_equal_to(element, error_range):
+ return False
+ self.statements.insert(0, element)
+ return True
+
+ def to_gerber(self, settings=FileSettings(), pitch=0, width=0):
+ from gerberex.dxf import DxfArcStatement
+ if pitch:
+ return
+
+ x0, y0 = self.statements[0].start
+ gerber = 'G01*\nX{0}Y{1}D02*\nG75*'.format(
+ write_gerber_value(x0, settings.format,
+ settings.zero_suppression),
+ write_gerber_value(y0, settings.format,
+ settings.zero_suppression),
+ )
+
+ for statement in self.statements:
+ x0, y0 = statement.start
+ x1, y1 = statement.end
+ if isinstance(statement, DxfArcStatement):
+ xc, yc = statement.center
+ gerber += '\nG{0}*\nX{1}Y{2}I{3}J{4}D01*'.format(
+ '03' if statement.end_angle > statement.start_angle else '02',
+ write_gerber_value(x1, settings.format,
+ settings.zero_suppression),
+ write_gerber_value(y1, settings.format,
+ settings.zero_suppression),
+ write_gerber_value(xc - x0, settings.format,
+ settings.zero_suppression),
+ write_gerber_value(yc - y0, settings.format,
+ settings.zero_suppression)
+ )
+ else:
+ gerber += '\nG01*\nX{0}Y{1}D01*'.format(
+ write_gerber_value(x1, settings.format,
+ settings.zero_suppression),
+ write_gerber_value(y1, settings.format,
+ settings.zero_suppression),
+ )
+
+ return gerber
+
+def generate_closed_paths(statements, error_range=0):
+ from gerberex.dxf import DxfLineStatement, DxfArcStatement
+
+ paths = [DxfPath(s, error_range) \
+ for s in filter(lambda s: isinstance(s, DxfLineStatement) or \
+ isinstance(s, DxfArcStatement), statements)]
+
+ prev_paths_num = 0
+ while prev_paths_num != len(paths):
+ working = []
+ for i in range(len(paths)):
+ mergee = paths[i]
+ for j in range(i + 1, len(paths) - 1):
+ target = paths[j]
+ if target.merge(mergee, error_range):
+ break
+ else:
+ working.append(mergee)
+ prev_paths_num = len(paths)
+ paths = working
+
+ return list(filter(lambda p: p.is_closed, paths))
+