From 690df56bb71020901167605a87ec451081fa18d7 Mon Sep 17 00:00:00 2001 From: opiopan Date: Sat, 23 Mar 2019 21:59:13 +0900 Subject: add rotation fuction --- gerberex/am_primitive.py | 35 +++++++++++++++++--------- gerberex/composition.py | 2 +- gerberex/dxf.py | 2 +- gerberex/excellon.py | 7 ++++++ gerberex/rs274x.py | 65 +++++++++++++++++++++++++++++++++++++++++++++++- gerberex/statements.py | 38 +++++++++++++++++++++++++++- gerberex/utility.py | 13 ++++++++++ 7 files changed, 146 insertions(+), 16 deletions(-) create mode 100644 gerberex/utility.py (limited to 'gerberex') diff --git a/gerberex/am_primitive.py b/gerberex/am_primitive.py index 3ce047a..82370f6 100644 --- a/gerberex/am_primitive.py +++ b/gerberex/am_primitive.py @@ -7,13 +7,21 @@ from gerber.utils import * from gerber.am_statements import * from gerber.am_eval import OpCode -from gerberex.am_expression import eval_macro +from gerberex.am_expression import eval_macro, AMConstantExpression, AMOperatorExpression class AMPrimitiveDef(AMPrimitive): - def __init__(self, code, exposure=None, rotation=0): + 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 @@ -44,12 +52,12 @@ class AMCommentPrimitiveDef(AMPrimitiveDef): class AMCirclePrimitiveDef(AMPrimitiveDef): @classmethod def from_modifiers(cls, code, modifiers): - exposure = 'on' if modifiers[0] == 1 else 'off', - diameter = modifiers[1], - center_x = modifiers[2], - center_y = modifiers[3], + exposure = 'on' if modifiers[0].value == 1 else 'off' + diameter = modifiers[1] + center_x = modifiers[2] + center_y = modifiers[3] rotation = modifiers[4] - return cls(code, expressions, center_x, center_y, rotation) + 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) @@ -87,7 +95,7 @@ class AMVectorLinePrimitiveDef(AMPrimitiveDef): @classmethod def from_modifiers(cls, code, modifiers): code = code - exposure = 'on' if modifiers[0] == 1 else 'off' + exposure = 'on' if modifiers[0].value == 1 else 'off' width = modifiers[1] start_x = modifiers[2] start_y = modifiers[3] @@ -141,7 +149,7 @@ class AMCenterLinePrimitiveDef(AMPrimitiveDef): @classmethod def from_modifiers(cls, code, modifiers): code = code - exposure = 'on' if modifiers[0] == 1 else 'off' + exposure = 'on' if modifiers[0].value == 1 else 'off' width = modifiers[1] height = modifiers[2] x = modifiers[3] @@ -191,7 +199,7 @@ class AMOutlinePrimitiveDef(AMPrimitiveDef): def from_modifiers(cls, code, modifiers): num_points = modifiers[1] + 1 code = code - exposure = 'on' if modifiers[0] == 1 else 'off' + exposure = 'on' if modifiers[0].value == 1 else 'off' addrs = modifiers[2:num_points * 2] rotation = modifiers[3 + num_points * 2] return cls(code, exposure, addrs, rotation) @@ -231,7 +239,7 @@ class AMPolygonPrimitiveDef(AMPrimitiveDef): @classmethod def from_modifiers(cls, code, modifiers): code = code - exposure = 'on' if modifiers[0] == 1 else 'off' + exposure = 'on' if modifiers[0].value == 1 else 'off' vertices = modifiers[1] x = modifiers[2] y = modifiers[3] @@ -417,6 +425,9 @@ class AMVariableDef(object): yield i yield (OpCode.STORE, self.number) + def rotate(self, angle, center=None): + pass + def to_primitive_defs(instructions): classes = { 0: AMCommentPrimitiveDef, @@ -434,4 +445,4 @@ def to_primitive_defs(instructions): yield AMVariableDef(-code, modifiers[0]) else: primitive = classes[code] - yield primitive.from_modifiers(code, modifiers) \ No newline at end of file + yield primitive.from_modifiers(code, modifiers) diff --git a/gerberex/composition.py b/gerberex/composition.py index 2613a61..afcaf97 100644 --- a/gerberex/composition.py +++ b/gerberex/composition.py @@ -101,7 +101,7 @@ class GerberComposition(Composition): if not self.settings: self.settings = file.settings - self.param_statements = file.header + self.param_statements = [file.header] def _register_aperture_macro(self, statement): diff --git a/gerberex/dxf.py b/gerberex/dxf.py index b641924..11072b5 100644 --- a/gerberex/dxf.py +++ b/gerberex/dxf.py @@ -111,7 +111,7 @@ class DxfArcStatement(DxfStatement): settings.zero_suppression), write_gerber_value(begin_y, settings.format, settings.zero_suppression), - '03' if deg0 > deg1 else '02', + '03', write_gerber_value(end_x, settings.format, settings.zero_suppression), write_gerber_value(end_y, settings.format, diff --git a/gerberex/excellon.py b/gerberex/excellon.py index 78e6e5f..90d6742 100644 --- a/gerberex/excellon.py +++ b/gerberex/excellon.py @@ -6,6 +6,7 @@ from gerber.excellon import (ExcellonParser, detect_excellon_format, ExcellonFile) from gerber.excellon_statements import UnitStmt from gerber.cam import FileSettings +from gerberex.utility import rotate def loads(data, filename=None, settings=None, tools=None, format=None): if not settings: @@ -27,6 +28,12 @@ class ExcellonFileEx(ExcellonFile): 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.position = rotate(hit.position[0], hit.position[1], angle, center) + class UnitStmtEx(UnitStmt): @classmethod def from_statement(cls, stmt): diff --git a/gerberex/rs274x.py b/gerberex/rs274x.py index e9d82cd..4b477d3 100644 --- a/gerberex/rs274x.py +++ b/gerberex/rs274x.py @@ -4,7 +4,9 @@ # Copyright 2019 Hiroshi Murayama import gerber.rs274x -from gerberex.statements import (AMParamStmt, AMParamStmtEx) +from gerber.gerber_statements import ADParamStmt, CoordStmt +from gerberex.statements import AMParamStmt, AMParamStmtEx +from gerberex.utility import rotate class GerberFile(gerber.rs274x.GerberFile): @classmethod @@ -23,3 +25,64 @@ class GerberFile(gerber.rs274x.GerberFile): def __init__(self, statements, settings, primitives, apertures, filename=None): super(GerberFile, self).__init__(statements, settings, primitives, apertures, filename) + + def rotate(self, angle, center=(0,0)): + if angle % 360 == 0: + return + self._generalize_aperture() + for statement in self.statements: + if isinstance(statement, AMParamStmtEx): + statement.rotate(angle, center) + elif isinstance(statement, CoordStmt) and statement.x != None and statement.y != None: + statement.x, statement.y = rotate(statement.x, statement.y, angle, center) + + 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 + insert_point = 0 + last_aperture = 0 + macros = {} + for idx in range(0, len(self.statements)): + statement = self.statements[idx] + if isinstance(statement, AMParamStmtEx): + macros[statement.name] = statement + if not need_to_change: + insert_point = idx + 1 + if isinstance(statement, ADParamStmt) and statement.shape in ['R', 'O', 'P']: + need_to_change = True + last_aperture = idx + + 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 macros: + name = '%s_%d' % (macro_def[0], num) + num += 1 + self.statements.insert(insert_point, macro_def[1](name)) + macro_defs[idx] = (name, macro_def[1]) + for idx in range(insert_point, last_aperture + len(macro_defs) + 1): + statement = self.statements[idx] + if isinstance(statement, ADParamStmt): + if statement.shape == 'R': + statement.shape = macro_defs[RECTANGLE][0] + elif statement.shape == 'O': + x = statement.modifiers[0] \ + if len(statement.modifiers) > 0 else 0 + y = statement.modifiers[1] \ + if len(statement.modifiers) > 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] diff --git a/gerberex/statements.py b/gerberex/statements.py index 77ca235..c41acb9 100644 --- a/gerberex/statements.py +++ b/gerberex/statements.py @@ -11,9 +11,41 @@ class AMParamStmtEx(AMParamStmt): def from_stmt(cls, stmt): return cls(stmt.param, stmt.name, stmt.macro) + @classmethod + def circle(cls, name): + return cls('AM', name, '1,1,$1,0,0,0*1,0,$2,0,0,0') + + @classmethod + def rectangle(cls, name): + return cls('AM', name, '21,1,$1,$2,0,0,0*1,0,$3,0,0,0') + + @classmethod + def landscape_obround(cls, name): + return cls( + 'AM', name, + '$4=$1-$2*' + '21,1,$1-$4,$2,0,0,0*' + '1,1,$4,$4/2,0,0*' + '1,1,$4,-$4/2,0,0*' + '1,0,$3,0,0,0') + + @classmethod + def portrate_obround(cls, name): + return cls( + 'AM', name, + '$4=$2-$1*' + '21,1,$1,$2-$4,0,0,0*' + '1,1,$4,0,$4/2,0*' + '1,1,$4,0,-$4/2,0*' + '1,0,$3,0,0,0') + + @classmethod + def polygon(cls, name): + return cls('AM', name, '5,1,$2,0,0,$1,$3*1,0,$4,0,0,0') + def __init__(self, param, name, macro): super(AMParamStmtEx, self).__init__(param, name, macro) - self.primitive_defs = to_primitive_defs(self.instructions) + self.primitive_defs = list(to_primitive_defs(self.instructions)) def to_inch(self): if self.units == 'metric': @@ -32,3 +64,7 @@ class AMParamStmtEx(AMParamStmt): 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) diff --git a/gerberex/utility.py b/gerberex/utility.py new file mode 100644 index 0000000..852519a --- /dev/null +++ b/gerberex/utility.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright 2019 Hiroshi Murayama + +from math import cos, sin, pi + +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]) -- cgit