Skip to content

Commit c85b5ab

Browse files
committed
WIP on curve for a Line
1 parent d3d6af3 commit c85b5ab

5 files changed

Lines changed: 112 additions & 15 deletions

File tree

CHANGES.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ protograf Changes
55
------------------
66
- Add example for Grid and Line connections
77
- Allow Card Images to use sequence value
8+
- Patch error when card copies run across pages
89
- [PLANNED-1] Enable curve for a Line
9-
- [PLANNED-2] Enable non-curve Line connections to Ellipse shapes
1010
- [PLANNED-2] Add Circle/Dot end for an arrowhead
11+
- [PLANNED-2] Enable non-curve Line connections to Ellipse shapes
1112
- [PLANNED-2] Enable Line connections to Stadium shapes
1213
- [PLANNED-2] Enable Line connections to Rhombus shapes
1314
- [PLANNED-2] Enable Line connections to Text shapes

protograf/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ def __init__(
385385
# ---- line
386386
self.connections = self.defaults.get("connections", None)
387387
self.connections_style = self.defaults.get("connections_style", None)
388-
self.curve = self.defaults.get("curve", None)
388+
self.curve = self.defaults.get("curve", 0.0)
389389
# ---- line / bezier
390390
self.x_1 = self.defaults.get("x1", 0.0)
391391
self.y_1 = self.defaults.get("y1", 0.0)
@@ -1043,7 +1043,7 @@ def __init__(self, _object: muShape = None, canvas: BaseCanvas = None, **kwargs)
10431043
# ---- line
10441044
self.connections = kwargs.get("connections", base.connections)
10451045
self.connections_style = kwargs.get("connections_style", base.connections_style)
1046-
self.curve = kwargs.get("curve", base.curve)
1046+
self.curve = self.kw_float(kwargs.get("curve", base.curve))
10471047
# ---- line / bezier / sector
10481048
self.x_1 = self.kw_float(kwargs.get("x1", base.x_1))
10491049
self.y_1 = self.kw_float(kwargs.get("y1", base.y_1))

protograf/shapes.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
# local
2323
from protograf import globals
24-
from protograf.shapes_utils import set_cached_dir, draw_line
24+
from protograf.shapes_utils import set_cached_dir, draw_line, draw_line_curve
2525
from protograf.base import (
2626
BaseShape,
2727
get_cache,
@@ -1187,7 +1187,7 @@ def draw(self, cnv=None, off_x=0, off_y=0, ID=None, **kwargs):
11871187
kwargs = self.kwargs | kwargs
11881188
cnv = cnv if cnv else globals.canvas # a new Page/Shape may now exist
11891189
super().draw(cnv, off_x, off_y, ID, **kwargs) # unit-based props
1190-
x, y, x_1, y_1 = None, None, None, None
1190+
x, y, x_1, y_1, ccx, ccy = None, None, None, None, None, None
11911191
# ---- EITHER connections draw
11921192
if self.connections:
11931193
conns = self.draw_connections(
@@ -1249,9 +1249,18 @@ def draw(self, cnv=None, off_x=0, off_y=0, ID=None, **kwargs):
12491249
raise ValueError(
12501250
f'Cannot calculate rotation point "{self.rotation_point}"', True
12511251
)
1252-
# ---- draw line
1253-
# breakpoint()
1254-
klargs = draw_line(cnv, Point(x, y), Point(x_1, y_1), shape=self, **kwargs)
1252+
# ---- draw straight line
1253+
if not self.curve:
1254+
klargs = draw_line(
1255+
cnv, Point(x, y), Point(x_1, y_1), shape=self, **kwargs
1256+
)
1257+
else:
1258+
# ---- draw curve line
1259+
if kwargs.get("wave_height") or kwargs.get("wave_style"):
1260+
feedback("A line cannot use a wave and curve together", True)
1261+
klargs, ccx, ccy = draw_line_curve(
1262+
cnv, Point(x, y), Point(x_1, y_1), self.curve, **kwargs
1263+
)
12551264
self.set_canvas_props(cnv=cnv, index=ID, **klargs) # shape.finish()
12561265
# ---- arrowhead
12571266
self.draw_arrow(cnv, Point(x, y), Point(x_1, y_1), **kwargs)
@@ -1262,7 +1271,10 @@ def draw(self, cnv=None, off_x=0, off_y=0, ID=None, **kwargs):
12621271
conn = conns[0]
12631272
x, y = conn[0].x, conn[0].y
12641273
x_1, y_1 = conn[1].x, conn[1].y
1265-
cx, cy = (x_1 + x) / 2.0, (y_1 + y) / 2.0
1274+
if ccx and ccy:
1275+
cx, cy = ccx, ccy # centre of point of curve line
1276+
else:
1277+
cx, cy = (x_1 + x) / 2.0, (y_1 + y) / 2.0
12661278
# ---- * centre shapes (with offsets)
12671279
if self.centre_shapes:
12681280
_, _angle = geoms.angles_from_points(Point(x_1, y_1), Point(x, y))

protograf/shapes_utils.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from protograf.base import (
1515
BaseShape,
1616
)
17-
from protograf.utils import tools
17+
from protograf.utils import geoms, tools
1818
from protograf.utils.tools import _lower
1919
from protograf.utils.constants import (
2020
BGG_IMAGES,
@@ -68,8 +68,6 @@ def draw_line(
6868
"""
6969
result = False
7070
if start and end and cnv:
71-
if kwargs.get("wave_height") and kwargs.get("curve"):
72-
feedback("A line cannot use a wave_style and curve together", True)
7371
if kwargs.get("wave_height"):
7472
_height = tools.as_float(kwargs.get("wave_height", 0.5), "wave_height")
7573
try:
@@ -102,3 +100,50 @@ def draw_line(
102100
klargs["fill"] = None
103101
return klargs
104102
return kwargs
103+
104+
105+
def draw_line_curve(
106+
cnv=None,
107+
start: Point = None,
108+
end: Point = None,
109+
curve_height: float = None,
110+
**kwargs,
111+
) -> dict:
112+
"""Draw a curved line on the canvas (Page) between two points for a Shape.
113+
114+
Args:
115+
116+
cnv (PyMuPDF Page object):
117+
where the line is drawn
118+
start (Point):
119+
start of the line
120+
end (Point):
121+
end of the line
122+
curve_height (float):
123+
height of curve above centre point of line
124+
125+
Returns:
126+
kwargs (modified for styled lines)
127+
"""
128+
result = False
129+
if start and end and curve_height:
130+
curve_centre = geoms.fraction_along_line(start, end, 0.5)
131+
_, rotation = geoms.angles_from_points(start, end)
132+
adjust = 90 if curve_height < 0 else -90
133+
u_curve_height = tools.unit(curve_height)
134+
curve_point = geoms.point_from_angle(
135+
curve_centre, abs(u_curve_height), rotation + adjust
136+
)
137+
ccentre, _ = geoms.centre_radius_from_points(start, curve_point, end)
138+
angle_width = geoms.circle_angle_between_points(start, end, ccentre)
139+
# feedback(f'***Line Curve: {start=} {end=} {u_curve_height=} {angle_width=}')
140+
cnv.draw_sector( # anti-clockwise from end pt; 90° default
141+
(ccentre.x, ccentre.y), (end.x, end.y), angle_width, fullSector=False
142+
)
143+
result = True
144+
if result:
145+
klargs = copy.copy(kwargs)
146+
klargs["closed"] = False
147+
klargs["fill"] = None # may want to allow this? curve_fill?
148+
return klargs, curve_point.x, curve_point.y
149+
return kwargs, None, None

protograf/utils/geoms.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,7 @@ def line_intersection_point(
731731

732732
def bezier_arc_segment(
733733
cx: float, cy: float, rx: float, ry: float, theta0: float, theta1: float
734-
):
734+
) -> tuple:
735735
"""Compute the control points for a Bezier arc with angles theta1-theta0 <= 90.
736736
737737
Points are computed for an arc with angle theta increasing in the
@@ -782,7 +782,7 @@ def bezier_arc_segment(
782782
return (x0, y0), (x1, y1, x2, y2, x3, y3)
783783

784784

785-
def circle_angles(radius: float, chord: float):
785+
def circle_angles(radius: float, chord: float) -> float:
786786
"""Calculate interior angles of isosceles triangle formed inside a circle.
787787
788788
Source:
@@ -799,7 +799,46 @@ def circle_angles(radius: float, chord: float):
799799
return math.degrees(top), base, base
800800

801801

802-
def equilateral_height(side: Any):
802+
def circle_angle_between_points(start: Point, end: Point, centre: Point) -> float:
803+
"""Calculate angles between two points lying on a circle of known centre.
804+
805+
Args:
806+
start: coordinates of first point
807+
end: coordinates of seconf point
808+
centre: coordinates of circle's centre
809+
810+
Returns:
811+
angle (degrees)
812+
813+
Source:
814+
Google AI!
815+
816+
Doc Test:
817+
818+
>>> P1 = Point(15, 10)
819+
>>> P2 = Point(10, 15)
820+
>>> C0 = Point(10, 10)
821+
>>> circle_angle_between_points(P1, P2, C0)
822+
90.0
823+
"""
824+
825+
# # Center of the circle
826+
# h, k = 10, 10
827+
828+
# # Two points on the circle
829+
# x1, y1 = 15, 10
830+
# x2, y2 = 10, 15
831+
832+
# Subtract the center from each point
833+
angle1 = math.atan2(start.y - centre.y, start.x - centre.x)
834+
angle2 = math.atan2(end.y - centre.y, end.x - centre.x)
835+
# Calculate the difference and normalize
836+
diff = math.degrees(angle2 - angle1)
837+
angle_between = diff % 360
838+
return angle_between
839+
840+
841+
def equilateral_height(side: Any) -> float:
803842
"""Calculate height of equilateral triangle from a side.
804843
805844
Doc Test:

0 commit comments

Comments
 (0)