Skip to content

Commit 794b7d9

Browse files
committed
Implement percentage for panel position
1 parent 95e897d commit 794b7d9

File tree

9 files changed

+124
-18
lines changed

9 files changed

+124
-18
lines changed

doc/panelizeCli.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,8 +499,9 @@ size from the source board. This feature is not supported on KiCAD 5
499499

500500
- `anchor` - Point of the panel to be placed at given position. Can be one of
501501
`tl`, `tr`, `bl`, `br` (corners), `mt`, `mb`, `ml`, `mr` (middle of sides),
502-
`c` (center). The anchors refer to the panel outline. Default `tl`
503-
- `posx`, `posy` - the position of the panel on the page. Default `15mm`
502+
`c` (center). The anchors refer to the panel outline. Default `mt`
503+
- `posx`, `posy` - the position of the panel on the page. Default `50%` for
504+
`posx` and `20mm` for `posy`.
504505

505506
### Custom
506507

kikit/defs.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from enum import Enum, IntEnum
2+
from .units import mm, inch
23

34
# These classes miss in the exported interface
45

@@ -88,3 +89,20 @@ class MODULE_ATTR_T(IntEnum):
8889
PAPER_SIZES = [f"A{size}" for size in range(6)] + ["A", "B", "C", "D", "E"] + \
8990
["USLetter", "USLegal", "USLedger"]
9091
PAPER_SIZES = PAPER_SIZES + [f"{paper}-portrait" for paper in PAPER_SIZES]
92+
93+
PAPER_DIMENSIONS = {
94+
"A5": (210 * mm, 148 * mm),
95+
"A4": (297 * mm, 210 * mm),
96+
"A3": (420 * mm, 297 * mm),
97+
"A2": (594 * mm, 420 * mm),
98+
"A1": (841 * mm, 594 * mm),
99+
"A0": (1198 * mm, 841 * mm),
100+
"A": (11 * inch, 8.5 * inch),
101+
"B": (17 * inch, 11 * inch),
102+
"C": (22 * inch, 17 * inch),
103+
"D": (34 * inch, 22 * inch),
104+
"E": (44 * inch, 34 * inch),
105+
"USLetter": (11 * inch, 8.5 * inch),
106+
"USLegal": (14 * inch, 8.5 * inch),
107+
"USLedger": (17 * inch, 11 * inch)
108+
}

kikit/kicadUtil.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from typing import Tuple
2+
from .common import KiLength
3+
from .units import mm
4+
from .sexpr import SExpr, findNode
5+
from .defs import PAPER_DIMENSIONS
6+
7+
def getPageDimensionsFromAst(ast: SExpr) -> Tuple[KiLength, KiLength]:
8+
paperNode = findNode(ast, "paper")
9+
if paperNode is None:
10+
# KiCAD 5 board use "page" instead of "paper"
11+
paperNode = findNode(ast, "page")
12+
if paperNode is None:
13+
raise RuntimeError("Source document doesn't contain paper size information")
14+
value = paperNode.items[1].value
15+
if value == "User":
16+
size = (paperNode[2].value, paperNode[3].value)
17+
return tuple(int(float(x) * mm) for x in size)
18+
try:
19+
size = PAPER_DIMENSIONS[value]
20+
if len(paperNode.items) >= 3 and paperNode.items[2] == "portrait":
21+
size = (size[1], size[0])
22+
return tuple(int(x) for x in size)
23+
except KeyError:
24+
raise RuntimeError(f"Uknown paper size {value}") from None
25+

kikit/panelize.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222

2323
from kikit import substrate
2424
from kikit import units
25+
from kikit.kicadUtil import getPageDimensionsFromAst
2526
from kikit.substrate import Substrate, linestringToKicad, extractRings
26-
from kikit.defs import STROKE_T, Layer, EDA_TEXT_HJUSTIFY_T, EDA_TEXT_VJUSTIFY_T, PAPER_SIZES
27+
from kikit.defs import PAPER_DIMENSIONS, STROKE_T, Layer, EDA_TEXT_HJUSTIFY_T, EDA_TEXT_VJUSTIFY_T, PAPER_SIZES
2728
from kikit.common import *
28-
from kikit.sexpr import parseSexprF, SExpr, Atom
29+
from kikit.sexpr import parseSexprF, SExpr, Atom, findNode
2930
from kikit.annotations import AnnotationReader, TabAnnotation
3031
from kikit.drc import DrcExclusion, readBoardDrcExclusions, serializeExclusion
3132

