kopia lustrzana https://gitlab.com/gerbolyze/gerbonara
Fix merging, bounding boxes and svg precision
rodzic
e422243a6e
commit
460ea625af
|
@ -236,7 +236,7 @@ class Polyline:
|
|||
(x0, y0), *rest = self.coords
|
||||
d = f'M {x0:.6} {y0:.6} ' + ' '.join(f'L {x:.6} {y:.6}' for x, y in rest)
|
||||
width = f'{self.width:.6}' if not math.isclose(self.width, 0) else '0.01mm'
|
||||
return tag('path', d=d, style=f'fill: none; stroke: {color}; stroke-width: {width}; stroke-linejoin: round; stroke-linecap: round')
|
||||
return tag('path', d=d, style=f'fill: none; stroke: {color}; stroke-width: {width:.6}; stroke-linejoin: round; stroke-linecap: round')
|
||||
|
||||
|
||||
class CamFile:
|
||||
|
@ -268,7 +268,7 @@ class CamFile:
|
|||
# setup viewport transform flipping y axis
|
||||
(content_min_x, content_min_y), (content_max_x, content_max_y) = bounds
|
||||
content_w, content_h = content_max_x - content_min_x, content_max_y - content_min_y
|
||||
xform = f'translate({content_min_x} {content_min_y+content_h}) scale(1 -1) translate({-content_min_x} {-content_min_y})'
|
||||
xform = f'translate({content_min_x:.6} {content_min_y+content_h:.6}) scale(1 -1) translate({-content_min_x:.6} {-content_min_y:.6})'
|
||||
tags = [tag('g', tags, transform=xform)]
|
||||
|
||||
return setup_svg(tags, bounds, margin=margin, arg_unit=arg_unit, svg_unit=svg_unit,
|
||||
|
|
|
@ -23,6 +23,8 @@ from dataclasses import dataclass, KW_ONLY, replace
|
|||
|
||||
from .utils import *
|
||||
|
||||
prec = lambda x: f'{x:.6}'
|
||||
|
||||
|
||||
@dataclass
|
||||
class GraphicPrimitive:
|
||||
|
@ -65,7 +67,7 @@ class Circle(GraphicPrimitive):
|
|||
|
||||
def to_svg(self, fg='black', bg='white', tag=Tag):
|
||||
color = fg if self.polarity_dark else bg
|
||||
return tag('circle', cx=self.x, cy=self.y, r=self.r, style=f'fill: {color}')
|
||||
return tag('circle', cx=prec(self.x), cy=prec(self.y), r=prec(self.r), style=f'fill: {color}')
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -255,6 +257,6 @@ class Rectangle(GraphicPrimitive):
|
|||
def to_svg(self, fg='black', bg='white', tag=Tag):
|
||||
color = fg if self.polarity_dark else bg
|
||||
x, y = self.x - self.w/2, self.y - self.h/2
|
||||
return tag('rect', x=x, y=y, width=self.w, height=self.h,
|
||||
return tag('rect', x=prec(x), y=prec(y), width=prec(self.w), height=prec(self.h),
|
||||
transform=svg_rotation(self.rotation, self.x, self.y), style=f'fill: {color}')
|
||||
|
||||
|
|
|
@ -203,11 +203,13 @@ def layername_autoguesser(fn):
|
|||
|
||||
class LayerStack:
|
||||
|
||||
def __init__(self, graphic_layers, drill_layers, netlist=None, board_name=None):
|
||||
def __init__(self, graphic_layers, drill_layers, netlist=None, board_name=None, original_path=None, was_zipped=False):
|
||||
self.graphic_layers = graphic_layers
|
||||
self.drill_layers = drill_layers
|
||||
self.board_name = board_name
|
||||
self.netlist = netlist
|
||||
self.original_path = original_path
|
||||
self.was_zipped = was_zipped
|
||||
|
||||
@classmethod
|
||||
def open(kls, path, board_name=None, lazy=False):
|
||||
|
@ -230,6 +232,8 @@ class LayerStack:
|
|||
|
||||
inst = kls.from_directory(tmp_indir, board_name=board_name, lazy=lazy)
|
||||
inst.tmpdir = tmpdir
|
||||
inst.original_path = filename
|
||||
inst.was_zipped = True
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
|
@ -240,10 +244,12 @@ class LayerStack:
|
|||
raise FileNotFoundError(f'{directory} is not a directory')
|
||||
|
||||
files = [ path for path in directory.glob('**/*') if path.is_file() ]
|
||||
return kls.from_files(files, board_name=board_name, lazy=lazy)
|
||||
return kls.from_files(files, board_name=board_name, lazy=lazy, original_path=directory)
|
||||
inst.original_path = directory
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def from_files(kls, files, board_name=None, lazy=False):
|
||||
def from_files(kls, files, board_name=None, lazy=False, original_path=None, was_zipped=False):
|
||||
generator, filemap = best_match(files)
|
||||
|
||||
if sum(len(files) for files in filemap.values()) < 6:
|
||||
|
@ -366,7 +372,16 @@ class LayerStack:
|
|||
board_name = common_prefix([l.original_path.name for l in layers.values() if l is not None])
|
||||
board_name = re.sub(r'^\W+', '', board_name)
|
||||
board_name = re.sub(r'\W+$', '', board_name)
|
||||
return kls(layers, drill_layers, netlist, board_name=board_name)
|
||||
return kls(layers, drill_layers, netlist, board_name=board_name,
|
||||
original_path=original_path, was_zipped=was_zipped)
|
||||
|
||||
def save_to_zipfile(self, path, naming_scheme={}):
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
self.save_to_directory(path, naming_scheme=naming_scheme)
|
||||
with ZipFile(path) as le_zip:
|
||||
for f in Path(tempdir.name).glob('*'):
|
||||
with le_zip.open(f, 'wb') as out:
|
||||
out.write(f.read_bytes())
|
||||
|
||||
def save_to_directory(self, path, naming_scheme={}, overwrite_existing=True):
|
||||
outdir = Path(path)
|
||||
|
@ -483,12 +498,17 @@ class LayerStack:
|
|||
|
||||
return setup_svg(tags, bounds, margin=margin, arg_unit=arg_unit, svg_unit=svg_unit, pagecolor="white", tag=tag)
|
||||
|
||||
|
||||
|
||||
def bounding_box(self, unit=MM, default=None):
|
||||
return sum_bounds(( layer.bounding_box(unit, default=default)
|
||||
for layer in itertools.chain(self.graphic_layers.values(), self.drill_layers) ), default=default)
|
||||
|
||||
|
||||
def board_bounds(self, unit=MM, default=None):
|
||||
if self.outline:
|
||||
return self.outline.instance.bounding_box(unit=unit, default=default)
|
||||
else:
|
||||
return self.bounding_box(unit=unit, default=default)
|
||||
|
||||
def merge_drill_layers(self):
|
||||
target = ExcellonFile(comments='Drill files merged by gerbonara')
|
||||
|
||||
|
|
|
@ -77,11 +77,12 @@ class GerberFile(CamFile):
|
|||
def to_gerber(self):
|
||||
return
|
||||
|
||||
def merge(self, other):
|
||||
def merge(self, other, mode='above', keep_settings=False):
|
||||
if other is None:
|
||||
return
|
||||
|
||||
self.import_settings = None
|
||||
if not keep_settings:
|
||||
self.import_settings = None
|
||||
self.comments += other.comments
|
||||
|
||||
# dedup apertures
|
||||
|
@ -96,7 +97,13 @@ class GerberFile(CamFile):
|
|||
replace_apertures[id(ap)] = new_apertures[gbr]
|
||||
self.apertures = list(new_apertures.values())
|
||||
|
||||
self.objects += other.objects
|
||||
# Join objects
|
||||
if mode == 'below':
|
||||
self.objects = other.objects + self.objects
|
||||
elif mode == 'above':
|
||||
self.objects += other.objects
|
||||
else:
|
||||
raise ValueError(f'Invalid mode "{mode}", must be one of "above" or "below".')
|
||||
for obj in self.objects:
|
||||
# If object has an aperture attribute, replace that aperture.
|
||||
if (ap := replace_apertures.get(id(getattr(obj, 'aperture', None)))):
|
||||
|
|
|
@ -269,10 +269,12 @@ def sum_bounds(bounds, *, default=None):
|
|||
:rtype: tuple
|
||||
"""
|
||||
|
||||
if not bounds:
|
||||
return default
|
||||
bounds = iter(bounds)
|
||||
|
||||
((min_x, min_y), (max_x, max_y)), *bounds = bounds
|
||||
for (min_x, min_y), (max_x, max_y) in bounds:
|
||||
break
|
||||
else:
|
||||
return default
|
||||
|
||||
for (min_x_2, min_y_2), (max_x_2, max_y_2) in bounds:
|
||||
min_x, min_y = min_none(min_x, min_x_2), min_none(min_y, min_y_2)
|
||||
|
|
Ładowanie…
Reference in New Issue