Skip to content
paulzapPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

SatForm3D

The engine is SatForm3D. The dataset it produces is SHAPE — Spacecraft Hierarchies, Articulation, Physics, Ensembles.

Dataset: SHAPE on Zenodo, https://doi.org/10.5281/zenodo.23892018 (CC BY 4.0). Data descriptor: Zapevalin, P. R. A synthetic dataset of articulated 3D spacecraft models with optical and mass properties (in preparation).

A data-driven generative engine for physically-consistent synthetic spacecraft geometry. Given a declarative JSON catalog, SatForm3D procedurally assembles diverse, structurally-distinct 3-D satellite models — bus + subsystem components

  • articulated deployables — enforces physical consistency (collision, keep-out, swept-motion gates), and exports them with per-facet optical properties (ρ, s, ε) and mass/inertia for light-curve / solar-radiation-pressure (SRP) modelling and ML pose / inspection algorithms.

It exists to fill the gap left by classified real-spacecraft CAD: a large, labelled, physically-consistent synthetic dataset that samples the spacecraft design space itself. That dataset is SHAPE, and the four words behind the acronym are the four things a record carries beyond a mesh: a retained component hierarchy, an articulation graph certified across its motion envelope, physics (per-facet optics plus mass, centre of mass and inertia), and an ensemble of 34,000 designs rather than a single object. Naming policy: docs/NAMING.md.

  • Version 2.3.0 · HDF5 schema v2.2 · C++20 · Apache-2.0

Highlights

  • 11 bus archetypes × 8 subsystems × 42 component types × 62 grammar rules, composed by a probabilistic assembly grammar with size/parameter variation.
  • 12 mission-conditioned regimes + an unconditioned exploratory regime, each gated independently on up to 8 statistical-realism criteria. Current result: 13 of 13 regimes pass every criterion scored for them — 8 of 8 for the unconditioned regime, 7 of 7 for eleven mission regimes, 6 of 6 for spent_stage (realism_gate, 300 models per regime, seed 12345). The size bands are derived from an external corpus of 134 cited real spacecraft, 105 of which carry a usable size, not from the generator's own output — a band read off the population it judges cannot fail by construction. Each band carries the number of spacecraft behind it and its leave-one-out coverage, and the report prints both: every regime band is built on at least 3 spacecraft, the fewest being 3 for nav_meo, 4 for cubesat and body_mounted and 5 for geo_comms.
  • 9 physical-consistency validators (no interpenetration, no swept-articulation collision, keep-out clear, outward normals, single connected assembly, plausible mass properties, per-facet optical consistency, functional-surface exposure, attached parts) — the property gate runs with zero failures, and satform generate --validate applies the same pipeline.
  • Articulation graph: single-DOF revolute/prismatic joints, named deployment states, and kinematic couplings (q = a·q_driver + b).
  • Reproducible: every model is seeded, with a /provenance record (seed, model index, generator version, and a SHA-256 digest of the config directory).
  • 9 named real-spacecraft reference models (Intelsat 29e, Iridium-NEXT, Sentinel-2, Jason-3, Envisat, GRACE-FO, 3U CubeSat, Hubble, Voyager 1).
  • Interoperable I/O: HDF5 (lossless master) + glTF 2.0 / GLB / URDF / OBJ, plus a bidirectional converter to STL, PLY, and VTK/VTP.
  • Embedded WebGL viewer and a Streamlit GUI (SatForm3D Studio).

Repository layout

Path Contents
src/ C++20 engine (core scene graph, assembly, physics, IO, CLI)
config/ Data-driven catalog: archetypes, components, grammar, missions, realism
config_named/ config/ plus five part types for hand-written craft (docs/NAMED_SATELLITES.md)
tests/ Unit, golden and property tests run by ctest
tools/ Python loader, analysis/figures, satform_ui/ (GUI + WebGL viewer)
examples/named/ Hand-authored real-satellite recipes
datasets/reference/ Curated cited real-spacecraft dimensions (source)
docs/ HDF5_FORMAT.md, CONVERTER.md, VIEWER.md, NAMED_SATELLITES.md, BENCHMARK.md
release/ REGENERATE.md, DATA_RECORDS.md (canonical-release recipe), licences

Build (Windows / MSVC + vcpkg)

Dependencies: Eigen 3.4, HighFive/HDF5, nlohmann-json (via vcpkg).

cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=<vcpkg>/scripts/buildsystems/vcpkg.cmake
cmake --build build --config Release

Cyrillic-path caveat: the HDF5 C runtime cannot open files whose path contains non-ASCII characters. Reading config from a non-ASCII path is fine; only the output directory must be ASCII (e.g. E:\satform_demo).

