From 91b99a04523e7efb37dab0a5b9378c1c5a230b74 Mon Sep 17 00:00:00 2001 From: jaseg Date: Fri, 21 Jul 2023 17:56:24 +0200 Subject: WIP --- gerbonara/cad/kicad/base_types.py | 7 +++- gerbonara/cad/kicad/schematic.py | 54 ++++++++++++++-------------- gerbonara/cad/kicad/symbols.py | 76 +++++++-------------------------------- gerbonara/newstroke.py | 28 ++++++++++++--- 4 files changed, 69 insertions(+), 96 deletions(-) diff --git a/gerbonara/cad/kicad/base_types.py b/gerbonara/cad/kicad/base_types.py index 8243c07..6c0526f 100644 --- a/gerbonara/cad/kicad/base_types.py +++ b/gerbonara/cad/kicad/base_types.py @@ -333,6 +333,10 @@ class TextMixin: def h_align(self): return 'left' if self.effects.justify.h else 'center' + @property + def mirrored(self): + return False, False + def to_svg(self, color='black', variables={}): if not self.effects or self.effects.hide or not self.effects.font: return @@ -344,12 +348,13 @@ class TextMixin: print(text, self.rotation, self.at, self.effects) yield font.render_svg(text, size=self.size or 1.27, - rotation=self.rotation, h_align=self.h_align, v_align=self.effects.justify.v or self.default_v_align, stroke=color, stroke_width=f'{self.line_width:.3f}', scale=(1,1), + rotation=self.rotation, + mirror=self.mirrored, transform=f'translate({self.at.x:.3f} {self.at.y:.3f})', ) diff --git a/gerbonara/cad/kicad/schematic.py b/gerbonara/cad/kicad/schematic.py index 7809685..0fbc47a 100644 --- a/gerbonara/cad/kicad/schematic.py +++ b/gerbonara/cad/kicad/schematic.py @@ -3,6 +3,7 @@ Library for handling KiCad's schematic files (`*.kicad_sch`). """ import math +import string from pathlib import Path from dataclasses import field, KW_ONLY from itertools import chain @@ -296,7 +297,10 @@ class DrawnProperty(TextMixin): # Alias value for text mixin @property def text(self): - return self.value + if self.key == 'Reference' and self.parent.unit > 0: + return f'{self.value}{string.ascii_uppercase[self.parent.unit-1]}' + else: + return self.value @text.setter def text(self, value): @@ -308,25 +312,20 @@ class DrawnProperty(TextMixin): @property def h_align(self): - j = self.effects.justify.h_str - if False: #self.at.rotation in (270): - return {'left': 'right', 'right': 'left'}.get(j, j) - else: - return j + return self.effects.justify.h_str @property def rotation(self): rot = -self.at.rotation rot += getattr(self.parent.at, 'rotation', 0) - if getattr(self.parent, 'reference', None) == 'C13': - print(self.value, self.at, self.parent.at, self.parent.mirror) - if hasattr(self.parent, 'mirror'): - if self.parent.mirror.y and rot in (90, 270): - rot = (rot+180)%360 - if self.parent.mirror.x and rot in (0, 180): - rot = (rot+180)%360 return rot%360 + @property + def mirrored(self): + if hasattr(self.parent, 'mirror'): + return self.parent.mirror.x, self.parent.mirror.y + return False, False + def to_svg(self, colorscheme=Colorscheme.KiCad): if not self.hide: yield from TextMixin.to_svg(self, colorscheme.text) @@ -396,28 +395,29 @@ class SymbolInstance: sym = self.schematic.lookup_symbol(self.lib_name, self.lib_id) - 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) + units = [unit for unit in sym.units if unit.unit_global or unit.unit_index == self.unit] - 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) + if self.reference in ('U18',): + print(self.reference, self.unit, self.at, self.mirror, units) xform = f'translate({self.at.x:.3f} {self.at.y:.3f})' - if rot: - xform += f'rotate({-rot})' - if self.mirror.x: + if self.mirror.y: + xform += f'scale(-1 -1)' + elif self.mirror.x: xform += f'scale(-1 1)' - if not self.mirror.y: + else: xform += f'scale(1 -1)' + if rot: + xform += f'rotate({rot})' + children = [foo for unit in units for elem in unit.graphical_elements for foo in elem.to_svg(colorscheme)] yield Tag('g', children=children, transform=xform, fill=colorscheme.fill, stroke=colorscheme.lines) - for prop in self.properties: - yield from prop.to_svg() + children = [foo for unit in units for pin in unit.pins for foo in pin.to_svg(colorscheme, self.mirror, rot)] + 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') diff --git a/gerbonara/cad/kicad/symbols.py b/gerbonara/cad/kicad/symbols.py index 94d30d3..5810d97 100644 --- a/gerbonara/cad/kicad/symbols.py +++ b/gerbonara/cad/kicad/symbols.py @@ -100,16 +100,20 @@ class Pin: return (x1, y1), (x2, y2) - def to_svg(self, colorscheme=Colorscheme.KiCad): + def to_svg(self, colorscheme, p_mirror, p_rotation): if self.hide: return - x1, y1 = 0, 0 - x2, y2 = self.length, 0 + if self.name.value in ('PA3', 'QA'): + print(self.name.value, self.at, p_rotation) + psx, psy = (-1 if p_mirror.x else 1), (-1 if p_mirror.y else 1) + x1, y1 = self.at.x, self.at.y + x2, y2 = self.at.x+self.length, self.at.y 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 0 0 L {self.length:.3f} 0') + return eps = 1 for tag in { @@ -403,7 +407,6 @@ class Unit: pins: List(Pin) = field(default_factory=list) unit_name: Named(str) = None _ : SEXP_END = None - global_units: list = field(default_factory=list) unit_global: Flag() = False style_global: Flag() = False demorgan_style: int = 1 @@ -420,7 +423,7 @@ class Unit: raise FormatError(f'Unit name "{self.name}" does not match symbol name "{self.symbol.name}"') self.demorgan_style = int(demorgan_style) self.unit_index = int(unit_index) - self.style_global = self._demorgan_style == 0 + self.style_global = self.demorgan_style == 0 self.unit_global = self.unit_index == 0 @property @@ -430,16 +433,10 @@ class Unit: yield from self.polylines yield from self.rectangles yield from self.texts - yield from self.pins def __before_sexp__(self): self.name = f'{self.symbol.name}_{self.unit_index}_{self.demorgan_style}' - def __getattr__(self, name): - if name.startswith('all_'): - name = name[4:] - return itertools.chain(getattr(self.global_units, name, []), getattr(self, name, [])) - def pin_stacks(self): stacks = defaultdict(lambda: set()) for pin in self.all_pins(): @@ -457,10 +454,8 @@ class Symbol: in_bom: Named(YesNoAtom()) = True on_board: Named(YesNoAtom()) = True properties: List(Property) = field(default_factory=list) - raw_units: List(Unit) = field(default_factory=list) + units: List(Unit) = field(default_factory=list) _ : SEXP_END = None - styles: {str: {str: Unit}} = None - global_units: {str: {str: Unit}} = None library = None name: str = None library_name: str = None @@ -469,8 +464,6 @@ class Symbol: self.library = parent self.library_name, _, self.name = self.raw_name.rpartition(':') - self.global_units = {} - self.styles = {} if self.extends: self.in_bom = None @@ -480,29 +473,7 @@ class Symbol: if (prop := self.properties.get('ki_fp_filters')): prop.value = prop.value.split() if prop.value else [] - for unit in self.raw_units: - if unit.unit_global or unit.style_global: - d = self.global_units.get(unit.demorgan_style, {}) - d[unit.name] = unit - self.global_units[unit.demorgan_style] = d - - for other in self.raw_units: - if other.unit_global or other.style_global or other == unit: - continue - if not (unit.unit_global or other.name == unit.name): - continue - if not (unit.style_global or other.demorgan_style == unit.demorgan_style): - continue - other.global_units.append(unit) - - else: - d = self.styles.get(unit.demorgan_style, {}) - d[unit.name] = unit - self.styles[unit.demorgan_style] = d - def __before_sexp__(self): - self.raw_units = ([unit for style in self.global_units.values() for unit in style.values()] + - [unit for style in self.styles.values() for unit in style.values()]) if (prop := self.properties.get('ki_fp_filters')): if not isinstance(prop.value, str): prop.value = ' '.join(prop.value) @@ -521,30 +492,11 @@ class Symbol: ]): self.properties[name] = Property(name=name, value=value, id=i, effects=TextEffect(hide=hide)) - def units(self, demorgan_style=None): + def resolve(self): if self.extends: - return self.library[self.extends].units(demorgan_style) + return self.library[self.extends] else: - return self.styles.get(demorgan_style or 'default', {}) - - def get_center_rectangle(self, units): - # return a polyline for the requested unit that is a rectangle - # and is closest to the center - candidates = {} - # building a dict with floats as keys.. there needs to be a rule against that^^ - pl_rects = [i.to_polyline() for i in self.rectangles] - pl_rects.extend(pl for pl in self.polylines if pl.is_rectangle()) - for pl in pl_rects: - if pl.unit in units: - # extract the center, calculate the distance to origin - (x, y) = pl.get_center_of_boundingbox() - dist = math.sqrt(x * x + y * y) - candidates[dist] = pl - - if candidates: - # sort the list return the first (smallest) item - return candidates[sorted(candidates.keys())[0]] - return None + return self def is_graphic_symbol(self): return self.extends is None and ( @@ -565,10 +517,6 @@ class Symbol: pins[pin.number].add(pin) return pins - def __getattr__(self, name): - if name.startswith('all_'): - return itertools.chain(getattr(unit, name) for unit in self.raw_units) - def filter_pins(self, name=None, direction=None, electrical_type=None): for pin in self.all_pins: if name and not fnmatch(pin.name, name): diff --git a/gerbonara/newstroke.py b/gerbonara/newstroke.py index d0ed951..c2f3d55 100644 --- a/gerbonara/newstroke.py +++ b/gerbonara/newstroke.py @@ -31,12 +31,13 @@ class Newstroke: def load(kls): return kls() - def render(self, text, size=1.0, x0=0, y0=0, rotation=0, h_align='left', v_align='bottom', space_width=DEFAULT_SPACE_WIDTH, char_gap=DEFAULT_CHAR_GAP, scale=(1, 1)): + def render(self, text, size=1.0, x0=0, y0=0, rotation=0, h_align='left', v_align='bottom', space_width=DEFAULT_SPACE_WIDTH, char_gap=DEFAULT_CHAR_GAP, scale=(1, 1), mirror=(False, False)): text = unicodedata.normalize('NFC', text) missing_glyph = self.glyphs['?'] sx, sy = scale + mx, my = mirror x = 0 - if text in ('VDDA', 'PA9', 'VSS'): + if text in ('VDDA', 'PA9', 'VSS', 'FB3'): print(text, x0, y0, rotation, h_align, v_align, scale) if rotation >= 180: @@ -44,6 +45,25 @@ class Newstroke: h_align = {'left': 'right', 'right': 'left'}.get(h_align, h_align) x0, y0 = -x0, y0 + if scale == (1, 1) and rotation == 90: + rotation = 270 + h_align = {'left': 'right', 'right': 'left'}.get(h_align, h_align) + v_align = {'top': 'bottom', 'bottom': 'top'}.get(v_align, v_align) + + #if mx: + # x0 = -x0 + # if rotation == 90: + # v_align = {'top': 'bottom', 'bottom': 'top'}.get(v_align, v_align) + # else: + # h_align = {'left': 'right', 'right': 'left'}.get(h_align, h_align) + + if my: + y0 = -y0 + if rotation == 0: + v_align = {'top': 'bottom', 'bottom': 'top'}.get(v_align, v_align) + else: + h_align = {'left': 'right', 'right': 'left'}.get(h_align, h_align) + x0, y0 = rotate_point(x0, y0, math.radians(-rotation)) alx, aly = 0, 0 @@ -77,7 +97,7 @@ class Newstroke: x += glyph_w*size - def render_svg(self, text, size=1.0, x0=0, y0=0, rotation=0, h_align='left', v_align='bottom', space_width=DEFAULT_SPACE_WIDTH, char_gap=DEFAULT_CHAR_GAP, scale=(1, -1), **svg_attrs): + def render_svg(self, text, size=1.0, x0=0, y0=0, rotation=0, h_align='left', v_align='bottom', space_width=DEFAULT_SPACE_WIDTH, char_gap=DEFAULT_CHAR_GAP, scale=(1, -1), mirror=(False, False), **svg_attrs): if 'stroke_linecap' not in svg_attrs: svg_attrs['stroke_linecap'] = 'round' if 'stroke_linejoin' not in svg_attrs: @@ -88,7 +108,7 @@ class Newstroke: strokes = ['M ' + ' L '.join(f'{x:.3f} {y:.3f}' for x, y in stroke) for stroke in self.render(text, size=size, x0=x0, y0=y0, rotation=rotation, h_align=h_align, - v_align=v_align, space_width=space_width, char_gap=char_gap, + v_align=v_align, mirror=mirror, space_width=space_width, char_gap=char_gap, scale=scale)] return Tag('path', d=' '.join(strokes), **svg_attrs) -- cgit