From b69e9fded4d3c135e4b729a2c2bca247a9ebda82 Mon Sep 17 00:00:00 2001 From: jaseg Date: Fri, 21 Jul 2023 01:46:11 +0200 Subject: Kicad schematic rendering WIP --- gerbonara/cad/kicad/base_types.py | 39 ++++++++++++++++++++++----- gerbonara/cad/kicad/schematic.py | 56 ++++++++++++++++++++++++++++++--------- gerbonara/cad/kicad/symbols.py | 13 +++++---- 3 files changed, 82 insertions(+), 26 deletions(-) diff --git a/gerbonara/cad/kicad/base_types.py b/gerbonara/cad/kicad/base_types.py index 9acef7b..d29586e 100644 --- a/gerbonara/cad/kicad/base_types.py +++ b/gerbonara/cad/kicad/base_types.py @@ -80,7 +80,8 @@ class Stroke: attrs = {'stroke': color, 'stroke_linecap': 'round', - 'stroke_widtj': self.width} + 'stroke_linejoin': 'round', + 'stroke_width': self.width or 0.254} if self.type not in (Atom.default, Atom.solid): attrs['stroke_dasharray'] = { @@ -313,6 +314,14 @@ class TextMixin: d = ' '.join(self.svg_path_data()) yield Tag('path', d=d, fill='none', stroke=color, stroke_width=f'{self.line_width:.3f}', stroke_linecap='round') + @property + def _text_offset(self): + return (0, 0) + + @property + def rotation(self): + return self.at.rotation + def render(self, variables={}): if not self.effects or self.effects.hide or not self.effects.font: return @@ -327,17 +336,30 @@ class TextMixin: w = max_x - min_x h = max_y - min_y - offx = -min_x + { + h_just = self.effects.justify.h if self.effects.justify else None + v_just = self.effects.justify.v if self.effects.justify else None + + rot = self.rotation + # KiCad already flips h_just in these cases, making the rotation param redundant. + if rot == 180: + rot = 0 + + if rot == 270 and self.at.rotation != 270: + h_just = {Atom.right: Atom.left, Atom.left: Atom.right}.get(h_just, h_just) + + offx, offy = self._text_offset + + offx += -min_x + { None: -w/2, Atom.right: -w, Atom.left: 0 - }[self.effects.justify.h if self.effects.justify else None] + }[h_just] - offy = { + offy += { None: self.size/2, Atom.top: self.size, Atom.bottom: 0 - }[self.effects.justify.v if self.effects.justify else None] + }[v_just] aperture = ap.CircleAperture(self.line_width or 0.2, unit=MM) for stroke in strokes: @@ -345,7 +367,7 @@ class TextMixin: for x, y in stroke: x, y = x+offx, y+offy - x, y = rotate_point(x, y, math.radians(self.at.rotation or 0)) + x, y = rotate_point(x, y, math.radians(-rot or 0)) x, y = x+self.at.x, y+self.at.y out.append((x, y)) @@ -429,6 +451,11 @@ class DrawnProperty(TextMixin): hide: Flag() = False tstamp: Timestamp = None effects: TextEffect = field(default_factory=TextEffect) + _ : SEXP_END = None + parent: object = None + + def __after_parse(self, parent=None): + self.parent = parent # Alias value for text mixin @property diff --git a/gerbonara/cad/kicad/schematic.py b/gerbonara/cad/kicad/schematic.py index 1a1af9d..00272dd 100644 --- a/gerbonara/cad/kicad/schematic.py +++ b/gerbonara/cad/kicad/schematic.py @@ -187,6 +187,10 @@ class LocalLabel(TextMixin): effects: TextEffect = field(default_factory=TextEffect) uuid: UUID = field(default_factory=UUID) + @property + def _text_offset(self): + return (0, -2*self.line_width) + def to_svg(self, colorscheme=Colorscheme.KiCad): yield from TextMixin.to_svg(self, colorscheme.text) @@ -283,6 +287,11 @@ class DrawnProperty(TextMixin): at: AtPos = field(default_factory=AtPos) hide: Flag() = False effects: TextEffect = field(default_factory=TextEffect) + _: SEXP_END = None + parent: object = None + + def __after_parse__(self, parent=None): + self.parent = parent # Alias value for text mixin @property @@ -293,6 +302,10 @@ class DrawnProperty(TextMixin): def text(self, value): self.value = value + @property + def rotation(self): + return self.parent.rotation + self.at.rotation + def to_svg(self, colorscheme=Colorscheme.KiCad): if not self.hide: yield from TextMixin.to_svg(self, colorscheme.text) @@ -323,26 +336,39 @@ class SymbolInstance: def __after_parse__(self, parent): self.schematic = parent + @property + def rotation(self): + return self.at.rotation + def to_svg(self, colorscheme=Colorscheme.KiCad): children = [] + rot = self.at.rotation - for prop in self.properties: - children += prop.to_svg() + sym = self.schematic.lookup_symbol(self.lib_name, self.lib_id) - sym = self.schematic.lookup_symbol(self.lib_name, self.lib_id).raw_units[self.unit - 1] - for elem in sym.graphical_elements: - children += elem.to_svg(colorscheme) + name = f'{sym.name}_0_1' + if name in sym.global_units.get(1, {}): + for elem in sym.global_units[1][name].graphical_elements: + children += elem.to_svg(colorscheme) + + name = f'{sym.name}_{self.unit}_1' + if name in sym.styles.get(1, {}): + for elem in sym.styles[1][name].graphical_elements: + children += elem.to_svg(colorscheme) xform = f'translate({self.at.x:.3f} {self.at.y:.3f})' - if self.at.rotation: - xform = f'rotate({self.at.rotation}) {xform}' + if rot: + xform += f'rotate({-rot})' if self.mirror.x: - xform = f'scale(-1 1) {xform}' - if self.mirror.y: - xform = f'scale(1 -1) {xform}' + xform += f'scale(-1 1)' + if not self.mirror.y: + xform += f'scale(1 -1)' yield Tag('g', children=children, transform=xform, fill=colorscheme.fill, stroke=colorscheme.lines) + for prop in self.properties: + yield from prop.to_svg() + @sexp_type('path') class SubsheetCrosslinkSheet: @@ -399,6 +425,10 @@ class Subsheet: def __before_sexp__(self): self._properties = [self.sheet_name, self.file_name] + @property + def rotation(self): + return 0 + def open(self, search_dir=None, safe=True): if search_dir is None: if not self.schematic.original_filename: @@ -496,18 +526,18 @@ class Schematic: @property def elements(self): + yield from self.images + yield from self.polylines + yield from self.symbols yield from self.junctions yield from self.no_connects yield from self.bus_entries yield from self.wires yield from self.buses - yield from self.images - yield from self.polylines yield from self.texts yield from self.local_labels yield from self.global_labels yield from self.hierarchical_labels - yield from self.symbols yield from self.subsheets def to_svg(self, colorscheme=Colorscheme.KiCad): diff --git a/gerbonara/cad/kicad/symbols.py b/gerbonara/cad/kicad/symbols.py index 6193b57..4767b88 100644 --- a/gerbonara/cad/kicad/symbols.py +++ b/gerbonara/cad/kicad/symbols.py @@ -96,12 +96,12 @@ class Pin: return (x1, y1), (x2, y2) def to_svg(self, colorscheme=Colorscheme.KiCad): - x1, y1 = self.at.x, self.at.y - x2, y2 = x1+self.length, y1 - xform = {'transform': f'rotate({-self.at.rotation} {x1} {y1})'} + x1, y1 = 0, 0 + x2, y2 = self.length, 0 + xform = {'transform': f'translate({self.at.x:.3f} {self.at.y:.3f}) rotate({self.at.rotation})'} style = {'stroke_width': 0.254, 'stroke': colorscheme.lines, 'stroke_linecap': 'round'} - yield Tag('path', **xform, **style, d=f'M {x1:.6f} {y1:.6f} L {x2:.6f} {y2:.6f}') + yield Tag('path', **xform, **style, d=f'M 0 0 L {self.length:.3f} 0') eps = 1 for tag in { @@ -160,8 +160,6 @@ class Pin: else: raise ValueError(f'Invalid pin rotation {self.at.rotation}') - yield f'M {line.x1:.3f} {line.y1:.3f} L {line.x2:.3f} {line.y2:.3f}' - d = [] for stroke in strokes: points = [] @@ -171,7 +169,8 @@ class Pin: x, y = x+self.at.x, y+self.at.y points.append(f'{x:.3f} {y:.3f}') d.append('M '+ ' L '.join(points) + ' ') - yield Tag('path', d=d, fill='none', stroke=colorscheme.text, stroke_width='0.254', stroke_linecap='round') + yield Tag('path', d=' '.join(d), fill='none', stroke=colorscheme.text, stroke_width='0.254', stroke_linecap='round', stroke_linejoin='round') + print('name', self.name.value) @sexp_type('fill') -- cgit