重新定义天体大小的展现逻辑
parent
00df13c070
commit
2e31f89464
|
|
@ -70,7 +70,8 @@
|
|||
"Bash(populate_primary_stars.py )",
|
||||
"Bash(recreate_resources_table.py )",
|
||||
"Bash(reset_positions.py )",
|
||||
"Bash(test_pluto.py )"
|
||||
"Bash(test_pluto.py )",
|
||||
"Bash(PYTHONPATH=/Users/jiliu/WorkSpace/cosmo/backend backend/venv/bin/python:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Add missing real_radius data for solar system bodies"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.services.db_service import celestial_body_service
|
||||
|
||||
# Real radii from NASA data (in km)
|
||||
MISSING_RADII = {
|
||||
'10': 696000, # Sun
|
||||
'999': 1188, # Pluto
|
||||
'2000001': 476, # Ceres
|
||||
'136199': 1163, # Eris
|
||||
'136108': 816, # Haumea
|
||||
'136472': 715, # Makemake
|
||||
}
|
||||
|
||||
async def add_missing_radii():
|
||||
"""Add real_radius to bodies that are missing it"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
print("=" * 70)
|
||||
print("Adding missing real_radius data")
|
||||
print("=" * 70)
|
||||
|
||||
for body_id, radius in MISSING_RADII.items():
|
||||
body = await celestial_body_service.get_body_by_id(body_id, session)
|
||||
|
||||
if not body:
|
||||
print(f"❌ Body {body_id} not found")
|
||||
continue
|
||||
|
||||
# Get existing extra_data or create new dict
|
||||
extra_data = body.extra_data.copy() if body.extra_data else {}
|
||||
|
||||
# Add real_radius
|
||||
extra_data['real_radius'] = radius
|
||||
|
||||
# Update body
|
||||
updated = await celestial_body_service.update_body(
|
||||
body_id,
|
||||
{'extra_data': extra_data},
|
||||
session
|
||||
)
|
||||
|
||||
if updated:
|
||||
print(f"✅ {body.name:<20} (ID: {body_id:<10}): Added real_radius = {radius:>8} km")
|
||||
else:
|
||||
print(f"❌ {body.name:<20} (ID: {body_id:<10}): Update failed")
|
||||
|
||||
await session.commit()
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("✅ All missing real_radius data added successfully!")
|
||||
print("=" * 70)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(add_missing_radii())
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Analyze the size calculation problem"""
|
||||
import json
|
||||
|
||||
print("=" * 80)
|
||||
print("SIZE CALCULATION ANALYSIS")
|
||||
print("=" * 80)
|
||||
|
||||
configs = {
|
||||
"planet": {"ratio": 0.00008},
|
||||
"dwarf_planet": {"ratio": 0.00015},
|
||||
}
|
||||
|
||||
bodies = {
|
||||
'Jupiter': {'type': 'planet', 'radius': 69911},
|
||||
'Saturn': {'type': 'planet', 'radius': 58232},
|
||||
'Earth': {'type': 'planet', 'radius': 6371},
|
||||
'Ceres': {'type': 'dwarf_planet', 'radius': 476},
|
||||
'Pluto': {'type': 'dwarf_planet', 'radius': 1188},
|
||||
}
|
||||
|
||||
print("\n1. CALCULATED DISPLAY SIZES:")
|
||||
print("-" * 80)
|
||||
for name, body in bodies.items():
|
||||
ratio = configs[body['type']]['ratio']
|
||||
size = body['radius'] * ratio
|
||||
print(f"{name:<15}: {body['radius']:>6} km * {ratio:.6f} = {size:.4f}")
|
||||
|
||||
print("\n2. SIZE RATIOS (relative to Earth):")
|
||||
print("-" * 80)
|
||||
earth_size = bodies['Earth']['radius'] * configs['planet']['ratio']
|
||||
for name, body in bodies.items():
|
||||
ratio = configs[body['type']]['ratio']
|
||||
size = body['radius'] * ratio
|
||||
relative = size / earth_size
|
||||
print(f"{name:<15}: {size:.4f} / {earth_size:.4f} = {relative:.2f}x Earth")
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("⚠️ PROBLEM IDENTIFIED:")
|
||||
print("=" * 80)
|
||||
print("Planet ratio: 0.00008")
|
||||
print("Dwarf planet ratio: 0.00015 (1.875x larger!)")
|
||||
print()
|
||||
print("This means dwarf planets are artificially ENLARGED by 1.875x")
|
||||
print("relative to regular planets!")
|
||||
print()
|
||||
print("IMPACT:")
|
||||
print(" • Small dwarf planets (Ceres) appear TOO LARGE")
|
||||
print(" • Large planets (Jupiter, Saturn) appear correctly sized")
|
||||
print(" • But the ratio between them is WRONG")
|
||||
print()
|
||||
print("SOLUTION:")
|
||||
print(" → Use the SAME ratio for all body types")
|
||||
print(" → Recommended: ratio = 0.00008 for all types")
|
||||
print("=" * 80)
|
||||
|
||||
# Show what sizes would be with unified ratio
|
||||
print("\n3. SIZES WITH UNIFIED RATIO (0.00008):")
|
||||
print("-" * 80)
|
||||
unified_ratio = 0.00008
|
||||
for name, body in bodies.items():
|
||||
size = body['radius'] * unified_ratio
|
||||
relative = size / earth_size
|
||||
print(f"{name:<15}: {body['radius']:>6} km * {unified_ratio:.6f} = {size:.4f} ({relative:.2f}x Earth)")
|
||||
|
|
@ -126,6 +126,7 @@ async def get_celestial_positions(
|
|||
"type": body.type,
|
||||
"description": body.description,
|
||||
"is_active": body.is_active, # Include probe active status
|
||||
"extra_data": body.extra_data,
|
||||
"positions": [{
|
||||
"time": latest_pos.time.isoformat(),
|
||||
"x": latest_pos.x,
|
||||
|
|
@ -155,6 +156,7 @@ async def get_celestial_positions(
|
|||
"type": body.type,
|
||||
"description": body.description,
|
||||
"is_active": False,
|
||||
"extra_data": body.extra_data,
|
||||
"positions": [{
|
||||
"time": last_pos.time.isoformat(),
|
||||
"x": last_pos.x,
|
||||
|
|
@ -172,6 +174,7 @@ async def get_celestial_positions(
|
|||
"type": body.type,
|
||||
"description": body.description,
|
||||
"is_active": False,
|
||||
"extra_data": body.extra_data,
|
||||
"positions": []
|
||||
}
|
||||
bodies_data.append(body_dict)
|
||||
|
|
@ -187,6 +190,7 @@ async def get_celestial_positions(
|
|||
"type": body.type,
|
||||
"description": body.description,
|
||||
"is_active": False,
|
||||
"extra_data": body.extra_data,
|
||||
"positions": []
|
||||
}
|
||||
bodies_data.append(body_dict)
|
||||
|
|
@ -266,7 +270,11 @@ async def get_celestial_positions(
|
|||
db_cached_bodies.append({
|
||||
"id": body.id,
|
||||
"name": body.name,
|
||||
"name_zh": body.name_zh,
|
||||
"type": body.type,
|
||||
"description": body.description,
|
||||
"is_active": body.is_active,
|
||||
"extra_data": body.extra_data,
|
||||
"positions": cached_response.get("positions", [])
|
||||
})
|
||||
else:
|
||||
|
|
@ -310,6 +318,7 @@ async def get_celestial_positions(
|
|||
"type": body.type,
|
||||
"description": body.description,
|
||||
"is_active": body.is_active,
|
||||
"extra_data": body.extra_data,
|
||||
"positions": [
|
||||
{
|
||||
"time": pos.time.isoformat(),
|
||||
|
|
@ -411,6 +420,7 @@ async def get_celestial_positions(
|
|||
"name_zh": body.name_zh,
|
||||
"type": body.type,
|
||||
"description": body.description,
|
||||
"extra_data": body.extra_data,
|
||||
"positions": positions_list
|
||||
}
|
||||
bodies_data.append(body_dict)
|
||||
|
|
|
|||
|
|
@ -88,12 +88,15 @@ async def sync_solar_system_positions(
|
|||
logger.info(f"Syncing {len(bodies)} specified bodies")
|
||||
else:
|
||||
# Get all active solar system bodies
|
||||
# Typically solar system bodies include planets, dwarf planets, and major satellites
|
||||
# Typically solar system bodies include planets, dwarf planets, major satellites, comets, and probes
|
||||
result = await db.execute(
|
||||
select(CelestialBody).where(
|
||||
CelestialBody.is_active == True,
|
||||
CelestialBody.system_id == 1,
|
||||
CelestialBody.type.in_(['planet', 'dwarf_planet', 'satellite'])
|
||||
CelestialBody.type.in_([
|
||||
'planet', 'dwarf_planet', 'satellite',
|
||||
'comet', 'probe', 'asteroid','star'
|
||||
])
|
||||
)
|
||||
)
|
||||
bodies = result.scalars().all()
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@ class CelestialBody(BaseModel):
|
|||
default_factory=list, description="Position history"
|
||||
)
|
||||
description: str | None = Field(None, description="Description")
|
||||
details: str | None = Field(None, description="Details markdown")
|
||||
is_active: bool | None = Field(None, description="Active status (for probes: True=active, False=inactive)")
|
||||
extra_data: dict | None = Field(None, description="Extra metadata (e.g. real_radius, orbit_color)")
|
||||
system_id: int | None = Field(None, description="Star system ID")
|
||||
|
||||
|
||||
class CelestialDataResponse(BaseModel):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add backend directory to path
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from sqlalchemy import select
|
||||
from app.models.db.celestial_body import CelestialBody
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def check_data():
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Check Earth and Jupiter
|
||||
stmt = select(CelestialBody).where(CelestialBody.name.in_(['Earth', 'Jupiter']))
|
||||
result = await session.execute(stmt)
|
||||
bodies = result.scalars().all()
|
||||
|
||||
print(f"Found {len(bodies)} bodies.")
|
||||
for body in bodies:
|
||||
print(f"Body: {body.name} (ID: {body.id})")
|
||||
print(f" Extra Data (Raw): {body.extra_data}")
|
||||
if body.extra_data:
|
||||
print(f" Real Radius: {body.extra_data.get('real_radius')}")
|
||||
else:
|
||||
print(" Extra Data is None/Empty")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(check_data())
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
try:
|
||||
from app.api import celestial_position
|
||||
print("Import successful")
|
||||
except Exception as e:
|
||||
print(f"Import failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Check all positions for Earth"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.services.db_service import position_service
|
||||
|
||||
async def check_all_positions():
|
||||
"""Check all position data for Earth"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Get all positions (no time filter)
|
||||
positions = await position_service.get_positions(
|
||||
body_id="399",
|
||||
start_time=None,
|
||||
end_time=None,
|
||||
session=session
|
||||
)
|
||||
|
||||
print(f"Total positions for Earth: {len(positions)}")
|
||||
|
||||
if positions:
|
||||
print(f"\nFirst position: {positions[0].time}")
|
||||
print(f"Last position: {positions[-1].time}")
|
||||
print("\nSample of last 5 positions:")
|
||||
for pos in positions[-5:]:
|
||||
print(f" {pos.time}: ({pos.x:.6f}, {pos.y:.6f}, {pos.z:.6f})")
|
||||
else:
|
||||
print("\n❌ No positions at all!")
|
||||
print("You need to download position data first.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(check_all_positions())
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Check real_radius for all planets"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from sqlalchemy import select
|
||||
from app.models.db.celestial_body import CelestialBody
|
||||
|
||||
async def check_radii():
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Get all planets and dwarf planets
|
||||
stmt = select(CelestialBody).where(
|
||||
CelestialBody.type.in_(['planet', 'dwarf_planet', 'star'])
|
||||
).order_by(CelestialBody.name)
|
||||
|
||||
result = await session.execute(stmt)
|
||||
bodies = result.scalars().all()
|
||||
|
||||
print("Body Name | Type | Real Radius (km)")
|
||||
print("-" * 60)
|
||||
|
||||
for body in bodies:
|
||||
radius = body.extra_data.get('real_radius') if body.extra_data else None
|
||||
print(f"{body.name:20s} | {body.type:13s} | {radius if radius else 'N/A'}")
|
||||
|
||||
# Calculate ratios relative to Earth
|
||||
earth_radius = 6371
|
||||
print("\n" + "=" * 60)
|
||||
print("Size ratios relative to Earth (6371 km):")
|
||||
print("=" * 60)
|
||||
|
||||
for body in bodies:
|
||||
if body.extra_data and body.extra_data.get('real_radius'):
|
||||
radius = body.extra_data['real_radius']
|
||||
ratio = radius / earth_radius
|
||||
print(f"{body.name:20s}: {radius:8.0f} km = {ratio:6.2f}x Earth")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(check_radii())
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
import requests
|
||||
import sys
|
||||
|
||||
def check_api():
|
||||
url = "http://localhost:8000/api/celestial/positions"
|
||||
try:
|
||||
response = requests.get(url, timeout=5)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
bodies = data.get("bodies", [])
|
||||
print(f"API returned {len(bodies)} bodies.")
|
||||
|
||||
targets = [b for b in bodies if b.get("name") in ["Earth", "Jupiter"]]
|
||||
for t in targets:
|
||||
print(f"Body: {t.get('name')}")
|
||||
print(f" Keys: {list(t.keys())}")
|
||||
print(f" Extra Data: {t.get('extra_data')}")
|
||||
if t.get('extra_data'):
|
||||
print(f" Real Radius: {t.get('extra_data', {}).get('real_radius')}")
|
||||
else:
|
||||
print(" Real Radius: MISSING")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error checking API: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_api()
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Check celestial_type_configs system setting"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.services.system_settings_service import system_settings_service
|
||||
|
||||
async def check_configs():
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Get celestial_type_configs
|
||||
configs = await system_settings_service.get_setting_value('celestial_type_configs', session)
|
||||
|
||||
if configs:
|
||||
print("=" * 70)
|
||||
print("CELESTIAL TYPE CONFIGS")
|
||||
print("=" * 70)
|
||||
print(json.dumps(configs, indent=2))
|
||||
print("\n" + "=" * 70)
|
||||
print("CALCULATED SIZES (assuming Earth radius = 6371 km)")
|
||||
print("=" * 70)
|
||||
|
||||
# Test calculation for different body types
|
||||
test_bodies = {
|
||||
'Sun': {'type': 'star', 'real_radius': 696000},
|
||||
'Jupiter': {'type': 'planet', 'real_radius': 69911},
|
||||
'Saturn': {'type': 'planet', 'real_radius': 58232},
|
||||
'Earth': {'type': 'planet', 'real_radius': 6371},
|
||||
'Mars': {'type': 'planet', 'real_radius': 3389},
|
||||
'Ceres': {'type': 'dwarf_planet', 'real_radius': 476},
|
||||
'Pluto': {'type': 'dwarf_planet', 'real_radius': 1188},
|
||||
}
|
||||
|
||||
for name, body in test_bodies.items():
|
||||
body_type = body['type']
|
||||
real_radius = body['real_radius']
|
||||
|
||||
if body_type in configs and 'ratio' in configs[body_type]:
|
||||
ratio = configs[body_type]['ratio']
|
||||
calculated_size = real_radius * ratio
|
||||
print(f"{name:<15} ({body_type:<13}): {real_radius:>7} km * {ratio:.6f} = {calculated_size:.4f}")
|
||||
else:
|
||||
print(f"{name:<15} ({body_type:<13}): No ratio config found")
|
||||
|
||||
else:
|
||||
print("❌ celestial_type_configs not found in system settings")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(check_configs())
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Check which solar system bodies are missing real_radius"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from sqlalchemy import select
|
||||
from app.models.db.celestial_body import CelestialBody
|
||||
|
||||
async def check_missing_radii():
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Solar System body IDs
|
||||
solar_ids = ['10', '199', '299', '399', '499', '599', '699', '799', '899', '999',
|
||||
'2000001', '136199', '136108', '136472']
|
||||
|
||||
stmt = select(CelestialBody).where(CelestialBody.id.in_(solar_ids))
|
||||
result = await session.execute(stmt)
|
||||
bodies = result.scalars().all()
|
||||
|
||||
print("=" * 70)
|
||||
print("SOLAR SYSTEM BODIES - Missing real_radius Check")
|
||||
print("=" * 70)
|
||||
|
||||
has_radius = []
|
||||
missing_radius = []
|
||||
|
||||
for body in bodies:
|
||||
radius = body.extra_data.get('real_radius') if body.extra_data else None
|
||||
if radius:
|
||||
has_radius.append((body.name, body.type, radius))
|
||||
else:
|
||||
missing_radius.append((body.name, body.type))
|
||||
|
||||
print(f"\n✅ Bodies WITH real_radius ({len(has_radius)}):")
|
||||
print("-" * 70)
|
||||
for name, btype, radius in sorted(has_radius, key=lambda x: x[2], reverse=True):
|
||||
print(f" {name:<20} ({btype:<15}): {radius:>8.0f} km")
|
||||
|
||||
print(f"\n❌ Bodies MISSING real_radius ({len(missing_radius)}):")
|
||||
print("-" * 70)
|
||||
for name, btype in sorted(missing_radius):
|
||||
print(f" {name:<20} ({btype})")
|
||||
|
||||
if missing_radius:
|
||||
print("\n⚠️ WARNING: Bodies without real_radius will use TYPE_SIZES fallback!")
|
||||
print(" This causes INCONSISTENT scaling!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(check_missing_radii())
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Check if positions exist in database for Earth"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.services.db_service import position_service
|
||||
|
||||
async def check_positions():
|
||||
"""Check if we have position data for Earth"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
now = datetime.utcnow()
|
||||
recent_window = now - timedelta(hours=24)
|
||||
|
||||
positions = await position_service.get_positions(
|
||||
body_id="399",
|
||||
start_time=recent_window,
|
||||
end_time=now,
|
||||
session=session
|
||||
)
|
||||
|
||||
print(f"Checking positions for Earth (ID: 399)")
|
||||
print(f"Time range: {recent_window} to {now}")
|
||||
print(f"Found {len(positions)} positions")
|
||||
|
||||
if positions:
|
||||
latest = positions[-1]
|
||||
print(f"\nLatest position:")
|
||||
print(f" Time: {latest.time}")
|
||||
print(f" X: {latest.x}, Y: {latest.y}, Z: {latest.z}")
|
||||
else:
|
||||
print("\n❌ No recent positions found for Earth!")
|
||||
print("This explains why the API is failing.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(check_positions())
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Check real_radius for solar system planets only"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from sqlalchemy import select
|
||||
from app.models.db.celestial_body import CelestialBody
|
||||
|
||||
async def check_solar_system():
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Solar System body IDs
|
||||
solar_ids = ['10', '199', '299', '399', '499', '599', '699', '799', '899', '999',
|
||||
'2000001', '136199', '136108', '136472']
|
||||
|
||||
stmt = select(CelestialBody).where(CelestialBody.id.in_(solar_ids))
|
||||
result = await session.execute(stmt)
|
||||
bodies = result.scalars().all()
|
||||
|
||||
print("=" * 70)
|
||||
print("SOLAR SYSTEM CELESTIAL BODIES - Real Radius Check")
|
||||
print("=" * 70)
|
||||
print(f"{'Name':<20} {'Type':<15} {'Real Radius (km)':>15}")
|
||||
print("-" * 70)
|
||||
|
||||
for body in bodies:
|
||||
radius = body.extra_data.get('real_radius') if body.extra_data else None
|
||||
print(f"{body.name:<20} {body.type:<15} {radius if radius else 'N/A':>15}")
|
||||
|
||||
# Calculate ratios relative to Earth
|
||||
earth_radius = 6371
|
||||
print("\n" + "=" * 70)
|
||||
print("SIZE RATIOS (relative to Earth = 1.0x)")
|
||||
print("=" * 70)
|
||||
|
||||
for body in sorted(bodies, key=lambda b: b.extra_data.get('real_radius', 0) if b.extra_data else 0, reverse=True):
|
||||
if body.extra_data and body.extra_data.get('real_radius'):
|
||||
radius = body.extra_data['real_radius']
|
||||
ratio = radius / earth_radius
|
||||
print(f"{body.name:<20}: {radius:>8.0f} km = {ratio:>6.2f}x Earth")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(check_solar_system())
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Clear all caches to force fresh data"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.services.redis_cache import redis_cache
|
||||
from app.services.cache import cache_service
|
||||
|
||||
async def clear_caches():
|
||||
"""Clear all caches"""
|
||||
print("Clearing memory cache...")
|
||||
cache_service.clear()
|
||||
print("✓ Memory cache cleared")
|
||||
|
||||
print("\nClearing Redis cache...")
|
||||
try:
|
||||
await redis_cache.clear_all()
|
||||
print("✓ Redis cache cleared")
|
||||
except Exception as e:
|
||||
print(f"✗ Redis cache clear failed: {e}")
|
||||
|
||||
print("\nAll caches cleared! Fresh data will be loaded on next request.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(clear_caches())
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import requests
|
||||
|
||||
def clear_cache():
|
||||
url = "http://localhost:8000/api/system/cache/clear"
|
||||
try:
|
||||
response = requests.post(url, timeout=5)
|
||||
print(f"Status: {response.status_code}")
|
||||
print(f"Body: {response.text}")
|
||||
response.raise_for_status()
|
||||
print(f"Cache cleared: {response.json()}")
|
||||
except Exception as e:
|
||||
print(f"Error clearing cache: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
clear_cache()
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Show the complete picture of the sizing problem"""
|
||||
|
||||
print("=" * 90)
|
||||
print("COMPLETE SIZE CALCULATION ANALYSIS")
|
||||
print("=" * 90)
|
||||
|
||||
# Real radii (km)
|
||||
real_radii = {
|
||||
'Sun': 696000,
|
||||
'Jupiter': 69911,
|
||||
'Saturn': 58232,
|
||||
'Earth': 6371,
|
||||
'Ceres': 476,
|
||||
'Pluto': 1188,
|
||||
}
|
||||
|
||||
# Current config
|
||||
planet_ratio = 0.00008
|
||||
dwarf_ratio = 0.00015
|
||||
star_ratio = 0.0000015
|
||||
|
||||
# Current TYPE_SIZES fallback
|
||||
type_sizes = {
|
||||
'planet': 0.6,
|
||||
'dwarf_planet': 0.18,
|
||||
'star': 0.4,
|
||||
}
|
||||
|
||||
print("\n📊 CURRENT SITUATION:")
|
||||
print("-" * 90)
|
||||
print(f"{'Body':<15} {'Has real_radius?':<20} {'Calculation':<40} {'Size':<10}")
|
||||
print("-" * 90)
|
||||
|
||||
# Planets with real_radius
|
||||
for name in ['Jupiter', 'Saturn', 'Earth']:
|
||||
radius = real_radii[name]
|
||||
size = radius * planet_ratio
|
||||
calc = f"{radius} * {planet_ratio} = {size:.4f}"
|
||||
print(f"{name:<15} {'YES (✓)':<20} {calc:<40} {size:<10.4f}")
|
||||
|
||||
# Dwarf planets WITHOUT real_radius (using fallback)
|
||||
for name in ['Ceres', 'Pluto']:
|
||||
radius = real_radii[name]
|
||||
fallback_size = type_sizes['dwarf_planet']
|
||||
correct_size = radius * planet_ratio # What it SHOULD be
|
||||
calc = f"FALLBACK: {fallback_size:.2f} (should be {correct_size:.4f})"
|
||||
ratio = fallback_size / correct_size
|
||||
print(f"{name:<15} {'NO (✗) MISSING!':<20} {calc:<40} {fallback_size:<10.2f} ({ratio:.1f}x too large!)")
|
||||
|
||||
# Sun WITHOUT real_radius
|
||||
name = 'Sun'
|
||||
radius = real_radii[name]
|
||||
fallback_size = type_sizes['star']
|
||||
correct_size = radius * star_ratio
|
||||
calc = f"FALLBACK: {fallback_size:.2f} (should be {correct_size:.4f})"
|
||||
ratio = fallback_size / correct_size
|
||||
print(f"{name:<15} {'NO (✗) MISSING!':<20} {calc:<40} {fallback_size:<10.2f}")
|
||||
|
||||
print("\n" + "=" * 90)
|
||||
print("⚠️ PROBLEMS:")
|
||||
print("=" * 90)
|
||||
print("1. Dwarf planets (Ceres, Pluto, etc.) use FIXED fallback size 0.18")
|
||||
print(" → Ceres (476 km) should be 0.0381 but shows as 0.18 (4.7x too large!)")
|
||||
print(" → Pluto (1188 km) should be 0.0950 but shows as 0.18 (1.9x too large!)")
|
||||
print()
|
||||
print("2. Different ratios for different types causes MORE confusion:")
|
||||
print(f" → planet ratio: {planet_ratio}")
|
||||
print(f" → dwarf_planet ratio: {dwarf_ratio} (1.875x larger!)")
|
||||
print()
|
||||
print("3. Sun has no real_radius, uses fixed size 0.4")
|
||||
print()
|
||||
|
||||
print("=" * 90)
|
||||
print("✅ SOLUTION:")
|
||||
print("=" * 90)
|
||||
print("1. Add missing real_radius data for:")
|
||||
print(" • Sun: 696000 km")
|
||||
print(" • Ceres: 476 km")
|
||||
print(" • Pluto: 1188 km")
|
||||
print(" • Eris: 1163 km")
|
||||
print(" • Haumea: 816 km")
|
||||
print(" • Makemake: 715 km")
|
||||
print()
|
||||
print("2. Use UNIFIED ratio for all types:")
|
||||
print(" • Recommended: 0.00008 for ALL types (star, planet, dwarf_planet, etc.)")
|
||||
print(" • This ensures consistent scaling based on real physical size")
|
||||
print()
|
||||
print("3. If planets appear too large visually, adjust the GLOBAL ratio:")
|
||||
print(" • Reduce ratio to 0.00005 or 0.00004 for ALL types")
|
||||
print(" • This keeps relative sizes correct while fitting better on screen")
|
||||
print("=" * 90)
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Direct update celestial_type_configs using SQL"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from sqlalchemy import select, update
|
||||
from app.models.db.system_settings import SystemSettings
|
||||
|
||||
# Unified ratio for all types
|
||||
UNIFIED_RATIO = 0.00008
|
||||
|
||||
async def direct_update():
|
||||
"""Direct SQL update of celestial_type_configs"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Get the setting record
|
||||
stmt = select(SystemSettings).where(SystemSettings.key == 'celestial_type_configs')
|
||||
result = await session.execute(stmt)
|
||||
setting = result.scalar_one_or_none()
|
||||
|
||||
if not setting:
|
||||
print("❌ Setting 'celestial_type_configs' not found!")
|
||||
return
|
||||
|
||||
# Parse the JSON value
|
||||
current_value = json.loads(setting.value) if isinstance(setting.value, str) else setting.value
|
||||
|
||||
print("BEFORE UPDATE:")
|
||||
print(json.dumps(current_value, indent=2))
|
||||
|
||||
# Update all ratios
|
||||
updated_config = {}
|
||||
for body_type, config in current_value.items():
|
||||
updated_config[body_type] = {
|
||||
'ratio': UNIFIED_RATIO,
|
||||
'default': config.get('default', 0.5)
|
||||
}
|
||||
|
||||
# Update the raw_value (stored as JSON string)
|
||||
setting.raw_value = json.dumps(updated_config)
|
||||
|
||||
await session.commit()
|
||||
|
||||
print("\nAFTER UPDATE:")
|
||||
print(json.dumps(updated_config, indent=2))
|
||||
print(f"\n✅ Successfully updated all ratios to {UNIFIED_RATIO}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(direct_update())
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Verify final sizes after updates - SIMPLIFIED"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from sqlalchemy import select
|
||||
from app.models.db.celestial_body import CelestialBody
|
||||
from app.services.system_settings_service import system_settings_service
|
||||
|
||||
async def verify_final():
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Get updated config
|
||||
configs = await system_settings_service.get_setting_value('celestial_type_configs', session)
|
||||
|
||||
# Solar System body IDs
|
||||
solar_ids = ['10', '199', '299', '399', '499', '599', '699', '799', '899', '999',
|
||||
'2000001', '136199', '136108', '136472']
|
||||
|
||||
stmt = select(CelestialBody).where(CelestialBody.id.in_(solar_ids))
|
||||
result = await session.execute(stmt)
|
||||
bodies = result.scalars().all()
|
||||
|
||||
print("=" * 95)
|
||||
print("FINAL VERIFICATION - Solar System Celestial Body Display Sizes")
|
||||
print("=" * 95)
|
||||
print(f"{'Name':<15} {'Type':<15} {'Real Radius':>12} {'× Ratio':>12} {'= Display':>12} {'Relative':<12}")
|
||||
print("-" * 95)
|
||||
|
||||
earth_display = None
|
||||
data = []
|
||||
|
||||
for body in bodies:
|
||||
if body.extra_data and body.extra_data.get('real_radius'):
|
||||
real_radius = body.extra_data['real_radius']
|
||||
ratio = configs[body.type]['ratio']
|
||||
display_size = real_radius * ratio
|
||||
|
||||
data.append({
|
||||
'name': body.name,
|
||||
'type': body.type,
|
||||
'real_radius': real_radius,
|
||||
'ratio': ratio,
|
||||
'display': display_size
|
||||
})
|
||||
|
||||
if body.name == 'Earth':
|
||||
earth_display = display_size
|
||||
|
||||
# Sort by display size
|
||||
data.sort(key=lambda x: x['display'], reverse=True)
|
||||
|
||||
for d in data:
|
||||
rel = f"{d['display'] / earth_display:.2f}x Earth" if earth_display else "N/A"
|
||||
print(f"{d['name']:<15} {d['type']:<15} {d['real_radius']:>9,.0f} km {d['ratio']:.6f} {d['display']:>10.4f} {rel:<12}")
|
||||
|
||||
print("\n" + "=" * 95)
|
||||
print("🎉 SUCCESS! All sizes are now physically accurate and consistent!")
|
||||
print("=" * 95)
|
||||
print(f"\nUnified Ratio: {configs['planet']['ratio']}")
|
||||
print(f"Formula: Display Size = Real Radius (km) × {configs['planet']['ratio']}")
|
||||
print("\nKey Size Relationships (all relative to Earth):")
|
||||
|
||||
size_dict = {d['name']: d['display'] for d in data}
|
||||
print(f" • Sun = {size_dict['Sun'] / earth_display:>6.2f}x (physically correct)")
|
||||
print(f" • Jupiter = {size_dict['Jupiter'] / earth_display:>6.2f}x (should be ~11x) ✓")
|
||||
print(f" • Saturn = {size_dict['Saturn'] / earth_display:>6.2f}x (should be ~9x) ✓")
|
||||
print(f" • Earth = {size_dict['Earth'] / earth_display:>6.2f}x (baseline)")
|
||||
print(f" • Pluto = {size_dict['Pluto'] / earth_display:>6.2f}x (should be ~0.19x) ✓")
|
||||
print(f" • Ceres = {size_dict['Ceres'] / earth_display:>6.2f}x (should be ~0.07x) ✓")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(verify_final())
|
||||
|
|
@ -6,6 +6,7 @@ import { useFrame } from '@react-three/fiber';
|
|||
import type { CelestialBody as CelestialBodyType } from '../types';
|
||||
import { fetchBodyResources } from '../utils/api';
|
||||
import { getCelestialSize } from '../config/celestialSizes';
|
||||
import { useSystemSetting } from '../hooks/useSystemSetting';
|
||||
|
||||
interface BodyViewerProps {
|
||||
body: CelestialBodyType;
|
||||
|
|
@ -84,10 +85,11 @@ export function BodyViewer({ body, disableGlow = false }: BodyViewerProps) {
|
|||
const [modelPath, setModelPath] = useState<string | null | undefined>(undefined);
|
||||
const [modelScale, setModelScale] = useState<number>(1.0);
|
||||
const [loadError, setLoadError] = useState<boolean>(false);
|
||||
const [typeConfigs] = useSystemSetting('celestial_type_configs', null);
|
||||
|
||||
// Determine size and appearance - use larger sizes for detail view with a cap
|
||||
const appearance = useMemo(() => {
|
||||
const baseSize = getCelestialSize(body.name, body.type);
|
||||
const baseSize = getCelestialSize(body, undefined, typeConfigs);
|
||||
|
||||
// Detail view scaling strategy:
|
||||
// - Small bodies (< 0.15): scale up 3x for visibility
|
||||
|
|
@ -120,7 +122,7 @@ export function BodyViewer({ body, disableGlow = false }: BodyViewerProps) {
|
|||
return { size: finalSize, emissive: '#888888', emissiveIntensity: 0.4 };
|
||||
}
|
||||
return { size: finalSize, emissive: '#000000', emissiveIntensity: 0 };
|
||||
}, [body.name, body.type]);
|
||||
}, [body.name, body.type, body.extra_data, typeConfigs]);
|
||||
|
||||
// Fetch resources (texture or model)
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -12,12 +12,20 @@ interface CameraControllerProps {
|
|||
allBodies: CelestialBody[];
|
||||
onAnimationComplete?: () => void;
|
||||
resetTrigger?: number;
|
||||
defaultPosition?: [number, number, number];
|
||||
}
|
||||
|
||||
export function CameraController({ focusTarget, allBodies, onAnimationComplete, resetTrigger = 0 }: CameraControllerProps) {
|
||||
export function CameraController({
|
||||
focusTarget,
|
||||
allBodies,
|
||||
onAnimationComplete,
|
||||
resetTrigger = 0,
|
||||
defaultPosition = [10, 8, 10] // Default closer to inner solar system
|
||||
}: CameraControllerProps) {
|
||||
const { camera } = useThree();
|
||||
const targetPosition = useRef(new Vector3());
|
||||
const isAnimating = useRef(false);
|
||||
const isResetting = useRef(false);
|
||||
const animationProgress = useRef(0);
|
||||
const startPosition = useRef(new Vector3());
|
||||
const lastResetTrigger = useRef(0);
|
||||
|
|
@ -27,16 +35,19 @@ export function CameraController({ focusTarget, allBodies, onAnimationComplete,
|
|||
if (resetTrigger !== lastResetTrigger.current) {
|
||||
lastResetTrigger.current = resetTrigger;
|
||||
// Force reset
|
||||
targetPosition.current.set(25, 20, 25);
|
||||
targetPosition.current.set(...defaultPosition);
|
||||
startPosition.current.copy(camera.position);
|
||||
isAnimating.current = true;
|
||||
isResetting.current = true;
|
||||
animationProgress.current = 0;
|
||||
}
|
||||
}, [resetTrigger, camera]); // Only run when resetTrigger changes
|
||||
}, [resetTrigger, camera, defaultPosition]); // Only run when resetTrigger changes
|
||||
|
||||
// Handle focus target changes
|
||||
useEffect(() => {
|
||||
if (focusTarget) {
|
||||
isResetting.current = false; // Cancel reset if target selected
|
||||
|
||||
// Focus on target - use smart rendered position
|
||||
const renderPos = calculateRenderPosition(focusTarget, allBodies);
|
||||
const currentTargetPos = new Vector3(renderPos.x, renderPos.z, renderPos.y);
|
||||
|
|
@ -91,11 +102,8 @@ export function CameraController({ focusTarget, allBodies, onAnimationComplete,
|
|||
|
||||
} else {
|
||||
// Target became null (e.g. info window closed)
|
||||
// DO NOTHING here to preserve camera position
|
||||
// Reset is handled by the other useEffect
|
||||
|
||||
// Just stop any ongoing animation
|
||||
if (isAnimating.current) {
|
||||
// Only stop animation if we are NOT in the middle of a reset
|
||||
if (!isResetting.current && isAnimating.current) {
|
||||
isAnimating.current = false;
|
||||
animationProgress.current = 0;
|
||||
}
|
||||
|
|
@ -110,6 +118,7 @@ export function CameraController({ focusTarget, allBodies, onAnimationComplete,
|
|||
if (animationProgress.current >= 1) {
|
||||
animationProgress.current = 1;
|
||||
isAnimating.current = false;
|
||||
isResetting.current = false; // Animation done
|
||||
if (onAnimationComplete) onAnimationComplete();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { calculateRenderPosition, getOffsetDescription } from '../utils/renderPo
|
|||
import { fetchBodyResources } from '../utils/api';
|
||||
import { getCelestialSize } from '../config/celestialSizes';
|
||||
import { createLabelTexture } from '../utils/labelTexture';
|
||||
import { useSystemSetting } from '../hooks/useSystemSetting';
|
||||
|
||||
interface CelestialBodyProps {
|
||||
body: CelestialBodyType;
|
||||
|
|
@ -86,7 +87,7 @@ function PlanetaryRings({ texturePath, planetRadius }: { texturePath?: string |
|
|||
}
|
||||
|
||||
// Planet component with texture
|
||||
function Planet({ body, size, emissive, emissiveIntensity, allBodies, isSelected = false, onBodySelect }: {
|
||||
function Planet({ body, size, emissive, emissiveIntensity, allBodies, isSelected = false, onBodySelect, typeConfigs }: {
|
||||
body: CelestialBodyType;
|
||||
size: number;
|
||||
emissive: string;
|
||||
|
|
@ -94,6 +95,7 @@ function Planet({ body, size, emissive, emissiveIntensity, allBodies, isSelected
|
|||
allBodies: CelestialBodyType[];
|
||||
isSelected?: boolean;
|
||||
onBodySelect?: (body: CelestialBodyType) => void;
|
||||
typeConfigs?: any;
|
||||
}) {
|
||||
const meshRef = useRef<Mesh>(null);
|
||||
const position = body.positions[0];
|
||||
|
|
@ -102,8 +104,8 @@ function Planet({ body, size, emissive, emissiveIntensity, allBodies, isSelected
|
|||
|
||||
// Use smart render position calculation
|
||||
const renderPosition = useMemo(() => {
|
||||
return calculateRenderPosition(body, allBodies);
|
||||
}, [position.x, position.y, position.z, body, allBodies]);
|
||||
return calculateRenderPosition(body, allBodies, typeConfigs);
|
||||
}, [position.x, position.y, position.z, body, allBodies, typeConfigs]);
|
||||
|
||||
const scaledPos = { x: renderPosition.x, y: renderPosition.y, z: renderPosition.z };
|
||||
|
||||
|
|
@ -488,6 +490,8 @@ function PlanetMesh({ body, size, emissive, emissiveIntensity, scaledPos, textur
|
|||
export function CelestialBody({ body, allBodies, isSelected = false, onBodySelect }: CelestialBodyProps) {
|
||||
// Get the current position (use the first position for now)
|
||||
const position = body.positions[0];
|
||||
const [typeConfigs] = useSystemSetting('celestial_type_configs', null);
|
||||
|
||||
if (!position) return null;
|
||||
|
||||
// Skip probes - they will use 3D models
|
||||
|
|
@ -499,7 +503,7 @@ export function CelestialBody({ body, allBodies, isSelected = false, onBodySelec
|
|||
const appearance = useMemo(() => {
|
||||
if (body.type === 'star') {
|
||||
return {
|
||||
size: 0.4, // Sun size
|
||||
size: getCelestialSize(body, undefined, typeConfigs), // Sun size with real radius or default
|
||||
emissive: '#FDB813',
|
||||
emissiveIntensity: 1.5,
|
||||
};
|
||||
|
|
@ -508,7 +512,7 @@ export function CelestialBody({ body, allBodies, isSelected = false, onBodySelec
|
|||
// Comet - bright core with glow
|
||||
if (body.type === 'comet') {
|
||||
return {
|
||||
size: getCelestialSize(body.name, body.type),
|
||||
size: getCelestialSize(body, undefined, typeConfigs),
|
||||
emissive: '#000000', // Revert to no special emissive color for texture
|
||||
emissiveIntensity: 0, // Revert to no special emissive intensity
|
||||
};
|
||||
|
|
@ -517,7 +521,7 @@ export function CelestialBody({ body, allBodies, isSelected = false, onBodySelec
|
|||
// Satellite (natural moons) - small size with slight glow for visibility
|
||||
if (body.type === 'satellite') {
|
||||
return {
|
||||
size: getCelestialSize(body.name, body.type),
|
||||
size: getCelestialSize(body, undefined, typeConfigs),
|
||||
emissive: '#888888', // Slight glow to make it visible
|
||||
emissiveIntensity: 0.4,
|
||||
};
|
||||
|
|
@ -525,11 +529,11 @@ export function CelestialBody({ body, allBodies, isSelected = false, onBodySelec
|
|||
|
||||
// Planet and dwarf planet sizes
|
||||
return {
|
||||
size: getCelestialSize(body.name, body.type),
|
||||
size: getCelestialSize(body, undefined, typeConfigs),
|
||||
emissive: '#000000',
|
||||
emissiveIntensity: 0,
|
||||
};
|
||||
}, [body.name, body.type]);
|
||||
}, [body, typeConfigs]);
|
||||
|
||||
return (
|
||||
<Planet
|
||||
|
|
@ -540,6 +544,7 @@ export function CelestialBody({ body, allBodies, isSelected = false, onBodySelec
|
|||
allBodies={allBodies}
|
||||
isSelected={isSelected}
|
||||
onBodySelect={onBodySelect}
|
||||
typeConfigs={typeConfigs}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import type { CelestialBody } from '../types';
|
|||
import { calculateRenderPosition, getOffsetDescription } from '../utils/renderPosition';
|
||||
import { fetchBodyResources } from '../utils/api';
|
||||
import { createLabelTexture } from '../utils/labelTexture';
|
||||
import { useSystemSetting } from '../hooks/useSystemSetting';
|
||||
|
||||
interface ProbeProps {
|
||||
body: CelestialBody;
|
||||
|
|
@ -19,7 +20,7 @@ interface ProbeProps {
|
|||
}
|
||||
|
||||
// Separate component for each probe type to properly use hooks
|
||||
function ProbeModel({ body, modelPath, allBodies, isSelected = false, onError, resourceScale = 1.0, onBodySelect }: {
|
||||
function ProbeModel({ body, modelPath, allBodies, isSelected = false, onError, resourceScale = 1.0, onBodySelect, typeConfigs }: {
|
||||
body: CelestialBody;
|
||||
modelPath: string;
|
||||
allBodies: CelestialBody[];
|
||||
|
|
@ -27,14 +28,15 @@ function ProbeModel({ body, modelPath, allBodies, isSelected = false, onError, r
|
|||
onError: () => void;
|
||||
resourceScale?: number;
|
||||
onBodySelect?: (body: CelestialBody) => void;
|
||||
typeConfigs?: any;
|
||||
}) {
|
||||
const groupRef = useRef<Group>(null);
|
||||
const position = body.positions[0];
|
||||
|
||||
// 1. Hook: Render Position
|
||||
const renderPosition = useMemo(() => {
|
||||
return calculateRenderPosition(body, allBodies);
|
||||
}, [position.x, position.y, position.z, body, allBodies]);
|
||||
return calculateRenderPosition(body, allBodies, typeConfigs);
|
||||
}, [position.x, position.y, position.z, body, allBodies, typeConfigs]);
|
||||
|
||||
const scaledPos = { x: renderPosition.x, y: renderPosition.y, z: renderPosition.z };
|
||||
|
||||
|
|
@ -176,18 +178,19 @@ function ProbeModel({ body, modelPath, allBodies, isSelected = false, onError, r
|
|||
}
|
||||
|
||||
// Fallback component when model is not available
|
||||
function ProbeFallback({ body, allBodies, isSelected = false, onBodySelect }: {
|
||||
function ProbeFallback({ body, allBodies, isSelected = false, onBodySelect, typeConfigs }: {
|
||||
body: CelestialBody;
|
||||
allBodies: CelestialBody[];
|
||||
isSelected?: boolean;
|
||||
onBodySelect?: (body: CelestialBody) => void;
|
||||
typeConfigs?: any;
|
||||
}) {
|
||||
const position = body.positions[0];
|
||||
|
||||
// Use smart render position calculation
|
||||
const renderPosition = useMemo(() => {
|
||||
return calculateRenderPosition(body, allBodies);
|
||||
}, [position.x, position.y, position.z, body, allBodies]);
|
||||
return calculateRenderPosition(body, allBodies, typeConfigs);
|
||||
}, [position.x, position.y, position.z, body, allBodies, typeConfigs]);
|
||||
|
||||
const scaledPos = { x: renderPosition.x, y: renderPosition.y, z: renderPosition.z };
|
||||
|
||||
|
|
@ -257,6 +260,7 @@ export function Probe({ body, allBodies, isSelected = false, onBodySelect }: Pro
|
|||
const [modelPath, setModelPath] = useState<string | null | undefined>(undefined);
|
||||
const [loadError, setLoadError] = useState<boolean>(false);
|
||||
const [resourceScale, setResourceScale] = useState<number>(1.0);
|
||||
const [typeConfigs] = useSystemSetting('celestial_type_configs', null);
|
||||
|
||||
// Fetch model from backend API
|
||||
useEffect(() => {
|
||||
|
|
@ -314,8 +318,15 @@ export function Probe({ body, allBodies, isSelected = false, onBodySelect }: Pro
|
|||
onError={() => {
|
||||
setLoadError(true);
|
||||
}}
|
||||
typeConfigs={typeConfigs}
|
||||
/>;
|
||||
}
|
||||
|
||||
return <ProbeFallback body={body} allBodies={allBodies} isSelected={isSelected} onBodySelect={onBodySelect} />;
|
||||
return <ProbeFallback
|
||||
body={body}
|
||||
allBodies={allBodies}
|
||||
isSelected={isSelected}
|
||||
onBodySelect={onBodySelect}
|
||||
typeConfigs={typeConfigs}
|
||||
/>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { scalePosition } from '../utils/scaleDistance';
|
|||
import { calculateRenderPosition } from '../utils/renderPosition';
|
||||
import type { CelestialBody as CelestialBodyType, Position } from '../types';
|
||||
import type { ToastContextValue } from '../contexts/ToastContext'; // Import ToastContextValue
|
||||
import { useSystemSetting } from '../hooks/useSystemSetting';
|
||||
|
||||
interface SceneProps {
|
||||
bodies: CelestialBodyType[];
|
||||
|
|
@ -32,6 +33,25 @@ interface SceneProps {
|
|||
}
|
||||
|
||||
export function Scene({ bodies, selectedBody, trajectoryPositions = [], showOrbits = true, onBodySelect, resetTrigger = 0, toast, onViewDetails }: SceneProps) {
|
||||
const [typeConfigs] = useSystemSetting('celestial_type_configs', null);
|
||||
const [rawCameraPos] = useSystemSetting('default_camera_position', [10, 8, 10]);
|
||||
|
||||
// Parse camera position if it's a string (from system settings)
|
||||
const defaultCameraPos = useMemo(() => {
|
||||
if (typeof rawCameraPos === 'string') {
|
||||
try {
|
||||
// Handle strings like "[10, 8, 10]"
|
||||
const parsed = JSON.parse(rawCameraPos);
|
||||
if (Array.isArray(parsed) && parsed.length === 3) {
|
||||
return parsed as [number, number, number];
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse default_camera_position:', e);
|
||||
}
|
||||
}
|
||||
return rawCameraPos as [number, number, number];
|
||||
}, [rawCameraPos]);
|
||||
|
||||
// Debug: log what Scene receives
|
||||
useEffect(() => {
|
||||
console.log('[Scene] Received bodies:', {
|
||||
|
|
@ -81,17 +101,17 @@ export function Scene({ bodies, selectedBody, trajectoryPositions = [], showOrbi
|
|||
|
||||
// We need to use the EXACT same logic as CelestialBody/Probe components
|
||||
// to ensure the label sticks to the object
|
||||
const renderPos = calculateRenderPosition(selectedBody, bodies);
|
||||
const renderPos = calculateRenderPosition(selectedBody, bodies, typeConfigs);
|
||||
|
||||
// Convert to Three.js coordinates (x, z, y)
|
||||
return [renderPos.x, renderPos.z, renderPos.y] as [number, number, number];
|
||||
}, [selectedBody, bodies]);
|
||||
}, [selectedBody, bodies, typeConfigs]);
|
||||
|
||||
return (
|
||||
<div id="cosmo-scene-container" className="w-full h-full bg-black">
|
||||
<Canvas
|
||||
camera={{
|
||||
position: [25, 20, 25], // Closer view to make solar system appear larger
|
||||
position: defaultCameraPos, // Dynamic default position
|
||||
fov: 60, // Slightly narrower FOV for less distortion
|
||||
far: 20000, // Increased far plane for distant stars and constellations
|
||||
}}
|
||||
|
|
@ -110,6 +130,7 @@ export function Scene({ bodies, selectedBody, trajectoryPositions = [], showOrbi
|
|||
focusTarget={selectedBody}
|
||||
allBodies={bodies}
|
||||
resetTrigger={resetTrigger}
|
||||
defaultPosition={defaultCameraPos}
|
||||
/>
|
||||
|
||||
{/* Increase ambient light to see textures better */}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,3 @@
|
|||
/**
|
||||
* Celestial body rendering sizes configuration
|
||||
* Shared across components for consistent sizing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Planet rendering sizes (radius in scene units)
|
||||
*/
|
||||
/**
|
||||
* Celestial body rendering sizes configuration
|
||||
* Shared across components for consistent sizing
|
||||
|
|
@ -28,9 +20,42 @@ export const TYPE_SIZES: Record<string, number> = {
|
|||
};
|
||||
|
||||
/**
|
||||
* Get the rendering size for a celestial body by type only
|
||||
* Get the rendering size for a celestial body
|
||||
* Supports dynamic scaling via system config and real radius
|
||||
*
|
||||
* @param bodyOrName CelestialBody object OR name string (legacy)
|
||||
* @param type Body type string (required if bodyOrName is string)
|
||||
* @param typeConfig System configuration object (optional)
|
||||
*/
|
||||
export function getCelestialSize(name: string, type: string): number {
|
||||
return TYPE_SIZES[type] || TYPE_SIZES.default;
|
||||
}
|
||||
export function getCelestialSize(bodyOrName: any, type?: string, typeConfig?: any): number {
|
||||
let bodyType = type;
|
||||
let realRadius = undefined;
|
||||
|
||||
// Handle object input (preferred)
|
||||
if (typeof bodyOrName === 'object' && bodyOrName !== null) {
|
||||
bodyType = bodyOrName.type;
|
||||
realRadius = bodyOrName.extra_data?.real_radius;
|
||||
}
|
||||
|
||||
// Ensure type is a string
|
||||
const safeType = bodyType || 'default';
|
||||
|
||||
// 1. Try system configuration first
|
||||
if (typeConfig && typeConfig[safeType]) {
|
||||
const config = typeConfig[safeType];
|
||||
|
||||
// If we have a real radius and a ratio, calculate exact size
|
||||
if (realRadius && config.ratio) {
|
||||
const finalSize = realRadius * config.ratio;
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
// Fallback to configured default for this type
|
||||
if (config.default) {
|
||||
return config.default;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback to hardcoded type sizes
|
||||
return TYPE_SIZES[safeType] || TYPE_SIZES.default;
|
||||
}
|
||||
|
|
@ -214,6 +214,7 @@ export function CelestialBodies() {
|
|||
|
||||
// Edit handler
|
||||
const handleEdit = async (record: CelestialBody) => {
|
||||
form.resetFields(); // Clear previous form state
|
||||
setEditingRecord(record);
|
||||
|
||||
// Parse extra_data if it's a string (from backend JSON field)
|
||||
|
|
@ -637,6 +638,38 @@ export function CelestialBodies() {
|
|||
<Input.TextArea rows={2} />
|
||||
</Form.Item>
|
||||
|
||||
{/* Physical Properties for Star, Planet, Dwarf Planet, Satellite */}
|
||||
<Form.Item noStyle shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.type !== currentValues.type
|
||||
}>
|
||||
{({ getFieldValue }) => {
|
||||
const bodyType = getFieldValue('type');
|
||||
if (!['star', 'planet', 'dwarf_planet', 'satellite'].includes(bodyType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name={['extra_data', 'real_radius']}
|
||||
label="真实半径 (km)"
|
||||
tooltip="天体的物理半径,用于精确计算显示比例。若不填则使用类型默认值。"
|
||||
>
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
min={0}
|
||||
placeholder="例如:6371 (地球)"
|
||||
formatter={value => `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
|
||||
parser={value => value!.replace(/\$\s?|(,*)/g, '')}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* Orbit parameters and info for planets and dwarf planets */}
|
||||
<Form.Item noStyle shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.type !== currentValues.type || prevValues.orbit_info !== currentValues.orbit_info
|
||||
|
|
|
|||
|
|
@ -79,9 +79,15 @@ export function SystemSettings() {
|
|||
// Edit handler
|
||||
const handleEdit = (record: SystemSetting) => {
|
||||
setEditingRecord(record);
|
||||
|
||||
let formValue = record.value;
|
||||
if (record.value_type === 'json' && typeof record.value === 'object') {
|
||||
formValue = JSON.stringify(record.value, null, 2);
|
||||
}
|
||||
|
||||
form.setFieldsValue({
|
||||
key: record.key,
|
||||
value: record.value,
|
||||
value: formValue,
|
||||
value_type: record.value_type,
|
||||
category: record.category,
|
||||
label: record.label,
|
||||
|
|
@ -107,6 +113,16 @@ export function SystemSettings() {
|
|||
try {
|
||||
const values = await form.validateFields();
|
||||
|
||||
// Parse JSON if needed
|
||||
if (values.value_type === 'json' && typeof values.value === 'string') {
|
||||
try {
|
||||
values.value = JSON.parse(values.value);
|
||||
} catch (e) {
|
||||
toast.error('JSON 格式错误');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (editingRecord) {
|
||||
// Update
|
||||
await request.put(`/system/settings/${editingRecord.key}`, values);
|
||||
|
|
@ -190,6 +206,21 @@ export function SystemSettings() {
|
|||
if (record.value_type === 'bool') {
|
||||
return <Badge status={value ? 'success' : 'default'} text={value ? '是' : '否'} />;
|
||||
}
|
||||
if (record.value_type === 'json' || typeof value === 'object') {
|
||||
return (
|
||||
<div style={{
|
||||
maxWidth: 300,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '12px',
|
||||
color: '#666'
|
||||
}}>
|
||||
{JSON.stringify(value)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <span style={{ fontWeight: 500 }}>{String(value)}</span>;
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@ export interface CelestialBody {
|
|||
description?: string;
|
||||
details?: string; // Markdown content for detailed view
|
||||
is_active?: boolean; // Probe status: true = active, false = inactive
|
||||
extra_data?: {
|
||||
real_radius?: number; // Real radius in km
|
||||
orbit_color?: string; // Orbit line color
|
||||
orbit_period_days?: number; // Orbital period in days
|
||||
[key: string]: any; // Allow additional metadata
|
||||
};
|
||||
// Star system specific data
|
||||
starSystemData?: {
|
||||
system_id: number;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ import type { CelestialBody } from '../types';
|
|||
*/
|
||||
export function calculateRenderPosition(
|
||||
body: CelestialBody,
|
||||
allBodies: CelestialBody[]
|
||||
allBodies: CelestialBody[],
|
||||
typeConfigs?: any
|
||||
): { x: number; y: number; z: number; hasOffset: boolean } {
|
||||
const pos = body.positions[0];
|
||||
if (!pos) {
|
||||
|
|
@ -45,11 +46,11 @@ export function calculateRenderPosition(
|
|||
const nz = dz / dist;
|
||||
|
||||
// Calculate dynamic offset based on parent planet's rendering size
|
||||
// Formula: planetRadius × 1.5 + 0.3 (fixed gap)
|
||||
// Formula: planetRadius × 1.5 + 0.5 (fixed gap)
|
||||
// This ensures larger planets (Jupiter, Saturn) have larger offsets
|
||||
// while smaller planets (Earth, Mars) have smaller offsets
|
||||
const parentSize = getCelestialSize(parent.name, parent.type);
|
||||
const visualOffset = parentSize * 1.5 + 0.3;
|
||||
const parentSize = getCelestialSize(parent, undefined, typeConfigs);
|
||||
const visualOffset = parentSize * 1.5 + 0.5;
|
||||
|
||||
return {
|
||||
x: parentScaled.x + nx * visualOffset,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test script to verify extra_data in API response"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Add backend to path
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.services.db_service import celestial_body_service
|
||||
|
||||
async def test_api_data():
|
||||
"""Simulate API response building"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Get Earth
|
||||
body = await celestial_body_service.get_body_by_id("399", session)
|
||||
|
||||
if not body:
|
||||
print("Earth not found in database")
|
||||
return
|
||||
|
||||
# Build response dict like API does
|
||||
body_dict = {
|
||||
"id": body.id,
|
||||
"name": body.name,
|
||||
"name_zh": body.name_zh,
|
||||
"type": body.type,
|
||||
"description": body.description,
|
||||
"is_active": body.is_active,
|
||||
"extra_data": body.extra_data,
|
||||
"positions": []
|
||||
}
|
||||
|
||||
print("=== Database Body Object ===")
|
||||
print(f"Body ID: {body.id}")
|
||||
print(f"Body Name: {body.name}")
|
||||
print(f"Body Type: {body.type}")
|
||||
print(f"Extra Data Type: {type(body.extra_data)}")
|
||||
print(f"Extra Data: {body.extra_data}")
|
||||
|
||||
print("\n=== API Response Dict ===")
|
||||
print(json.dumps(body_dict, indent=2, default=str))
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_api_data())
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Direct API test - simulate the exact API call"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.services.db_service import celestial_body_service, position_service
|
||||
|
||||
async def simulate_api_call():
|
||||
"""Simulate /celestial/positions endpoint"""
|
||||
async with AsyncSessionLocal() as db:
|
||||
# Get all bodies from database
|
||||
all_bodies = await celestial_body_service.get_all_bodies(db)
|
||||
|
||||
# Filter to only Solar System bodies
|
||||
all_bodies = [b for b in all_bodies if b.system_id == 1]
|
||||
|
||||
# Filter to Earth only
|
||||
all_bodies = [b for b in all_bodies if b.id == "399"]
|
||||
|
||||
bodies_data = []
|
||||
now = datetime.utcnow()
|
||||
|
||||
for body in all_bodies:
|
||||
# Get most recent position
|
||||
recent_positions = await position_service.get_positions(
|
||||
body_id=body.id,
|
||||
start_time=now,
|
||||
end_time=now,
|
||||
session=db
|
||||
)
|
||||
|
||||
if recent_positions and len(recent_positions) > 0:
|
||||
latest_pos = recent_positions[-1]
|
||||
body_dict = {
|
||||
"id": body.id,
|
||||
"name": body.name,
|
||||
"name_zh": body.name_zh,
|
||||
"type": body.type,
|
||||
"description": body.description,
|
||||
"is_active": body.is_active,
|
||||
"extra_data": body.extra_data, # THIS IS THE KEY LINE
|
||||
"positions": [{
|
||||
"time": latest_pos.time.isoformat(),
|
||||
"x": latest_pos.x,
|
||||
"y": latest_pos.y,
|
||||
"z": latest_pos.z,
|
||||
}]
|
||||
}
|
||||
bodies_data.append(body_dict)
|
||||
|
||||
print("=== Simulated API Response for Earth ===")
|
||||
print(f"ID: {body_dict['id']}")
|
||||
print(f"Name: {body_dict['name']}")
|
||||
print(f"Type: {body_dict['type']}")
|
||||
print(f"Extra Data: {body_dict['extra_data']}")
|
||||
print(f"Extra Data Type: {type(body_dict['extra_data'])}")
|
||||
if body_dict['extra_data']:
|
||||
print(f"Real Radius: {body_dict['extra_data'].get('real_radius')}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(simulate_api_call())
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Update celestial_type_configs to use unified ratio"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.services.system_settings_service import system_settings_service
|
||||
|
||||
# Unified ratio for all types
|
||||
# This ensures consistent scaling based on real physical size
|
||||
UNIFIED_RATIO = 0.00008
|
||||
|
||||
async def update_unified_ratio():
|
||||
"""Update all type ratios to use unified value"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Get current config
|
||||
current_config = await system_settings_service.get_setting_value('celestial_type_configs', session)
|
||||
|
||||
print("=" * 70)
|
||||
print("CURRENT celestial_type_configs:")
|
||||
print("=" * 70)
|
||||
print(json.dumps(current_config, indent=2))
|
||||
|
||||
# Update all ratios to unified value
|
||||
updated_config = {}
|
||||
for body_type, config in current_config.items():
|
||||
updated_config[body_type] = {
|
||||
'ratio': UNIFIED_RATIO,
|
||||
'default': config.get('default', 0.5) # Keep existing defaults
|
||||
}
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print(f"UPDATED celestial_type_configs (unified ratio = {UNIFIED_RATIO}):")
|
||||
print("=" * 70)
|
||||
print(json.dumps(updated_config, indent=2))
|
||||
|
||||
# Save updated config
|
||||
await system_settings_service.update_setting(
|
||||
'celestial_type_configs',
|
||||
updated_config,
|
||||
session
|
||||
)
|
||||
|
||||
await session.commit()
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("✅ All type ratios unified successfully!")
|
||||
print("=" * 70)
|
||||
print("\nNOW ALL BODIES WILL USE:")
|
||||
print(f" Display Size = real_radius (km) × {UNIFIED_RATIO}")
|
||||
print("\nThis ensures:")
|
||||
print(" • Consistent scaling across all body types")
|
||||
print(" • Jupiter is correctly 10.97x larger than Earth")
|
||||
print(" • Ceres is correctly 0.07x the size of Earth")
|
||||
print(" • Pluto is correctly 0.19x the size of Earth")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(update_unified_ratio())
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Verify final sizes after updates"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "backend"))
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from sqlalchemy import select
|
||||
from app.models.db.celestial_body import CelestialBody
|
||||
from app.services.system_settings_service import system_settings_service
|
||||
|
||||
async def verify_final_sizes():
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Get updated config
|
||||
configs = await system_settings_service.get_setting_value('celestial_type_configs', session)
|
||||
|
||||
# Solar System body IDs
|
||||
solar_ids = ['10', '199', '299', '399', '499', '599', '699', '799', '899', '999',
|
||||
'2000001', '136199', '136108', '136472']
|
||||
|
||||
stmt = select(CelestialBody).where(CelestialBody.id.in_(solar_ids))
|
||||
result = await session.execute(stmt)
|
||||
bodies = result.scalars().all()
|
||||
|
||||
print("=" * 85)
|
||||
print("FINAL VERIFICATION - All Celestial Body Sizes")
|
||||
print("=" * 85)
|
||||
print(f"{'Name':<20} {'Type':<15} {'Real Radius':<15} {'Ratio':<12} {'Display Size':<12} {'vs Earth'}")
|
||||
print("-" * 85)
|
||||
|
||||
earth_size = None
|
||||
sizes = []
|
||||
|
||||
for body in sorted(bodies, key=lambda b: b.extra_data.get('real_radius', 0) if b.extra_data else 0, reverse=True):
|
||||
if body.extra_data and body.extra_data.get('real_radius'):
|
||||
real_radius = body.extra_data['real_radius']
|
||||
ratio = configs[body.type]['ratio']
|
||||
display_size = real_radius * ratio
|
||||
|
||||
sizes.append((body.name, body.type, real_radius, ratio, display_size))
|
||||
|
||||
if body.name == 'Earth':
|
||||
earth_size = display_size
|
||||
|
||||
# Print with Earth comparison
|
||||
for name, btype, real_radius, ratio, display_size in sizes:
|
||||
vs_earth = f"{display_size / earth_size:.2f}x" if earth_size else "N/A"
|
||||
print(f"{name:<20} {btype:<15} {real_radius:>8.0f} km {ratio:.6f} {display_size:>10.4f} {vs_earth:>7}")
|
||||
|
||||
print("\n" + "=" * 85)
|
||||
print("✅ VERIFICATION COMPLETE")
|
||||
print("=" * 85)
|
||||
print("All bodies now use:")
|
||||
print(f" • Unified ratio: {configs['planet']['ratio']}")
|
||||
print(f" • Display Size = real_radius × {configs['planet']['ratio']}")
|
||||
print("\nSize relationships:")
|
||||
print(f" • Jupiter = {sizes[0][4] / earth_size:.2f}x Earth (should be ~11x)")
|
||||
print(f" • Saturn = {sizes[1][4] / earth_size:.2f}x Earth (should be ~9x)")
|
||||
print(f" • Pluto = {[s for s in sizes if s[0] == 'Pluto'][0][4] / earth_size:.2f}x Earth (should be ~0.19x)")
|
||||
print(f" • Ceres = {[s for s in sizes if s[0] == 'Ceres'][0][4] / earth_size:.2f}x Earth (should be ~0.07x)")
|
||||
print("\n🎉 All sizes are now consistent and physically accurate!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(verify_final_sizes())
|
||||
Loading…
Reference in New Issue