@@ -734,6 +735,14 @@ def inheritPageSize(self, board: Union[pcbnew.BOARD, str]) -> None:
734735
if not isinstance(board, pcbnew.BOARD):
735736
board = pcbnew.LoadBoard(board)
736737
self.board.SetPageSettings(board.GetPageSettings())
738+
self.pageSize = None
739+
740+
# What follows is a hack as KiCAD has no API for page access. Therefore,
741+
# we have to read out the page size from the source board and save it so
742+
# we can recover it.
743+
with open(board.GetFileName(), "r") as f:
744+
tree = parseSexprF(f, limit=10) # Introduce limit to speed up parsing
745+
self._inheritedPageDimensions = getPageDimensionsFromAst(tree)
737746

738747
def setPageSize(self, size: Union[str, Tuple[int, int]] ) -> None:
739748
"""
@@ -744,6 +753,23 @@ def setPageSize(self, size: Union[str, Tuple[int, int]] ) -> None:
744753
raise RuntimeError(f"Unknown paper size: {size}")
745754
self.pageSize = size
746755

756+
def getPageDimensions(self) -> Tuple[KiLength, KiLength]:
757+
"""
758+
Get page size in KiCAD units for the current panel
759+
"""
760+
if self.pageSize is None:
761+
return self._inheritedPageDimensions
762+
if isinstance(self.pageSize, tuple):
763+
return self.pageSize
764+
if isinstance(self.pageSize, str):
765+
if self.pageSize.endswith("-portrait"):
766+
# Portrait
767+
pageSize = PAPER_DIMENSIONS[pageSize.split("-")[0]]
768+
return pageSize[1], pageSize[0]
769+
else:
770+
return PAPER_DIMENSIONS[pageSize]
771+
raise RuntimeError("Unknown page dimension - this is probably a bug and you should report it.")
772+
747773
def setProperties(self, properties):
748774
"""
749775
Set text properties cached in the board

kikit/panelize_ui.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,8 @@ def doPanelization(input, output, preset, plugins=[]):
289289
ki.buildCopperfill(preset["copperfill"], panel)
290290

291291
ki.setStackup(preset["source"], panel)
292-
ki.positionPanel(preset["page"], panel)
293292
ki.setPageSize(preset["page"], panel, board)
293+
ki.positionPanel(preset["page"], panel)
294294

295295
ki.runUserScript(preset["post"], panel)
296296
useHookPlugins(lambda x: x.finish(panel))
@@ -348,8 +348,8 @@ def separate(input, output, source, page, debug, keepannotations, preservearcs):
348348
panel.appendBoard(input, destination, sourceArea,
349349
interpretAnnotations=(not keepannotations))
350350
ki.setStackup(preset["source"], panel)
351-
ki.positionPanel(preset["page"], panel)
352351
ki.setPageSize(preset["page"], panel, board)
352+
ki.positionPanel(preset["page"], panel)
353353

354354
panel.save(reconstructArcs=preservearcs)
355355
except Exception as e:

