diff --git a/README.md b/README.md index 12c91a0..7874b7f 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ SOLSYS/ │ ├── tabbys_star_lightcurve.csv # Downsampled Kepler LC for Tabby's Star inset │ └── textures/ # Blender body maps (earth/…; moons/asteroids later) │ ├── README.md # Attribution + pack layout -│ └── bodies/earth/{color,clouds}.png # NASA Blue Marble + clouds +│ └── bodies/{earth,moon}/… # NASA Blue Marble / LROC packs │ ├── animate/ # MAIN PRODUCT — GIF animations │ ├── __init__.py @@ -67,7 +67,7 @@ SOLSYS/ │ └── blender/ # Blender close-up pipeline (catalog → JSON → bpy) │ ├── README.md # Pipeline docs + CLI examples │ ├── body_scene.py # Versioned body-scene schema + PlanetCatalog build -│ ├── export_body.py # Write output/animate/blender/*_body_scene.json +│ ├── export_body.py # Write blender/{planets,moons}//*.json │ ├── load_body.py # Blender ingest (stdlib + optional bpy) │ ├── body_appearance.py # Shared texture packs (planets/moons/asteroids) │ ├── flyby_camera.py # Body-centered close-up camera path @@ -116,7 +116,7 @@ SOLSYS/ │ ├── trappist_1/ # trappist_1_planets_{light,dark}.gif │ ├── tabbys_star/ # tabbys_star_dust_{light,dark}.gif │ ├── interstellar_objects/ # {oumuamua,borisov,atlas}_{side,oblique}_{light,dark}.gif -│ └── blender/ # body JSON + earth_flyby_{light,dark}.gif +│ └── blender/{planets,moons}// # body JSON + *_flyby_{light,dark}.gif ├── 2d/ # Static top-down zoom JPGs (*_{light,dark}.jpg) ├── 3d/ # Static perspective zoom JPGs (*_{light,dark}.jpg) └── neighborhood/ # interstellar_neighborhood_*ly.jpg @@ -163,7 +163,13 @@ solsys.motion → moving asteroid fields for animation frames | Light | Dark | |-------|------| -| ![Earth close-up light](https://github.com/ThomasAFink/SOLSYS/blob/main/output/animate/blender/earth_flyby_light.gif?raw=true) | ![Earth close-up dark](https://github.com/ThomasAFink/SOLSYS/blob/main/output/animate/blender/earth_flyby_dark.gif?raw=true) | +| ![Earth close-up light](https://github.com/ThomasAFink/SOLSYS/blob/main/output/animate/blender/planets/earth/earth_flyby_light.gif?raw=true) | ![Earth close-up dark](https://github.com/ThomasAFink/SOLSYS/blob/main/output/animate/blender/planets/earth/earth_flyby_dark.gif?raw=true) | + +**Moon Blender close-up** + +| Light | Dark | +|-------|------| +| ![Moon close-up light](https://github.com/ThomasAFink/SOLSYS/blob/main/output/animate/blender/moons/moon/moon_flyby_light.gif?raw=true) | ![Moon close-up dark](https://github.com/ThomasAFink/SOLSYS/blob/main/output/animate/blender/moons/moon/moon_flyby_dark.gif?raw=true) | ### Static zoom views (side) @@ -308,11 +314,12 @@ Or render by product: .venv/bin/python render.py static --dimension 2d .venv/bin/python render.py neighborhood --ly 10 .venv/bin/python render.py blender --body Earth +.venv/bin/python render.py blender --body Moon --flyby # Moon close-up GIFs .venv/bin/python render.py blender --body Earth --load # optional debug ingest .venv/bin/python render.py blender --body Earth --flyby # light/dark close-up GIFs ``` -Blender close-ups: `render.py blender` exports Keplerian body-scene JSON (`#11`). `render.py blender --flyby` renders a body-centered close-up via EEVEE (`#12`/`#36`) to `output/animate/blender/_flyby_{light,dark}.gif`. Earth uses a NASA Blue Marble color map from `data/textures/bodies/`; moons/asteroids can share the same pack layout later. +Blender close-ups: `render.py blender` exports Keplerian body-scene JSON (`#11`). `render.py blender --flyby` renders a body-centered close-up via EEVEE (`#12`/`#36`/`#41`) to `output/animate/blender/{planets,moons}//` (`*_flyby_{light,dark}.gif`). Texture packs live under `data/textures/bodies/`. Alpha Centauri (issue #1) is one `system_id` covering A, B, and Proxima. Animations render to `output/animate/alpha_centauri/`: @@ -324,10 +331,14 @@ Sol → Alpha Centauri cinematic (issue #10) flies from our solar system to the - `sol_centauri_cinematic_{light,dark}.gif` — Sol → AB → wide → Proxima planets -Earth Blender close-up (issues #12 / #36) is a body-centered EEVEE view of Earth with NASA Blue Marble texture (light/dark). Render with `render.py blender --body Earth --flyby` to `output/animate/blender/`: +Earth Blender close-up (issues #12 / #36) is a body-centered EEVEE view of Earth with NASA Blue Marble texture (light/dark). Render with `render.py blender --body Earth --flyby` to `output/animate/blender/planets/earth/`: - `earth_flyby_{light,dark}.gif` — textured sphere, clouds, thin atmosphere limb, elevated turntable camera + spin +Moon Blender close-up (issue #41) uses the shared pack path with an LRO LROC color map (airless — no atmosphere/clouds) under `output/animate/blender/moons/moon/`: + +- `moon_flyby_{light,dark}.gif` — textured lunar sphere, elevated turntable camera + spin + Barnard's Star (issue #15) is a nearby M dwarf (~6 ly) with four confirmed sub-Earth planets. Animations render via `exoplanet_system` to `output/animate/barnards_star/`: - `barnards_star_planets_{light,dark}.gif` — compact planets d, b, c, e @@ -361,4 +372,5 @@ CLI: `render.py animate --system interstellar` (all) or `--object oumuamua|boris ## Roadmap - Additional star systems via `SystemCatalog` / `data/systems.csv` (`exoplanet_system.py` for planet disks; dedicated scenes for dust / other phenomena) -- More Blender close-ups (moons, asteroids, additional planets) via shared `data/textures/bodies/` packs +- More Blender close-ups (additional moons, asteroids, planets) via shared `data/textures/bodies/` packs +- Splice Earth/Moon Blender opens into the Sol→α Cen cinematic (#42) diff --git a/animate/scenes/blender/README.md b/animate/scenes/blender/README.md index b763fce..fd3ea15 100644 --- a/animate/scenes/blender/README.md +++ b/animate/scenes/blender/README.md @@ -8,8 +8,8 @@ Pipeline for Blender-based planet flybys / zoom-ins that plug into the SOLSYS an | Stage | Module | Runs in | Role | |-------|--------|---------|------| -| 1. Build | `body_scene.py` | SOLSYS venv | Load one `PlanetCatalog` body; sample Keplerian positions into `solsys.blender_body_scene/v1` | -| 2. Export | `export_body.py` | SOLSYS venv | Write `output/animate/blender/_body_scene.json` | +| 1. Build | `body_scene.py` | SOLSYS venv | Load one PlanetCatalog / MoonCatalog body into `solsys.blender_body_scene/v1` | +| 2. Export | `export_body.py` | SOLSYS venv | Write `output/animate/blender/{planets,moons}//*_body_scene.json` | | 3. Ingest | `load_body.py` | Blender (`bpy`) or host dry-run | Debug UV sphere + orbit keyframes (**stdlib-only**) | | 4. Appearance | `body_appearance.py` | SOLSYS venv | Shared texture packs under `data/textures/bodies//` (planets/moons/asteroids) | | 5. Camera | `flyby_camera.py` | SOLSYS venv | Body-centered elevated turntable orbit + spin samples | @@ -36,20 +36,25 @@ Render the first polished Earth flyby (light + dark GIFs; needs `blender` on `PA .venv/bin/python render.py blender --body Earth --flyby --theme dark ``` -Outputs: +Outputs (organized by kind): -- `output/animate/blender/earth_flyby_light.gif` -- `output/animate/blender/earth_flyby_dark.gif` -- `output/animate/blender/earth_body_scene.json` (catalog export still written) -- `output/animate/blender/earth_flyby_{light,dark}_job.json` (last close-up jobs) +```text +output/animate/blender/ +├── planets/earth/ # *_body_scene.json, *_flyby_{light,dark}.gif (+ jobs) +└── moons/moon/ +``` + +Earth uses NASA Blue Marble color + cloud maps under `data/textures/bodies/earth/` (see `data/textures/README.md`) plus a shared fresnel **atmosphere** shell from `BodyAtmosphere`. Moon uses an LRO LROC color pack under `data/textures/bodies/moon/` (airless). Future Jupiter / asteroid packs drop into the same folder layout and register in `body_appearance.py` (clouds/atmosphere optional per body). -Earth uses NASA Blue Marble color + cloud maps under `data/textures/bodies/earth/` (see `data/textures/README.md`) plus a shared fresnel **atmosphere** shell from `BodyAtmosphere`. Future Moon / Jupiter / asteroid packs drop into the same folder layout and register in `body_appearance.py` (clouds/atmosphere optional per body). +```bash +.venv/bin/python render.py blender --body Moon --flyby +``` ### Debug ingest (orbit view, not the flyby) ```bash blender --python animate/scenes/blender/load_body.py -- \ - output/animate/blender/earth_body_scene.json + output/animate/blender/planets/earth/earth_body_scene.json ``` That also writes `earth_body_scene.blend` (Blender may keep `*.blend1` as a save backup). diff --git a/animate/scenes/blender/body_appearance.py b/animate/scenes/blender/body_appearance.py index ef8225e..ccb5d21 100644 --- a/animate/scenes/blender/body_appearance.py +++ b/animate/scenes/blender/body_appearance.py @@ -103,7 +103,7 @@ def _mapsForBodyId(bodyId: str) -> BodyTextureMaps: ) -# Registry: add Moon / Jupiter / Ceres here as packs land under data/textures/bodies/. +# Registry: add Jupiter / Ceres / other moons here as packs land under data/textures/bodies/. _BODY_APPEARANCES: tuple[BodyAppearance, ...] = ( BodyAppearance( bodyId='earth', @@ -121,9 +121,16 @@ def _mapsForBodyId(bodyId: str) -> BodyTextureMaps: fresnelBlend=0.14, ), ), - # Future examples (no files yet → colorRgba fallback until packs exist): - # BodyAppearance(..., atmosphere=BodyAtmosphere(enabled=True)) # thin-air moons - # BodyAppearance('ceres', 'asteroid', ..., atmosphere=BodyAtmosphere(enabled=False)) + BodyAppearance( + bodyId='moon', + kind='moon', + catalogNames=('Moon',), + textures=_mapsForBodyId('moon'), + # Airless regolith: matte, no atmosphere / clouds. + roughness=0.82, + specular=0.04, + atmosphere=BodyAtmosphere(enabled=False), + ), ) diff --git a/animate/scenes/blender/body_scene.py b/animate/scenes/blender/body_scene.py index 296f670..42f54f5 100644 --- a/animate/scenes/blender/body_scene.py +++ b/animate/scenes/blender/body_scene.py @@ -9,6 +9,7 @@ import numpy as np from solsys.physics.astronomical_constants import AstronomicalConstants +from solsys.physics.catalogs.moon_catalog import MoonCatalog, MoonOrbit from solsys.physics.catalogs.planet_catalog import PlanetCatalog, PlanetOrbit from solsys.physics.orbit_calculator import OrbitCalculator @@ -17,6 +18,8 @@ # Matplotlib / catalog color names → RGBA for Blender materials. _COLOR_RGBA: dict[str, tuple[float, float, float, float]] = { 'gray': (0.55, 0.55, 0.55, 1.0), + 'lightgray': (0.75, 0.75, 0.75, 1.0), + 'darkgray': (0.35, 0.35, 0.35, 1.0), 'yellow': (0.95, 0.85, 0.2, 1.0), 'blue': (0.25, 0.45, 0.95, 1.0), 'red': (0.9, 0.25, 0.2, 1.0), @@ -24,8 +27,13 @@ 'gold': (0.9, 0.75, 0.25, 1.0), 'lightblue': (0.55, 0.8, 0.95, 1.0), 'brown': (0.55, 0.35, 0.2, 1.0), + 'tan': (0.82, 0.71, 0.55, 1.0), + 'wheat': (0.90, 0.85, 0.70, 1.0), + 'silver': (0.75, 0.75, 0.78, 1.0), + 'whitesmoke': (0.90, 0.90, 0.90, 1.0), + 'white': (0.95, 0.95, 0.95, 1.0), + 'gainsboro': (0.86, 0.86, 0.86, 1.0), } - # Physical planet radii are ~1e-5 AU; exaggerate so a UV sphere is visible near the body. _DISPLAY_RADIUS_EXAGGERATION = 800.0 _MIN_DISPLAY_RADIUS_AU = 0.002 @@ -147,6 +155,55 @@ def _bodyFromPlanet(planet: PlanetOrbit, constants: AstronomicalConstants) -> Bo ) +def _bodyFromMoon( + moon: MoonOrbit, + constants: AstronomicalConstants, + moonCatalog: MoonCatalog, +) -> BodySceneBody: + return BodySceneBody( + name=moon.name, + kind='moon', + systemId='sol', + semiMajorAxisAu=moonCatalog.semiMajorAxisAu(moon), + eccentricity=0.0, + inclinationDeg=moon.inclinationDeg, + orbitalPeriodDays=moon.orbitalPeriodDays, + diameterKm=moon.diameterKm, + color=moon.color, + colorRgba=colorRgbaForName(moon.color), + displayRadiusAu=displayRadiusAu(moon.diameterKm, constants), + ) + + +def _orbitKeyframes( + *, + semiMajorAxisAu: float, + eccentricity: float, + inclinationDeg: float, + orbitalPeriodDays: float, + frameCount: int, +) -> tuple[BodyKeyframe, ...]: + calculator = OrbitCalculator() + trueAnomalies = np.linspace(0.0, 2.0 * np.pi, frameCount, endpoint=False) + daysPerFrame = orbitalPeriodDays / frameCount + keyframes: list[BodyKeyframe] = [] + for frame, trueAnomaly in enumerate(trueAnomalies): + positionX, positionY, positionZ = calculator.ellipticalPosition( + semiMajorAxisAu, + eccentricity, + inclinationDeg, + float(trueAnomaly), + ) + keyframes.append( + BodyKeyframe( + frame=frame, + day=frame * daysPerFrame, + positionAu=(float(positionX), float(positionY), float(positionZ)), + ) + ) + return tuple(keyframes) + + def buildPlanetBodyScene( planetName: str = 'Earth', *, @@ -166,30 +223,73 @@ def buildPlanetBodyScene( raise ValueError(f'Unknown planet {planetName!r}. Known: {known}') from error body = _bodyFromPlanet(planet, constants) - calculator = OrbitCalculator() - trueAnomalies = np.linspace(0.0, 2.0 * np.pi, frameCount, endpoint=False) - daysPerFrame = planet.orbitalPeriodDays / frameCount + keyframes = _orbitKeyframes( + semiMajorAxisAu=planet.semiMajorAxisAu, + eccentricity=planet.eccentricity, + inclinationDeg=planet.inclinationDeg, + orbitalPeriodDays=planet.orbitalPeriodDays, + frameCount=frameCount, + ) + cameraHintDistanceAu = max(body.displayRadiusAu * 8.0, 0.05) + return BodyScene( + schema=SCHEMA_ID, + body=body, + keyframes=keyframes, + cameraHintDistanceAu=cameraHintDistanceAu, + ) - keyframes: list[BodyKeyframe] = [] - for frame, trueAnomaly in enumerate(trueAnomalies): - positionX, positionY, positionZ = calculator.ellipticalPosition( - planet.semiMajorAxisAu, - planet.eccentricity, - planet.inclinationDeg, - float(trueAnomaly), - ) - keyframes.append( - BodyKeyframe( - frame=frame, - day=frame * daysPerFrame, - positionAu=(float(positionX), float(positionY), float(positionZ)), - ) - ) +def buildMoonBodyScene( + moonName: str = 'Moon', + *, + frameCount: int = 120, + constants: AstronomicalConstants | None = None, +) -> BodyScene: + """Build a Blender-ingestible scene for one Sol moon from ``MoonCatalog``.""" + if frameCount < 2: + raise ValueError('frameCount must be >= 2') + + constants = constants or AstronomicalConstants() + catalog = MoonCatalog() + try: + moon = catalog.moons[moonName] + except KeyError as error: + known = ', '.join(sorted(catalog.moons)) + raise ValueError(f'Unknown moon {moonName!r}. Known: {known}') from error + + body = _bodyFromMoon(moon, constants, catalog) + keyframes = _orbitKeyframes( + semiMajorAxisAu=body.semiMajorAxisAu, + eccentricity=body.eccentricity, + inclinationDeg=body.inclinationDeg, + orbitalPeriodDays=body.orbitalPeriodDays, + frameCount=frameCount, + ) cameraHintDistanceAu = max(body.displayRadiusAu * 8.0, 0.05) return BodyScene( schema=SCHEMA_ID, body=body, - keyframes=tuple(keyframes), + keyframes=keyframes, cameraHintDistanceAu=cameraHintDistanceAu, ) + + +def buildBodyScene( + bodyName: str = 'Earth', + *, + frameCount: int = 120, + constants: AstronomicalConstants | None = None, +) -> BodyScene: + """Build a body scene from PlanetCatalog or MoonCatalog by name.""" + constants = constants or AstronomicalConstants() + planets = PlanetCatalog(constants).planets + moons = MoonCatalog().moons + if bodyName in planets: + return buildPlanetBodyScene(bodyName, frameCount=frameCount, constants=constants) + if bodyName in moons: + return buildMoonBodyScene(bodyName, frameCount=frameCount, constants=constants) + knownPlanets = ', '.join(sorted(planets)) + knownMoons = ', '.join(sorted(moons)) + raise ValueError( + f'Unknown body {bodyName!r}. Known planets: {knownPlanets}. Known moons: {knownMoons}' + ) diff --git a/animate/scenes/blender/export_body.py b/animate/scenes/blender/export_body.py index 15141dc..3210cb8 100644 --- a/animate/scenes/blender/export_body.py +++ b/animate/scenes/blender/export_body.py @@ -1,13 +1,46 @@ -"""Write Blender body-scene JSON under output/animate/blender/.""" +"""Write Blender body-scene JSON under output/animate/blender/{planets,moons}/…""" from __future__ import annotations from pathlib import Path -from animate.scenes.blender.body_scene import BodyScene, buildPlanetBodyScene +from animate.scenes.blender.body_scene import BodyScene, buildBodyScene, buildPlanetBodyScene DEFAULT_OUTPUT_DIRECTORY = Path('output/animate/blender') +_KIND_DIRECTORY: dict[str, str] = { + 'planet': 'planets', + 'moon': 'moons', + 'asteroid': 'asteroids', + 'dwarf_planet': 'dwarf_planets', +} + + +def bodyStem(bodyName: str) -> str: + return bodyName.lower().replace(' ', '_') + + +def bodyOutputDirectory( + kind: str, + bodyName: str, + *, + root: Path | str = DEFAULT_OUTPUT_DIRECTORY, +) -> Path: + """``output/animate/blender/planets/earth`` / ``…/moons/moon`` (etc.).""" + kindDirectory = _KIND_DIRECTORY.get(kind, 'bodies') + return Path(root) / kindDirectory / bodyStem(bodyName) + + +def exportBodyScene( + bodyName: str = 'Earth', + *, + frameCount: int = 120, + outputDirectory: Path | str = DEFAULT_OUTPUT_DIRECTORY, +) -> Path: + """Export one catalog planet or moon to JSON for Blender ingest.""" + scene = buildBodyScene(bodyName, frameCount=frameCount) + return writeBodyScene(scene, outputDirectory=outputDirectory) + def exportPlanetBodyScene( planetName: str = 'Earth', @@ -25,9 +58,10 @@ def writeBodyScene( *, outputDirectory: Path | str = DEFAULT_OUTPUT_DIRECTORY, ) -> Path: - directory = Path(outputDirectory) + """Write ``_body_scene.json`` under ``{root}/{planets|moons}//``.""" + directory = bodyOutputDirectory(scene.body.kind, scene.body.name, root=outputDirectory) directory.mkdir(parents=True, exist_ok=True) - stem = scene.body.name.lower().replace(' ', '_') + stem = bodyStem(scene.body.name) outputPath = directory / f'{stem}_body_scene.json' outputPath.write_text(scene.toJson(), encoding='utf-8') return outputPath diff --git a/animate/scenes/blender/flyby_scene.py b/animate/scenes/blender/flyby_scene.py index 104fb44..31a1f8a 100644 --- a/animate/scenes/blender/flyby_scene.py +++ b/animate/scenes/blender/flyby_scene.py @@ -12,8 +12,8 @@ from PIL import Image from animate.scenes.blender.body_appearance import appearanceForCatalogName -from animate.scenes.blender.body_scene import buildPlanetBodyScene -from animate.scenes.blender.export_body import DEFAULT_OUTPUT_DIRECTORY, exportPlanetBodyScene +from animate.scenes.blender.body_scene import buildBodyScene +from animate.scenes.blender.export_body import DEFAULT_OUTPUT_DIRECTORY, bodyStem, exportBodyScene from animate.scenes.blender.flyby_camera import buildFlybyCameraPath from animate.scenes.blender.render_flyby import JOB_SCHEMA_ID @@ -27,21 +27,21 @@ def preparePlanetFlybyExport( - planetName: str = 'Earth', + bodyName: str = 'Earth', *, frameCount: int = 120, outputDirectory: Path | str = DEFAULT_OUTPUT_DIRECTORY, ) -> Path: """Export catalog body-scene JSON used as flyby input metadata.""" - return exportPlanetBodyScene( - planetName, + return exportBodyScene( + bodyName, frameCount=frameCount, outputDirectory=outputDirectory, ) def buildFlybyJob( - planetName: str = 'Earth', + bodyName: str = 'Earth', *, theme: Theme = 'dark', frameCount: int = DEFAULT_FLYBY_FRAMES, @@ -49,10 +49,10 @@ def buildFlybyJob( fps: int = DEFAULT_FLYBY_FPS, framesDirectory: Path | str, ) -> dict: - """Build a Blender flyby job dict from PlanetCatalog + camera path.""" + """Build a Blender flyby job dict from PlanetCatalog/MoonCatalog + camera path.""" if theme not in ('light', 'dark'): raise ValueError(f'theme must be light or dark, got {theme!r}') - bodyScene = buildPlanetBodyScene(planetName, frameCount=max(frameCount, 2)) + bodyScene = buildBodyScene(bodyName, frameCount=max(frameCount, 2)) cameraPath = buildFlybyCameraPath(bodyScene.body.displayRadiusAu, frameCount=frameCount) appearance = appearanceForCatalogName(bodyScene.body.name) job: dict = { @@ -138,7 +138,7 @@ def _runBlenderFlybyJob(jobPath: Path) -> None: def renderPlanetFlyby( - planetName: str = 'Earth', + bodyName: str = 'Earth', *, theme: Theme | Literal['all'] = 'all', frameCount: int = DEFAULT_FLYBY_FRAMES, @@ -146,7 +146,7 @@ def renderPlanetFlyby( fps: int = DEFAULT_FLYBY_FPS, outputDirectory: Path | str = DEFAULT_OUTPUT_DIRECTORY, ) -> tuple[Path, ...]: - """Render light/dark planet flyby GIFs via Blender; returns written GIF paths.""" + """Render light/dark body flyby GIFs via Blender; returns written GIF paths.""" themes: tuple[Theme, ...] if theme == 'all': themes = ('light', 'dark') @@ -158,28 +158,29 @@ def renderPlanetFlyby( outputRoot = Path(outputDirectory) outputRoot.mkdir(parents=True, exist_ok=True) # Keep catalog export in the product tree so the #11 pipeline stays exercised. - preparePlanetFlybyExport(planetName, outputDirectory=outputRoot) + # Export lands under blender/{planets|moons}// — GIFs/jobs join it. + bodyDirectory = preparePlanetFlybyExport(bodyName, outputDirectory=outputRoot).parent written: list[Path] = [] - stem = planetName.lower().replace(' ', '_') + stem = bodyStem(bodyName) for themeName in themes: with tempfile.TemporaryDirectory(prefix=f'solsys_flyby_{stem}_{themeName}_') as temporary: framesDirectory = Path(temporary) / 'frames' job = buildFlybyJob( - planetName, + bodyName, theme=themeName, frameCount=frameCount, resolution=resolution, fps=fps, framesDirectory=framesDirectory, ) - jobPath = outputRoot / f'{stem}_flyby_{themeName}_job.json' + jobPath = bodyDirectory / f'{stem}_flyby_{themeName}_job.json' writeFlybyJob(job, jobPath) _runBlenderFlybyJob(jobPath) framePaths = sorted(framesDirectory.glob('frame_*.png')) if not framePaths: raise RuntimeError(f'No frames rendered for theme={themeName}') - gifPath = outputRoot / f'{stem}_flyby_{themeName}.gif' + gifPath = bodyDirectory / f'{stem}_flyby_{themeName}.gif' assembleGifFromPngs(framePaths, gifPath, fps=fps) written.append(gifPath) print(f'Wrote flyby GIF → {gifPath}') diff --git a/animate/scenes/blender/load_body.py b/animate/scenes/blender/load_body.py index f34ee3b..04b4aa9 100644 --- a/animate/scenes/blender/load_body.py +++ b/animate/scenes/blender/load_body.py @@ -4,12 +4,13 @@ 1. Host dry-run (no bpy):: - python animate/scenes/blender/load_body.py output/animate/blender/earth_body_scene.json + python animate/scenes/blender/load_body.py \\ + output/animate/blender/planets/earth/earth_body_scene.json 2. Inside Blender:: blender --background --python animate/scenes/blender/load_body.py -- \\ - output/animate/blender/earth_body_scene.json + output/animate/blender/planets/earth/earth_body_scene.json Keep this module stdlib-only so Blender's bundled Python can import it. """ diff --git a/animate/scenes/blender/render_flyby.py b/animate/scenes/blender/render_flyby.py index 52a4a49..cc38949 100644 --- a/animate/scenes/blender/render_flyby.py +++ b/animate/scenes/blender/render_flyby.py @@ -4,7 +4,7 @@ close-up and writes PNG frames:: blender --background --python animate/scenes/blender/render_flyby.py -- \\ - output/animate/blender/earth_flyby_dark_job.json + output/animate/blender/planets/earth/earth_flyby_dark_job.json """ from __future__ import annotations diff --git a/data/textures/README.md b/data/textures/README.md index 4dd3370..a1d0446 100644 --- a/data/textures/README.md +++ b/data/textures/README.md @@ -10,7 +10,8 @@ data/textures/ ├── earth/ │ ├── color.png # equirectangular color map │ └── clouds.png # cloud layer (RGBA; alpha = coverage) - ├── moon/ # future + ├── moon/ + │ └── color.png # equirectangular color map (airless) ├── jupiter/ # future └── ceres/ # future (asteroids use the same layout) ``` @@ -38,4 +39,12 @@ Atmosphere is not a texture file: enable a fresnel limb-haze shell per body in ` - **Credit:** Matt Eason / Live Cloud Maps; underlying imagery from NASA & NOAA sources - **License:** NASA media generally in the U.S. public domain ([NASA image use policy](https://www.nasa.gov/nasa-brand-center/images-and-media/)) +## Moon + +- **Color:** `bodies/moon/color.png` (2048×1024) + - **Source:** NASA SVS — [CGI Moon Kit / LRO LROC WAC color (SVS 4720)](https://svs.gsfc.nasa.gov/4720) (`lroc_color_2k.jpg`) + - **Credit:** NASA/GSFC Scientific Visualization Studio; LRO / LROC +- **Atmosphere / clouds:** none (airless pack in `body_appearance.py`) +- **License:** NASA media generally in the U.S. public domain ([NASA image use policy](https://www.nasa.gov/nasa-brand-center/images-and-media/)) + Do not replace vendor textures without updating this README attribution. \ No newline at end of file diff --git a/data/textures/bodies/moon/color.png b/data/textures/bodies/moon/color.png new file mode 100644 index 0000000..a95d7b0 Binary files /dev/null and b/data/textures/bodies/moon/color.png differ diff --git a/output/animate/blender/moons/moon/moon_body_scene.json b/output/animate/blender/moons/moon/moon_body_scene.json new file mode 100644 index 0000000..12b2aa0 --- /dev/null +++ b/output/animate/blender/moons/moon/moon_body_scene.json @@ -0,0 +1,1104 @@ +{ + "schema": "solsys.blender_body_scene/v1", + "body": { + "name": "Moon", + "kind": "moon", + "systemId": "sol", + "semiMajorAxisAu": 0.0025695552897999907, + "eccentricity": 0.0, + "inclinationDeg": 0.0, + "orbitalPeriodDays": 27.3, + "diameterKm": 3474, + "color": "lightgray", + "colorRgba": [ + 0.75, + 0.75, + 0.75, + 1.0 + ], + "displayRadiusAu": 0.009288902265104232 + }, + "keyframes": [ + { + "frame": 0, + "day": 0.0, + "positionAu": [ + 0.0025695552897999907, + 0.0, + 0.0 + ] + }, + { + "frame": 1, + "day": 0.2275, + "positionAu": [ + 0.0025660338035791187, + 0.00013448013321079716, + 0.0 + ] + }, + { + "frame": 2, + "day": 0.455, + "positionAu": [ + 0.0025554789970654596, + 0.00026859166572406294, + 0.0 + ] + }, + { + "frame": 3, + "day": 0.6825, + "positionAu": [ + 0.0025379198002500112, + 0.00040196700715115693, + 0.0 + ] + }, + { + "frame": 4, + "day": 0.91, + "positionAu": [ + 0.00251340434167072, + 0.0005342405849520336, + 0.0 + ] + }, + { + "frame": 5, + "day": 1.1375, + "positionAu": [ + 0.002481999816495502, + 0.0006650498464441646, + 0.0 + ] + }, + { + "frame": 6, + "day": 1.365, + "positionAu": [ + 0.0024437923023449626, + 0.0007940362525342401, + 0.0 + ] + }, + { + "frame": 7, + "day": 1.5925, + "positionAu": [ + 0.0023988865233596163, + 0.0009208462604489024, + 0.0 + ] + }, + { + "frame": 8, + "day": 1.82, + "positionAu": [ + 0.0023474055631582985, + 0.001045132292770913, + 0.0 + ] + }, + { + "frame": 9, + "day": 2.0475, + "positionAu": [ + 0.002289490527474524, + 0.0011665536901246938, + 0.0 + ] + }, + { + "frame": 10, + "day": 2.275, + "positionAu": [ + 0.0022253001573954772, + 0.0012847776448999951, + 0.0 + ] + }, + { + "frame": 11, + "day": 2.5025, + "positionAu": [ + 0.002155010394263727, + 0.001399480113454425, + 0.0 + ] + }, + { + "frame": 12, + "day": 2.73, + "positionAu": [ + 0.0020788138974342355, + 0.0015103467042945464, + 0.0 + ] + }, + { + "frame": 13, + "day": 2.9575, + "positionAu": [ + 0.0019969195162084594, + 0.0016170735398011084, + 0.0 + ] + }, + { + "frame": 14, + "day": 3.185, + "positionAu": [ + 0.0019095517173929296, + 0.0017193680891364792, + 0.0 + ] + }, + { + "frame": 15, + "day": 3.4125, + "positionAu": [ + 0.0018169499700513379, + 0.0018169499700513376, + 0.0 + ] + }, + { + "frame": 16, + "day": 3.64, + "positionAu": [ + 0.0017193680891364794, + 0.0019095517173929293, + 0.0 + ] + }, + { + "frame": 17, + "day": 3.8675, + "positionAu": [ + 0.0016170735398011087, + 0.001996919516208459, + 0.0 + ] + }, + { + "frame": 18, + "day": 4.095, + "positionAu": [ + 0.0015103467042945464, + 0.0020788138974342355, + 0.0 + ] + }, + { + "frame": 19, + "day": 4.3225, + "positionAu": [ + 0.0013994801134544251, + 0.0021550103942637264, + 0.0 + ] + }, + { + "frame": 20, + "day": 4.55, + "positionAu": [ + 0.0012847776448999956, + 0.0022253001573954772, + 0.0 + ] + }, + { + "frame": 21, + "day": 4.7775, + "positionAu": [ + 0.001166553690124694, + 0.002289490527474524, + 0.0 + ] + }, + { + "frame": 22, + "day": 5.005, + "positionAu": [ + 0.0010451322927709137, + 0.002347405563158298, + 0.0 + ] + }, + { + "frame": 23, + "day": 5.2325, + "positionAu": [ + 0.0009208462604489028, + 0.0023988865233596163, + 0.0 + ] + }, + { + "frame": 24, + "day": 5.46, + "positionAu": [ + 0.0007940362525342402, + 0.0024437923023449626, + 0.0 + ] + }, + { + "frame": 25, + "day": 5.6875, + "positionAu": [ + 0.0006650498464441651, + 0.002481999816495502, + 0.0 + ] + }, + { + "frame": 26, + "day": 5.915, + "positionAu": [ + 0.0005342405849520339, + 0.0025134043416707194, + 0.0 + ] + }, + { + "frame": 27, + "day": 6.1425, + "positionAu": [ + 0.0004019670071511571, + 0.0025379198002500112, + 0.0 + ] + }, + { + "frame": 28, + "day": 6.37, + "positionAu": [ + 0.00026859166572406354, + 0.0025554789970654596, + 0.0 + ] + }, + { + "frame": 29, + "day": 6.5975, + "positionAu": [ + 0.0001344801332107975, + 0.0025660338035791187, + 0.0 + ] + }, + { + "frame": 30, + "day": 6.825, + "positionAu": [ + 1.5733988304428542e-19, + 0.0025695552897999907, + 0.0 + ] + }, + { + "frame": 31, + "day": 7.0525, + "positionAu": [ + -0.00013448013321079661, + 0.0025660338035791187, + 0.0 + ] + }, + { + "frame": 32, + "day": 7.28, + "positionAu": [ + -0.0002685916657240626, + 0.0025554789970654596, + 0.0 + ] + }, + { + "frame": 33, + "day": 7.5075, + "positionAu": [ + -0.0004019670071511568, + 0.0025379198002500112, + 0.0 + ] + }, + { + "frame": 34, + "day": 7.735, + "positionAu": [ + -0.000534240584952033, + 0.00251340434167072, + 0.0 + ] + }, + { + "frame": 35, + "day": 7.9625, + "positionAu": [ + -0.0006650498464441642, + 0.002481999816495502, + 0.0 + ] + }, + { + "frame": 36, + "day": 8.19, + "positionAu": [ + -0.00079403625253424, + 0.002443792302344963, + 0.0 + ] + }, + { + "frame": 37, + "day": 8.4175, + "positionAu": [ + -0.0009208462604489019, + 0.0023988865233596163, + 0.0 + ] + }, + { + "frame": 38, + "day": 8.645, + "positionAu": [ + -0.0010451322927709128, + 0.0023474055631582985, + 0.0 + ] + }, + { + "frame": 39, + "day": 8.8725, + "positionAu": [ + -0.0011665536901246936, + 0.002289490527474524, + 0.0 + ] + }, + { + "frame": 40, + "day": 9.1, + "positionAu": [ + -0.001284777644899995, + 0.0022253001573954772, + 0.0 + ] + }, + { + "frame": 41, + "day": 9.3275, + "positionAu": [ + -0.0013994801134544243, + 0.0021550103942637273, + 0.0 + ] + }, + { + "frame": 42, + "day": 9.555, + "positionAu": [ + -0.0015103467042945462, + 0.0020788138974342355, + 0.0 + ] + }, + { + "frame": 43, + "day": 9.7825, + "positionAu": [ + -0.001617073539801108, + 0.0019969195162084594, + 0.0 + ] + }, + { + "frame": 44, + "day": 10.01, + "positionAu": [ + -0.0017193680891364786, + 0.0019095517173929298, + 0.0 + ] + }, + { + "frame": 45, + "day": 10.2375, + "positionAu": [ + -0.0018169499700513376, + 0.0018169499700513379, + 0.0 + ] + }, + { + "frame": 46, + "day": 10.465, + "positionAu": [ + -0.0019095517173929287, + 0.00171936808913648, + 0.0 + ] + }, + { + "frame": 47, + "day": 10.6925, + "positionAu": [ + -0.0019969195162084586, + 0.0016170735398011093, + 0.0 + ] + }, + { + "frame": 48, + "day": 10.92, + "positionAu": [ + -0.002078813897434235, + 0.0015103467042945468, + 0.0 + ] + }, + { + "frame": 49, + "day": 11.1475, + "positionAu": [ + -0.0021550103942637264, + 0.0013994801134544258, + 0.0 + ] + }, + { + "frame": 50, + "day": 11.375, + "positionAu": [ + -0.002225300157395477, + 0.0012847776448999962, + 0.0 + ] + }, + { + "frame": 51, + "day": 11.602500000000001, + "positionAu": [ + -0.0022894905274745236, + 0.001166553690124694, + 0.0 + ] + }, + { + "frame": 52, + "day": 11.83, + "positionAu": [ + -0.002347405563158298, + 0.0010451322927709137, + 0.0 + ] + }, + { + "frame": 53, + "day": 12.057500000000001, + "positionAu": [ + -0.002398886523359616, + 0.0009208462604489035, + 0.0 + ] + }, + { + "frame": 54, + "day": 12.285, + "positionAu": [ + -0.0024437923023449626, + 0.0007940362525342403, + 0.0 + ] + }, + { + "frame": 55, + "day": 12.512500000000001, + "positionAu": [ + -0.002481999816495502, + 0.0006650498464441652, + 0.0 + ] + }, + { + "frame": 56, + "day": 12.74, + "positionAu": [ + -0.0025134043416707194, + 0.0005342405849520347, + 0.0 + ] + }, + { + "frame": 57, + "day": 12.967500000000001, + "positionAu": [ + -0.002537919800250011, + 0.00040196700715115725, + 0.0 + ] + }, + { + "frame": 58, + "day": 13.195, + "positionAu": [ + -0.0025554789970654596, + 0.0002685916657240637, + 0.0 + ] + }, + { + "frame": 59, + "day": 13.422500000000001, + "positionAu": [ + -0.0025660338035791187, + 0.00013448013321079824, + 0.0 + ] + }, + { + "frame": 60, + "day": 13.65, + "positionAu": [ + -0.0025695552897999907, + 3.1467976608857084e-19, + 0.0 + ] + }, + { + "frame": 61, + "day": 13.877500000000001, + "positionAu": [ + -0.0025660338035791187, + -0.00013448013321079648, + -0.0 + ] + }, + { + "frame": 62, + "day": 14.105, + "positionAu": [ + -0.0025554789970654596, + -0.0002685916657240619, + -0.0 + ] + }, + { + "frame": 63, + "day": 14.332500000000001, + "positionAu": [ + -0.0025379198002500112, + -0.0004019670071511566, + -0.0 + ] + }, + { + "frame": 64, + "day": 14.56, + "positionAu": [ + -0.00251340434167072, + -0.0005342405849520329, + -0.0 + ] + }, + { + "frame": 65, + "day": 14.7875, + "positionAu": [ + -0.0024819998164955026, + -0.0006650498464441636, + -0.0 + ] + }, + { + "frame": 66, + "day": 15.015, + "positionAu": [ + -0.002443792302344963, + -0.0007940362525342397, + -0.0 + ] + }, + { + "frame": 67, + "day": 15.2425, + "positionAu": [ + -0.0023988865233596163, + -0.0009208462604489017, + -0.0 + ] + }, + { + "frame": 68, + "day": 15.47, + "positionAu": [ + -0.002347405563158299, + -0.0010451322927709122, + -0.0 + ] + }, + { + "frame": 69, + "day": 15.6975, + "positionAu": [ + -0.002289490527474524, + -0.0011665536901246936, + -0.0 + ] + }, + { + "frame": 70, + "day": 15.925, + "positionAu": [ + -0.0022253001573954777, + -0.0012847776448999947, + -0.0 + ] + }, + { + "frame": 71, + "day": 16.1525, + "positionAu": [ + -0.0021550103942637273, + -0.0013994801134544243, + -0.0 + ] + }, + { + "frame": 72, + "day": 16.38, + "positionAu": [ + -0.0020788138974342355, + -0.0015103467042945462, + -0.0 + ] + }, + { + "frame": 73, + "day": 16.6075, + "positionAu": [ + -0.00199691951620846, + -0.001617073539801108, + -0.0 + ] + }, + { + "frame": 74, + "day": 16.835, + "positionAu": [ + -0.0019095517173929302, + -0.0017193680891364786, + -0.0 + ] + }, + { + "frame": 75, + "day": 17.0625, + "positionAu": [ + -0.001816949970051338, + -0.0018169499700513376, + -0.0 + ] + }, + { + "frame": 76, + "day": 17.29, + "positionAu": [ + -0.00171936808913648, + -0.0019095517173929287, + -0.0 + ] + }, + { + "frame": 77, + "day": 17.517500000000002, + "positionAu": [ + -0.0016170735398011095, + -0.0019969195162084586, + -0.0 + ] + }, + { + "frame": 78, + "day": 17.745, + "positionAu": [ + -0.0015103467042945468, + -0.002078813897434235, + -0.0 + ] + }, + { + "frame": 79, + "day": 17.9725, + "positionAu": [ + -0.0013994801134544269, + -0.0021550103942637256, + -0.0 + ] + }, + { + "frame": 80, + "day": 18.2, + "positionAu": [ + -0.0012847776448999964, + -0.0022253001573954764, + -0.0 + ] + }, + { + "frame": 81, + "day": 18.427500000000002, + "positionAu": [ + -0.0011665536901246942, + -0.0022894905274745236, + -0.0 + ] + }, + { + "frame": 82, + "day": 18.655, + "positionAu": [ + -0.001045132292770915, + -0.0023474055631582977, + -0.0 + ] + }, + { + "frame": 83, + "day": 18.8825, + "positionAu": [ + -0.0009208462604489037, + -0.0023988865233596154, + -0.0 + ] + }, + { + "frame": 84, + "day": 19.11, + "positionAu": [ + -0.0007940362525342405, + -0.0024437923023449626, + -0.0 + ] + }, + { + "frame": 85, + "day": 19.337500000000002, + "positionAu": [ + -0.0006650498464441665, + -0.0024819998164955018, + -0.0 + ] + }, + { + "frame": 86, + "day": 19.565, + "positionAu": [ + -0.0005342405849520348, + -0.0025134043416707194, + -0.0 + ] + }, + { + "frame": 87, + "day": 19.7925, + "positionAu": [ + -0.00040196700715115736, + -0.002537919800250011, + -0.0 + ] + }, + { + "frame": 88, + "day": 20.02, + "positionAu": [ + -0.00026859166572406495, + -0.0025554789970654596, + -0.0 + ] + }, + { + "frame": 89, + "day": 20.247500000000002, + "positionAu": [ + -0.0001344801332107984, + -0.0025660338035791187, + -0.0 + ] + }, + { + "frame": 90, + "day": 20.475, + "positionAu": [ + -4.720196491328562e-19, + -0.0025695552897999907, + -0.0 + ] + }, + { + "frame": 91, + "day": 20.7025, + "positionAu": [ + 0.00013448013321079518, + -0.002566033803579119, + -0.0 + ] + }, + { + "frame": 92, + "day": 20.93, + "positionAu": [ + 0.00026859166572406175, + -0.0025554789970654596, + -0.0 + ] + }, + { + "frame": 93, + "day": 21.157500000000002, + "positionAu": [ + 0.00040196700715115644, + -0.0025379198002500112, + -0.0 + ] + }, + { + "frame": 94, + "day": 21.385, + "positionAu": [ + 0.0005342405849520316, + -0.00251340434167072, + -0.0 + ] + }, + { + "frame": 95, + "day": 21.6125, + "positionAu": [ + 0.0006650498464441634, + -0.0024819998164955026, + -0.0 + ] + }, + { + "frame": 96, + "day": 21.84, + "positionAu": [ + 0.0007940362525342396, + -0.002443792302344963, + -0.0 + ] + }, + { + "frame": 97, + "day": 22.0675, + "positionAu": [ + 0.0009208462604489006, + -0.002398886523359617, + -0.0 + ] + }, + { + "frame": 98, + "day": 22.295, + "positionAu": [ + 0.001045132292770912, + -0.002347405563158299, + -0.0 + ] + }, + { + "frame": 99, + "day": 22.5225, + "positionAu": [ + 0.0011665536901246934, + -0.002289490527474524, + -0.0 + ] + }, + { + "frame": 100, + "day": 22.75, + "positionAu": [ + 0.0012847776448999936, + -0.002225300157395478, + -0.0 + ] + }, + { + "frame": 101, + "day": 22.9775, + "positionAu": [ + 0.001399480113454424, + -0.0021550103942637273, + -0.0 + ] + }, + { + "frame": 102, + "day": 23.205000000000002, + "positionAu": [ + 0.001510346704294546, + -0.002078813897434236, + -0.0 + ] + }, + { + "frame": 103, + "day": 23.4325, + "positionAu": [ + 0.001617073539801107, + -0.0019969195162084603, + -0.0 + ] + }, + { + "frame": 104, + "day": 23.66, + "positionAu": [ + 0.0017193680891364783, + -0.0019095517173929302, + -0.0 + ] + }, + { + "frame": 105, + "day": 23.8875, + "positionAu": [ + 0.0018169499700513372, + -0.001816949970051338, + -0.0 + ] + }, + { + "frame": 106, + "day": 24.115000000000002, + "positionAu": [ + 0.0019095517173929278, + -0.001719368089136481, + -0.0 + ] + }, + { + "frame": 107, + "day": 24.3425, + "positionAu": [ + 0.0019969195162084586, + -0.0016170735398011095, + -0.0 + ] + }, + { + "frame": 108, + "day": 24.57, + "positionAu": [ + 0.002078813897434235, + -0.001510346704294547, + -0.0 + ] + }, + { + "frame": 109, + "day": 24.7975, + "positionAu": [ + 0.0021550103942637256, + -0.0013994801134544269, + -0.0 + ] + }, + { + "frame": 110, + "day": 25.025000000000002, + "positionAu": [ + 0.0022253001573954764, + -0.0012847776448999964, + -0.0 + ] + }, + { + "frame": 111, + "day": 25.2525, + "positionAu": [ + 0.0022894905274745236, + -0.0011665536901246942, + -0.0 + ] + }, + { + "frame": 112, + "day": 25.48, + "positionAu": [ + 0.0023474055631582977, + -0.0010451322927709152, + -0.0 + ] + }, + { + "frame": 113, + "day": 25.7075, + "positionAu": [ + 0.0023988865233596154, + -0.0009208462604489038, + -0.0 + ] + }, + { + "frame": 114, + "day": 25.935000000000002, + "positionAu": [ + 0.0024437923023449626, + -0.0007940362525342407, + -0.0 + ] + }, + { + "frame": 115, + "day": 26.1625, + "positionAu": [ + 0.0024819998164955018, + -0.0006650498464441667, + -0.0 + ] + }, + { + "frame": 116, + "day": 26.39, + "positionAu": [ + 0.0025134043416707194, + -0.000534240584952035, + -0.0 + ] + }, + { + "frame": 117, + "day": 26.6175, + "positionAu": [ + 0.002537919800250011, + -0.0004019670071511576, + -0.0 + ] + }, + { + "frame": 118, + "day": 26.845000000000002, + "positionAu": [ + 0.0025554789970654596, + -0.0002685916657240651, + -0.0 + ] + }, + { + "frame": 119, + "day": 27.0725, + "positionAu": [ + 0.0025660338035791187, + -0.00013448013321079854, + -0.0 + ] + } + ], + "cameraHintDistanceAu": 0.07431121812083386 +} diff --git a/output/animate/blender/moons/moon/moon_flyby_dark.gif b/output/animate/blender/moons/moon/moon_flyby_dark.gif new file mode 100644 index 0000000..3abb7e6 Binary files /dev/null and b/output/animate/blender/moons/moon/moon_flyby_dark.gif differ diff --git a/output/animate/blender/moons/moon/moon_flyby_dark_job.json b/output/animate/blender/moons/moon/moon_flyby_dark_job.json new file mode 100644 index 0000000..7e39dfc --- /dev/null +++ b/output/animate/blender/moons/moon/moon_flyby_dark_job.json @@ -0,0 +1,680 @@ +{ + "schema": "solsys.blender_flyby_job/v1", + "theme": "dark", + "body": { + "name": "Moon", + "kind": "moon", + "systemId": "sol", + "diameterKm": 3474, + "color": "lightgray", + "colorRgba": [ + 0.75, + 0.75, + 0.75, + 1.0 + ], + "displayRadiusAu": 0.009288902265104232 + }, + "frames": [ + { + "frame": 0, + "cameraAu": [ + 0.032182755537859827, + 0.02253460802796973, + 0.011265621218701805 + ], + "bodyRotationDeg": 0.0 + }, + { + "frame": 1, + "cameraAu": [ + 0.03091761261437237, + 0.024241277672834977, + 0.011265621218701805 + ], + "bodyRotationDeg": 1.971830985915493 + }, + { + "frame": 2, + "cameraAu": [ + 0.029562066554216046, + 0.025877065792473192, + 0.011265621218701805 + ], + "bodyRotationDeg": 3.943661971830986 + }, + { + "frame": 3, + "cameraAu": [ + 0.028120080975709828, + 0.02743718934044461, + 0.011265621218701805 + ], + "bodyRotationDeg": 5.915492957746479 + }, + { + "frame": 4, + "cameraAu": [ + 0.026595872246428863, + 0.028917086513608522, + 0.011265621218701805 + ], + "bodyRotationDeg": 7.887323943661972 + }, + { + "frame": 5, + "cameraAu": [ + 0.024993897154540586, + 0.03031243009084117, + 0.011265621218701805 + ], + "bodyRotationDeg": 9.859154929577466 + }, + { + "frame": 6, + "cameraAu": [ + 0.02331883987715072, + 0.031619140085835516, + 0.011265621218701805 + ], + "bodyRotationDeg": 11.830985915492958 + }, + { + "frame": 7, + "cameraAu": [ + 0.02157559828376379, + 0.03283339567698616, + 0.011265621218701805 + ], + "bodyRotationDeg": 13.80281690140845 + }, + { + "frame": 8, + "cameraAu": [ + 0.01976926961490671, + 0.033951646379476376, + 0.011265621218701805 + ], + "bodyRotationDeg": 15.774647887323944 + }, + { + "frame": 9, + "cameraAu": [ + 0.017905135577791362, + 0.03497062242689991, + 0.011265621218701805 + ], + "bodyRotationDeg": 17.746478873239436 + }, + { + "frame": 10, + "cameraAu": [ + 0.015988646902596323, + 0.03588734433206199, + 0.011265621218701805 + ], + "bodyRotationDeg": 19.71830985915493 + }, + { + "frame": 11, + "cameraAu": [ + 0.014025407404525202, + 0.03669913159900347, + 0.011265621218701805 + ], + "bodyRotationDeg": 21.69014084507042 + }, + { + "frame": 12, + "cameraAu": [ + 0.012021157598244235, + 0.03740361056077421, + 0.011265621218701805 + ], + "bodyRotationDeg": 23.661971830985916 + }, + { + "frame": 13, + "cameraAu": [ + 0.009981757912610464, + 0.03799872132003817, + 0.011265621218701805 + ], + "bodyRotationDeg": 25.633802816901408 + }, + { + "frame": 14, + "cameraAu": [ + 0.007913171554770653, + 0.03848272377221568, + 0.011265621218701805 + ], + "bodyRotationDeg": 27.6056338028169 + }, + { + "frame": 15, + "cameraAu": [ + 0.00582144707373637, + 0.038854202693551325, + 0.011265621218701805 + ], + "bodyRotationDeg": 29.577464788732392 + }, + { + "frame": 16, + "cameraAu": [ + 0.0037127006744193994, + 0.03911207187922996, + 0.011265621218701805 + ], + "bodyRotationDeg": 31.549295774647888 + }, + { + "frame": 17, + "cameraAu": [ + 0.0015930983338411646, + 0.03925557731944083, + 0.011265621218701805 + ], + "bodyRotationDeg": 33.52112676056338 + }, + { + "frame": 18, + "cameraAu": [ + -0.0005311622281915372, + 0.03928429940410326, + 0.011265621218701805 + ], + "bodyRotationDeg": 35.49295774647887 + }, + { + "frame": 19, + "cameraAu": [ + -0.0026538696712267453, + 0.03919815414980695, + 0.011265621218701805 + ], + "bodyRotationDeg": 37.46478873239436 + }, + { + "frame": 20, + "cameraAu": [ + -0.004768817196134325, + 0.038997393445379634, + 0.011265621218701805 + ], + "bodyRotationDeg": 39.43661971830986 + }, + { + "frame": 21, + "cameraAu": [ + -0.006869820693794128, + 0.038682604315363814, + 0.011265621218701805 + ], + "bodyRotationDeg": 41.40845070422535 + }, + { + "frame": 22, + "cameraAu": [ + -0.008950736827438549, + 0.038254707203556275, + 0.011265621218701805 + ], + "bodyRotationDeg": 43.38028169014084 + }, + { + "frame": 23, + "cameraAu": [ + -0.01100548099577663, + 0.03771495328162935, + 0.011265621218701805 + ], + "bodyRotationDeg": 45.352112676056336 + }, + { + "frame": 24, + "cameraAu": [ + -0.013028045124375635, + 0.0370649207907034, + 0.011265621218701805 + ], + "bodyRotationDeg": 47.32394366197183 + }, + { + "frame": 25, + "cameraAu": [ + -0.015012515233277932, + 0.03630651042656789, + 0.011265621218701805 + ], + "bodyRotationDeg": 49.29577464788732 + }, + { + "frame": 26, + "cameraAu": [ + -0.01695308872948554, + 0.035441939782044594, + 0.011265621218701805 + ], + "bodyRotationDeg": 51.267605633802816 + }, + { + "frame": 27, + "cameraAu": [ + -0.01884409137374908, + 0.034473736862743506, + 0.011265621218701805 + ], + "bodyRotationDeg": 53.23943661971831 + }, + { + "frame": 28, + "cameraAu": [ + -0.02067999387205019, + 0.03340473269517138, + 0.011265621218701805 + ], + "bodyRotationDeg": 55.2112676056338 + }, + { + "frame": 29, + "cameraAu": [ + -0.022455428043263937, + 0.03223805304880679, + 0.011265621218701805 + ], + "bodyRotationDeg": 57.183098591549296 + }, + { + "frame": 30, + "cameraAu": [ + -0.0241652025157269, + 0.030977109296346415, + 0.011265621218701805 + ], + "bodyRotationDeg": 59.154929577464785 + }, + { + "frame": 31, + "cameraAu": [ + -0.025804317906814197, + 0.029625588438847137, + 0.011265621218701805 + ], + "bodyRotationDeg": 61.12676056338028 + }, + { + "frame": 32, + "cameraAu": [ + -0.027367981441140322, + 0.02818744232493051, + 0.011265621218701805 + ], + "bodyRotationDeg": 63.098591549295776 + }, + { + "frame": 33, + "cameraAu": [ + -0.028851620964640216, + 0.026666876095572535, + 0.011265621218701805 + ], + "bodyRotationDeg": 65.07042253521126 + }, + { + "frame": 34, + "cameraAu": [ + -0.030250898313553157, + 0.025068335888266527, + 0.011265621218701805 + ], + "bodyRotationDeg": 67.04225352112675 + }, + { + "frame": 35, + "cameraAu": [ + -0.03156172199921903, + 0.02339649583651147, + 0.011265621218701805 + ], + "bodyRotationDeg": 69.01408450704226 + }, + { + "frame": 36, + "cameraAu": [ + -0.03278025917159587, + 0.021656244402640315, + 0.011265621218701805 + ], + "bodyRotationDeg": 70.98591549295774 + }, + { + "frame": 37, + "cameraAu": [ + -0.03390294682651829, + 0.019852670083949885, + 0.011265621218701805 + ], + "bodyRotationDeg": 72.95774647887323 + }, + { + "frame": 38, + "cameraAu": [ + -0.034926502223925765, + 0.017991046533928863, + 0.011265621218701805 + ], + "bodyRotationDeg": 74.92957746478872 + }, + { + "frame": 39, + "cameraAu": [ + -0.03584793248659864, + 0.016076817142088504, + 0.011265621218701805 + ], + "bodyRotationDeg": 76.90140845070424 + }, + { + "frame": 40, + "cameraAu": [ + -0.036664543351334684, + 0.014115579117485212, + 0.011265621218701805 + ], + "bodyRotationDeg": 78.87323943661973 + }, + { + "frame": 41, + "cameraAu": [ + -0.037373947046978016, + 0.012113067122474319, + 0.011265621218701805 + ], + "bodyRotationDeg": 80.84507042253522 + }, + { + "frame": 42, + "cameraAu": [ + -0.03797406927626477, + 0.010075136504550667, + 0.011265621218701805 + ], + "bodyRotationDeg": 82.8169014084507 + }, + { + "frame": 43, + "cameraAu": [ + -0.03846315528107087, + 0.008007746175305207, + 0.011265621218701805 + ], + "bodyRotationDeg": 84.78873239436619 + }, + { + "frame": 44, + "cameraAu": [ + -0.038839774973326885, + 0.005916941186560482, + 0.011265621218701805 + ], + "bodyRotationDeg": 86.76056338028168 + }, + { + "frame": 45, + "cameraAu": [ + -0.03910282711659726, + 0.003808835054631705, + 0.011265621218701805 + ], + "bodyRotationDeg": 88.73239436619718 + }, + { + "frame": 46, + "cameraAu": [ + -0.039251542546096994, + 0.0016895918843976814, + 0.011265621218701805 + ], + "bodyRotationDeg": 90.70422535211267 + }, + { + "frame": 47, + "cameraAu": [ + -0.03928548641773042, + -0.0004345916545493137, + 0.011265621218701805 + ], + "bodyRotationDeg": 92.67605633802818 + }, + { + "frame": 48, + "cameraAu": [ + -0.03920455947957582, + -0.002557504446972914, + 0.011265621218701805 + ], + "bodyRotationDeg": 94.64788732394366 + }, + { + "frame": 49, + "cameraAu": [ + -0.039008998362098236, + -0.004672939093301126, + 0.011265621218701805 + ], + "bodyRotationDeg": 96.61971830985915 + }, + { + "frame": 50, + "cameraAu": [ + -0.03869937488624162, + -0.006774710060070178, + 0.011265621218701805 + ], + "bodyRotationDeg": 98.59154929577464 + }, + { + "frame": 51, + "cameraAu": [ + -0.03827659439142377, + -0.008856671766431852, + 0.011265621218701805 + ], + "bodyRotationDeg": 100.56338028169014 + }, + { + "frame": 52, + "cameraAu": [ + -0.03774189308832274, + -0.010912736553839337, + 0.011265621218701805 + ], + "bodyRotationDeg": 102.53521126760563 + }, + { + "frame": 53, + "cameraAu": [ + -0.037096834444195335, + -0.012936892486368094, + 0.011265621218701805 + ], + "bodyRotationDeg": 104.50704225352112 + }, + { + "frame": 54, + "cameraAu": [ + -0.036343304611296985, + -0.014923220929623896, + 0.011265621218701805 + ], + "bodyRotationDeg": 106.47887323943662 + }, + { + "frame": 55, + "cameraAu": [ + -0.0354835069117702, + -0.01686591385683685, + 0.011265621218701805 + ], + "bodyRotationDeg": 108.45070422535211 + }, + { + "frame": 56, + "cameraAu": [ + -0.034519955395128046, + -0.01875929083153878, + 0.011265621218701805 + ], + "bodyRotationDeg": 110.4225352112676 + }, + { + "frame": 57, + "cameraAu": [ + -0.033455467487170265, + -0.020597815617166264, + 0.011265621218701805 + ], + "bodyRotationDeg": 112.3943661971831 + }, + { + "frame": 58, + "cameraAu": [ + -0.032293155751826876, + -0.022376112365023015, + 0.011265621218701805 + ], + "bodyRotationDeg": 114.36619718309859 + }, + { + "frame": 59, + "cameraAu": [ + -0.031036418790017447, + -0.02408898133326806, + 0.011265621218701805 + ], + "bodyRotationDeg": 116.33802816901408 + }, + { + "frame": 60, + "cameraAu": [ + -0.029688931302137914, + -0.025731414090967106, + 0.011265621218701805 + ], + "bodyRotationDeg": 118.30985915492957 + }, + { + "frame": 61, + "cameraAu": [ + -0.028254633343232156, + -0.027298608162750693, + 0.011265621218701805 + ], + "bodyRotationDeg": 120.28169014084507 + }, + { + "frame": 62, + "cameraAu": [ + -0.02673771880226632, + -0.028785981071257922, + 0.011265621218701805 + ], + "bodyRotationDeg": 122.25352112676056 + }, + { + "frame": 63, + "cameraAu": [ + -0.025142623139192372, + -0.030189183736305844, + 0.011265621218701805 + ], + "bodyRotationDeg": 124.22535211267606 + }, + { + "frame": 64, + "cameraAu": [ + -0.02347401041565791, + -0.031504113191605135, + 0.011265621218701805 + ], + "bodyRotationDeg": 126.19718309859155 + }, + { + "frame": 65, + "cameraAu": [ + -0.021736759657284128, + -0.032726924581838575, + 0.011265621218701805 + ], + "bodyRotationDeg": 128.16901408450704 + }, + { + "frame": 66, + "cameraAu": [ + -0.019935950587389183, + -0.0338540424050225, + 0.011265621218701805 + ], + "bodyRotationDeg": 130.14084507042253 + }, + { + "frame": 67, + "cameraAu": [ + -0.018076848773870863, + -0.034882170967279094, + 0.011265621218701805 + ], + "bodyRotationDeg": 132.11267605633802 + }, + { + "frame": 68, + "cameraAu": [ + -0.01616489023267991, + -0.0358083040194491, + 0.011265621218701805 + ], + "bodyRotationDeg": 134.0845070422535 + }, + { + "frame": 69, + "cameraAu": [ + -0.014205665532902577, + -0.03662973354736817, + 0.011265621218701805 + ], + "bodyRotationDeg": 136.05633802816902 + }, + { + "frame": 70, + "cameraAu": [ + -0.012204903449930096, + -0.03734405769010338, + 0.011265621218701805 + ], + "bodyRotationDeg": 138.0281690140845 + }, + { + "frame": 71, + "cameraAu": [ + -0.010168454214512266, + -0.03794918776299768, + 0.011265621218701805 + ], + "bodyRotationDeg": 140.0 + } + ], + "outputDirectory": "/var/folders/nx/wwmkd72x7jn3p284yz62yz980000gn/T/solsys_flyby_moon_dark_7xpu_sma/frames", + "resolution": 640, + "fps": 18, + "appearance": { + "bodyId": "moon", + "kind": "moon", + "textures": { + "color": "/Users/tom/Desktop/SOLSYS/data/textures/bodies/moon/color.png" + }, + "roughness": 0.82, + "specular": 0.04 + } +} diff --git a/output/animate/blender/moons/moon/moon_flyby_light.gif b/output/animate/blender/moons/moon/moon_flyby_light.gif new file mode 100644 index 0000000..726b34d Binary files /dev/null and b/output/animate/blender/moons/moon/moon_flyby_light.gif differ diff --git a/output/animate/blender/moons/moon/moon_flyby_light_job.json b/output/animate/blender/moons/moon/moon_flyby_light_job.json new file mode 100644 index 0000000..b6926e8 --- /dev/null +++ b/output/animate/blender/moons/moon/moon_flyby_light_job.json @@ -0,0 +1,680 @@ +{ + "schema": "solsys.blender_flyby_job/v1", + "theme": "light", + "body": { + "name": "Moon", + "kind": "moon", + "systemId": "sol", + "diameterKm": 3474, + "color": "lightgray", + "colorRgba": [ + 0.75, + 0.75, + 0.75, + 1.0 + ], + "displayRadiusAu": 0.009288902265104232 + }, + "frames": [ + { + "frame": 0, + "cameraAu": [ + 0.032182755537859827, + 0.02253460802796973, + 0.011265621218701805 + ], + "bodyRotationDeg": 0.0 + }, + { + "frame": 1, + "cameraAu": [ + 0.03091761261437237, + 0.024241277672834977, + 0.011265621218701805 + ], + "bodyRotationDeg": 1.971830985915493 + }, + { + "frame": 2, + "cameraAu": [ + 0.029562066554216046, + 0.025877065792473192, + 0.011265621218701805 + ], + "bodyRotationDeg": 3.943661971830986 + }, + { + "frame": 3, + "cameraAu": [ + 0.028120080975709828, + 0.02743718934044461, + 0.011265621218701805 + ], + "bodyRotationDeg": 5.915492957746479 + }, + { + "frame": 4, + "cameraAu": [ + 0.026595872246428863, + 0.028917086513608522, + 0.011265621218701805 + ], + "bodyRotationDeg": 7.887323943661972 + }, + { + "frame": 5, + "cameraAu": [ + 0.024993897154540586, + 0.03031243009084117, + 0.011265621218701805 + ], + "bodyRotationDeg": 9.859154929577466 + }, + { + "frame": 6, + "cameraAu": [ + 0.02331883987715072, + 0.031619140085835516, + 0.011265621218701805 + ], + "bodyRotationDeg": 11.830985915492958 + }, + { + "frame": 7, + "cameraAu": [ + 0.02157559828376379, + 0.03283339567698616, + 0.011265621218701805 + ], + "bodyRotationDeg": 13.80281690140845 + }, + { + "frame": 8, + "cameraAu": [ + 0.01976926961490671, + 0.033951646379476376, + 0.011265621218701805 + ], + "bodyRotationDeg": 15.774647887323944 + }, + { + "frame": 9, + "cameraAu": [ + 0.017905135577791362, + 0.03497062242689991, + 0.011265621218701805 + ], + "bodyRotationDeg": 17.746478873239436 + }, + { + "frame": 10, + "cameraAu": [ + 0.015988646902596323, + 0.03588734433206199, + 0.011265621218701805 + ], + "bodyRotationDeg": 19.71830985915493 + }, + { + "frame": 11, + "cameraAu": [ + 0.014025407404525202, + 0.03669913159900347, + 0.011265621218701805 + ], + "bodyRotationDeg": 21.69014084507042 + }, + { + "frame": 12, + "cameraAu": [ + 0.012021157598244235, + 0.03740361056077421, + 0.011265621218701805 + ], + "bodyRotationDeg": 23.661971830985916 + }, + { + "frame": 13, + "cameraAu": [ + 0.009981757912610464, + 0.03799872132003817, + 0.011265621218701805 + ], + "bodyRotationDeg": 25.633802816901408 + }, + { + "frame": 14, + "cameraAu": [ + 0.007913171554770653, + 0.03848272377221568, + 0.011265621218701805 + ], + "bodyRotationDeg": 27.6056338028169 + }, + { + "frame": 15, + "cameraAu": [ + 0.00582144707373637, + 0.038854202693551325, + 0.011265621218701805 + ], + "bodyRotationDeg": 29.577464788732392 + }, + { + "frame": 16, + "cameraAu": [ + 0.0037127006744193994, + 0.03911207187922996, + 0.011265621218701805 + ], + "bodyRotationDeg": 31.549295774647888 + }, + { + "frame": 17, + "cameraAu": [ + 0.0015930983338411646, + 0.03925557731944083, + 0.011265621218701805 + ], + "bodyRotationDeg": 33.52112676056338 + }, + { + "frame": 18, + "cameraAu": [ + -0.0005311622281915372, + 0.03928429940410326, + 0.011265621218701805 + ], + "bodyRotationDeg": 35.49295774647887 + }, + { + "frame": 19, + "cameraAu": [ + -0.0026538696712267453, + 0.03919815414980695, + 0.011265621218701805 + ], + "bodyRotationDeg": 37.46478873239436 + }, + { + "frame": 20, + "cameraAu": [ + -0.004768817196134325, + 0.038997393445379634, + 0.011265621218701805 + ], + "bodyRotationDeg": 39.43661971830986 + }, + { + "frame": 21, + "cameraAu": [ + -0.006869820693794128, + 0.038682604315363814, + 0.011265621218701805 + ], + "bodyRotationDeg": 41.40845070422535 + }, + { + "frame": 22, + "cameraAu": [ + -0.008950736827438549, + 0.038254707203556275, + 0.011265621218701805 + ], + "bodyRotationDeg": 43.38028169014084 + }, + { + "frame": 23, + "cameraAu": [ + -0.01100548099577663, + 0.03771495328162935, + 0.011265621218701805 + ], + "bodyRotationDeg": 45.352112676056336 + }, + { + "frame": 24, + "cameraAu": [ + -0.013028045124375635, + 0.0370649207907034, + 0.011265621218701805 + ], + "bodyRotationDeg": 47.32394366197183 + }, + { + "frame": 25, + "cameraAu": [ + -0.015012515233277932, + 0.03630651042656789, + 0.011265621218701805 + ], + "bodyRotationDeg": 49.29577464788732 + }, + { + "frame": 26, + "cameraAu": [ + -0.01695308872948554, + 0.035441939782044594, + 0.011265621218701805 + ], + "bodyRotationDeg": 51.267605633802816 + }, + { + "frame": 27, + "cameraAu": [ + -0.01884409137374908, + 0.034473736862743506, + 0.011265621218701805 + ], + "bodyRotationDeg": 53.23943661971831 + }, + { + "frame": 28, + "cameraAu": [ + -0.02067999387205019, + 0.03340473269517138, + 0.011265621218701805 + ], + "bodyRotationDeg": 55.2112676056338 + }, + { + "frame": 29, + "cameraAu": [ + -0.022455428043263937, + 0.03223805304880679, + 0.011265621218701805 + ], + "bodyRotationDeg": 57.183098591549296 + }, + { + "frame": 30, + "cameraAu": [ + -0.0241652025157269, + 0.030977109296346415, + 0.011265621218701805 + ], + "bodyRotationDeg": 59.154929577464785 + }, + { + "frame": 31, + "cameraAu": [ + -0.025804317906814197, + 0.029625588438847137, + 0.011265621218701805 + ], + "bodyRotationDeg": 61.12676056338028 + }, + { + "frame": 32, + "cameraAu": [ + -0.027367981441140322, + 0.02818744232493051, + 0.011265621218701805 + ], + "bodyRotationDeg": 63.098591549295776 + }, + { + "frame": 33, + "cameraAu": [ + -0.028851620964640216, + 0.026666876095572535, + 0.011265621218701805 + ], + "bodyRotationDeg": 65.07042253521126 + }, + { + "frame": 34, + "cameraAu": [ + -0.030250898313553157, + 0.025068335888266527, + 0.011265621218701805 + ], + "bodyRotationDeg": 67.04225352112675 + }, + { + "frame": 35, + "cameraAu": [ + -0.03156172199921903, + 0.02339649583651147, + 0.011265621218701805 + ], + "bodyRotationDeg": 69.01408450704226 + }, + { + "frame": 36, + "cameraAu": [ + -0.03278025917159587, + 0.021656244402640315, + 0.011265621218701805 + ], + "bodyRotationDeg": 70.98591549295774 + }, + { + "frame": 37, + "cameraAu": [ + -0.03390294682651829, + 0.019852670083949885, + 0.011265621218701805 + ], + "bodyRotationDeg": 72.95774647887323 + }, + { + "frame": 38, + "cameraAu": [ + -0.034926502223925765, + 0.017991046533928863, + 0.011265621218701805 + ], + "bodyRotationDeg": 74.92957746478872 + }, + { + "frame": 39, + "cameraAu": [ + -0.03584793248659864, + 0.016076817142088504, + 0.011265621218701805 + ], + "bodyRotationDeg": 76.90140845070424 + }, + { + "frame": 40, + "cameraAu": [ + -0.036664543351334684, + 0.014115579117485212, + 0.011265621218701805 + ], + "bodyRotationDeg": 78.87323943661973 + }, + { + "frame": 41, + "cameraAu": [ + -0.037373947046978016, + 0.012113067122474319, + 0.011265621218701805 + ], + "bodyRotationDeg": 80.84507042253522 + }, + { + "frame": 42, + "cameraAu": [ + -0.03797406927626477, + 0.010075136504550667, + 0.011265621218701805 + ], + "bodyRotationDeg": 82.8169014084507 + }, + { + "frame": 43, + "cameraAu": [ + -0.03846315528107087, + 0.008007746175305207, + 0.011265621218701805 + ], + "bodyRotationDeg": 84.78873239436619 + }, + { + "frame": 44, + "cameraAu": [ + -0.038839774973326885, + 0.005916941186560482, + 0.011265621218701805 + ], + "bodyRotationDeg": 86.76056338028168 + }, + { + "frame": 45, + "cameraAu": [ + -0.03910282711659726, + 0.003808835054631705, + 0.011265621218701805 + ], + "bodyRotationDeg": 88.73239436619718 + }, + { + "frame": 46, + "cameraAu": [ + -0.039251542546096994, + 0.0016895918843976814, + 0.011265621218701805 + ], + "bodyRotationDeg": 90.70422535211267 + }, + { + "frame": 47, + "cameraAu": [ + -0.03928548641773042, + -0.0004345916545493137, + 0.011265621218701805 + ], + "bodyRotationDeg": 92.67605633802818 + }, + { + "frame": 48, + "cameraAu": [ + -0.03920455947957582, + -0.002557504446972914, + 0.011265621218701805 + ], + "bodyRotationDeg": 94.64788732394366 + }, + { + "frame": 49, + "cameraAu": [ + -0.039008998362098236, + -0.004672939093301126, + 0.011265621218701805 + ], + "bodyRotationDeg": 96.61971830985915 + }, + { + "frame": 50, + "cameraAu": [ + -0.03869937488624162, + -0.006774710060070178, + 0.011265621218701805 + ], + "bodyRotationDeg": 98.59154929577464 + }, + { + "frame": 51, + "cameraAu": [ + -0.03827659439142377, + -0.008856671766431852, + 0.011265621218701805 + ], + "bodyRotationDeg": 100.56338028169014 + }, + { + "frame": 52, + "cameraAu": [ + -0.03774189308832274, + -0.010912736553839337, + 0.011265621218701805 + ], + "bodyRotationDeg": 102.53521126760563 + }, + { + "frame": 53, + "cameraAu": [ + -0.037096834444195335, + -0.012936892486368094, + 0.011265621218701805 + ], + "bodyRotationDeg": 104.50704225352112 + }, + { + "frame": 54, + "cameraAu": [ + -0.036343304611296985, + -0.014923220929623896, + 0.011265621218701805 + ], + "bodyRotationDeg": 106.47887323943662 + }, + { + "frame": 55, + "cameraAu": [ + -0.0354835069117702, + -0.01686591385683685, + 0.011265621218701805 + ], + "bodyRotationDeg": 108.45070422535211 + }, + { + "frame": 56, + "cameraAu": [ + -0.034519955395128046, + -0.01875929083153878, + 0.011265621218701805 + ], + "bodyRotationDeg": 110.4225352112676 + }, + { + "frame": 57, + "cameraAu": [ + -0.033455467487170265, + -0.020597815617166264, + 0.011265621218701805 + ], + "bodyRotationDeg": 112.3943661971831 + }, + { + "frame": 58, + "cameraAu": [ + -0.032293155751826876, + -0.022376112365023015, + 0.011265621218701805 + ], + "bodyRotationDeg": 114.36619718309859 + }, + { + "frame": 59, + "cameraAu": [ + -0.031036418790017447, + -0.02408898133326806, + 0.011265621218701805 + ], + "bodyRotationDeg": 116.33802816901408 + }, + { + "frame": 60, + "cameraAu": [ + -0.029688931302137914, + -0.025731414090967106, + 0.011265621218701805 + ], + "bodyRotationDeg": 118.30985915492957 + }, + { + "frame": 61, + "cameraAu": [ + -0.028254633343232156, + -0.027298608162750693, + 0.011265621218701805 + ], + "bodyRotationDeg": 120.28169014084507 + }, + { + "frame": 62, + "cameraAu": [ + -0.02673771880226632, + -0.028785981071257922, + 0.011265621218701805 + ], + "bodyRotationDeg": 122.25352112676056 + }, + { + "frame": 63, + "cameraAu": [ + -0.025142623139192372, + -0.030189183736305844, + 0.011265621218701805 + ], + "bodyRotationDeg": 124.22535211267606 + }, + { + "frame": 64, + "cameraAu": [ + -0.02347401041565791, + -0.031504113191605135, + 0.011265621218701805 + ], + "bodyRotationDeg": 126.19718309859155 + }, + { + "frame": 65, + "cameraAu": [ + -0.021736759657284128, + -0.032726924581838575, + 0.011265621218701805 + ], + "bodyRotationDeg": 128.16901408450704 + }, + { + "frame": 66, + "cameraAu": [ + -0.019935950587389183, + -0.0338540424050225, + 0.011265621218701805 + ], + "bodyRotationDeg": 130.14084507042253 + }, + { + "frame": 67, + "cameraAu": [ + -0.018076848773870863, + -0.034882170967279094, + 0.011265621218701805 + ], + "bodyRotationDeg": 132.11267605633802 + }, + { + "frame": 68, + "cameraAu": [ + -0.01616489023267991, + -0.0358083040194491, + 0.011265621218701805 + ], + "bodyRotationDeg": 134.0845070422535 + }, + { + "frame": 69, + "cameraAu": [ + -0.014205665532902577, + -0.03662973354736817, + 0.011265621218701805 + ], + "bodyRotationDeg": 136.05633802816902 + }, + { + "frame": 70, + "cameraAu": [ + -0.012204903449930096, + -0.03734405769010338, + 0.011265621218701805 + ], + "bodyRotationDeg": 138.0281690140845 + }, + { + "frame": 71, + "cameraAu": [ + -0.010168454214512266, + -0.03794918776299768, + 0.011265621218701805 + ], + "bodyRotationDeg": 140.0 + } + ], + "outputDirectory": "/var/folders/nx/wwmkd72x7jn3p284yz62yz980000gn/T/solsys_flyby_moon_light_7ium9erm/frames", + "resolution": 640, + "fps": 18, + "appearance": { + "bodyId": "moon", + "kind": "moon", + "textures": { + "color": "/Users/tom/Desktop/SOLSYS/data/textures/bodies/moon/color.png" + }, + "roughness": 0.82, + "specular": 0.04 + } +} diff --git a/output/animate/blender/earth_body_scene.json b/output/animate/blender/planets/earth/earth_body_scene.json similarity index 100% rename from output/animate/blender/earth_body_scene.json rename to output/animate/blender/planets/earth/earth_body_scene.json diff --git a/output/animate/blender/earth_flyby_dark.gif b/output/animate/blender/planets/earth/earth_flyby_dark.gif similarity index 100% rename from output/animate/blender/earth_flyby_dark.gif rename to output/animate/blender/planets/earth/earth_flyby_dark.gif diff --git a/output/animate/blender/earth_flyby_dark_job.json b/output/animate/blender/planets/earth/earth_flyby_dark_job.json similarity index 100% rename from output/animate/blender/earth_flyby_dark_job.json rename to output/animate/blender/planets/earth/earth_flyby_dark_job.json diff --git a/output/animate/blender/earth_flyby_light.gif b/output/animate/blender/planets/earth/earth_flyby_light.gif similarity index 100% rename from output/animate/blender/earth_flyby_light.gif rename to output/animate/blender/planets/earth/earth_flyby_light.gif diff --git a/output/animate/blender/earth_flyby_light_job.json b/output/animate/blender/planets/earth/earth_flyby_light_job.json similarity index 100% rename from output/animate/blender/earth_flyby_light_job.json rename to output/animate/blender/planets/earth/earth_flyby_light_job.json diff --git a/render.py b/render.py index 4f0a2bb..66eec02 100644 --- a/render.py +++ b/render.py @@ -12,7 +12,7 @@ from animate import renderAllAnimations from animate.scenes.alpha_centauri import renderAlphaCentauriAnimations from animate.scenes.barnards_star import renderBarnardsStarAnimations -from animate.scenes.blender.export_body import exportPlanetBodyScene +from animate.scenes.blender.export_body import exportBodyScene from animate.scenes.blender.flyby_scene import renderPlanetFlyby from animate.scenes.interstellar_objects import renderInterstellarObjectAnimations from animate.scenes.sol_centauri_cinematic import renderSolCentauriCinematicAnimations @@ -178,12 +178,12 @@ def buildParser() -> argparse.ArgumentParser: blenderParser = subparsers.add_parser( 'blender', - help='Export Sol planet catalog state or render Blender planet flybys', + help='Export Sol planet/moon catalog state or render Blender close-up flybys', ) blenderParser.add_argument( '--body', default='Earth', - help='PlanetCatalog name to export / flyby (default: Earth)', + help='PlanetCatalog or MoonCatalog name to export / flyby (default: Earth)', ) blenderParser.add_argument( '--frames', @@ -325,7 +325,7 @@ def main(argv: list[str] | None = None) -> None: print(f'Flyby ready → {gifPath}') return - scenePath = exportPlanetBodyScene( + scenePath = exportBodyScene( args.body, frameCount=args.frames, outputDirectory=args.output_dir, diff --git a/tests/test_blender_pipeline.py b/tests/test_blender_pipeline.py index d1ab729..b99ce9d 100644 --- a/tests/test_blender_pipeline.py +++ b/tests/test_blender_pipeline.py @@ -17,10 +17,12 @@ from animate.scenes.blender.body_scene import ( SCHEMA_ID, BodyScene, + buildBodyScene, + buildMoonBodyScene, buildPlanetBodyScene, loadBodyScene, ) -from animate.scenes.blender.export_body import exportPlanetBodyScene +from animate.scenes.blender.export_body import exportBodyScene, exportPlanetBodyScene from animate.scenes.blender.flyby_camera import buildFlybyCameraPath, flybyCameraLocation from animate.scenes.blender.flyby_scene import ( assembleGifFromPngs, @@ -53,6 +55,21 @@ def test_unknown_planet_raises(self) -> None: with self.assertRaises(ValueError): buildPlanetBodyScene('Nibiru') + def test_build_moon_scene(self) -> None: + scene = buildMoonBodyScene('Moon', frameCount=16) + self.assertEqual(scene.body.name, 'Moon') + self.assertEqual(scene.body.kind, 'moon') + self.assertEqual(scene.body.systemId, 'sol') + self.assertEqual(len(scene.keyframes), 16) + self.assertGreater(scene.body.displayRadiusAu, 0.0) + viaDispatch = buildBodyScene('Moon', frameCount=8) + self.assertEqual(viaDispatch.body.kind, 'moon') + self.assertEqual(len(viaDispatch.keyframes), 8) + + def test_unknown_body_raises(self) -> None: + with self.assertRaises(ValueError): + buildBodyScene('Nibiru') + def test_round_trip_json(self) -> None: scene = buildPlanetBodyScene('Jupiter', frameCount=8) restored = BodyScene.fromDict(json.loads(scene.toJson())) @@ -68,6 +85,7 @@ def test_export_and_host_load(self) -> None: path = exportPlanetBodyScene('Earth', frameCount=12, outputDirectory=outputDirectory) self.assertTrue(path.is_file()) self.assertEqual(path.name, 'earth_body_scene.json') + self.assertEqual(path.parent.as_posix().endswith('planets/earth'), True) scene = loadBodyScene(path) self.assertEqual(scene.body.name, 'Earth') @@ -112,6 +130,22 @@ def test_earth_pack_resolves_color_map(self) -> None: self.assertTrue((TEXTURE_BODIES_ROOT / 'earth' / 'color.png').is_file()) self.assertTrue((TEXTURE_BODIES_ROOT / 'earth' / 'clouds.png').is_file()) + def test_moon_pack_resolves_color_map_without_atmosphere(self) -> None: + appearance = appearanceForCatalogName('Moon') + self.assertIsNotNone(appearance) + assert appearance is not None + self.assertEqual(appearance.bodyId, 'moon') + self.assertEqual(appearance.kind, 'moon') + maps = appearance.textures.existingMaps() + self.assertIn('color', maps) + self.assertNotIn('clouds', maps) + self.assertFalse(appearance.atmosphere.enabled) + jobAppearance = appearance.toJobDict() + self.assertNotIn('atmosphere', jobAppearance) + self.assertIn('color', jobAppearance['textures']) + self.assertIn('Moon', registeredCatalogNames()) + self.assertTrue((TEXTURE_BODIES_ROOT / 'moon' / 'color.png').is_file()) + def test_unknown_catalog_name_has_no_pack(self) -> None: self.assertIsNone(appearanceForCatalogName('Nibiru')) @@ -124,6 +158,7 @@ def test_prepare_export(self) -> None: ) self.assertTrue(path.is_file()) self.assertEqual(path.name, 'mars_body_scene.json') + self.assertTrue(path.parent.as_posix().endswith('planets/mars')) def test_camera_path_moves_around_body(self) -> None: path = buildFlybyCameraPath(0.03, frameCount=12) @@ -165,6 +200,23 @@ def test_build_and_validate_flyby_job(self) -> None: self.assertEqual(completed.returncode, 0, completed.stderr) self.assertIn('dry-run', completed.stdout.lower()) + def test_build_moon_flyby_job(self) -> None: + with tempfile.TemporaryDirectory() as temporaryDirectory: + path = exportBodyScene('Moon', frameCount=6, outputDirectory=temporaryDirectory) + self.assertEqual(path.name, 'moon_body_scene.json') + self.assertTrue(path.parent.as_posix().endswith('moons/moon')) + job = buildFlybyJob( + 'Moon', + theme='light', + frameCount=6, + framesDirectory=Path(temporaryDirectory) / 'frames', + ) + self.assertEqual(job['body']['name'], 'Moon') + self.assertEqual(job['body']['kind'], 'moon') + self.assertEqual(job['appearance']['bodyId'], 'moon') + self.assertIn('color', job['appearance']['textures']) + self.assertNotIn('atmosphere', job['appearance']) + def test_assemble_gif_from_pngs(self) -> None: with tempfile.TemporaryDirectory() as temporaryDirectory: root = Path(temporaryDirectory)