Quickstart

$exe = "build/Release/satform.exe"

# Procedural generation (writes .h5 + .obj; add --gltf --urdf for more formats)
& $exe generate --config config --out E:\satform_demo --count 100 --seed 0 --mission leo_eo

# Build a named real satellite from its recipe.
# NOTE --no-collision: a deployed wing's rest hull spans the bus, so the curated
# references are built with the placement gate off (see docs/NAMED_SATELLITES.md).
& $exe build examples/named/recipe_grace_fo.json --config config --out E:\satform_demo\grace.h5 --no-collision

# Convert between formats (HDF5 is the lossless master; conversions warn about the main losses)
& $exe convert E:\satform_demo\grace.h5 E:\satform_demo\grace.glb

# Population statistics + realism report
& $exe stats --config config --count 2000 --seed 12345 --out stats.json

GUI + embedded WebGL viewer

$env:SATFORM_WORK_DIR = "C:\satform_ui_work"   # ASCII scratch dir (HDF5-safe)
python -m streamlit run tools/satform_ui/app.py

Generate a model (or pick a named satellite) and inspect it in the embedded Three.js viewer — mouse-orbit, per-subsystem show/hide, material shading, and live deployment sliders. three.js is vendored (tools/satform_ui/viewer/vendor/), so the viewer is fully offline/firewall-safe. See docs/VIEWER.md.

Invariant gates

Every change is verified by the test suite:

# One command runs everything:
ctest --test-dir build -C Release --output-on-failure
# ...or the gates on populations:
ctest --test-dir build -C Release -L gate --output-on-failure

build/Release/property_test.exe    config 10000         # 9 validators, zero failures
build/Release/statistics_test.exe                       # realism: 13 of 13 regimes pass

The gates measure populations — distributions, feasibility, plausibility. None of them asks whether an individual part is a sound solid, which is how four separate open-mesh defects once reached shipped data. Two tests cover that:

