From 4eb0e063bcd34c21b737023aa6ed5baed80658d1 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 13 Jun 2021 15:00:17 +0200 Subject: Repo re-org, make gerberex tests run --- gerberex/__init__.py | 15 - gerberex/am_expression.py | 184 ---------- gerberex/am_primitive.py | 448 ------------------------ gerberex/common.py | 40 --- gerberex/composition.py | 192 ---------- gerberex/dxf.py | 796 ------------------------------------------ gerberex/dxf_path.py | 412 ---------------------- gerberex/excellon.py | 404 --------------------- gerberex/gerber_statements.py | 115 ------ gerberex/rs274x.py | 331 ------------------ gerberex/utility.py | 27 -- 11 files changed, 2964 deletions(-) delete mode 100644 gerberex/__init__.py delete mode 100644 gerberex/am_expression.py delete mode 100644 gerberex/am_primitive.py delete mode 100644 gerberex/common.py delete mode 100644 gerberex/composition.py delete mode 100644 gerberex/dxf.py delete mode 100644 gerberex/dxf_path.py delete mode 100644 gerberex/excellon.py delete mode 100644 gerberex/gerber_statements.py delete mode 100644 gerberex/rs274x.py delete mode 100644 gerberex/utility.py (limited to 'gerberex') diff --git a/gerberex/__init__.py b/gerberex/__init__.py deleted file mode 100644 index f379c1c..0000000 --- a/gerberex/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2019 Hiroshi Murayama -""" -Gerber Tools Extension -====================== -**Gerber Tools Extenstion** -gerber-tools-extension is a extention package for gerber-tools. -This package provide panelizing of PCB fucntion. -""" - -from gerberex.common import read, loads, rectangle -from gerberex.composition import GerberComposition, DrillComposition -from gerberex.dxf import DxfFile diff --git a/gerberex/am_expression.py b/gerberex/am_expression.py deleted file mode 100644 index f43ba2e..0000000 --- a/gerberex/am_expression.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2019 Hiroshi Murayama - -from gerber.utils import * -from gerber.am_eval import OpCode -from gerber.am_statements import * - -class AMExpression(object): - CONSTANT = 1 - VARIABLE = 2 - OPERATOR = 3 - - def __init__(self, kind): - self.kind = kind - - @property - def value(self): - return self - - def optimize(self): - pass - - def to_inch(self): - return AMOperatorExpression(AMOperatorExpression.DIV, self, - AMConstantExpression(MILLIMETERS_PER_INCH)) - - def to_metric(self): - return AMOperatorExpression(AMOperatorExpression.MUL, self, - AMConstantExpression(MILLIMETERS_PER_INCH)) - - def to_gerber(self, settings=None): - pass - - def to_instructions(self): - pass - -class AMConstantExpression(AMExpression): - def __init__(self, value): - super(AMConstantExpression, self).__init__(AMExpression.CONSTANT) - self._value = value - - @property - def value(self): - return self._value - - def optimize(self): - return self - - def to_gerber(self, settings=None): - if isinstance(self._value, str): - return self._value - gerber = '%.6g' % self._value - return '%.6f' % self._value if 'e' in gerber else gerber - - def to_instructions(self): - return [(OpCode.PUSH, self._value)] - -class AMVariableExpression(AMExpression): - def __init__(self, number): - super(AMVariableExpression, self).__init__(AMExpression.VARIABLE) - self.number = number - - def optimize(self): - return self - - def to_gerber(self, settings=None): - return '$%d' % self.number - - def to_instructions(self): - return (OpCode.LOAD, self.number) - -class AMOperatorExpression(AMExpression): - ADD = '+' - SUB = '-' - MUL = 'X' - DIV = '/' - - def __init__(self, op, lvalue, rvalue): - super(AMOperatorExpression, self).__init__(AMExpression.OPERATOR) - self.op = op - self.lvalue = lvalue - self.rvalue = rvalue - - def optimize(self): - self.lvalue = self.lvalue.optimize() - self.rvalue = self.rvalue.optimize() - - if isinstance(self.lvalue, AMConstantExpression) and isinstance(self.rvalue, AMConstantExpression): - lvalue = float(self.lvalue.value) - rvalue = float(self.rvalue.value) - value = lvalue + rvalue if self.op == self.ADD else \ - lvalue - rvalue if self.op == self.SUB else \ - lvalue * rvalue if self.op == self.MUL else \ - lvalue / rvalue if self.op == self.DIV else None - return AMConstantExpression(value) - elif self.op == self.ADD: - if self.rvalue.value == 0: - return self.lvalue - elif self.lvalue.value == 0: - return self.rvalue - elif self.op == self.SUB: - if self.rvalue.value == 0: - return self.lvalue - elif self.lvalue.value == 0 and isinstance(self.rvalue, AMConstantExpression): - return AMConstantExpression(-self.rvalue.value) - elif self.op == self.MUL: - if self.rvalue.value == 1: - return self.lvalue - elif self.lvalue.value == 1: - return self.rvalue - elif self.lvalue == 0 or self.rvalue == 0: - return AMConstantExpression(0) - elif self.op == self.DIV: - if self.rvalue.value == 1: - return self.lvalue - elif self.lvalue.value == 0: - return AMConstantExpression(0) - - return self - - def to_gerber(self, settings=None): - return '(%s)%s(%s)' % (self.lvalue.to_gerber(settings), self.op, self.rvalue.to_gerber(settings)) - - def to_instructions(self): - for i in self.lvalue.to_instructions(): - yield i - for i in self.rvalue.to_instructions(): - yield i - op = OpCode.ADD if self.op == self.ADD else\ - OpCode.SUB if self.op == self.SUB else\ - OpCode.MUL if self.op == self.MUL else\ - OpCode.DIV - yield (op, None) - -def eval_macro(instructions): - stack = [] - - def pop(): - return stack.pop() - - def push(op): - stack.append(op) - - def top(): - return stack[-1] - - def empty(): - return len(stack) == 0 - - for opcode, argument in instructions: - if opcode == OpCode.PUSH: - push(AMConstantExpression(argument)) - - elif opcode == OpCode.LOAD: - push(AMVariableExpression(argument)) - - elif opcode == OpCode.STORE: - yield (-argument, [pop()]) - - elif opcode == OpCode.ADD: - op1 = pop() - op2 = pop() - push(AMOperatorExpression(AMOperatorExpression.ADD, op2, op1)) - - elif opcode == OpCode.SUB: - op1 = pop() - op2 = pop() - push(AMOperatorExpression(AMOperatorExpression.SUB, op2, op1)) - - elif opcode == OpCode.MUL: - op1 = pop() - op2 = pop() - push(AMOperatorExpression(AMOperatorExpression.MUL, op2, op1)) - - elif opcode == OpCode.DIV: - op1 = pop() - op2 = pop() - push(AMOperatorExpression(AMOperatorExpression.DIV, op2, op1)) - - elif opcode == OpCode.PRIM: - yield (argument, stack) - stack = [] diff --git a/gerberex/am_primitive.py b/gerberex/am_primitive.py deleted file mode 100644 index a3ad824..0000000 --- a/gerberex/am_primitive.py +++ /dev/null @@ -1,448 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2019 Hiroshi Murayama - -from gerber.utils import * -from gerber.am_statements import * -from gerber.am_eval import OpCode - -from gerberex.am_expression import eval_macro, AMConstantExpression, AMOperatorExpression - -class AMPrimitiveDef(AMPrimitive): - def __init__(self, code, exposure=None, rotation=None): - super(AMPrimitiveDef, self).__init__(code, exposure) - if not rotation: - rotation = AMConstantExpression(0) - self.rotation = rotation - - def rotate(self, angle, center=None): - self.rotation = AMOperatorExpression(AMOperatorExpression.ADD, - self.rotation, - AMConstantExpression(float(angle))) - self.rotation = self.rotation.optimize() - - def to_inch(self): - pass - - def to_metric(self): - pass - - def to_gerber(self, settings=None): - pass - - def to_instructions(self): - pass - -class AMCommentPrimitiveDef(AMPrimitiveDef): - @classmethod - def from_modifiers(cls, code, modifiers): - return cls(code, modifiers[0]) - - def __init__(self, code, comment): - super(AMCommentPrimitiveDef, self).__init__(code) - self.comment = comment - - def to_gerber(self, settings=None): - return '%d %s*' % (self.code, self.comment.to_gerber()) - - def to_instructions(self): - return [(OpCode.PUSH, self.comment), (OpCode.PRIM, self.code)] - -class AMCirclePrimitiveDef(AMPrimitiveDef): - @classmethod - def from_modifiers(cls, code, modifiers): - exposure = 'on' if modifiers[0].value == 1 else 'off' - diameter = modifiers[1] - center_x = modifiers[2] - center_y = modifiers[3] - rotation = modifiers[4] if len(modifiers)>4 else AMConstantExpression(float(0)) - return cls(code, exposure, diameter, center_x, center_y, rotation) - - def __init__(self, code, exposure, diameter, center_x, center_y, rotation): - super(AMCirclePrimitiveDef, self).__init__(code, exposure, rotation) - self.diameter = diameter - self.center_x = center_x - self.center_y = center_y - - def to_inch(self): - self.diameter = self.diameter.to_inch().optimize() - self.center_x = self.center_x.to_inch().optimize() - self.center_y = self.center_y.to_inch().optimize() - - def to_metric(self): - self.diameter = self.diameter.to_metric().optimize() - self.center_x = self.center_x.to_metric().optimize() - self.center_y = self.center_y.to_metric().optimize() - - def to_gerber(self, settings=None): - data = dict(code = self.code, - exposure = 1 if self.exposure == 'on' else 0, - diameter = self.diameter.to_gerber(settings), - x = self.center_x.to_gerber(settings), - y = self.center_y.to_gerber(settings), - rotation = self.rotation.to_gerber(settings)) - return '{code},{exposure},{diameter},{x},{y},{rotation}*'.format(**data) - - def to_instructions(self): - yield (OpCode.PUSH, 1 if self.exposure == 'on' else 0) - for modifier in [self.diameter, self.center_x, self.center_y, self.rotation]: - for i in modifier.to_instructions(): - yield i - yield (OpCode.PRIM, self.code) - -class AMVectorLinePrimitiveDef(AMPrimitiveDef): - @classmethod - def from_modifiers(cls, code, modifiers): - code = code - exposure = 'on' if modifiers[0].value == 1 else 'off' - width = modifiers[1] - start_x = modifiers[2] - start_y = modifiers[3] - end_x = modifiers[4] - end_y = modifiers[5] - rotation = modifiers[6] - return cls(code, exposure, width, start_x, start_y, end_x, end_y, rotation) - - def __init__(self, code, exposure, width, start_x, start_y, end_x, end_y, rotation): - super(AMVectorLinePrimitiveDef, self).__init__(code, exposure, rotation) - self.width = width - self.start_x = start_x - self.start_y = start_y - self.end_x = end_x - self.end_y = end_y - - def to_inch(self): - self.width = self.width.to_inch().optimize() - self.start_x = self.start_x.to_inch().optimize() - self.start_y = self.start_y.to_inch().optimize() - self.end_x = self.end_x.to_inch().optimize() - self.end_y = self.end_y.to_inch().optimize() - - def to_metric(self): - self.width = self.width.to_metric().optimize() - self.start_x = self.start_x.to_metric().optimize() - self.start_y = self.start_y.to_metric().optimize() - self.end_x = self.end_x.to_metric().optimize() - self.end_y = self.end_y.to_metric().optimize() - - def to_gerber(self, settings=None): - data = dict(code = self.code, - exposure = 1 if self.exposure == 'on' else 0, - width = self.width.to_gerber(settings), - start_x = self.start_x.to_gerber(settings), - start_y = self.start_y.to_gerber(settings), - end_x = self.end_x.to_gerber(settings), - end_y = self.end_y.to_gerber(settings), - rotation = self.rotation.to_gerber(settings)) - return '{code},{exposure},{width},{start_x},{start_y},{end_x},{end_y},{rotation}*'.format(**data) - - def to_instructions(self): - yield (OpCode.PUSH, 1 if self.exposure == 'on' else 0) - modifiers = [self.width, self.start_x, self.start_y, self.end_x, self.end_y, self.rotation] - for modifier in modifiers: - for i in modifier.to_instructions(): - yield i - yield (OpCode.PRIM, self.code) - -class AMCenterLinePrimitiveDef(AMPrimitiveDef): - @classmethod - def from_modifiers(cls, code, modifiers): - code = code - exposure = 'on' if modifiers[0].value == 1 else 'off' - width = modifiers[1] - height = modifiers[2] - x = modifiers[3] - y = modifiers[4] - rotation = modifiers[5] - return cls(code, exposure, width, height, x, y, rotation) - - def __init__(self, code, exposure, width, height, x, y, rotation): - super(AMCenterLinePrimitiveDef, self).__init__(code, exposure, rotation) - self.width = width - self.height = height - self.x = x - self.y = y - - def to_inch(self): - self.width = self.width.to_inch().optimize() - self.height = self.height.to_inch().optimize() - self.x = self.x.to_inch().optimize() - self.y = self.y.to_inch().optimize() - - def to_metric(self): - self.width = self.width.to_metric().optimize() - self.height = self.height.to_metric().optimize() - self.x = self.x.to_metric().optimize() - self.y = self.y.to_metric().optimize() - - def to_gerber(self, settings=None): - data = dict(code = self.code, - exposure = 1 if self.exposure == 'on' else 0, - width = self.width.to_gerber(settings), - height = self.height.to_gerber(settings), - x = self.x.to_gerber(settings), - y = self.y.to_gerber(settings), - rotation = self.rotation.to_gerber(settings)) - return '{code},{exposure},{width},{height},{x},{y},{rotation}*'.format(**data) - - def to_instructions(self): - yield (OpCode.PUSH, 1 if self.exposure == 'on' else 0) - modifiers = [self.width, self.height, self.x, self.y, self.rotation] - for modifier in modifiers: - for i in modifier.to_instructions(): - yield i - yield (OpCode.PRIM, self.code) - -class AMOutlinePrimitiveDef(AMPrimitiveDef): - @classmethod - def from_modifiers(cls, code, modifiers): - num_points = int(modifiers[1].value + 1) - code = code - exposure = 'on' if modifiers[0].value == 1 else 'off' - addrs = modifiers[2:num_points * 2 + 2] - rotation = modifiers[2 + num_points * 2] - return cls(code, exposure, addrs, rotation) - - def __init__(self, code, exposure, addrs, rotation): - super(AMOutlinePrimitiveDef, self).__init__(code, exposure, rotation) - self.addrs = addrs - - def to_inch(self): - self.addrs = [i.to_inch().optimize() for i in self.addrs] - - def to_metric(self): - self.addrs = [i.to_metric().optimize() for i in self.addrs] - - def to_gerber(self, settings=None): - def strs(): - yield '%d,%d,%d' % (self.code, - 1 if self.exposure == 'on' else 0, - len(self.addrs) / 2 - 1) - for i in self.addrs: - yield i.to_gerber(settings) - yield self.rotation.to_gerber(settings) - - return '%s*' % ','.join(strs()) - - def to_instructions(self): - yield (OpCode.PUSH, 1 if self.exposure == 'on' else 0) - yield (OpCode.PUSH, int(len(self.addrs) / 2 - 1)) - for modifier in self.addrs: - for i in modifier.to_instructions(): - yield i - for i in self.rotation.to_instructions(): - yield i - yield (OpCode.PRIM, self.code) - -class AMPolygonPrimitiveDef(AMPrimitiveDef): - @classmethod - def from_modifiers(cls, code, modifiers): - code = code - exposure = 'on' if modifiers[0].value == 1 else 'off' - vertices = modifiers[1] - x = modifiers[2] - y = modifiers[3] - diameter = modifiers[4] - rotation = modifiers[5] - return cls(code, exposure, vertices, x, y, diameter, rotation) - - def __init__(self, code, exposure, vertices, x, y, diameter, rotation): - super(AMPolygonPrimitiveDef, self).__init__(code, exposure, rotation) - self.vertices = vertices - self.x = x - self.y = y - self.diameter = diameter - - def to_inch(self): - self.x = self.x.to_inch().optimize() - self.y = self.y.to_inch().optimize() - self.diameter = self.diameter.to_inch().optimize() - - def to_metric(self): - self.x = self.x.to_metric().optimize() - self.y = self.y.to_metric().optimize() - self.diameter = self.diameter.to_metric().optimize() - - def to_gerber(self, settings=None): - data = dict(code = self.code, - exposure = 1 if self.exposure == 'on' else 0, - vertices = self.vertices.to_gerber(settings), - x = self.x.to_gerber(settings), - y = self.y.to_gerber(settings), - diameter = self.diameter.to_gerber(settings), - rotation = self.rotation.to_gerber(settings)) - return '{code},{exposure},{vertices},{x},{y},{diameter},{rotation}*'.format(**data) - - def to_instructions(self): - yield (OpCode.PUSH, 1 if self.exposure == 'on' else 0) - modifiers = [self.vertices, self.x, self.y, self.diameter, self.rotation] - for modifier in modifiers: - for i in modifier.to_instructions(): - yield i - yield (OpCode.PRIM, self.code) - -class AMMoirePrimitiveDef(AMPrimitiveDef): - @classmethod - def from_modifiers(cls, code, modifiers): - code = code - exposure = 'on' - x = modifiers[0] - y = modifiers[1] - diameter = modifiers[2] - ring_thickness = modifiers[3] - gap = modifiers[4] - max_rings = modifiers[5] - crosshair_thickness = modifiers[6] - crosshair_length = modifiers[7] - rotation = modifiers[8] - return cls(code, exposure, x, y, diameter, ring_thickness, gap, - max_rings, crosshair_thickness, crosshair_length, rotation) - - def __init__(self, code, exposure, x, y, diameter, ring_thickness, gap, max_rings, crosshair_thickness, crosshair_length, rotation): - super(AMMoirePrimitiveDef, self).__init__(code, exposure, rotation) - self.x = x - self.y = y - self.diameter = diameter - self.ring_thickness = ring_thickness - self.gap = gap - self.max_rings = max_rings - self.crosshair_thickness = crosshair_thickness - self.crosshair_length = crosshair_length - - def to_inch(self): - self.x = self.x.to_inch().optimize() - self.y = self.y.to_inch().optimize() - self.diameter = self.diameter.to_inch().optimize() - self.ring_thickness = self.ring_thickness.to_inch().optimize() - self.gap = self.gap.to_inch().optimize() - self.crosshair_thickness = self.crosshair_thickness.to_inch().optimize() - self.crosshair_length = self.crosshair_length.to_inch().optimize() - - def to_metric(self): - self.x = self.x.to_metric().optimize() - self.y = self.y.to_metric().optimize() - self.diameter = self.diameter.to_metric().optimize() - self.ring_thickness = self.ring_thickness.to_metric().optimize() - self.gap = self.gap.to_metric().optimize() - self.crosshair_thickness = self.crosshair_thickness.to_metric().optimize() - self.crosshair_length = self.crosshair_length.to_metric().optimize() - - def to_gerber(self, settings=None): - data = dict(code = self.code, - x = self.x.to_gerber(settings), - y = self.y.to_gerber(settings), - diameter = self.diameter.to_gerber(settings), - ring_thickness = self.ring_thickness.to_gerber(settings), - gap = self.gap.to_gerber(settings), - max_rings = self.max_rings.to_gerber(settings), - crosshair_thickness = self.crosshair_thickness.to_gerber(settings), - crosshair_length = self.crosshair_length.to_gerber(settings), - rotation = self.rotation.to_gerber(settings)) - return '{code},{x},{y},{diameter},{ring_thickness},{gap},{max_rings},'\ - '{crosshair_thickness},{crosshair_length},{rotation}*'.format(**data) - - def to_instructions(self): - modifiers = [self.x, self.y, self.diameter, - self.ring_thickness, self.gap, self.max_rings, - self.crosshair_thickness, self.crosshair_length, - self.rotation] - for modifier in modifiers: - for i in modifier.to_instructions(): - yield i - yield (OpCode.PRIM, self.code) - -class AMThermalPrimitiveDef(AMPrimitiveDef): - @classmethod - def from_modifiers(cls, code, modifiers): - code = code - exposure = 'on' - x = modifiers[0] - y = modifiers[1] - outer_diameter = modifiers[2] - inner_diameter = modifiers[3] - gap = modifiers[4] - rotation = modifiers[5] - return cls(code, exposure, x, y, outer_diameter, inner_diameter, gap, rotation) - - def __init__(self, code, exposure, x, y, outer_diameter, inner_diameter, gap, rotation): - super(AMThermalPrimitiveDef, self).__init__(code, exposure, rotation) - self.x = x - self.y = y - self.outer_diameter = outer_diameter - self.inner_diameter = inner_diameter - self.gap = gap - - def to_inch(self): - self.x = self.x.to_inch().optimize() - self.y = self.y.to_inch().optimize() - self.outer_diameter = self.outer_diameter.to_inch().optimize() - self.inner_diameter = self.inner_diameter.to_inch().optimize() - self.gap = self.gap.to_inch().optimize() - - def to_metric(self): - self.x = self.x.to_metric().optimize() - self.y = self.y.to_metric().optimize() - self.outer_diameter = self.outer_diameter.to_metric().optimize() - self.inner_diameter = self.inner_diameter.to_metric().optimize() - self.gap = self.gap.to_metric().optimize() - - def to_gerber(self, settings=None): - data = dict(code = self.code, - x = self.x.to_gerber(settings), - y = self.y.to_gerber(settings), - outer_diameter = self.outer_diameter.to_gerber(settings), - inner_diameter = self.inner_diameter.to_gerber(settings), - gap = self.gap.to_gerber(settings), - rotation = self.rotation.to_gerber(settings)) - return '{code},{x},{y},{outer_diameter},{inner_diameter},'\ - '{gap},{rotation}*'.format(**data) - - def to_instructions(self): - modifiers = [self.x, self.y, self.outer_diameter, - self.inner_diameter, self.gap, self.rotation] - for modifier in modifiers: - for i in modifier.to_instructions(): - yield i - yield (OpCode.PRIM, self.code) - -class AMVariableDef(object): - def __init__(self, number, value): - self.number = number - self.value = value - - def to_inch(self): - return self - - def to_metric(self): - return self - - def to_gerber(self, settings=None): - return '$%d=%s*' % (self.number, self.value.to_gerber(settings)) - - def to_instructions(self): - for i in self.value.to_instructions(): - yield i - yield (OpCode.STORE, self.number) - - def rotate(self, angle, center=None): - pass - -def to_primitive_defs(instructions): - classes = { - 0: AMCommentPrimitiveDef, - 1: AMCirclePrimitiveDef, - 2: AMVectorLinePrimitiveDef, - 20: AMVectorLinePrimitiveDef, - 21: AMCenterLinePrimitiveDef, - 4: AMOutlinePrimitiveDef, - 5: AMPolygonPrimitiveDef, - 6: AMMoirePrimitiveDef, - 7: AMThermalPrimitiveDef, - } - for code, modifiers in eval_macro(instructions): - if code < 0: - yield AMVariableDef(-code, modifiers[0]) - else: - primitive = classes[code] - yield primitive.from_modifiers(code, modifiers) diff --git a/gerberex/common.py b/gerberex/common.py deleted file mode 100644 index 6e8a832..0000000 --- a/gerberex/common.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2019 Hiroshi Murayama - -import os -from gerber.common import loads as loads_org -from gerber.exceptions import ParseError -from gerber.utils import detect_file_format -import gerber.rs274x -import gerber.ipc356 -import gerberex.rs274x -import gerberex.excellon -import gerberex.dxf - -def read(filename, format=None): - with open(filename, 'rU') as f: - data = f.read() - return loads(data, filename, format=format) - - -def loads(data, filename=None, format=None): - if os.path.splitext(filename if filename else '')[1].lower() == '.dxf': - return gerberex.dxf.loads(data, filename) - - fmt = detect_file_format(data) - if fmt == 'rs274x': - file = gerberex.rs274x.loads(data, filename=filename) - return gerberex.rs274x.GerberFile.from_gerber_file(file) - elif fmt == 'excellon': - return gerberex.excellon.loads(data, filename=filename, format=format) - elif fmt == 'ipc_d_356': - return ipc356.loads(data, filename=filename) - else: - raise ParseError('Unable to detect file format') - - -def rectangle(width, height, left=0, bottom=0, units='metric', draw_mode=None, filename=None): - return gerberex.dxf.DxfFile.rectangle( - width, height, left, bottom, units, draw_mode, filename) diff --git a/gerberex/composition.py b/gerberex/composition.py deleted file mode 100644 index b5dffb1..0000000 --- a/gerberex/composition.py +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2019 Hiroshi Murayama -import os -from functools import reduce -from gerber.cam import FileSettings -from gerber.gerber_statements import EofStmt -from gerber.excellon_statements import * -from gerber.excellon import DrillSlot, DrillHit -import gerberex.rs274x -import gerberex.excellon -import gerberex.dxf - -class Composition(object): - def __init__(self, settings = None, comments = None): - self.settings = settings - self.comments = comments if comments != None else [] - -class GerberComposition(Composition): - APERTURE_ID_BIAS = 10 - - def __init__(self, settings=None, comments=None): - super(GerberComposition, self).__init__(settings, comments) - self.aperture_macros = {} - self.apertures = [] - self.drawings = [] - - def merge(self, file): - if isinstance(file, gerberex.rs274x.GerberFile): - self._merge_gerber(file) - elif isinstance(file, gerberex.dxf.DxfFile): - self._merge_dxf(file) - else: - raise Exception('unsupported file type') - - def dump(self, path): - def statements(): - for k in self.aperture_macros: - yield self.aperture_macros[k] - for s in self.apertures: - yield s - for s in self.drawings: - yield s - yield EofStmt() - self.settings.notation = 'absolute' - self.settings.zeros = 'trailing' - with open(path, 'w') as f: - gerberex.rs274x.write_gerber_header(f, self.settings) - for statement in statements(): - f.write(statement.to_gerber(self.settings) + '\n') - - def _merge_gerber(self, file): - aperture_macro_map = {} - aperture_map = {} - - if self.settings: - if self.settings.units == 'metric': - file.to_metric() - else: - file.to_inch() - - for macro in file.aperture_macros: - statement = file.aperture_macros[macro] - name = statement.name - newname = self._register_aperture_macro(statement) - aperture_macro_map[name] = newname - - for statement in file.aperture_defs: - if statement.param == 'AD': - if statement.shape in aperture_macro_map: - statement.shape = aperture_macro_map[statement.shape] - dnum = statement.d - newdnum = self._register_aperture(statement) - aperture_map[dnum] = newdnum - - for statement in file.main_statements: - if statement.type == 'APERTURE': - statement.d = aperture_map[statement.d] - self.drawings.append(statement) - - if not self.settings: - self.settings = file.context - - def _merge_dxf(self, file): - if self.settings: - if self.settings.units == 'metric': - file.to_metric() - else: - file.to_inch() - - file.dcode = self._register_aperture(file.aperture) - self.drawings.append(file.statements) - - if not self.settings: - self.settings = file.settings - - - def _register_aperture_macro(self, statement): - name = statement.name - newname = name - offset = 0 - while newname in self.aperture_macros: - offset += 1 - newname = '%s_%d' % (name, offset) - statement.name = newname - self.aperture_macros[newname] = statement - return newname - - def _register_aperture(self, statement): - statement.d = len(self.apertures) + self.APERTURE_ID_BIAS - self.apertures.append(statement) - return statement.d - -class DrillComposition(Composition): - def __init__(self, settings=None, comments=None): - super(DrillComposition, self).__init__(settings, comments) - 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') - - def dump(self, path): - def statements(): - for t in self.tools: - yield ToolSelectionStmt(t.number).to_excellon(self.settings) - for h in self.hits: - if h.tool.number == t.number: - yield h.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() - - self.settings.notation = 'absolute' - self.settings.zeros = 'trailing' - with open(path, 'w') as f: - gerberex.excellon.write_excellon_header(f, self.settings, self.tools) - for statement in statements(): - f.write(statement + '\n') - - def _merge_excellon(self, file): - tool_map = {} - - if not self.settings: - self.settings = file.settings - else: - if self.settings.units == 'metric': - file.to_metric() - else: - file.to_inch() - - for tool in iter(file.tools.values()): - num = tool.number - tool_map[num] = self._register_tool(tool) - - for hit in file.hits: - 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() - - 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): - return existing - new_tool = ExcellonTool.from_tool(tool) - new_tool.settings = self.settings - def toolnums(): - for tool in self.tools: - yield tool.number - max_num = reduce(lambda x, y: x if x > y else y, toolnums(), 0) - new_tool.number = max_num + 1 - self.tools.append(new_tool) - return new_tool diff --git a/gerberex/dxf.py b/gerberex/dxf.py deleted file mode 100644 index 2341092..0000000 --- a/gerberex/dxf.py +++ /dev/null @@ -1,796 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2019 Hiroshi Murayama - -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, rotate_point -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, 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 - - distance = abs(dy * x1 - dx * y1) / dr - - D2 = D * D - dr2 = dr * dr - r2 = radius * radius - delta = r2 * dr2 - D2 - if distance > radius - error_range and distance < radius + error_range: - 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 - self.start = None - self.end = None - self.is_closed = False - - def to_inch(self): - pass - - def to_metric(self): - pass - - def is_equal_to(self, target, error_range=0): - return False - - 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): - start = (entity.start[0], entity.start[1]) - 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 - self.end = end - - def to_inch(self): - 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.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, 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, offset=0): - x0, y0 = self.start - x1, y1 = self.end - y1 = self.end[1] - xp = x1 - x0 - yp = y1 - y0 - l = sqrt(xp * xp + yp * yp) - xd = xp * pitch / l - yd = yp * pitch / l - x0 += xp * offset / l - y0 += yp * offset / l - - 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) - 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) - - 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): - super(DxfArcStatement, self).__init__(entity) - if entity.dxftype == 'CIRCLE': - self.radius = self.entity.radius - self.center = (self.entity.center[0], self.entity.center[1]) - self.start = (self.center[0] + self.radius, self.center[1]) - self.end = self.start - self.start_angle = 0 - self.end_angle = 360 - self.is_closed = True - 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') - 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) - 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.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 - - 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) - 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) - 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 - - 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 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 is_contained(p2_angle, region, 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): - 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.end = (self.entity.points[-1][0], self.entity.points[-1][1]) - - 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: - 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 - 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)) - 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] - - 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])) - - 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])) - - 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, 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 = 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 self._polarity_command() - yield 'D{0}*'.format(self.dcode) - if self.draw_mode == DxfFile.DM_FILL: - yield 'G36*' - 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 - 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 self.draw_mode == DxfFile.DM_FILL: - return - def drills(): - 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 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 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 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 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 - DM_FILL = 1 - DM_MOUSE_BITES = 2 - - FM_SIMPLE = 0 - FM_TURN_OVER = 1 - - FT_RX274X = 0 - FT_EXCELLON = 1 - - @classmethod - def from_dxf(cls, dxf, settings=None, draw_mode=None, filename=None): - fsettings = settings if settings else \ - FileSettings(zero_suppression='leading') - - if dxf.header['$INSUNITS'] == 1: - 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(DxfArcStatement(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 = 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 - self._fill_mode = self.FM_TURN_OVER - - 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, fill_mode=self.filename) - - @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),],) - self.statements.width = 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 - - @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 - - @pitch.setter - def pitch(self, value): - self.statements.pitch = value - - def write(self, filename=None, filetype=FT_RX274X): - 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: - 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') - else: - 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') - - - def to_inch(self): - if self.units == 'metric': - self.aperture.to_inch() - self.statements.to_inch() - self.pitch = inch(self.pitch) - self.units = 'inch' - - def to_metric(self): - if self.units == 'inch': - self.aperture.to_metric() - self.statements.to_metric() - self.pitch = metric(self.pitch) - self.units = 'metric' - - 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 negate_polarity(self): - self.statements.polarity = not self.statements.polarity - -def loads(data, filename=None): - if sys.version_info.major == 2: - data = unicode(data) - stream = io.StringIO(data) - dxf = dxfgrabber.read(stream) - return DxfFile.from_dxf(dxf) diff --git a/gerberex/dxf_path.py b/gerberex/dxf_path.py deleted file mode 100644 index 960b054..0000000 --- a/gerberex/dxf_path.py +++ /dev/null @@ -1,412 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2019 Hiroshi Murayama - -from gerber.utils import inch, metric, write_gerber_value -from gerber.cam import FileSettings -from gerberex.utility import is_equal_point, is_equal_value, normalize_vec2d, dot_vec2d -from gerberex.excellon import CoordinateStmtEx - -class DxfPath(object): - def __init__(self, statements, error_range=0): - self.statements = statements - self.error_range = error_range - self.bounding_box = statements[0].bounding_box - self.containers = [] - for statement in statements[1:]: - self._merge_bounding_box(statement.bounding_box) - - @property - def start(self): - return self.statements[0].start - - @property - def end(self): - return self.statements[-1].end - - @property - def is_closed(self): - if len(self.statements) == 1: - return self.statements[0].is_closed - else: - return is_equal_point(self.start, self.end, self.error_range) - - def is_equal_to(self, target, error_range=0): - if not isinstance(target, DxfPath): - 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 contain(self, target, error_range=0): - for statement in self.statements: - if statement.is_equal_to(target, error_range): - return True - else: - return False - - def to_inch(self): - self.error_range = inch(self.error_range) - for statement in self.statements: - statement.to_inch() - - def to_metric(self): - self.error_range = metric(self.error_range) - for statement in self.statements: - statement.to_metric() - - def offset(self, offset_x, offset_y): - for statement in self.statements: - statement.offset(offset_x, offset_y) - - def rotate(self, angle, center=(0, 0)): - for statement in self.statements: - statement.rotate(angle, center) - - def reverse(self): - rlist = [] - for statement in reversed(self.statements): - statement.reverse() - rlist.append(statement) - self.statements = rlist - - def merge(self, element, error_range=0): - 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] - for statement in mergee: - self._merge_bounding_box(statement.bounding_box) - 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._merge_bounding_box(element.bounding_box) - 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 _merge_bounding_box(self, box): - self.bounding_box = (min(self.bounding_box[0], box[0]), - min(self.bounding_box[1], box[1]), - max(self.bounding_box[2], box[2]), - max(self.bounding_box[3], box[3])) - - def may_be_in_collision(self, path): - if self.bounding_box[0] >= path.bounding_box[2] or \ - self.bounding_box[1] >= path.bounding_box[3] or \ - self.bounding_box[2] <= path.bounding_box[0] or \ - self.bounding_box[3] <= path.bounding_box[1]: - return False - else: - return True - - def to_gerber(self, settings=FileSettings(), pitch=0, width=0): - from gerberex.dxf import DxfArcStatement - if pitch == 0: - x0, y0 = self.statements[0].start - gerber = 'G01*\nX{0}Y{1}D02*\nG75*'.format( - write_gerber_value(x0, settings.format, - settings.zero_suppression), - write_gerber_value(y0, settings.format, - settings.zero_suppression), - ) - - for statement in self.statements: - x0, y0 = statement.start - x1, y1 = statement.end - if isinstance(statement, DxfArcStatement): - xc, yc = statement.center - gerber += '\nG{0}*\nX{1}Y{2}I{3}J{4}D01*'.format( - '03' if statement.end_angle > statement.start_angle else '02', - write_gerber_value(x1, settings.format, - settings.zero_suppression), - write_gerber_value(y1, settings.format, - settings.zero_suppression), - write_gerber_value(xc - x0, settings.format, - settings.zero_suppression), - write_gerber_value(yc - y0, settings.format, - settings.zero_suppression) - ) - else: - gerber += '\nG01*\nX{0}Y{1}D01*'.format( - write_gerber_value(x1, settings.format, - settings.zero_suppression), - write_gerber_value(y1, settings.format, - settings.zero_suppression), - ) - else: - def ploter(x, y): - return 'X{0}Y{1}D03*\n'.format( - write_gerber_value(x, settings.format, - settings.zero_suppression), - write_gerber_value(y, settings.format, - settings.zero_suppression), - ) - gerber = self._plot_dots(pitch, width, ploter) - - return gerber - - def to_excellon(self, settings=FileSettings(), pitch=0, width=0): - from gerberex.dxf import DxfArcStatement - if pitch == 0: - x0, y0 = self.statements[0].start - excellon = 'G00{0}\nM15\n'.format( - CoordinateStmtEx(x=x0, y=y0).to_excellon(settings)) - - for statement in self.statements: - x0, y0 = statement.start - x1, y1 = statement.end - if isinstance(statement, DxfArcStatement): - i = statement.center[0] - x0 - j = statement.center[1] - y0 - excellon += '{0}{1}\n'.format( - 'G03' if statement.end_angle > statement.start_angle else 'G02', - CoordinateStmtEx(x=x1, y=y1, i=i, j=j).to_excellon(settings)) - else: - excellon += 'G01{0}\n'.format( - CoordinateStmtEx(x=x1, y=y1).to_excellon(settings)) - - excellon += 'M16\nG05\n' - else: - def ploter(x, y): - return CoordinateStmtEx(x=x, y=y).to_excellon(settings) + '\n' - excellon = self._plot_dots(pitch, width, ploter) - - return excellon - - def _plot_dots(self, pitch, width, ploter): - out = '' - offset = 0 - for idx in range(0, len(self.statements)): - statement = self.statements[idx] - if offset < 0: - offset += pitch - for dot, offset in statement.dots(pitch, width, offset): - if dot is None: - break - if offset > 0 and (statement.is_closed or idx != len(self.statements) - 1): - break - #if idx == len(self.statements) - 1 and statement.is_closed and offset > -pitch: - # break - out += ploter(dot[0], dot[1]) - return out - - def intersections_with_halfline(self, point_from, point_to, error_range=0): - def calculator(statement): - return statement.intersections_with_halfline(point_from, point_to, error_range) - def validator(pt, statement, idx): - if is_equal_point(pt, statement.end, error_range) and \ - not self._judge_cross(point_from, point_to, idx, error_range): - return False - return True - return self._collect_intersections(calculator, validator, error_range) - - def intersections_with_arc(self, center, radius, angle_regions, error_range=0): - def calculator(statement): - return statement.intersections_with_arc(center, radius, angle_regions, error_range) - return self._collect_intersections(calculator, None, error_range) - - def _collect_intersections(self, calculator, validator, error_range): - allpts = [] - last = allpts - for i in range(0, len(self.statements)): - statement = self.statements[i] - cur = calculator(statement) - if cur: - for pt in cur: - for dest in allpts: - if is_equal_point(pt, dest, error_range): - break - else: - if validator is not None and not validator(pt, statement, i): - continue - allpts.append(pt) - last = cur - return allpts - - def _judge_cross(self, from_pt, to_pt, index, error_range): - standard = normalize_vec2d((to_pt[0] - from_pt[0], to_pt[1] - from_pt[1])) - normal = (standard[1], -standard[0]) - def statements(): - for i in range(index, len(self.statements)): - yield self.statements[i] - for i in range(0, index): - yield self.statements[i] - dot_standard = None - for statement in statements(): - tstart = statement.start - tend = statement.end - target = normalize_vec2d((tend[0] - tstart[0], tend[1] - tstart[1])) - dot= dot_vec2d(normal, target) - if dot_standard is None: - dot_standard = dot - continue - if is_equal_point(standard, target, error_range): - continue - return (dot_standard > 0 and dot > 0) or (dot_standard < 0 and dot < 0) - raise Exception('inconsistensy is detected while cross judgement between paths') - -def generate_paths(statements, error_range=0): - from gerberex.dxf import DxfPolylineStatement - - paths = [] - for statement in filter(lambda s: isinstance(s, DxfPolylineStatement), statements): - units = [unit for unit in statement.disassemble()] - paths.append(DxfPath(units, error_range)) - - unique_statements = [] - redundant = 0 - for statement in filter(lambda s: not isinstance(s, DxfPolylineStatement), statements): - for path in paths: - if path.contain(statement): - redundant += 1 - break - else: - for target in unique_statements: - if statement.is_equal_to(target, error_range): - redundant += 1 - break - else: - unique_statements.append(statement) - - paths.extend([DxfPath([s], error_range) for s in unique_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)): - target = paths[j] - if target.merge(mergee, error_range): - break - else: - working.append(mergee) - prev_paths_num = len(paths) - paths = working - - closed_path = list(filter(lambda p: p.is_closed, paths)) - open_path = list(filter(lambda p: not p.is_closed, paths)) - return (closed_path, open_path) - -def judge_containment(path1, path2, error_range=0): - from gerberex.dxf import DxfArcStatement, DxfLineStatement - - nocontainment = (None, None) - if not path1.may_be_in_collision(path2): - return nocontainment - - def is_in_line_segment(point_from, point_to, point): - dx = point_to[0] - point_from[0] - ratio = (point[0] - point_from[0]) / dx if dx != 0 else \ - (point[1] - point_from[1]) / (point_to[1] - point_from[1]) - return ratio >= 0 and ratio <= 1 - - def contain_in_path(statement, path): - if isinstance(statement, DxfLineStatement): - segment = (statement.start, statement.end) - elif isinstance(statement, DxfArcStatement): - if statement.start == statement.end: - segment = (statement.start, statement.center) - else: - segment = (statement.start, statement.end) - else: - raise Exception('invalid dxf statement type') - pts = path.intersections_with_halfline(segment[0], segment[1], error_range) - if len(pts) % 2 == 0: - return False - for pt in pts: - if is_in_line_segment(segment[0], segment[1], pt): - return False - if isinstance(statement, DxfArcStatement): - pts = path.intersections_with_arc( - statement.center, statement.radius, statement.angle_regions, error_range) - if len(pts) > 0: - return False - return True - - if contain_in_path(path1.statements[0], path2): - containment = [path1, path2] - elif contain_in_path(path2.statements[0], path1): - containment = [path2, path1] - else: - return nocontainment - for i in range(1, len(containment[0].statements)): - if not contain_in_path(containment[0].statements[i], containment[1]): - return nocontainment - return containment diff --git a/gerberex/excellon.py b/gerberex/excellon.py deleted file mode 100644 index f7787d3..0000000 --- a/gerberex/excellon.py +++ /dev/null @@ -1,404 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2019 Hiroshi Murayama - -import operator - -import gerber.excellon -from gerber.excellon import ExcellonParser, detect_excellon_format, ExcellonFile, DrillHit, DrillSlot -from gerber.excellon_statements import ExcellonStatement, UnitStmt, CoordinateStmt, UnknownStmt, \ - SlotStmt, DrillModeStmt, RouteModeStmt, LinearModeStmt, \ - ToolSelectionStmt, ZAxisRoutPositionStmt, \ - RetractWithClampingStmt, RetractWithoutClampingStmt, \ - EndOfProgramStmt -from gerber.cam import FileSettings -from gerber.utils import inch, metric, write_gerber_value, parse_gerber_value -from gerberex.utility import rotate - -def loads(data, filename=None, settings=None, tools=None, format=None): - if not settings: - settings = FileSettings(**detect_excellon_format(data)) - if format: - settings.format = format - gerber.excellon.CoordinateStmt = CoordinateStmtEx - gerber.excellon.UnitStmt = UnitStmtEx - file = ExcellonParser(settings, tools).parse_raw(data, filename) - return ExcellonFileEx.from_file(file) - -def write_excellon_header(file, settings, tools): - file.write('M48\nFMAT,2\nICI,OFF\n%s\n' % - UnitStmtEx(settings.units, settings.zeros, settings.format).to_excellon(settings)) - for tool in tools: - file.write(tool.to_excellon(settings) + '\n') - file.write('%%\nG90\n%s\n' % ('M72' if settings.units == 'inch' else 'M71')) - -class ExcellonFileEx(ExcellonFile): - @classmethod - def from_file(cls, file): - def correct_statements(): - for stmt in file.statements: - if isinstance(stmt, UnknownStmt): - line = stmt.stmt.strip() - if line[:3] == 'G02': - yield CircularCWModeStmt() - if len(line) > 3: - yield CoordinateStmtEx.from_excellon(line[3:], file.settings) - elif line[:3] == 'G03': - yield CircularCCWModeStmt() - if len(line) > 3: - yield CoordinateStmtEx.from_excellon(line[3:], file.settings) - elif line[0] == 'X' or line[0] == 'Y' or line[0] == 'A' or line[0] == 'I': - yield CoordinateStmtEx.from_excellon(line, file.settings) - else: - yield stmt - else: - yield stmt - - def generate_hits(statements): - class CoordinateCtx: - def __init__(self, notation): - self.notation = notation - self.x = 0. - self.y = 0. - self.radius = None - self.center_offset = None - - def update(self, x=None, y=None, radius=None, center_offset=None): - if self.notation == 'absolute': - if x is not None: - self.x = x - if y is not None: - self.y = y - else: - if x is not None: - self.x += x - if y is not None: - self.y += y - if radius is not None: - self.radius = radius - if center_offset is not None: - self.center_offset = center_offset - - def node(self, mode, center_offset): - radius, offset = None, None - if mode == DrillRout.MODE_CIRCULER_CW or mode == DrillRout.MODE_CIRCULER_CCW: - if center_offset is None: - radius = self.radius - offset = self.center_offset - else: - radius = None - offset = center_offset - return DrillRout.Node(mode, self.x, self.y, radius, offset) - - STAT_DRILL = 0 - STAT_ROUT_UP = 1 - STAT_ROUT_DOWN = 2 - - status = STAT_DRILL - current_tool = None - rout_mode = None - coordinate_ctx = CoordinateCtx(file.notation) - rout_nodes = [] - - last_position = (0., 0.) - last_radius = None - last_center_offset = None - - def make_rout(status, nodes): - if status != STAT_ROUT_DOWN or len(nodes) == 0 or current_tool is None: - return None - return DrillRout(current_tool, nodes) - - for stmt in statements: - if isinstance(stmt, ToolSelectionStmt): - current_tool = file.tools[stmt.tool] - elif isinstance(stmt, DrillModeStmt): - rout = make_rout(status, rout_nodes) - rout_nodes = [] - if rout is not None: - yield rout - status = STAT_DRILL - rout_mode = None - elif isinstance(stmt, RouteModeStmt): - if status == STAT_DRILL: - status = STAT_ROUT_UP - rout_mode = DrillRout.MODE_ROUT - else: - rout_mode = DrillRout.MODE_LINEAR - - elif isinstance(stmt, LinearModeStmt): - rout_mode = DrillRout.MODE_LINEAR - elif isinstance(stmt, CircularCWModeStmt): - rout_mode = DrillRout.MODE_CIRCULER_CW - elif isinstance(stmt, CircularCCWModeStmt): - rout_mode = DrillRout.MODE_CIRCULER_CCW - elif isinstance(stmt, ZAxisRoutPositionStmt) and status == STAT_ROUT_UP: - status = STAT_ROUT_DOWN - elif isinstance(stmt, RetractWithClampingStmt) or isinstance(stmt, RetractWithoutClampingStmt): - rout = make_rout(status, rout_nodes) - rout_nodes = [] - if rout is not None: - yield rout - status = STAT_ROUT_UP - elif isinstance(stmt, SlotStmt): - coordinate_ctx.update(stmt.x_start, stmt.y_start) - x_start = coordinate_ctx.x - y_start = coordinate_ctx.y - coordinate_ctx.update(stmt.x_end, stmt.y_end) - x_end = coordinate_ctx.x - y_end = coordinate_ctx.y - yield DrillSlotEx(current_tool, (x_start, y_start), - (x_end, y_end), DrillSlotEx.TYPE_G85) - elif isinstance(stmt, CoordinateStmtEx): - center_offset = (stmt.i, stmt.j) \ - if stmt.i is not None and stmt.j is not None else None - coordinate_ctx.update(stmt.x, stmt.y, stmt.radius, center_offset) - if stmt.x is not None or stmt.y is not None: - if status == STAT_DRILL: - yield DrillHitEx(current_tool, (coordinate_ctx.x, coordinate_ctx.y)) - elif status == STAT_ROUT_UP: - rout_nodes = [coordinate_ctx.node(DrillRout.MODE_ROUT, None)] - elif status == STAT_ROUT_DOWN: - rout_nodes.append(coordinate_ctx.node(rout_mode, center_offset)) - - statements = [s for s in correct_statements()] - hits = [h for h in generate_hits(statements)] - return cls(statements, file.tools, hits, file.settings, file.filename) - - @property - def primitives(self): - return [] - - def __init__(self, statements, tools, hits, settings, filename=None): - super(ExcellonFileEx, self).__init__(statements, tools, hits, settings, filename) - - def rotate(self, angle, center=(0,0)): - if angle % 360 == 0: - return - for hit in self.hits: - hit.rotate(angle, center) - - def to_inch(self): - if self.units == 'metric': - for stmt in self.statements: - stmt.to_inch() - for tool in self.tools: - self.tools[tool].to_inch() - for hit in self.hits: - hit.to_inch() - self.units = 'inch' - - def to_metric(self): - if self.units == 'inch': - for stmt in self.statements: - stmt.to_metric() - for tool in self.tools: - self.tools[tool].to_metric() - for hit in self.hits: - hit.to_metric() - self.units = 'metric' - - def write(self, filename=None): - self.notation = 'absolute' - self.zeros = 'trailing' - filename = filename if filename is not None else self.filename - with open(filename, 'w') as f: - write_excellon_header(f, self.settings, [self.tools[t] for t in self.tools]) - for tool in iter(self.tools.values()): - f.write(ToolSelectionStmt( - tool.number).to_excellon(self.settings) + '\n') - for hit in self.hits: - if hit.tool.number == tool.number: - f.write(hit.to_excellon(self.settings) + '\n') - f.write(EndOfProgramStmt().to_excellon() + '\n') - -class DrillHitEx(DrillHit): - def to_inch(self): - self.position = tuple(map(inch, self.position)) - - def to_metric(self): - self.position = tuple(map(metric, self.position)) - - def rotate(self, angle, center=(0, 0)): - self.position = rotate(*self.position, angle, center) - - def to_excellon(self, settings): - return CoordinateStmtEx(*self.position).to_excellon(settings) - -class DrillSlotEx(DrillSlot): - def to_inch(self): - self.start = tuple(map(inch, self.start)) - self.end = tuple(map(inch, self.end)) - - def to_metric(self): - self.start = tuple(map(metric, self.start)) - self.end = tuple(map(metric, self.end)) - - def rotate(self, angle, center=(0,0)): - self.start = rotate(*self.start, angle, center) - self.end = rotate(*self.end, angle, center) - - def to_excellon(self, settings): - return SlotStmt(*self.start, *self.end).to_excellon(settings) - -class DrillRout(object): - MODE_ROUT = 'G00' - MODE_LINEAR = 'G01' - MODE_CIRCULER_CW = 'G02' - MODE_CIRCULER_CCW = 'G03' - - class Node(object): - def __init__(self, mode, x, y, radius=None, center_offset=None): - self.mode = mode - self.position = (x, y) - self.radius = radius - self.center_offset = center_offset - - def to_excellon(self, settings): - center_offset = self.center_offset \ - if self.center_offset is not None else (None, None) - return self.mode + CoordinateStmtEx( - *self.position, self.radius, *center_offset).to_excellon(settings) - - def __init__(self, tool, nodes): - self.tool = tool - self.nodes = nodes - self.nodes[0].mode = self.MODE_ROUT - - def to_excellon(self, settings): - excellon = self.nodes[0].to_excellon(settings) + '\nM15\n' - for node in self.nodes[1:]: - excellon += node.to_excellon(settings) + '\n' - excellon += 'M16\nG05' - return excellon - - def to_inch(self): - for node in self.nodes: - node.position = tuple(map(inch, node.position)) - node.radius = inch( - node.radius) if node.radius is not None else None - if node.center_offset is not None: - node.center_offset = tuple(map(inch, node.center_offset)) - - def to_metric(self): - for node in self.nodes: - node.position = tuple(map(metric, node.position)) - node.radius = metric( - node.radius) if node.radius is not None else None - if node.center_offset is not None: - node.center_offset = tuple(map(metric, node.center_offset)) - - def offset(self, x_offset=0, y_offset=0): - for node in self.nodes: - node.position = tuple(map(operator.add, node.position, (x_offset, y_offset))) - - def rotate(self, angle, center=(0, 0)): - for node in self.nodes: - node.position = rotate(*node.position, angle, center) - if node.center_offset is not None: - node.center_offset = rotate(*node.center_offset, angle, (0., 0.)) - -class UnitStmtEx(UnitStmt): - @classmethod - def from_statement(cls, stmt): - return cls(units=stmt.units, zeros=stmt.zeros, format=stmt.format, id=stmt.id) - - def __init__(self, units='inch', zeros='leading', format=None, **kwargs): - super(UnitStmtEx, self).__init__(units, zeros, format, **kwargs) - - def to_excellon(self, settings=None): - format = settings.format if settings else self.format - stmt = None - if self.units == 'inch' and format == (2, 4): - stmt = 'INCH,%s' % ('LZ' if self.zeros == 'leading' else 'TZ') - else: - stmt = '%s,%s,%s.%s' % ('INCH' if self.units == 'inch' else 'METRIC', - 'LZ' if self.zeros == 'leading' else 'TZ', - '0' * format[0], '0' * format[1]) - return stmt - -class CircularCWModeStmt(ExcellonStatement): - - def __init__(self, **kwargs): - super(CircularCWModeStmt, self).__init__(**kwargs) - - def to_excellon(self, settings=None): - return 'G02' - -class CircularCCWModeStmt(ExcellonStatement): - - def __init__(self, **kwargs): - super(CircularCCWModeStmt, self).__init__(**kwargs) - - def to_excellon(self, settings=None): - return 'G02' - -class CoordinateStmtEx(CoordinateStmt): - @classmethod - def from_statement(cls, stmt): - newStmt = cls(x=stmt.x, y=stmt.y) - newStmt.radius = stmt.radius if isinstance(stmt, CoordinateStmtEx) else None - return newStmt - - @classmethod - def from_excellon(cls, line, settings, **kwargs): - stmt = None - if 'A' in line: - parts = line.split('A') - stmt = cls.from_statement(CoordinateStmt.from_excellon(parts[0], settings)) \ - if parts[0] != '' else cls() - stmt.radius = parse_gerber_value( - parts[1], settings.format, settings.zero_suppression) - elif 'I' in line: - jparts = line.split('J') - iparts = jparts[0].split('I') - stmt = cls.from_statement(CoordinateStmt.from_excellon(iparts[0], settings)) \ - if iparts[0] != '' else cls() - stmt.i = parse_gerber_value( - iparts[1], settings.format, settings.zero_suppression) - stmt.j = parse_gerber_value( - jparts[1], settings.format, settings.zero_suppression) - else: - stmt = cls.from_statement(CoordinateStmt.from_excellon(line, settings)) - - return stmt - - def __init__(self, x=None, y=None, radius=None, i=None, j=None, **kwargs): - super(CoordinateStmtEx, self).__init__(x, y, **kwargs) - self.radius = radius - self.i = i - self.j = j - - def to_excellon(self, settings): - stmt = '' - if self.x is not None: - stmt += 'X%s' % write_gerber_value(self.x, settings.format, - settings.zero_suppression) - if self.y is not None: - stmt += 'Y%s' % write_gerber_value(self.y, settings.format, - settings.zero_suppression) - if self.radius is not None: - stmt += 'A%s' % write_gerber_value(self.radius, settings.format, - settings.zero_suppression) - elif self.i is not None and self.j is not None: - stmt += 'I%sJ%s' % (write_gerber_value(self.i, settings.format, - settings.zero_suppression), - write_gerber_value(self.j, settings.format, - settings.zero_suppression)) - return stmt - - def __str__(self): - coord_str = '' - if self.x is not None: - coord_str += 'X: %g ' % self.x - if self.y is not None: - coord_str += 'Y: %g ' % self.y - if self.radius is not None: - coord_str += 'A: %g ' % self.radius - if self.i is not None: - coord_str += 'I: %g ' % self.i - if self.j is not None: - coord_str += 'J: %g ' % self.j - - return '' % (coord_str) diff --git a/gerberex/gerber_statements.py b/gerberex/gerber_statements.py deleted file mode 100644 index c2eb565..0000000 --- a/gerberex/gerber_statements.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2019 Hiroshi Murayama - -from gerber.gerber_statements import AMParamStmt, ADParamStmt -from gerber.utils import inch, metric -from gerberex.am_primitive import to_primitive_defs - -class AMParamStmtEx(AMParamStmt): - @classmethod - def from_stmt(cls, stmt): - return cls(stmt.param, stmt.name, stmt.macro, stmt.units) - - @classmethod - def circle(cls, name, units): - return cls('AM', name, '1,1,$1,0,0,0*1,0,$2,0,0,0', units) - - @classmethod - def rectangle(cls, name, units): - return cls('AM', name, '21,1,$1,$2,0,0,0*1,0,$3,0,0,0', units) - - @classmethod - def landscape_obround(cls, name, units): - return cls( - 'AM', name, - '$4=$1-$2*' - '$5=$1-$4*' - '21,1,$5,$2,0,0,0*' - '1,1,$4,$4/2,0,0*' - '1,1,$4,-$4/2,0,0*' - '1,0,$3,0,0,0', units) - - @classmethod - def portrate_obround(cls, name, units): - return cls( - 'AM', name, - '$4=$2-$1*' - '$5=$2-$4*' - '21,1,$1,$5,0,0,0*' - '1,1,$4,0,$4/2,0*' - '1,1,$4,0,-$4/2,0*' - '1,0,$3,0,0,0', units) - - @classmethod - def polygon(cls, name, units): - return cls('AM', name, '5,1,$2,0,0,$1,$3*1,0,$4,0,0,0', units) - - def __init__(self, param, name, macro, units): - super(AMParamStmtEx, self).__init__(param, name, macro) - self.units = units - self.primitive_defs = list(to_primitive_defs(self.instructions)) - - def to_inch(self): - if self.units == 'metric': - self.units = 'inch' - for p in self.primitive_defs: - p.to_inch() - - def to_metric(self): - if self.units == 'inch': - self.units = 'metric' - for p in self.primitive_defs: - p.to_metric() - - def to_gerber(self, settings = None): - def plist(): - for p in self.primitive_defs: - yield p.to_gerber(settings) - return "%%AM%s*\n%s%%" % (self.name, '\n'.join(plist())) - - def rotate(self, angle, center=None): - for primitive_def in self.primitive_defs: - primitive_def.rotate(angle, center) - -class ADParamStmtEx(ADParamStmt): - GEOMETRIES = { - 'C': [0,1], - 'R': [0,1,2], - 'O': [0,1,2], - 'P': [0,3], - } - - @classmethod - def from_stmt(cls, stmt): - modstr = ','.join([ - 'X'.join(['{0}'.format(x) for x in modifier]) - for modifier in stmt.modifiers]) - return cls(stmt.param, stmt.d, stmt.shape, modstr, stmt.units) - - def __init__(self, param, d, shape, modifiers, units): - super(ADParamStmtEx, self).__init__(param, d, shape, modifiers) - self.units = units - - def to_inch(self): - if self.units == 'inch': - return - self.units = 'inch' - if self.shape in self.GEOMETRIES: - indices = self.GEOMETRIES[self.shape] - self.modifiers = [tuple([ - inch(self.modifiers[0][i]) if i in indices else self.modifiers[0][i] \ - for i in range(len(self.modifiers[0])) - ])] - - def to_metric(self): - if self.units == 'metric': - return - self.units = 'metric' - if self.shape in self.GEOMETRIES: - indices = self.GEOMETRIES[self.shape] - self.modifiers = [tuple([ - metric(self.modifiers[0][i]) if i in indices else self.modifiers[0][i] \ - for i in range(len(self.modifiers[0])) - ])] diff --git a/gerberex/rs274x.py b/gerberex/rs274x.py deleted file mode 100644 index 3a3a712..0000000 --- a/gerberex/rs274x.py +++ /dev/null @@ -1,331 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2019 Hiroshi Murayama - -from gerber.cam import FileSettings -import gerber.rs274x -from gerber.gerber_statements import * -from gerberex.gerber_statements import AMParamStmt, AMParamStmtEx, ADParamStmtEx -from gerberex.utility import rotate -import re - -def loads(data, filename=None): - cls = gerber.rs274x.GerberParser - cls.SF = \ - r"(?PSF)(A(?P{decimal}))?(B(?P{decimal}))?".format(decimal=cls.DECIMAL) - cls.PARAMS = (cls.FS, cls.MO, cls.LP, cls.AD_CIRCLE, - cls.AD_RECT, cls.AD_OBROUND, cls.AD_POLY, - cls.AD_MACRO, cls.AM, cls.AS, cls.IF, cls.IN, - cls.IP, cls.IR, cls.MI, cls.OF, cls.SF, cls.LN) - cls.PARAM_STMT = [re.compile(r"%?{0}\*%?".format(p)) for p in cls.PARAMS] - return cls().parse_raw(data, filename) - -def write_gerber_header(file, settings): - file.write('%s\n%s\n%%IPPOS*%%\n' % ( - MOParamStmt('MO', settings.units).to_gerber(settings), - FSParamStmt('FS', settings.zero_suppression, - settings.notation, settings.format).to_gerber(settings))) - -class GerberFile(gerber.rs274x.GerberFile): - @classmethod - def from_gerber_file(cls, gerber_file): - if not isinstance(gerber_file, gerber.rs274x.GerberFile): - raise Exception('only gerber.rs274x.GerberFile object is specified') - - return cls(gerber_file.statements, gerber_file.settings, gerber_file.primitives,\ - gerber_file.apertures, gerber_file.filename) - - def __init__(self, statements, settings, primitives, apertures, filename=None): - super(GerberFile, self).__init__(statements, settings, primitives, apertures, filename) - self.context = GerberContext.from_settings(self.settings) - self.aperture_macros = {} - self.aperture_defs = [] - self.main_statements = [] - for stmt in self.statements: - type, stmts = self.context.normalize_statement(stmt) - if type == self.context.TYPE_AM: - for mdef in stmts: - self.aperture_macros[mdef.name] = mdef - elif type == self.context.TYPE_AD: - self.aperture_defs.extend(stmts) - elif type == self.context.TYPE_MAIN: - self.main_statements.extend(stmts) - if self.context.angle != 0: - self.rotate(self.context.angle) - if self.context.is_negative: - self.nagate_polarity() - self.context.notation = 'absolute' - self.context.zeros = 'trailing' - - def write(self, filename=None): - self.context.notation = 'absolute' - self.context.zeros = 'trailing' - self.context.format = self.format - self.units = self.units - filename=filename if filename is not None else self.filename - with open(filename, 'w') as f: - write_gerber_header(f, self.context) - for macro in self.aperture_macros: - f.write(self.aperture_macros[macro].to_gerber(self.context) + '\n') - for aperture in self.aperture_defs: - f.write(aperture.to_gerber(self.context) + '\n') - for statement in self.main_statements: - f.write(statement.to_gerber(self.context) + '\n') - f.write('M02*\n') - - def to_inch(self): - if self.units == 'metric': - for macro in self.aperture_macros: - self.aperture_macros[macro].to_inch() - for aperture in self.aperture_defs: - aperture.to_inch() - for statement in self.statements: - statement.to_inch() - self.units = 'inch' - self.context.units = 'inch' - - def to_metric(self): - if self.units == 'inch': - for macro in self.aperture_macros: - self.aperture_macros[macro].to_metric() - for aperture in self.aperture_defs: - aperture.to_metric() - for statement in self.statements: - statement.to_metric() - self.units='metric' - self.context.units='metric' - - def offset(self, x_offset=0, y_offset=0): - for statement in self.main_statements: - if isinstance(statement, CoordStmt): - if statement.x is not None: - statement.x += x_offset - if statement.y is not None: - statement.y += y_offset - for primitive in self.primitives: - primitive.offset(x_offset, y_offset) - - def rotate(self, angle, center=(0,0)): - if angle % 360 == 0: - return - self._generalize_aperture() - last_x = 0 - last_y = 0 - last_rx = 0 - last_ry = 0 - for name in self.aperture_macros: - self.aperture_macros[name].rotate(angle, center) - for statement in self.main_statements: - if isinstance(statement, CoordStmt) and statement.x != None and statement.y != None: - if statement.i != None and statement.j != None: - cx = last_x + statement.i - cy = last_y + statement.j - cx, cy = rotate(cx, cy, angle, center) - statement.i = cx - last_rx - statement.j = cy - last_ry - last_x = statement.x - last_y = statement.y - last_rx, last_ry = rotate(statement.x, statement.y, angle, center) - statement.x = last_rx - statement.y = last_ry - - def nagate_polarity(self): - for statement in self.main_statements: - if isinstance(statement, LPParamStmt): - statement.lp = 'dark' if statement.lp == 'clear' else 'clear' - - def _generalize_aperture(self): - RECTANGLE = 0 - LANDSCAPE_OBROUND = 1 - PORTRATE_OBROUND = 2 - POLYGON = 3 - macro_defs = [ - ('MACR', AMParamStmtEx.rectangle), - ('MACLO', AMParamStmtEx.landscape_obround), - ('MACPO', AMParamStmtEx.portrate_obround), - ('MACP', AMParamStmtEx.polygon) - ] - - need_to_change = False - for statement in self.aperture_defs: - if isinstance(statement, ADParamStmt) and statement.shape in ['R', 'O', 'P']: - need_to_change = True - - if need_to_change: - for idx in range(0, len(macro_defs)): - macro_def = macro_defs[idx] - name = macro_def[0] - num = 1 - while name in self.aperture_macros: - name = '%s_%d' % (macro_def[0], num) - num += 1 - self.aperture_macros[name] = macro_def[1](name, self.units) - macro_defs[idx] = (name, macro_def[1]) - for statement in self.aperture_defs: - if isinstance(statement, ADParamStmt): - if statement.shape == 'R': - statement.shape = macro_defs[RECTANGLE][0] - elif statement.shape == 'O': - x = statement.modifiers[0][0] \ - if len(statement.modifiers[0]) > 0 else 0 - y = statement.modifiers[0][1] \ - if len(statement.modifiers[0]) > 1 else 0 - statement.shape = macro_defs[LANDSCAPE_OBROUND][0] \ - if x > y else macro_defs[PORTRATE_OBROUND][0] - elif statement.shape == 'P': - statement.shape = macro_defs[POLYGON][0] - -class GerberContext(FileSettings): - TYPE_NONE = 'none' - TYPE_AM = 'am' - TYPE_AD = 'ad' - TYPE_MAIN = 'main' - IP_LINEAR = 'lenear' - IP_ARC = 'arc' - DIR_CLOCKWISE = 'cw' - DIR_COUNTERCLOCKWISE = 'ccw' - - ignored_stmt = ('FSParamStmt', 'MOParamStmt', 'ASParamStmt', - 'INParamStmt', 'IPParamStmt', 'IRParamStmt', - 'MIParamStmt', 'OFParamStmt', 'SFParamStmt', - 'LNParamStmt', 'CommentStmt', 'EofStmt',) - - @classmethod - def from_settings(cls, settings): - return cls(settings.notation, settings.units, settings.zero_suppression, - settings.format, settings.zeros, settings.angle_units) - - def __init__(self, notation='absolute', units='inch', - zero_suppression=None, format=(2, 5), zeros=None, - angle_units='degrees', - name=None, - mirror=(False, False), offset=(0., 0.), scale=(1., 1.), - angle=0., axis='xy'): - super(GerberContext, self).__init__(notation, units, zero_suppression, - format, zeros, angle_units) - self.name = name - self.mirror = mirror - self.offset = offset - self.scale = scale - self.angle = angle - self.axis = axis - - self.matrix = (1, 0, - 1, 0, - 1, 1) - - self.is_negative = False - self.is_first_coordinate = True - self.no_polarity = True - self.in_single_quadrant_mode = False - self.op = None - self.interpolation = self.IP_LINEAR - self.direction = self.DIR_CLOCKWISE - self.x = 0. - self.y = 0. - - def normalize_statement(self, stmt): - additional_stmts = None - if isinstance(stmt, INParamStmt): - self.name = stmt.name - elif isinstance(stmt, MIParamStmt): - self.mirror = (stmt.a, stmt.b) - self._update_matrix() - elif isinstance(stmt, OFParamStmt): - self.offset = (stmt.a, stmt.b) - self._update_matrix() - elif isinstance(stmt, SFParamStmt): - self.scale = (stmt.a, stmt.b) - self._update_matrix() - elif isinstance(stmt, ASParamStmt): - self.axis = 'yx' if stmt.mode == 'AYBX' else 'xy' - self._update_matrix() - elif isinstance(stmt, IRParamStmt): - self.angle = stmt.angle - elif isinstance(stmt, AMParamStmt) and not isinstance(stmt, AMParamStmtEx): - stmt = AMParamStmtEx.from_stmt(stmt) - return (self.TYPE_AM, [stmt]) - elif isinstance(stmt, ADParamStmt) and not isinstance(stmt, AMParamStmtEx): - stmt = ADParamStmtEx.from_stmt(stmt) - return (self.TYPE_AD, [stmt]) - elif isinstance(stmt, QuadrantModeStmt): - self.in_single_quadrant_mode = stmt.mode == 'single-quadrant' - stmt.mode = 'multi-quadrant' - elif isinstance(stmt, IPParamStmt): - self.is_negative = stmt.ip == 'negative' - elif isinstance(stmt, LPParamStmt): - self.no_polarity = False - elif isinstance(stmt, CoordStmt): - self._normalize_coordinate(stmt) - if self.is_first_coordinate: - self.is_first_coordinate = False - if self.no_polarity: - additional_stmts = [LPParamStmt('LP', 'dark'), stmt] - - if type(stmt).__name__ in self.ignored_stmt: - return (self.TYPE_NONE, None) - elif additional_stmts is not None: - return (self.TYPE_MAIN, additional_stmts) - else: - return (self.TYPE_MAIN, [stmt]) - - def _update_matrix(self): - if self.axis == 'xy': - mx = -1 if self.mirror[0] else 1 - my = -1 if self.mirror[1] else 1 - self.matrix = ( - self.scale[0] * mx, self.offset[0], - self.scale[1] * my, self.offset[1], - self.scale[0] * mx, self.scale[1] * my) - else: - mx = -1 if self.mirror[1] else 1 - my = -1 if self.mirror[0] else 1 - self.matrix = ( - self.scale[1] * mx, self.offset[1], - self.scale[0] * my, self.offset[0], - self.scale[1] * mx, self.scale[0] * my) - - def _normalize_coordinate(self, stmt): - if stmt.function == 'G01' or stmt.function == 'G1': - self.interpolation = self.IP_LINEAR - elif stmt.function == 'G02' or stmt.function == 'G2': - self.interpolation = self.IP_ARC - self.direction = self.DIR_CLOCKWISE - if self.mirror[0] != self.mirror[1]: - stmt.function = 'G03' - elif stmt.function == 'G03' or stmt.function == 'G3': - self.interpolation = self.IP_ARC - self.direction = self.DIR_COUNTERCLOCKWISE - if self.mirror[0] != self.mirror[1]: - stmt.function = 'G02' - if stmt.only_function: - return - - last_x = self.x - last_y = self.y - if self.notation == 'absolute': - x = stmt.x if stmt.x is not None else self.x - y = stmt.y if stmt.y is not None else self.y - else: - x = self.x + stmt.x if stmt.x is not None else 0 - y = self.y + stmt.y if stmt.y is not None else 0 - self.x, self.y = x, y - self.op = stmt.op if stmt.op is not None else self.op - - stmt.op = self.op - stmt.x = self.matrix[0] * x + self.matrix[1] - stmt.y = self.matrix[2] * y + self.matrix[3] - if stmt.op == 'D01' and self.interpolation == self.IP_ARC: - qx, qy = 1, 1 - if self.in_single_quadrant_mode: - if self.direction == self.DIR_CLOCKWISE: - qx = 1 if y > last_y else -1 - qy = 1 if x < last_x else -1 - else: - qx = 1 if y < last_y else -1 - qy = 1 if x > last_x else -1 - if last_x == x and last_y == y: - qx, qy = 0, 0 - stmt.i = qx * self.matrix[4] * stmt.i if stmt.i is not None else 0 - stmt.j = qy * self.matrix[5] * stmt.j if stmt.j is not None else 0 diff --git a/gerberex/utility.py b/gerberex/utility.py deleted file mode 100644 index 37de5e8..0000000 --- a/gerberex/utility.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2019 Hiroshi Murayama - -from math import cos, sin, pi, sqrt - -def rotate(x, y, angle, center): - x0 = x - center[0] - y0 = y - center[1] - angle = angle * pi / 180.0 - return (cos(angle) * x0 - sin(angle) * y0 + center[0], - sin(angle) * x0 + cos(angle) * y0 + center[1]) - -def is_equal_value(a, b, error_range=0): - return (a - b) * (a - b) <= error_range * 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) - -def normalize_vec2d(vec): - length = sqrt(vec[0] * vec[0] + vec[1] * vec[1]) - return (vec[0] / length, vec[1] / length) - -def dot_vec2d(vec1, vec2): - return vec1[0] * vec2[0] + vec1[1] * vec2[1] \ No newline at end of file -- cgit