thresholdfloor 0.3.2__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {thresholdfloor-0.3.2/src/thresholdfloor.egg-info → thresholdfloor-0.3.3}/PKG-INFO +1 -1
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/pyproject.toml +1 -1
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor/floor_sigil.py +235 -52
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor/threshold_floor.py +69 -9
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3/src/thresholdfloor.egg-info}/PKG-INFO +1 -1
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/LICENSE +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/MANIFEST.in +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/README.md +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/setup.cfg +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor/__init__.py +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor/aether_thresher.py +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor/assets_bundle.pkl +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor/bundle.py +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor/elevation.py +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor/shadow_calibration.py +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor/shadow_simulation.py +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor/tests/__init__.py +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor/tests/test_geometric.py +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor/tests/test_solar.py +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor/tests/test_threshold.py +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor.egg-info/SOURCES.txt +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor.egg-info/dependency_links.txt +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor.egg-info/requires.txt +0 -0
- {thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor.egg-info/top_level.txt +0 -0
|
@@ -17,11 +17,63 @@ try:
|
|
|
17
17
|
except Exception:
|
|
18
18
|
pass
|
|
19
19
|
try:
|
|
20
|
-
from PIL import Image, ImageChops, ImageOps, ImageFilter, ImageDraw, ImageFont, ImageTk, ImageEnhance
|
|
20
|
+
from PIL import Image, ImageOps, ImageChops, ImageOps, ImageFilter, ImageDraw, ImageFont, ImageTk, ImageEnhance, ImageColor
|
|
21
21
|
PIL_EXISTS = True
|
|
22
22
|
except Exception:
|
|
23
23
|
pass
|
|
24
24
|
|
|
25
|
+
DEFAULT_OUTER_RING_COLOR = (180, 150, 255, 180)
|
|
26
|
+
PHASE_COLOR_RGBA = {
|
|
27
|
+
"black": (0, 0, 0, 90),
|
|
28
|
+
"white": (170, 170, 170, 170),
|
|
29
|
+
"yellow-gold": (170, 100, 40, 90),
|
|
30
|
+
"crimson-red": (190, 12, 20, 90),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _coerce_ring_color(color, default=DEFAULT_OUTER_RING_COLOR):
|
|
35
|
+
if isinstance(color, str):
|
|
36
|
+
keyed = color.strip().lower().replace("_", "-").replace(" ", "-")
|
|
37
|
+
if keyed in PHASE_COLOR_RGBA:
|
|
38
|
+
return PHASE_COLOR_RGBA[keyed]
|
|
39
|
+
|
|
40
|
+
image_color = globals().get("ImageColor")
|
|
41
|
+
if image_color is not None:
|
|
42
|
+
try:
|
|
43
|
+
r, g, b = image_color.getrgb(color)
|
|
44
|
+
return (r, g, b, default[3])
|
|
45
|
+
except ValueError:
|
|
46
|
+
return default
|
|
47
|
+
|
|
48
|
+
if isinstance(color, (tuple, list)) and len(color) in (3, 4):
|
|
49
|
+
values = tuple(int(c) for c in color)
|
|
50
|
+
if len(values) == 3:
|
|
51
|
+
return (*values, default[3])
|
|
52
|
+
return values
|
|
53
|
+
|
|
54
|
+
return default
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _phase_outer_ring_color(floor, default=DEFAULT_OUTER_RING_COLOR):
|
|
58
|
+
phase = getattr(floor, "current_phase", None)
|
|
59
|
+
if not phase and hasattr(floor, "get_phase"):
|
|
60
|
+
try:
|
|
61
|
+
phase = floor.get_phase()
|
|
62
|
+
except Exception:
|
|
63
|
+
phase = None
|
|
64
|
+
|
|
65
|
+
if not phase:
|
|
66
|
+
return default
|
|
67
|
+
|
|
68
|
+
COLORS = {
|
|
69
|
+
"Nigredo": "black",
|
|
70
|
+
"Albedo": "white",
|
|
71
|
+
"Citrinitas": "yellow-gold",
|
|
72
|
+
"Rubedo": "crimson-red",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return _coerce_ring_color(COLORS.get(phase), default)
|
|
76
|
+
|
|
25
77
|
SIGNS = {
|
|
26
78
|
'♈': 'Aries',
|
|
27
79
|
'♉': 'Taurus',
|
|
@@ -151,7 +203,6 @@ sprite_lookup = {
|
|
|
151
203
|
}
|
|
152
204
|
|
|
153
205
|
def overlay_shadow_tree(base_img, rune, cx, cy, azimuth, altitude, size=64):
|
|
154
|
-
from PIL import Image, ImageEnhance, ImageFilter, ImageOps
|
|
155
206
|
import math
|
|
156
207
|
#cx, cy = cx + size, cy + size
|
|
157
208
|
sprite_key = sprite_lookup.get(rune)
|
|
@@ -285,6 +336,8 @@ def overlay_tree_sprite(base_img, rune, position=None, size=48):
|
|
|
285
336
|
else:
|
|
286
337
|
return base_img
|
|
287
338
|
|
|
339
|
+
sprite = ImageEnhance.Brightness(sprite).enhance(0.6)
|
|
340
|
+
|
|
288
341
|
sprite = sprite.resize((size, size), Image.LANCZOS)
|
|
289
342
|
if position is None:
|
|
290
343
|
x = base_img.width - size - 10
|
|
@@ -396,7 +449,10 @@ def _draw_sigil_glyphs(img, floor, font, cx, cy, r, observed_at):
|
|
|
396
449
|
#color = tuple(int(c * 0.4) for c in COLOR_PALLET.get(sign, (200, 200, 255, 240)))
|
|
397
450
|
|
|
398
451
|
# All bright
|
|
399
|
-
color = COLOR_PALLET.get(sign, (200, 200, 255, 240))
|
|
452
|
+
#color = COLOR_PALLET.get(sign, (200, 200, 255, 240))
|
|
453
|
+
|
|
454
|
+
# All dark
|
|
455
|
+
color = tuple(int(c * 0.4) for c in COLOR_PALLET.get(sign, (200, 200, 255, 240)))
|
|
400
456
|
|
|
401
457
|
glyph_img = Image.new("RGBA", (64, 64), (0, 0, 0, 0))
|
|
402
458
|
glyph_draw = ImageDraw.Draw(glyph_img)
|
|
@@ -442,6 +498,75 @@ def _draw_sigil_shadow(img, cx, cy, alt, az, size=82):
|
|
|
442
498
|
)
|
|
443
499
|
return img
|
|
444
500
|
|
|
501
|
+
def _polar(cx, cy, radius, angle_deg):
|
|
502
|
+
theta = math.radians(angle_deg)
|
|
503
|
+
return (
|
|
504
|
+
cx + radius * -math.sin(theta),
|
|
505
|
+
cy + radius * math.cos(theta),
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _draw_sigil_ticks(img, cx, cy, r, every=5, major_every=30):
|
|
510
|
+
draw = ImageDraw.Draw(img)
|
|
511
|
+
|
|
512
|
+
outer = r * 1.33
|
|
513
|
+
minor_len = r * 0.025
|
|
514
|
+
major_len = r * 0.055
|
|
515
|
+
|
|
516
|
+
for deg in range(0, 360, every):
|
|
517
|
+
is_major = deg % major_every == 0
|
|
518
|
+
length = major_len if is_major else minor_len
|
|
519
|
+
width = 2 if is_major else 1
|
|
520
|
+
|
|
521
|
+
x1, y1 = _polar(cx, cy, outer - length, deg)
|
|
522
|
+
x2, y2 = _polar(cx, cy, outer, deg)
|
|
523
|
+
|
|
524
|
+
draw.line(
|
|
525
|
+
(x1, y1, x2, y2),
|
|
526
|
+
fill=(150, 115, 70, 150),
|
|
527
|
+
width=width,
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
return img
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def _draw_diamond(img, cx, cy, radius, angle_deg, scale=4, fill=(210, 165, 90, 210)):
|
|
534
|
+
draw = ImageDraw.Draw(img)
|
|
535
|
+
x, y = _polar(cx, cy, radius, angle_deg)
|
|
536
|
+
|
|
537
|
+
pts = [
|
|
538
|
+
(x, y - scale),
|
|
539
|
+
(x + scale, y),
|
|
540
|
+
(x, y + scale),
|
|
541
|
+
(x - scale, y),
|
|
542
|
+
]
|
|
543
|
+
|
|
544
|
+
draw.line(pts + [pts[0]], fill=fill, width=1)
|
|
545
|
+
return img
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def _draw_motto_brackets(img, cx, cy, r):
|
|
549
|
+
ornament_r = r * 0.84
|
|
550
|
+
|
|
551
|
+
# Bracket ornaments around TEMPUS FUGIT
|
|
552
|
+
_draw_diamond(img, cx, cy, ornament_r, 38, scale=3)
|
|
553
|
+
_draw_diamond(img, cx, cy, ornament_r, 52, scale=3)
|
|
554
|
+
|
|
555
|
+
# Bracket ornaments around FESTINA LENTE
|
|
556
|
+
_draw_diamond(img, cx, cy, ornament_r, 310, scale=3)
|
|
557
|
+
_draw_diamond(img, cx, cy, ornament_r, 320, scale=3)
|
|
558
|
+
|
|
559
|
+
# Small keel mark at bottom center
|
|
560
|
+
_draw_diamond(img, cx, cy, ornament_r, 0, scale=4)
|
|
561
|
+
|
|
562
|
+
return img
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def _draw_sigil_ornaments(img, cx, cy, size, r):
|
|
566
|
+
_draw_sigil_ticks(img, cx, cy, r)
|
|
567
|
+
_draw_motto_brackets(img, cx, cy, r)
|
|
568
|
+
return img
|
|
569
|
+
|
|
445
570
|
def _draw_curved_text(
|
|
446
571
|
img,
|
|
447
572
|
cx,
|
|
@@ -531,11 +656,14 @@ def _draw_curved_text(
|
|
|
531
656
|
|
|
532
657
|
# Your current setup seems to like this orientation.
|
|
533
658
|
# If letters face inward/upside-down, set rotation_offset=180.
|
|
659
|
+
|
|
534
660
|
glyph = glyph.rotate(
|
|
535
661
|
-angle_deg + rotation_offset,
|
|
536
662
|
resample=Image.BICUBIC,
|
|
537
663
|
expand=True,
|
|
538
664
|
)
|
|
665
|
+
#if reverse:
|
|
666
|
+
#glyph = ImageOps.mirror(glyph)
|
|
539
667
|
|
|
540
668
|
ww, hh = glyph.size
|
|
541
669
|
img.alpha_composite(glyph, (int(x - ww / 2), int(y - hh / 2)))
|
|
@@ -547,8 +675,11 @@ def _draw_sigil_inscribe(img, cx, cy, size, r):
|
|
|
547
675
|
motto_font = _load_motto_font(max(8, int(size * 12 / 400)))
|
|
548
676
|
motto_r = r * 0.72
|
|
549
677
|
tracking_deg=1.8
|
|
550
|
-
latin_take = "
|
|
551
|
-
latin_late = "
|
|
678
|
+
latin_take = "LENTE"
|
|
679
|
+
latin_late = "FESTINA"
|
|
680
|
+
#latin_take = "FESTINA LENTE"
|
|
681
|
+
#latin_late = "TEMPUS FUGIT"
|
|
682
|
+
#latin_late = "SERIUS EST"
|
|
552
683
|
# Bottom-right-ish
|
|
553
684
|
_draw_curved_text(
|
|
554
685
|
img,
|
|
@@ -563,6 +694,7 @@ def _draw_sigil_inscribe(img, cx, cy, size, r):
|
|
|
563
694
|
stroke_fill=(20, 15, 10, 210),
|
|
564
695
|
reverse=True,
|
|
565
696
|
tracking_deg=tracking_deg,
|
|
697
|
+
rotation_offset=0
|
|
566
698
|
)
|
|
567
699
|
# Southwest
|
|
568
700
|
_draw_curved_text(
|
|
@@ -578,6 +710,7 @@ def _draw_sigil_inscribe(img, cx, cy, size, r):
|
|
|
578
710
|
stroke_fill=(20, 15, 10, 210),
|
|
579
711
|
reverse=True,
|
|
580
712
|
tracking_deg=tracking_deg,
|
|
713
|
+
rotation_offset=0
|
|
581
714
|
)
|
|
582
715
|
|
|
583
716
|
return img
|
|
@@ -586,21 +719,111 @@ def _draw_sigil_inscribe(img, cx, cy, size, r):
|
|
|
586
719
|
print(e)
|
|
587
720
|
return img
|
|
588
721
|
|
|
722
|
+
def _draw_vestal_ring(
|
|
723
|
+
img,
|
|
724
|
+
cx,
|
|
725
|
+
cy,
|
|
726
|
+
phase,
|
|
727
|
+
waxing=True,
|
|
728
|
+
r=100,
|
|
729
|
+
color=(180, 150, 255, 180),
|
|
730
|
+
width=3,
|
|
731
|
+
):
|
|
732
|
+
"""
|
|
733
|
+
Draw a lunar phase ring.
|
|
734
|
+
|
|
735
|
+
phase:
|
|
736
|
+
0.0 = new moon
|
|
737
|
+
1.0 = full moon
|
|
738
|
+
|
|
739
|
+
waxing:
|
|
740
|
+
True = right-side illumination
|
|
741
|
+
False = left-side illumination
|
|
742
|
+
"""
|
|
743
|
+
|
|
744
|
+
draw = ImageDraw.Draw(img)
|
|
745
|
+
|
|
746
|
+
# Convert phase into angular sweep.
|
|
747
|
+
sweep = max(0.0, min(1.0, phase)) * 180.0
|
|
589
748
|
|
|
590
|
-
|
|
749
|
+
if waxing:
|
|
750
|
+
# Right side grows upward/downward from south.
|
|
751
|
+
start = 0 - sweep
|
|
752
|
+
end = 0 + sweep
|
|
753
|
+
|
|
754
|
+
else:
|
|
755
|
+
# Left side.
|
|
756
|
+
start = 180 - sweep
|
|
757
|
+
end = 180 + sweep
|
|
758
|
+
|
|
759
|
+
bbox = (
|
|
760
|
+
cx - r,
|
|
761
|
+
cy - r,
|
|
762
|
+
cx + r,
|
|
763
|
+
cy + r,
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
draw.arc(
|
|
767
|
+
bbox,
|
|
768
|
+
start=start,
|
|
769
|
+
end=end,
|
|
770
|
+
fill=color,
|
|
771
|
+
width=width,
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
return img
|
|
775
|
+
|
|
776
|
+
def _render_clock_sigil_frame(floor, observed_at, size=512, ornaments=True, inscription=True, glyph=True, tree=True, shadow=True, vestal=False):
|
|
591
777
|
try:
|
|
592
778
|
img = _draw_sigil_background(size)
|
|
593
779
|
cx, cy = size // 2, size // 2
|
|
594
780
|
tree_size = max(1, int(size * (82 / 400)))
|
|
595
|
-
_draw_sigil_tree_axis(img, cx, cy, size=tree_size)
|
|
596
781
|
font = _load_sigil_font(max(1, int(size * (36 / 400))))
|
|
597
782
|
r = int((size // 2) * 0.75)
|
|
598
|
-
alt, az = _draw_sigil_glyphs(img, floor, font, cx, cy, r, observed_at)
|
|
599
783
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
784
|
+
alt, az = _draw_sigil_glyphs(img, floor, font, cx, cy, r, observed_at)
|
|
785
|
+
if shadow:
|
|
786
|
+
_draw_sigil_shadow(img, cx, cy, alt, az, size=tree_size)
|
|
787
|
+
if tree:
|
|
788
|
+
_draw_sigil_tree_axis(img, cx, cy, size=tree_size)
|
|
789
|
+
|
|
790
|
+
if ornaments:
|
|
791
|
+
_draw_sigil_ornaments(img, cx, cy, size, r)
|
|
792
|
+
if inscription:
|
|
793
|
+
_draw_sigil_inscribe(img, cx, cy, size=size, r=r)
|
|
794
|
+
if vestal:
|
|
795
|
+
mt = floor.now_mt()
|
|
796
|
+
def infer_waxing() -> bool | None:
|
|
797
|
+
phase = getattr(mt, "moon_phase", None) or ""
|
|
798
|
+
p = str(phase).lower()
|
|
799
|
+
if p.startswith("wax"):
|
|
800
|
+
return True
|
|
801
|
+
if p.startswith("wan"):
|
|
802
|
+
return False
|
|
803
|
+
if "first quarter" in p:
|
|
804
|
+
return True
|
|
805
|
+
if "last quarter" in p:
|
|
806
|
+
return False
|
|
807
|
+
return None
|
|
808
|
+
|
|
809
|
+
waxing = infer_waxing()
|
|
810
|
+
illum_raw = mt.moon_illum / 100
|
|
811
|
+
|
|
812
|
+
if illum_raw > 100:
|
|
813
|
+
illum = illum_raw / 1000.0
|
|
814
|
+
else:
|
|
815
|
+
illum = illum_raw / 100.0
|
|
816
|
+
moon_phase_fraction = max(0.0, min(1.0, float(illum)))
|
|
603
817
|
|
|
818
|
+
_draw_vestal_ring(
|
|
819
|
+
img,
|
|
820
|
+
cx,
|
|
821
|
+
cy,
|
|
822
|
+
r=r * 1.15,
|
|
823
|
+
color=_phase_outer_ring_color(floor),
|
|
824
|
+
phase=moon_phase_fraction,
|
|
825
|
+
waxing=waxing
|
|
826
|
+
)
|
|
604
827
|
|
|
605
828
|
return img
|
|
606
829
|
except Exception as e:
|
|
@@ -734,47 +957,7 @@ def tf_sigil(floor, size=400):
|
|
|
734
957
|
|
|
735
958
|
img.paste(glyph_img, (int(px), int(py)), glyph_img)
|
|
736
959
|
lon += 30
|
|
737
|
-
# 🌙 Add Latin mottos inside the zodiac band
|
|
738
|
-
# 🌙 Big readable Latin mottos inside the zodiac band
|
|
739
|
-
try:
|
|
740
|
-
motto_font = _load_motto_font(max(14, int(size * 28 / 400)))
|
|
741
|
-
motto_r = r * 0.67
|
|
742
|
-
latin_take = "ACCIPE TEMPUS TUUM"
|
|
743
|
-
latin_late = "SERIUS EST QUAM COGITAS"
|
|
744
|
-
|
|
745
|
-
def _draw_motto(text, angle_deg):
|
|
746
|
-
theta_m = math.radians(angle_deg)
|
|
747
|
-
|
|
748
|
-
mx = cx + motto_r * -math.sin(theta_m)
|
|
749
|
-
my = cy + motto_r * math.cos(theta_m)
|
|
750
|
-
|
|
751
|
-
bb = motto_font.getbbox(text)
|
|
752
|
-
mw, mh = bb[2] - bb[0], bb[3] - bb[1]
|
|
753
|
-
|
|
754
|
-
# Simple shadow stroke so blind little society gremlins can see it
|
|
755
|
-
for ox, oy in [(-2, 0), (2, 0), (0, -2), (0, 2)]:
|
|
756
|
-
draw.text(
|
|
757
|
-
(mx - mw / 2 + ox, my - mh / 2 + oy),
|
|
758
|
-
text,
|
|
759
|
-
font=motto_font,
|
|
760
|
-
fill=(20, 15, 10, 230),
|
|
761
|
-
)
|
|
762
|
-
|
|
763
|
-
draw.text(
|
|
764
|
-
(mx - mw / 2, my - mh / 2),
|
|
765
|
-
text,
|
|
766
|
-
font=motto_font,
|
|
767
|
-
fill=(235, 205, 150, 255),
|
|
768
|
-
)
|
|
769
|
-
|
|
770
|
-
# Bottom-right-ish: Take your time
|
|
771
|
-
_draw_motto(latin_take, 315)
|
|
772
|
-
|
|
773
|
-
# Southwest in your reversed/south-facing setup
|
|
774
|
-
_draw_motto(latin_late, 45)
|
|
775
960
|
|
|
776
|
-
except Exception as e:
|
|
777
|
-
print(e)
|
|
778
961
|
except Exception as e:
|
|
779
962
|
print(e)
|
|
780
963
|
# 🌳 Tree (axis)
|
|
@@ -916,7 +1099,7 @@ def animate_sigil(canvas, base_image_path, duration=4, floor=None, frame_count=2
|
|
|
916
1099
|
|
|
917
1100
|
for i in range(frame_count):
|
|
918
1101
|
observed_at = start + (tick * i)
|
|
919
|
-
frame = _render_clock_sigil_frame(floor, observed_at, size=512)
|
|
1102
|
+
frame = _render_clock_sigil_frame(floor, observed_at, size=512, ornaments=True, inscription=True, glyph=True, tree=True, shadow=True, vestal=True)
|
|
920
1103
|
#frames.append(frame.copy())
|
|
921
1104
|
|
|
922
1105
|
frames.append(glow_layer(frame.copy(), passes=1))
|
|
@@ -381,6 +381,9 @@ class ThresholdFloor:
|
|
|
381
381
|
self.visual_state = "idle"
|
|
382
382
|
self.mode = "threshing"
|
|
383
383
|
self.current_phase = None
|
|
384
|
+
self._direction = None
|
|
385
|
+
self._warm_side = None
|
|
386
|
+
self._position_label = None
|
|
384
387
|
self.water_level = 0.0
|
|
385
388
|
self.blood_level = 0.0
|
|
386
389
|
self.wine_level = 0.0
|
|
@@ -493,7 +496,7 @@ class ThresholdFloor:
|
|
|
493
496
|
max_length=getattr(self, "max_shadow_length", None),
|
|
494
497
|
)
|
|
495
498
|
|
|
496
|
-
def shade_voice(self, timestamp: str | None = None) -> str:
|
|
499
|
+
def shade_voice(self, timestamp: str | None = None, prompt: str | None = None) -> str:
|
|
497
500
|
"""
|
|
498
501
|
Return a simple textual cue based on the current shadow's azimuth.
|
|
499
502
|
|
|
@@ -514,18 +517,23 @@ class ThresholdFloor:
|
|
|
514
517
|
"""
|
|
515
518
|
shadow = self.simulate_shadow(timestamp=timestamp)
|
|
516
519
|
if shadow is None:
|
|
517
|
-
return
|
|
520
|
+
return "Si sol deficit, respicit me nemo." # If the sun is gone, nobody will look at me.
|
|
521
|
+
if prompt:
|
|
522
|
+
if prompt.lower() == "who are you?":
|
|
523
|
+
return "Umbra sumus." # I am shade
|
|
518
524
|
|
|
519
525
|
az = getattr(shadow, "shadow_azimuth_deg", None)
|
|
520
526
|
if az is None:
|
|
521
|
-
return
|
|
527
|
+
return "Utere, non numera" # Use the hours, do not count them.
|
|
522
528
|
|
|
523
529
|
az = float(az) % 360.0
|
|
524
530
|
if 20.0 <= az <= 160.0:
|
|
525
|
-
return "
|
|
531
|
+
return "Carpe diem." # Sieze the day
|
|
526
532
|
if 200.0 <= az <= 350.0:
|
|
527
|
-
return "It
|
|
528
|
-
|
|
533
|
+
return "Serius est quam cogitas." # It is later than you think.
|
|
534
|
+
if az > 350.0:
|
|
535
|
+
return "Mox nox." # Soon it is night.
|
|
536
|
+
return "Si sol deficit, respicit me nemo." # If the sun is gone, nobody will look at me
|
|
529
537
|
|
|
530
538
|
def add_shadow_mark_from_simulation(
|
|
531
539
|
self,
|
|
@@ -626,6 +634,51 @@ class ThresholdFloor:
|
|
|
626
634
|
def get_phase(self):
|
|
627
635
|
return self.alchemy_phase()["phase"]
|
|
628
636
|
|
|
637
|
+
@property
|
|
638
|
+
def direction(self):
|
|
639
|
+
if getattr(self, "_direction", None) is None:
|
|
640
|
+
self.alchemy_phase()
|
|
641
|
+
|
|
642
|
+
return self._direction
|
|
643
|
+
|
|
644
|
+
@property
|
|
645
|
+
def hemisphere(self):
|
|
646
|
+
if getattr(self, "_hemisphere", None) is None:
|
|
647
|
+
self.alchemy_phase()
|
|
648
|
+
|
|
649
|
+
return self._hemisphere
|
|
650
|
+
|
|
651
|
+
@property
|
|
652
|
+
def warm_side(self):
|
|
653
|
+
if getattr(self, "_warm_side", None) is None:
|
|
654
|
+
self.alchemy_phase()
|
|
655
|
+
|
|
656
|
+
return self._warm_side
|
|
657
|
+
|
|
658
|
+
@property
|
|
659
|
+
def position_label(self):
|
|
660
|
+
if getattr(self, "_position_label", None) is None:
|
|
661
|
+
self.alchemy_phase()
|
|
662
|
+
|
|
663
|
+
return self._position_label
|
|
664
|
+
|
|
665
|
+
def get_job(self):
|
|
666
|
+
if self.current_phase is None:
|
|
667
|
+
self.alchemy_phase()
|
|
668
|
+
|
|
669
|
+
return FIELD_JOBS.get(self.current_phase)
|
|
670
|
+
|
|
671
|
+
def get_migration_state(self):
|
|
672
|
+
if self.current_phase is None:
|
|
673
|
+
self.alchemy_phase()
|
|
674
|
+
|
|
675
|
+
return {
|
|
676
|
+
"direction": self.direction,
|
|
677
|
+
"phase": self.current_phase,
|
|
678
|
+
"warm_side": self.warm_side,
|
|
679
|
+
"position": self.position_label,
|
|
680
|
+
}
|
|
681
|
+
|
|
629
682
|
def alchemy_phase(self) -> Dict[str, Any]:
|
|
630
683
|
"""Discern alchemical phase from prior-day sunrise movement and position.
|
|
631
684
|
|
|
@@ -707,7 +760,14 @@ class ThresholdFloor:
|
|
|
707
760
|
phase = 'Nigredo' if hemisphere == 'north' else 'Citrinitas'
|
|
708
761
|
else:
|
|
709
762
|
phase = 'Rubedo' if warm_side else 'Nigredo' if hemisphere == 'north' else 'Nigredo' if warm_side else 'Rubedo'
|
|
710
|
-
self.
|
|
763
|
+
self.current_phase = phase
|
|
764
|
+
if heading == hemisphere:
|
|
765
|
+
self._direction = 'ascending'
|
|
766
|
+
else:
|
|
767
|
+
self._direction = 'descending'
|
|
768
|
+
self._hemisphere = hemisphere
|
|
769
|
+
self._warm_side = warm_side
|
|
770
|
+
self._position_label = position_label
|
|
711
771
|
return {
|
|
712
772
|
'phase': phase,
|
|
713
773
|
'heading': heading,
|
|
@@ -717,6 +777,7 @@ class ThresholdFloor:
|
|
|
717
777
|
'east_arch': east_arch,
|
|
718
778
|
}
|
|
719
779
|
|
|
780
|
+
|
|
720
781
|
def sun_delay(self) -> Dict[str, Any]:
|
|
721
782
|
angle = scan_vector(self.latitude, self.longitude, self.get_sunrise())
|
|
722
783
|
delay = estimate_sun_delay(angle)
|
|
@@ -786,8 +847,7 @@ class ThresholdFloor:
|
|
|
786
847
|
return datetime.now(tzinfo)
|
|
787
848
|
|
|
788
849
|
def now_mt(self) -> datetime:
|
|
789
|
-
MoonTime.
|
|
790
|
-
return MoonTime.from_dt(self.now())
|
|
850
|
+
return MoonTime.from_datetime(self.now())
|
|
791
851
|
|
|
792
852
|
def weather(self) -> str:
|
|
793
853
|
if not WEATHER_EXISTS:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{thresholdfloor-0.3.2 → thresholdfloor-0.3.3}/src/thresholdfloor.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|