build/Release/geometry_test.exe        # primitives: closedness, dimensions, winding
python tools/analysis/mesh_quality.py --generate ./build/Release/satform.exe `
       --config config --count 30 --assert-watertight

mesh_quality.py also runs against an existing dataset directory, reporting the watertight fraction and the worst offenders grouped by part name — so a regression points at a recipe rather than at a model index.

Closed is not the same as free of self-intersection: two closed boxes pushed into each other and stored as one mesh are closed edge for edge. stats --self-intersect N measures that over the first N generated models, mesh by mesh — pairs of triangles that cross, pairs that overlap in a common plane, and any other contact away from a shared vertex or edge — and breaks the count down by catalog id and by geometry recipe. It is a measurement, not a validator; the definitions and the tolerance are in src/core/MeshSelfIntersection.h.

build/Release/satform.exe stats --config config --count 150 --seed 4242 --self-intersect 150
build/Release/self_intersection_test.exe   # the pair test, known meshes, every primitive

On that sample all 7,933 component meshes have no two triangles meeting anywhere but at a vertex or an edge they share, and so do all 182,694 over 300 models of each of the 13 regimes (seed 12345). On an earlier build, where the two samples held 8,937 and 170,679 meshes, the measurement first found 1 and 86 that did not, every one an L-shaped bracket or instrument part: Primitives::lBracket wrote two boxes into one mesh, and both contained the corner block. It now meshes the union of the two as one solid.

Self-shadowing

Projected cross-section is reported as the Cauchy bound S/4, exact for a convex body and an upper bound for a spacecraft. A BVH ray caster measures the real thing:

build/Release/satform.exe stats --config config --count 200 --seed 4242 --shadow 50

Measured by that command the true area is 0.87 × (S/4) at the median (p10 0.79), so the bound overstates the effective cross-section by ~15% typically. The ratio differs between regimes. Over 40 models of each mission regime at its release seed, at 256 directions, the median is lowest for deep_space (0.77), servicer (0.78) and body_mounted (0.79) and highest for megaconstellation (0.94), spent_stage and geo_comms (0.88), with cubesat at 0.84. Correctness is pinned by the Cauchy identity itself — for a convex body the direction-averaged area must equal S/4 exactly, which ray_test checks on a sphere, a cube and a cylinder.

A model can carry that measurement itself. generate --illuminated-area (and build --illuminated-area) stores it on the file's root, beside total_surface_area, as mean_illuminated_area [m²] with the lattice size in illuminated_area_directions — 256 unless --illuminated-area-dirs D says otherwise. The flag is off by default and a file written without it has neither attribute:

from satform_loader import load_model
m = load_model("model_0000.h5")
m.mean_illuminated_area          # m², or None if the file was not measured
m.total_surface_area / 4         # the Cauchy bound S/4, for comparison

stats --shadow N --csv per_model.csv writes the measured area of each of the first N models in a column illuminated_area_m2, with illuminated_area_to_mass beside it; at --shadow-dirs 256 it is the number generate stores (stats defaults to 64 directions). projected_area, area_to_mass and the realism criteria stay defined on S/4.

SRP coefficient table

The same visibility structure turns a mesh into a force model. generate --srp N appends an optional /srp group: force and torque over N Sun directions, already divided by the solar pressure (so m² and m³ — multiply by the P your orbit implies), occlusion included, torque referenced to the centre of mass.

from satform_loader import load_model
m = load_model("model_0000.h5")
F = m.srp.force(4.56e-6)                 # N at 1 AU, one row per direction
i = m.srp.nearest([0, 0, 1])             # index of the tabulated +Z direction

Pinned by closed forms, not plausible curves: at normal incidence an absorber gives |F|/P = A, a mirror 2A, a Lambertian A(1+2/3); off-normal the absorber follows A cos θ; symmetric plates give identically zero net torque. A 23 t generated model comes out at 2.3–6.1 × 10⁻⁸ m/s² at 1 AU, the range spacecraft of that class fly.

Dataset & paper

  • The dataset is deposited on Zenodo: https://doi.org/10.5281/zenodo.23892018.
  • The canonical release recipe is in release/REGENERATE.md; the record layout is in release/DATA_RECORDS.md. python tools/release/build_final.py runs the whole recipe, from the build and the tests to the packed deposit.
  • Python loading (tools/satform_loader.py):
from satform_loader import load_model, model_to_facets
model = load_model("model_0042.h5")
bus = next(c for c in model.root.children if c.component_type == "bus")
print(bus.catalog_type_id)          # bus archetype, e.g. "bus_cube"
facets = model_to_facets(model)     # per-facet arrays + subsystem labels

ML labels and splits

model_to_facets returns three distinct label levels, because a segmentation task needs the right one:

field question it answers
subsystem_id semantic: this facet is power
instance_id instance: this facet belongs to the second of the two wings
part_id part: the wing's strut versus its panel

An instance is one grammar-rule firing, so a three-panel folding wing is one instance and a truss mast of two dozen struts is one instance — which is what an instance-segmentation metric has to count. (A launch-adapter ring is one instance and, being a single mesh, one part; its angular sectors are a collision-only partition of that mesh, the convex_piece dataset, and are not parts.) catalog_type names the generative source, so a model can be regrouped by what the catalogue calls a thing rather than by what it happens to look like.

Frozen splits hold out whole bus archetypes for test:

python tools/make_splits.py dataset -o release/splits.json

A random split measures almost nothing here — every model comes from one grammar, so a random test set contains the same archetypes as training. Holding out morphologies asks the harder question: does a model trained on some spacecraft shapes transfer to shapes it has never seen? Verified disjoint: on a 180-model sample, bus_hex and bus_trapezoid appear only in test, every other archetype only in train/val, zero leakage.

Symmetry annotation

A pose metric is ill-defined on a symmetric object: if a rotation maps the spacecraft onto itself, an estimate differing from the label by exactly that rotation is not an error. generate --symmetry writes an optional /symmetry group; the canonical release recipe enables it.

m = load_model("model_0000.h5")
R = m.symmetry.equivalent_rotations()   # (K,3,3) — quotient these out in a metric

Detected from geometry, not read off the grammar — and the measurement is why. The grammar is full of symmetric operators (bilateral_pair, radial_n), which made "derive it from the rules" look obvious. Measured, 93–100 % of models are C1: the wings are placed as a mirrored pair, then one antenna or boom breaks the symmetry of the whole model. Deriving it from the grammar would have declared almost every two-winged model symmetric.

equivalent_rotations excludes mirrors deliberately: a pose is an element of SO(3), so an improper transform is not an alternative pose and quotienting by one would forgive a handedness error.

Citation

If you use the SatForm3D engine or the SHAPE dataset, please cite them — see CITATION.cff. The engine and the dataset are separately licensed: Apache-2.0 for the code, CC BY 4.0 for the data.

License

Apache License 2.0 — see LICENSE.

One exception: tools/satform_ui/viewer/vendor/ holds revision 160 of the Three.js rendering library, which carries its own MIT licence — see tools/satform_ui/viewer/vendor/LICENSE.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages