#!/usr/bin/env python3 """Gera plugin/contents/ui/starcatalog.js a partir do Yale Bright Star Catalog. Baixa bsc5-short.json (RA/Dec/V/K), filtra V <= MAG_LIMIT, converte a temperatura de cor em RGB (aprox. corpo negro, quantizada numa paleta pequena) e emite um JS compacto: const STAR_PALETTE = [[r,g,b], ...]; const STAR_DATA = [[raDeg, decDeg, vmag, colorIdx], ...]; // ordenado por mag Uso: python3 tools/build_catalog.py """ import json import math import re import urllib.request from pathlib import Path URL = "https://raw.githubusercontent.com/brettonw/YaleBrightStarCatalog/master/bsc5-short.json" OUT = Path(__file__).resolve().parent.parent / "plugin/contents/ui/starcatalog.js" MAG_LIMIT = 5.5 # Limites (K) e centro representativo de cada faixa de temperatura TEMP_BINS = [ (0, 3500, 3000), # M — alaranjado-avermelhado (3500, 4500, 4000), # K — laranja (4500, 5500, 5000), # G — amarelo-branco (5500, 6500, 6000), # F — branco-amarelado (6500, 8000, 7200), # A — branco (8000, 11000, 9500), # B tardio — branco-azulado (11000, 16000, 13000), # B — azul-branco (16000, 10**9, 20000), # O/B quente — azul ] SATURATION = 0.6 # estrelas reais parecem quase brancas; 1.0 = cor plena def blackbody_rgb(kelvin: float) -> tuple[int, int, int]: """Aproximação de Tanner Helland (válida ~1000K-40000K).""" t = kelvin / 100.0 if t <= 66: r = 255.0 g = 99.4708025861 * math.log(t) - 161.1195681661 b = 0.0 if t <= 19 else 138.5177312231 * math.log(t - 10) - 305.0447927307 else: r = 329.698727446 * (t - 60) ** -0.1332047592 g = 288.1221695283 * (t - 60) ** -0.0755148492 b = 255.0 def clamp(x): return max(0, min(255, round(x))) return clamp(r), clamp(g), clamp(b) def desaturate(rgb, amount): r, g, b = rgb lum = 0.2126 * r + 0.7152 * g + 0.0722 * b mix = lambda c: round(lum + (c - lum) * amount) return mix(r), mix(g), mix(b) def parse_ra(s: str) -> float: m = re.match(r"(\d+)h\s*(\d+)m\s*([\d.]+)s", s) h, mi, se = float(m.group(1)), float(m.group(2)), float(m.group(3)) return (h + mi / 60 + se / 3600) * 15.0 def parse_dec(s: str) -> float: m = re.match(r"([+-])(\d+)°\s*(\d+)′\s*([\d.]+)″", s) sign = -1.0 if m.group(1) == "-" else 1.0 d, mi, se = float(m.group(2)), float(m.group(3)), float(m.group(4)) return sign * (d + mi / 60 + se / 3600) def temp_index(kelvin: float) -> int: for i, (lo, hi, _) in enumerate(TEMP_BINS): if lo <= kelvin < hi: return i return len(TEMP_BINS) - 1 def main(): raw = json.loads(urllib.request.urlopen(URL, timeout=30).read()) stars = [] for entry in raw: if "V" not in entry or "RA" not in entry or "Dec" not in entry: continue vmag = float(entry["V"]) if vmag > MAG_LIMIT: continue kelvin = float(entry.get("K", 6000)) stars.append(( round(parse_ra(entry["RA"]), 2), round(parse_dec(entry["Dec"]), 2), round(vmag, 2), temp_index(kelvin), )) stars.sort(key=lambda s: s[2]) # mais brilhantes primeiro palette = [desaturate(blackbody_rgb(center), SATURATION) for _, _, center in TEMP_BINS] rows = ",\n".join( f"[{ra},{dec},{mag},{ci}]" for ra, dec, mag, ci in stars ) OUT.write_text( "/* Gerado por tools/build_catalog.py — NÃO editar à mão.\n" f" Yale Bright Star Catalog, V <= {MAG_LIMIT}, {len(stars)} estrelas.\n" " Formato: [raGraus, decGraus, magV, idxPaleta] ordenado por magnitude. */\n" f"const STAR_PALETTE = {json.dumps([list(p) for p in palette])};\n" f"const STAR_DATA = [\n{rows}\n];\n" ) print(f"{len(stars)} estrelas -> {OUT} ({OUT.stat().st_size / 1024:.0f} KB)") if __name__ == "__main__": main()