Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/kiutils/items/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

from __future__ import annotations

import math

from dataclasses import dataclass, field
from typing import Optional, List, Dict

Expand Down Expand Up @@ -44,6 +46,31 @@ class Position():
unlocked: bool = False
"""The ``unlocked`` token's description has to be defined yet .."""

def rotate_around_center(self, center, angleDegrees):
"""Rotate this point around a center point

Args:
- center (Position): position to rotate this point around
- angleDegrees (float): angle in degrees to rotate the point counterclockwise

References:
Implementation based on KiCad source code:
- https://gitlab.com/kicad/code/kicad/-/blob/master/libs/kimath/src/trigo.cpp#L183
- https://gitlab.com/kicad/code/kicad/-/blob/master/libs/kimath/src/trigo.cpp#L235
"""

ox = self.X - center.X
oy = self.Y - center.Y

sinus = math.sin(math.radians(angleDegrees))
cosinus = math.cos(math.radians(angleDegrees))

ox = (oy * sinus) + (ox * cosinus)
oy = (oy * cosinus) - (ox * sinus)

self.X = ox + center.X
self.Y = oy + center.Y

@classmethod
def from_sexpr(cls, exp: list) -> Position:
"""Convert the given S-Expresstion into a Position object
Expand Down
56 changes: 53 additions & 3 deletions src/kiutils/items/fpitems.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from dataclasses import dataclass, field
from typing import Optional, List
from copy import deepcopy

from kiutils.items.common import Stroke, Position, Effects
from kiutils.utils.strings import dequote
Expand Down Expand Up @@ -474,6 +475,7 @@ def from_sexpr(cls, exp: list) -> FpArc:
Raises:
- Exception: When given parameter's type is not a list
- Exception: When the first item of the list is not fp_arc
- Exception: When a legacy `angle` token and a modern `mid` token are both provided

Returns:
- FpArc: Object of the class initialized with the given S-Expression
Expand All @@ -485,14 +487,25 @@ def from_sexpr(cls, exp: list) -> FpArc:
raise Exception("Expression does not have the correct type")

object = cls()

# HACK: Assumes both legacy and modern `fp_arc`s are possible in the same file.
# Parsing method should be determined by footprint version number.
# KiCad source has version <= "20210925" activate legacy fp_arc parsing.
# Source: https://gitlab.com/kicad/code/kicad/-/blob/master/pcbnew/plugins/kicad/pcb_plugin.h#L136
start = None
mid = None
end = None
angle = None

for item in exp:
if type(item) != type([]):
if item == 'locked': object.locked = True
else: continue

if item[0] == 'start': object.start = Position.from_sexpr(item)
if item[0] == 'mid': object.mid = Position.from_sexpr(item)
if item[0] == 'end': object.end = Position.from_sexpr(item)
if item[0] == 'start': start = Position.from_sexpr(item)
if item[0] == 'mid': mid = Position.from_sexpr(item)
if item[0] == 'end': end = Position.from_sexpr(item)
if item[0] == 'angle': angle = item[1]
if item[0] == 'layer': object.layer = item[1]
if item[0] == 'tstamp': object.tstamp = item[1]
if item[0] == 'width':
Expand All @@ -501,6 +514,43 @@ def from_sexpr(cls, exp: list) -> FpArc:
if item[0] == 'stroke':
object.stroke = Stroke.from_sexpr(item)
object.width = None

# FIXME: Legacy upgrade code has not been tested
if angle is not None and mid is not None:
raise Exception('Legacy `angle` token and modern `mid` token both provided for fp_arc')

if angle is not None:
# Legacy fp_arc is as follows:
# (start x y): The position of the centre of the circle that is the basis
# of the arc.
# (end x y): The starting point of the arc. Both the radius of the arc
# and the start angle are calculated from this point.
# (angle a): The angular span that the arc covers, from the start angle, in
# clock-wise direction. It is added to the start angle to find the end angle.
# Source: https://www.compuphase.com/electronics/LibraryFileFormats.pdf

centerOfCircle = start
startingPointOfArc = end

# Following code based off of KiCad source code:
# https://gitlab.com/kicad/code/kicad/-/blob/master/pcbnew/fp_shape.cpp#L193
endOfArc = deepcopy(startingPointOfArc)

# Get midpoint by rotating halfway
endOfArc.rotate_around_center(centerOfCircle, -1 * (angle / 2))
mid = deepcopy(endOfArc)
endOfArc.rotate_around_center(centerOfCircle, -1 * (angle / 2))

# Swap points on negative angle as that is what the KiCad source does
if angle < 0.0:
startingPointOfArc, endOfArc = endOfArc, startingPointOfArc

start = startingPointOfArc
end = endOfArc

object.start = start
object.mid = mid
object.end = end

return object

Expand Down