kikit/panelize_ui_impl.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from shapely.geometry import box
66
from kikit.plugin import HookPlugin
77
from kikit.text import kikitTextVars
8-
from kikit.units import BaseValue
8+
from kikit.units import BaseValue, PercentageValue
99
from kikit.panelize_ui_sections import *
1010
from kikit.substrate import SubstrateNeighbors
1111
from kikit.common import resolveAnchor
@@ -644,8 +644,12 @@ def positionPanel(preset, panel):
644644
Position the panel on the paper
645645
"""
646646
try:
647-
origin = resolveAnchor(preset["anchor"])(panel.boardSubstrate.boundingBox())
648-
translateVec = (-origin[0] + preset["posx"], -origin[1] + preset["posy"])
647+
bBox = panel.boardSubstrate.boundingBox()
648+
pageSize = panel.getPageDimensions()
649+
posx = preset["posx"] * pageSize[0] if isinstance(preset["posx"], PercentageValue) else preset["posx"]
650+
posy = preset["posy"] * pageSize[1] if isinstance(preset["posy"], PercentageValue) else preset["posy"]
651+
origin = resolveAnchor(preset["anchor"])(bBox)
652+
translateVec = (-origin[0] + posx, -origin[1] + posy)
649653
panel.translate(translateVec)
650654
except KeyError as e:
651655
raise PresetError(f"Missing parameter '{e}' in section 'page'")

kikit/panelize_ui_sections.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
from typing import Any, List
44
from kikit import plugin
5-
from kikit.units import readLength, readAngle
5+
from kikit.units import readLength, readAngle, readPercents
66
from kikit.defs import Layer, EDA_TEXT_HJUSTIFY_T, EDA_TEXT_VJUSTIFY_T, PAPER_SIZES
77

88
class PresetError(RuntimeError):
@@ -31,6 +31,16 @@ def __init__(self, *args, **kwargs):
3131
def validate(self, x):
3232
return readLength(x)
3333

34+
class SLengthOrPercent(SectionBase):
35+
def __init__(self, *args, **kwargs):
36+
super().__init__(*args, **kwargs)
37+
38+
def validate(self, x):
39+
x = x.strip()
40+
if x.endswith("%"):
41+
return readPercents(x)
42+
return readLength(x)
43+
3444
class SAngle(SectionBase):
3545
def __init__(self, *args, **kwargs):
3646
super().__init__(*args, **kwargs)
@@ -648,12 +658,12 @@ def ppPost(section):
648658
ANCHORS,
649659
always(),
650660
"Anchor for positioning the panel on the page"),
651-
"posx": SLength(
661+
"posx": SLengthOrPercent(
652662
always(),
653-
"X position of the panel"),
654-
"posy": SLength(
663+
"X position of the panel. Length or percents of page width."),
664+
"posy": SLengthOrPercent(
655665
always(),
656-
"Y position of the panel"),
666+
"Y position of the panel. Length or percents of page height."),
657667
"width": SLength(
658668
typeIn(["user"]),
659669
"Width of custom paper"),

kikit/resources/panelizePresets/default.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,9 @@
172172
},
173173
"page": {
174174
"type": "inherit",
175-
"anchor": "tl",
176-
"posx": "15mm",
177-
"posy": "15mm",
175+
"anchor": "mt",
176+
"posx": "50%",
177+
"posy": "20mm",
178178
"width": "1000mm",
179179
"height": "1000mm"
180180
},

kikit/units.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class UnitError(RuntimeError):
1515
deg = 10
1616
rad = 180 / math.pi * deg
1717

18-
UNIT_SPLIT = re.compile(r"\s*(-?\s*\d+(\.\d*)?)\s*(\w+)$")
18+
UNIT_SPLIT = re.compile(r"\s*(-?\s*\d+(\.\d*)?)\s*(\w+|\%)$")
1919

2020
class BaseValue(int):
2121
"""
@@ -33,6 +33,24 @@ def __repr__(self):
3333
return f"<BaseValue: {int(self)}, {self.str} >"
3434

3535

36+
class PercentageValue(float):
37+
"""
38+
Value in percents that remembers its original string representation.
39+
40+
Value is stored as floating point number where 1 corresponds to 100 %.
41+
"""
42+
def __new__(cls, value, strRepr):
43+
x = super().__new__(cls, value)
44+
x.str = strRepr
45+
return x
46+
47+
def __str__(self):
48+
return self.str
49+
50+
def __repr__(self):
51+
return f"<BaseValue: {int(self)}, {self.str} >"
52+
53+
3654
def readUnit(unitDir, unitStr):
3755
match = UNIT_SPLIT.match(unitStr)
3856
if not match:
@@ -70,3 +88,7 @@ def readAngle(unitStr):
7088
if not isinstance(unitStr, str):
7189
raise RuntimeError(f"Got '{unitStr}', an angle with units was expected")
7290
return BaseValue(readUnit(unitDir, unitStr), unitStr)
91+
92+
def readPercents(unitStr):
93+
unitDir = { "%": 0.01 }
94+
return PercentageValue(readUnit(unitDir, unitStr), unitStr)

0 commit comments

Comments
 (0)