diff options
Diffstat (limited to 'gerbonara/cad/kicad/graphical_primitives.py')
-rw-r--r-- | gerbonara/cad/kicad/graphical_primitives.py | 91 |
1 files changed, 57 insertions, 34 deletions
diff --git a/gerbonara/cad/kicad/graphical_primitives.py b/gerbonara/cad/kicad/graphical_primitives.py index 94a61a4..f86fcc7 100644 --- a/gerbonara/cad/kicad/graphical_primitives.py +++ b/gerbonara/cad/kicad/graphical_primitives.py @@ -9,7 +9,7 @@ from .primitives import * from ... import graphic_objects as go from ... import apertures as ap from ...newstroke import Newstroke -from ...utils import rotate_point, MM +from ...utils import rotate_point, MM, arc_bounds @sexp_type('layer') class TextLayer: @@ -18,10 +18,11 @@ class TextLayer: @sexp_type('gr_text') -class Text(TextMixin): +class Text(TextMixin, BBoxMixin): text: str = '' at: AtPos = field(default_factory=AtPos) layer: TextLayer = field(default_factory=TextLayer) + uuid: UUID = field(default_factory=UUID) tstamp: Timestamp = None effects: TextEffect = field(default_factory=TextEffect) render_cache: RenderCache = None @@ -31,14 +32,15 @@ class Text(TextMixin): @sexp_type('gr_text_box') -class TextBox: +class TextBox(BBoxMixin): locked: Flag() = False text: str = '' start: Named(XYCoord) = None end: Named(XYCoord) = None - pts: PointList = field(default_factory=PointList) + pts: PointList = field(default_factory=list) angle: OmitDefault(Named(float)) = 0.0 layer: Named(str) = "" + uuid: UUID = field(default_factory=UUID) tstamp: Timestamp = None effects: TextEffect = field(default_factory=TextEffect) stroke: Stroke = field(default_factory=Stroke) @@ -53,7 +55,7 @@ class TextBox: raise ValueError('Vector font text with empty render cache') for poly in render_cache.polygons: - reg = go.Region([(p.x, -p.y) for p in poly.pts.xy], unit=MM) + reg = go.Region([(p.x, -p.y) for p in poly.pts], unit=MM) if self.stroke: if self.stroke.type not in (None, Atom.default, Atom.solid): @@ -69,13 +71,14 @@ class TextBox: @sexp_type('gr_line') -class Line: +class Line(WidthMixin): start: Rename(XYCoord) = None end: Rename(XYCoord) = None angle: Named(float) = None # wat layer: Named(str) = None width: Named(float) = None stroke: Stroke = field(default_factory=Stroke) + uuid: UUID = field(default_factory=UUID) tstamp: Timestamp = None def rotate(self, angle, cx=None, cy=None): @@ -98,6 +101,12 @@ class Line: self.start = self.start.with_offset(x, y) self.end = self.end.with_offset(x, y) + def bounding_box(self, unit=MM): + x_min, x_max = min(self.start.x, self.end.x), max(self.start.x, self.end.x) + y_min, y_max = min(self.start.y, self.end.y), max(self.start.y, self.end.y) + w = self.stroke.width if self.stroke else self.width + return (x_min-w, y_max-w), (x_max+w, y_max+w) + @sexp_type('fill') class FillMode: @@ -113,13 +122,14 @@ class FillMode: yield [Atom.fill, Atom.solid if value else Atom.none] @sexp_type('gr_rect') -class Rectangle: +class Rectangle(BBoxMixin, WidthMixin): start: Rename(XYCoord) = None end: Rename(XYCoord) = None layer: Named(str) = None width: Named(float) = None stroke: Stroke = field(default_factory=Stroke) fill: FillMode = False + uuid: UUID = field(default_factory=UUID) tstamp: Timestamp = None def render(self, variables=None): @@ -130,9 +140,9 @@ class Rectangle: if self.fill: yield rect - if self.width: + if (w := self.stroke.width if self.stroke else self.width): # FIXME stroke support - yield from rect.outline_objects(aperture=ap.CircleAperture(self.width, unit=MM)) + yield from rect.outline_objects(aperture=ap.CircleAperture(w, unit=MM)) @property def top_left(self): @@ -145,21 +155,23 @@ class Rectangle: @sexp_type('gr_circle') -class Circle: +class Circle(BBoxMixin, WidthMixin): center: Rename(XYCoord) = None end: Rename(XYCoord) = None layer: Named(str) = None width: Named(float) = None stroke: Stroke = field(default_factory=Stroke) fill: FillMode = False + uuid: UUID = field(default_factory=UUID) tstamp: Timestamp = None def render(self, variables=None): r = math.dist((self.center.x, -self.center.y), (self.end.x, -self.end.y)) - aperture = ap.CircleAperture(self.width or 0, unit=MM) + w = self.stroke.width if self.stroke else self.width + aperture = ap.CircleAperture(w or 0, unit=MM) arc = go.Arc.from_circle(self.center.x, -self.center.y, r, aperture=aperture, unit=MM) - if self.width: + if w: # FIXME stroke support yield arc @@ -170,15 +182,20 @@ class Circle: self.center = self.center.with_offset(x, y) self.end = self.end.with_offset(x, y) + def rotate(self, angle, cx=0, cy=0): + self.center = self.center.with_rotation(angle, cx, cy) + self.end = self.end.with_rotation(angle, cx, cy) + @sexp_type('gr_arc') -class Arc: +class Arc(WidthMixin, BBoxMixin): start: Rename(XYCoord) = None mid: Rename(XYCoord) = None end: Rename(XYCoord) = None layer: Named(str) = None width: Named(float) = None stroke: Stroke = field(default_factory=Stroke) + uuid: UUID = field(default_factory=UUID) tstamp: Timestamp = None _: SEXP_END = None center: XYCoord = None @@ -192,35 +209,35 @@ class Arc: self.mid = center_arc_to_kicad_mid(XYCoord(self.center), self.start, self.end) self.center = None - def rotate(self, angle, cx=None, cy=None): - self.start.x, self.start.y = rotate_point(self.start.x, self.start.y, angle, cx, cy) - self.mid.x, self.mid.y = rotate_point(self.mid.x, self.mid.y, angle, cx, cy) - self.end.x, self.end.y = rotate_point(self.end.x, self.end.y, angle, cx, cy) - def render(self, variables=None): - # FIXME stroke support - if not self.width: + if not (w := self.stroke.width if self.stroke else self.width): return - aperture = ap.CircleAperture(self.width, unit=MM) + aperture = ap.CircleAperture(w, unit=MM) x1, y1 = self.start.x, self.start.y x2, y2 = self.end.x, self.end.y - (cx, cy), _r = kicad_mid_to_center_arc(self.mid, self.start, self.end) - yield go.Arc(x1, -y1, x2, -y2, cx-x1, -(cy-y1), aperture=aperture, clockwise=False, unit=MM) + (cx, cy), _r, clockwise = kicad_mid_to_center_arc(self.mid, self.start, self.end) + yield go.Arc(x1, -y1, x2, -y2, cx-x1, -(cy-y1), aperture=aperture, clockwise=not clockwise, unit=MM) def offset(self, x=0, y=0): self.start = self.start.with_offset(x, y) self.mid = self.mid.with_offset(x, y) self.end = self.end.with_offset(x, y) + def rotate(self, angle, cx=None, cy=None): + self.start.x, self.start.y = rotate_point(self.start.x, self.start.y, angle, cx, cy) + self.mid.x, self.mid.y = rotate_point(self.mid.x, self.mid.y, angle, cx, cy) + self.end.x, self.end.y = rotate_point(self.end.x, self.end.y, angle, cx, cy) + @sexp_type('gr_poly') -class Polygon: +class Polygon(BBoxMixin, WidthMixin): pts: ArcPointList = field(default_factory=list) layer: Named(str) = None width: Named(float) = None stroke: Stroke = field(default_factory=Stroke) fill: FillMode = True + uuid: UUID = field(default_factory=UUID) tstamp: Timestamp = None def render(self, variables=None): @@ -236,35 +253,40 @@ class Polygon: else: # base_types.Arc points.append((point_or_arc.start.x, -point_or_arc.start.y)) points.append((point_or_arc.end.x, -point_or_arc.end.y)) - (cx, cy), _r = kicad_mid_to_center_arc(point_or_arc.mid, point_or_arc.start, point_or_arc.end) - centers.append((False, (cx, -cy))) + (cx, cy), _r, clockwise = kicad_mid_to_center_arc(point_or_arc.mid, point_or_arc.start, point_or_arc.end) + centers.append((not clockwise, (cx, -cy))) reg = go.Region(points, centers, unit=MM) reg.close() + w = self.stroke.width if self.stroke else self.width # FIXME stroke support - if self.width and self.width >= 0.005 or self.stroke.width and self.stroke.width > 0.005: - yield from reg.outline_objects(aperture=ap.CircleAperture(self.width, unit=MM)) + if w and w >= 0.005: + yield from reg.outline_objects(aperture=ap.CircleAperture(w, unit=MM)) if self.fill: yield reg def offset(self, x=0, y=0): - self.pts = PointList([pt.with_offset(x, y) for pt in self.pts]) + self.pts = [pt.with_offset(x, y) for pt in self.pts] + + def rotate(self, angle, cx=0, cy=0): + self.pts = [pt.with_rotation(angle, cx, cy) for pt in self.pts] @sexp_type('gr_curve') -class Curve: - pts: PointList = field(default_factory=PointList) +class Curve(BBoxMixin, WidthMixin): + pts: PointList = field(default_factory=list) layer: Named(str) = None width: Named(float) = None + uuid: UUID = field(default_factory=UUID) tstamp: Timestamp = None def render(self, variables=None): raise NotImplementedError('Bezier rendering is not yet supported. Please raise an issue and provide an example file.') def offset(self, x=0, y=0): - self.pts = PointList([pt.with_offset(x, y) for pt in self.pts]) + self.pts =[pt.with_offset(x, y) for pt in self.pts] @sexp_type('gr_bbox') @@ -319,8 +341,9 @@ class Dimension: locked: Flag() = False dimension_type: Named(AtomChoice(Atom.aligned, Atom.leader, Atom.center, Atom.orthogonal, Atom.radial), name='type') = Atom.aligned layer: Named(str) = 'Dwgs.User' + uuid: UUID = field(default_factory=UUID) tstamp: Timestamp = field(default_factory=Timestamp) - pts: PointList = field(default_factory=PointList) + pts: PointList = field(default_factory=list) height: Named(float) = None orientation: Named(int) = None leader_length: Named(float) = None @@ -332,5 +355,5 @@ class Dimension: raise NotImplementedError('Dimension rendering is not yet supported. Please raise an issue.') def offset(self, x=0, y=0): - self.pts = PointList([pt.with_offset(x, y) for pt in self.pts]) + self.pts = [pt.with_offset(x, y) for pt in self.pts] |