pretty-lattice 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pretty_lattice-0.1.0/.github/workflows/ci.yml +69 -0
- pretty_lattice-0.1.0/.gitignore +50 -0
- pretty_lattice-0.1.0/AGENTS.md +7 -0
- pretty_lattice-0.1.0/LICENSE +21 -0
- pretty_lattice-0.1.0/PKG-INFO +96 -0
- pretty_lattice-0.1.0/README.md +82 -0
- pretty_lattice-0.1.0/assets/Ba2Ca2Cu3HgO8-color-schemes.png +0 -0
- pretty_lattice-0.1.0/assets/SrTiO3-material-presets.png +0 -0
- pretty_lattice-0.1.0/assets/demo.png +0 -0
- pretty_lattice-0.1.0/docs/constitution.md +88 -0
- pretty_lattice-0.1.0/docs/decisions/cyclic-crystal-camera-orientation.md +158 -0
- pretty_lattice-0.1.0/docs/decisions/index.md +7 -0
- pretty_lattice-0.1.0/docs/decisions/keep-camera-interaction-in-threejs.md +99 -0
- pretty_lattice-0.1.0/docs/decisions/use-pymatgen-backend.md +69 -0
- pretty_lattice-0.1.0/docs/development.md +59 -0
- pretty_lattice-0.1.0/docs/front-end.md +38 -0
- pretty_lattice-0.1.0/docs/index.md +18 -0
- pretty_lattice-0.1.0/docs/notes/ChatGPT-3D/350/247/206/350/247/222/350/260/203/346/216/247/344/270/216/346/223/215/344/275/234.md +431 -0
- pretty_lattice-0.1.0/docs/notes/ChatGPT-VESTA standard view/350/247/243/346/236/220.md" +96 -0
- pretty_lattice-0.1.0/docs/notes/ChatGPT-VESTA /351/273/230/350/256/244/345/217/226/345/220/221/345/210/206/346/236/220.md" +146 -0
- pretty_lattice-0.1.0/docs/notes/ChatGPT-/345/212/250/346/200/201/345/210/206/351/205/215/351/242/234/350/211/262/346/226/271/346/241/210.md +547 -0
- pretty_lattice-0.1.0/docs/notes/ChatGPT-/346/231/266/344/275/223/347/273/223/346/236/204/345/217/257/350/247/206/345/214/226/345/273/272/350/256/256.md +155 -0
- pretty_lattice-0.1.0/docs/notes/backend_architecture_scan_2026-06-27.md +169 -0
- pretty_lattice-0.1.0/docs/notes/camera_orientation_math.md +143 -0
- pretty_lattice-0.1.0/docs/notes/crystal_toolkit_boundary_report.md +119 -0
- pretty_lattice-0.1.0/docs/notes/frontend_architecture_scan_2026-06-27.md +183 -0
- pretty_lattice-0.1.0/docs/notes/index.md +27 -0
- pretty_lattice-0.1.0/docs/notes/naumann_standard_view_algorithm.md +346 -0
- pretty_lattice-0.1.0/docs/notes/polyhedra_rendering_research.md +136 -0
- pretty_lattice-0.1.0/docs/notes/pymatgen_prl_report.md +277 -0
- pretty_lattice-0.1.0/docs/notes/thermo_nuclear_code_quality_review_2026-06-30.md +295 -0
- pretty_lattice-0.1.0/docs/notes/threejs-best-practices-100-tips.md +1404 -0
- pretty_lattice-0.1.0/docs/notes/threejs_preview_performance_handoff_2026-06-27.md +114 -0
- pretty_lattice-0.1.0/docs/notes/vercel_design.md +499 -0
- pretty_lattice-0.1.0/docs/notes/vesta_standard_view_notes.md +335 -0
- pretty_lattice-0.1.0/docs/notes/webgpu-threejs-migration-guide.md +834 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-21-load-structure-preview/.openspec.yaml +2 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-21-load-structure-preview/design.md +107 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-21-load-structure-preview/proposal.md +41 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-21-load-structure-preview/specs/structure-preview/spec.md +146 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-21-load-structure-preview/tasks.md +26 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-element-legend/.openspec.yaml +2 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-element-legend/design.md +116 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-element-legend/proposal.md +39 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-element-legend/specs/structure-preview/spec.md +71 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-element-legend/tasks.md +17 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-periodic-boundary-display/.openspec.yaml +2 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-periodic-boundary-display/design.md +67 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-periodic-boundary-display/proposal.md +26 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-periodic-boundary-display/specs/structure-preview/spec.md +118 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-periodic-boundary-display/tasks.md +17 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-add-interactive-view-controls/.openspec.yaml +2 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-add-interactive-view-controls/design.md +83 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-add-interactive-view-controls/proposal.md +32 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-add-interactive-view-controls/specs/structure-preview/spec.md +151 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-add-interactive-view-controls/tasks.md +26 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-migrate-to-pymatgen-backend/.openspec.yaml +2 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-migrate-to-pymatgen-backend/design.md +87 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-migrate-to-pymatgen-backend/proposal.md +46 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-migrate-to-pymatgen-backend/specs/structure-backend/spec.md +88 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-migrate-to-pymatgen-backend/specs/structure-preview/spec.md +84 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-migrate-to-pymatgen-backend/tasks.md +32 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-bonds-components-panel/.openspec.yaml +2 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-bonds-components-panel/design.md +81 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-bonds-components-panel/proposal.md +34 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-bonds-components-panel/specs/structure-backend/spec.md +74 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-bonds-components-panel/specs/structure-preview/spec.md +249 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-bonds-components-panel/tasks.md +31 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-crystal-toolkit-polyhedra/.openspec.yaml +2 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-crystal-toolkit-polyhedra/design.md +82 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-crystal-toolkit-polyhedra/proposal.md +32 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-crystal-toolkit-polyhedra/specs/structure-backend/spec.md +70 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-crystal-toolkit-polyhedra/specs/structure-preview/spec.md +144 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-crystal-toolkit-polyhedra/tasks.md +28 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-25-add-figure-export/.openspec.yaml +2 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-25-add-figure-export/design.md +149 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-25-add-figure-export/proposal.md +52 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-25-add-figure-export/specs/structure-preview/spec.md +169 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-25-add-figure-export/tasks.md +38 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-26-add-crystal-camera-controls/.openspec.yaml +2 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-26-add-crystal-camera-controls/design.md +76 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-26-add-crystal-camera-controls/proposal.md +31 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-26-add-crystal-camera-controls/specs/structure-preview/spec.md +187 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-26-add-crystal-camera-controls/tasks.md +27 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-27-add-material-presets/.openspec.yaml +2 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-27-add-material-presets/design.md +71 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-27-add-material-presets/proposal.md +32 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-27-add-material-presets/specs/structure-preview/spec.md +96 -0
- pretty_lattice-0.1.0/openspec/changes/archive/2026-06-27-add-material-presets/tasks.md +33 -0
- pretty_lattice-0.1.0/openspec/config.yaml +16 -0
- pretty_lattice-0.1.0/openspec/specs/structure-backend/spec.md +234 -0
- pretty_lattice-0.1.0/openspec/specs/structure-preview/spec.md +887 -0
- pretty_lattice-0.1.0/pyproject.toml +40 -0
- pretty_lattice-0.1.0/scripts/build_release.py +141 -0
- pretty_lattice-0.1.0/scripts/generate_static_scene.py +24 -0
- pretty_lattice-0.1.0/scripts/soften_colormap.py +307 -0
- pretty_lattice-0.1.0/src/pretty_lattice/__init__.py +3 -0
- pretty_lattice-0.1.0/src/pretty_lattice/cli.py +91 -0
- pretty_lattice-0.1.0/src/pretty_lattice/server/__init__.py +1 -0
- pretty_lattice-0.1.0/src/pretty_lattice/server/app.py +175 -0
- pretty_lattice-0.1.0/src/pretty_lattice/server/routes.py +66 -0
- pretty_lattice-0.1.0/src/pretty_lattice/structures/__init__.py +1 -0
- pretty_lattice-0.1.0/src/pretty_lattice/structures/connectivity.py +260 -0
- pretty_lattice-0.1.0/src/pretty_lattice/structures/periodic_images.py +296 -0
- pretty_lattice-0.1.0/src/pretty_lattice/structures/polyhedra.py +349 -0
- pretty_lattice-0.1.0/src/pretty_lattice/structures/readers.py +55 -0
- pretty_lattice-0.1.0/src/pretty_lattice/structures/scene.py +50 -0
- pretty_lattice-0.1.0/src/pretty_lattice/structures/scene_builder.py +138 -0
- pretty_lattice-0.1.0/src/pretty_lattice/structures/scene_contract.json +30 -0
- pretty_lattice-0.1.0/src/pretty_lattice/structures/schema.py +131 -0
- pretty_lattice-0.1.0/src/pretty_lattice/structures/summary.py +100 -0
- pretty_lattice-0.1.0/src/pretty_lattice/structures/symmetry.py +50 -0
- pretty_lattice-0.1.0/src/pretty_lattice/structures/visibility.py +83 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/Geist-MediumItalic-tzJ114MP.ttf +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/Geist-Regular-BhgZPHH5.ttf +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/exportRenderer-CZpjPvzO.js +1 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/fontkit.es-B4yDa3WR.js +47 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-400-normal-B40WzpMT.woff2 +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-400-normal-cWY99Cna.woff +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-500-italic-C61nW-2l.woff +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-500-italic-U3jG8IXN.woff2 +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-500-normal-CHEM4JuE.woff +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-500-normal-CTWBw9NS.woff2 +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-600-italic-BAmgU4ZR.woff +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-600-italic-VAfbuvIv.woff2 +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-600-normal-BeQEdSAO.woff +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-600-normal-CSETrqM2.woff2 +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-700-italic-B0LJ-tM0.woff +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-700-italic-CiSW-G5G.woff2 +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-700-normal-CFi8mLqe.woff2 +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-700-normal-RGxhsL9r.woff +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-mono-latin-400-normal-BXAprPdR.woff +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-mono-latin-400-normal-DKaoCDn5.woff2 +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-mono-latin-500-normal-C3sF8Y1B.woff2 +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-mono-latin-500-normal-YfPbDI_o.woff +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-mono-latin-600-normal-BHzjB6_C.woff2 +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-mono-latin-600-normal-C5fp8g0r.woff +0 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/index-ClX6i6DT.css +1 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/index-CwkjM2YR.js +51 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/index-xllU6-g1.js +4442 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/index-ztFMT2Ax.js +1 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/examples/Al2O3.scene.json +1 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/examples/LiFePO4.scene.json +6524 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/favicon.svg +16 -0
- pretty_lattice-0.1.0/src/pretty_lattice/web_static/index.html +14 -0
- pretty_lattice-0.1.0/tests/fixtures/structures/Al2O3.cif +68 -0
- pretty_lattice-0.1.0/tests/fixtures/structures/Ba2Ca2Cu3HgO8.cif +42 -0
- pretty_lattice-0.1.0/tests/fixtures/structures/Hg3Cl4O.cif +58 -0
- pretty_lattice-0.1.0/tests/fixtures/structures/LiFePO4.cif +61 -0
- pretty_lattice-0.1.0/tests/fixtures/structures/MoS2.cif +37 -0
- pretty_lattice-0.1.0/tests/fixtures/structures/NaCl.cif +39 -0
- pretty_lattice-0.1.0/tests/fixtures/structures/SOURCE_NOTES.md +38 -0
- pretty_lattice-0.1.0/tests/fixtures/structures/Si.cif +38 -0
- pretty_lattice-0.1.0/tests/fixtures/structures/Sm(Mo3S4)2.cif +77 -0
- pretty_lattice-0.1.0/tests/fixtures/structures/SrTiO3.cif +37 -0
- pretty_lattice-0.1.0/tests/fixtures/structures/TiO2.cif +32 -0
- pretty_lattice-0.1.0/tests/test_api.py +246 -0
- pretty_lattice-0.1.0/tests/test_cli.py +88 -0
- pretty_lattice-0.1.0/tests/test_structures.py +710 -0
- pretty_lattice-0.1.0/uv.lock +1669 -0
- pretty_lattice-0.1.0/vercel.json +8 -0
- pretty_lattice-0.1.0/web/bun.lock +632 -0
- pretty_lattice-0.1.0/web/bunfig.toml +2 -0
- pretty_lattice-0.1.0/web/components.json +21 -0
- pretty_lattice-0.1.0/web/doctor.config.json +23 -0
- pretty_lattice-0.1.0/web/index.html +13 -0
- pretty_lattice-0.1.0/web/package.json +53 -0
- pretty_lattice-0.1.0/web/public/examples/Al2O3.scene.json +1 -0
- pretty_lattice-0.1.0/web/public/examples/LiFePO4.scene.json +6524 -0
- pretty_lattice-0.1.0/web/public/favicon.svg +16 -0
- pretty_lattice-0.1.0/web/src/api/scene.ts +232 -0
- pretty_lattice-0.1.0/web/src/app/App.tsx +659 -0
- pretty_lattice-0.1.0/web/src/app/AtomInspectorCard.tsx +112 -0
- pretty_lattice-0.1.0/web/src/app/atomInspector.ts +70 -0
- pretty_lattice-0.1.0/web/src/app/cameraInteractionStore.ts +1 -0
- pretty_lattice-0.1.0/web/src/app/colorSchemes.ts +1 -0
- pretty_lattice-0.1.0/web/src/app/controls/CommonControlsPanel.tsx +4 -0
- pretty_lattice-0.1.0/web/src/app/controls/ViewControlRail.tsx +337 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/CommonControlsPanel.tsx +361 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/DisplayTab.tsx +377 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/ExportTab.tsx +733 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/MaterialPresetToken3D.tsx +202 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/OrientationTab.tsx +52 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/StyleTab.tsx +755 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/controlFeedback.ts +2 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/orientation/PrimaryAxisRollSection.tsx +105 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/orientation/RollControl.tsx +264 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/orientation/ScreenAxisChooser.tsx +387 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/orientation/VectorEditor.tsx +382 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/orientation/orientationControlMath.ts +71 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/orientation/vectorEditorModel.ts +117 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/sharedControls.tsx +269 -0
- pretty_lattice-0.1.0/web/src/app/controls/commonPanel/styles.ts +5 -0
- pretty_lattice-0.1.0/web/src/app/elementLegend.ts +40 -0
- pretty_lattice-0.1.0/web/src/app/elementRadii.ts +1 -0
- pretty_lattice-0.1.0/web/src/app/exportFigure.ts +173 -0
- pretty_lattice-0.1.0/web/src/app/hooks/useFigureExportController.ts +186 -0
- pretty_lattice-0.1.0/web/src/app/hooks/useLockedInteractionFeedback.ts +189 -0
- pretty_lattice-0.1.0/web/src/app/hooks/usePreviewCameraCommands.ts +411 -0
- pretty_lattice-0.1.0/web/src/app/hooks/useStructurePreview.ts +248 -0
- pretty_lattice-0.1.0/web/src/app/inspector/InspectorSidebar.tsx +648 -0
- pretty_lattice-0.1.0/web/src/app/layout/overlayLayout.ts +63 -0
- pretty_lattice-0.1.0/web/src/app/legend/ElementLegend.tsx +162 -0
- pretty_lattice-0.1.0/web/src/app/materialPresets.ts +1 -0
- pretty_lattice-0.1.0/web/src/app/panels/StructureSummaryCard.tsx +237 -0
- pretty_lattice-0.1.0/web/src/app/panels/structureSummaryFormatting.tsx +155 -0
- pretty_lattice-0.1.0/web/src/app/previewState.ts +23 -0
- pretty_lattice-0.1.0/web/src/app/surface.ts +19 -0
- pretty_lattice-0.1.0/web/src/app/symmetryNotation.tsx +54 -0
- pretty_lattice-0.1.0/web/src/app/viewState.ts +178 -0
- pretty_lattice-0.1.0/web/src/assets/fonts/Geist-MediumItalic.ttf +0 -0
- pretty_lattice-0.1.0/web/src/assets/fonts/Geist-Regular.ttf +0 -0
- pretty_lattice-0.1.0/web/src/components/ui/alert.tsx +87 -0
- pretty_lattice-0.1.0/web/src/components/ui/angle-slider.tsx +233 -0
- pretty_lattice-0.1.0/web/src/components/ui/badge.tsx +33 -0
- pretty_lattice-0.1.0/web/src/components/ui/button.tsx +55 -0
- pretty_lattice-0.1.0/web/src/components/ui/checkbox.tsx +37 -0
- pretty_lattice-0.1.0/web/src/components/ui/context-menu.tsx +215 -0
- pretty_lattice-0.1.0/web/src/components/ui/input.tsx +21 -0
- pretty_lattice-0.1.0/web/src/components/ui/popover.tsx +40 -0
- pretty_lattice-0.1.0/web/src/components/ui/select.tsx +188 -0
- pretty_lattice-0.1.0/web/src/components/ui/separator.tsx +26 -0
- pretty_lattice-0.1.0/web/src/components/ui/switch.tsx +59 -0
- pretty_lattice-0.1.0/web/src/components/ui/tabs.tsx +89 -0
- pretty_lattice-0.1.0/web/src/components/ui/toggle-group.tsx +83 -0
- pretty_lattice-0.1.0/web/src/components/ui/toggle.tsx +45 -0
- pretty_lattice-0.1.0/web/src/components/ui/tooltip.tsx +51 -0
- pretty_lattice-0.1.0/web/src/data/colormaps/catalog.json +30 -0
- pretty_lattice-0.1.0/web/src/data/colormaps/presets/jmol-soft.json +116 -0
- pretty_lattice-0.1.0/web/src/data/colormaps/presets/jmol.json +116 -0
- pretty_lattice-0.1.0/web/src/data/colormaps/presets/vesta-soft.json +102 -0
- pretty_lattice-0.1.0/web/src/data/colormaps/presets/vesta.json +102 -0
- pretty_lattice-0.1.0/web/src/data/element-radii.json +586 -0
- pretty_lattice-0.1.0/web/src/data/material-presets/catalog.json +5 -0
- pretty_lattice-0.1.0/web/src/data/material-presets/material-preset-template.jsonc +87 -0
- pretty_lattice-0.1.0/web/src/data/material-presets/presets/2-5d.json +42 -0
- pretty_lattice-0.1.0/web/src/data/material-presets/presets/2d.json +17 -0
- pretty_lattice-0.1.0/web/src/data/material-presets/presets/classic-matte.json +38 -0
- pretty_lattice-0.1.0/web/src/data/material-presets/presets/glossy.json +40 -0
- pretty_lattice-0.1.0/web/src/data/material-presets/presets/metallic.json +40 -0
- pretty_lattice-0.1.0/web/src/data/material-presets/presets/modern-matte.json +40 -0
- pretty_lattice-0.1.0/web/src/export/combinedExportFile.ts +54 -0
- pretty_lattice-0.1.0/web/src/export/combinedExportRaster.ts +302 -0
- pretty_lattice-0.1.0/web/src/export/crystalAxesExport.ts +103 -0
- pretty_lattice-0.1.0/web/src/export/fileNames.ts +9 -0
- pretty_lattice-0.1.0/web/src/export/legendExport.ts +252 -0
- pretty_lattice-0.1.0/web/src/export/pdfTextExport.ts +131 -0
- pretty_lattice-0.1.0/web/src/export/rasterCanvas.ts +142 -0
- pretty_lattice-0.1.0/web/src/export/structureExportFile.ts +60 -0
- pretty_lattice-0.1.0/web/src/export/structureRasterExport.ts +57 -0
- pretty_lattice-0.1.0/web/src/export/types.ts +31 -0
- pretty_lattice-0.1.0/web/src/export/zipExport.ts +188 -0
- pretty_lattice-0.1.0/web/src/lib/utils.ts +6 -0
- pretty_lattice-0.1.0/web/src/main.tsx +11 -0
- pretty_lattice-0.1.0/web/src/model/appearance.ts +123 -0
- pretty_lattice-0.1.0/web/src/model/cameraInteractionStore.ts +97 -0
- pretty_lattice-0.1.0/web/src/model/colorSchemes/autoDistinct.ts +432 -0
- pretty_lattice-0.1.0/web/src/model/colorSchemes/oklch.ts +143 -0
- pretty_lattice-0.1.0/web/src/model/colorSchemes.ts +387 -0
- pretty_lattice-0.1.0/web/src/model/crystalCameraState.ts +13 -0
- pretty_lattice-0.1.0/web/src/model/displayState.ts +181 -0
- pretty_lattice-0.1.0/web/src/model/elementRadii.ts +28 -0
- pretty_lattice-0.1.0/web/src/model/exportSettings.ts +501 -0
- pretty_lattice-0.1.0/web/src/model/index.ts +9 -0
- pretty_lattice-0.1.0/web/src/model/layout.ts +30 -0
- pretty_lattice-0.1.0/web/src/model/materialPresets.ts +472 -0
- pretty_lattice-0.1.0/web/src/model/previewFpsStore.ts +27 -0
- pretty_lattice-0.1.0/web/src/model/rendering.ts +26 -0
- pretty_lattice-0.1.0/web/src/model/structureLimits.ts +4 -0
- pretty_lattice-0.1.0/web/src/model/vector.ts +1 -0
- pretty_lattice-0.1.0/web/src/model/viewState.ts +193 -0
- pretty_lattice-0.1.0/web/src/scene/AtomSelectionRing.tsx +112 -0
- pretty_lattice-0.1.0/web/src/scene/BatchedBonds.tsx +260 -0
- pretty_lattice-0.1.0/web/src/scene/BatchedPolyhedra.tsx +496 -0
- pretty_lattice-0.1.0/web/src/scene/BondRenderItems.ts +81 -0
- pretty_lattice-0.1.0/web/src/scene/CameraHeadlight.tsx +63 -0
- pretty_lattice-0.1.0/web/src/scene/CellFrame.tsx +66 -0
- pretty_lattice-0.1.0/web/src/scene/ExportSceneContent.tsx +81 -0
- pretty_lattice-0.1.0/web/src/scene/InstancedAtoms.tsx +418 -0
- pretty_lattice-0.1.0/web/src/scene/LatticeScene.tsx +347 -0
- pretty_lattice-0.1.0/web/src/scene/MaterialPresetLights.tsx +132 -0
- pretty_lattice-0.1.0/web/src/scene/OrientationGizmo.tsx +564 -0
- pretty_lattice-0.1.0/web/src/scene/PreviewCameraController.tsx +745 -0
- pretty_lattice-0.1.0/web/src/scene/StructureMaterial.tsx +139 -0
- pretty_lattice-0.1.0/web/src/scene/StructureSceneObjects.tsx +355 -0
- pretty_lattice-0.1.0/web/src/scene/atomHighlight.ts +21 -0
- pretty_lattice-0.1.0/web/src/scene/cameraPose.ts +56 -0
- pretty_lattice-0.1.0/web/src/scene/crystalCamera.ts +622 -0
- pretty_lattice-0.1.0/web/src/scene/exportFrame.ts +269 -0
- pretty_lattice-0.1.0/web/src/scene/exportRenderer.tsx +650 -0
- pretty_lattice-0.1.0/web/src/scene/materialPresetResolver.ts +82 -0
- pretty_lattice-0.1.0/web/src/scene/orientationGizmoHitTesting.ts +137 -0
- pretty_lattice-0.1.0/web/src/scene/orientationGizmoMath.ts +58 -0
- pretty_lattice-0.1.0/web/src/scene/renderAppearance.ts +6 -0
- pretty_lattice-0.1.0/web/src/scene/rendererParameters.ts +5 -0
- pretty_lattice-0.1.0/web/src/scene/sceneGeometry.ts +75 -0
- pretty_lattice-0.1.0/web/src/scene/sceneLayout.ts +178 -0
- pretty_lattice-0.1.0/web/src/scene/structureGeometry.ts +118 -0
- pretty_lattice-0.1.0/web/src/scene/viewMath.ts +203 -0
- pretty_lattice-0.1.0/web/src/styles/global.css +557 -0
- pretty_lattice-0.1.0/web/src/vite-env.d.ts +10 -0
- pretty_lattice-0.1.0/web/tests/00-exportFigure.test.ts +58 -0
- pretty_lattice-0.1.0/web/tests/App.test.tsx +2301 -0
- pretty_lattice-0.1.0/web/tests/angleSlider.test.tsx +134 -0
- pretty_lattice-0.1.0/web/tests/atomInspector.test.ts +131 -0
- pretty_lattice-0.1.0/web/tests/colorSchemes.test.ts +210 -0
- pretty_lattice-0.1.0/web/tests/crystalCamera.test.ts +253 -0
- pretty_lattice-0.1.0/web/tests/elementLegend.test.ts +130 -0
- pretty_lattice-0.1.0/web/tests/elementRadii.test.ts +29 -0
- pretty_lattice-0.1.0/web/tests/exportLayout.test.ts +80 -0
- pretty_lattice-0.1.0/web/tests/helpers/appHarness.tsx +106 -0
- pretty_lattice-0.1.0/web/tests/latticeScene.test.ts +919 -0
- pretty_lattice-0.1.0/web/tests/latticeSceneCameraController.test.tsx +723 -0
- pretty_lattice-0.1.0/web/tests/materialPresets.test.ts +292 -0
- pretty_lattice-0.1.0/web/tests/orientationControlMath.test.ts +50 -0
- pretty_lattice-0.1.0/web/tests/orientationGizmoHitTesting.test.ts +87 -0
- pretty_lattice-0.1.0/web/tests/previewFpsStore.test.ts +33 -0
- pretty_lattice-0.1.0/web/tests/settings.test.ts +617 -0
- pretty_lattice-0.1.0/web/tests/setup.ts +12 -0
- pretty_lattice-0.1.0/web/tests/symmetryNotation.test.tsx +13 -0
- pretty_lattice-0.1.0/web/tests/vectorEditorModel.test.ts +99 -0
- pretty_lattice-0.1.0/web/tests/viewState.test.ts +157 -0
- pretty_lattice-0.1.0/web/tsconfig.json +26 -0
- pretty_lattice-0.1.0/web/vite.config.ts +32 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
python:
|
|
14
|
+
name: Python ${{ matrix.python-version }}
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
python-version:
|
|
20
|
+
- "3.12"
|
|
21
|
+
- "3.13"
|
|
22
|
+
- "3.14"
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- name: Check out repository
|
|
26
|
+
uses: actions/checkout@v7
|
|
27
|
+
|
|
28
|
+
- name: Set up Python
|
|
29
|
+
uses: actions/setup-python@v6
|
|
30
|
+
with:
|
|
31
|
+
python-version: ${{ matrix.python-version }}
|
|
32
|
+
|
|
33
|
+
- name: Set up uv
|
|
34
|
+
uses: astral-sh/setup-uv@v8.2.0
|
|
35
|
+
|
|
36
|
+
- name: Run ruff
|
|
37
|
+
run: uv run --python ${{ matrix.python-version }} ruff check .
|
|
38
|
+
|
|
39
|
+
- name: Run pytest
|
|
40
|
+
run: uv run --python ${{ matrix.python-version }} pytest
|
|
41
|
+
|
|
42
|
+
web:
|
|
43
|
+
name: Web
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
|
|
46
|
+
steps:
|
|
47
|
+
- name: Check out repository
|
|
48
|
+
uses: actions/checkout@v7
|
|
49
|
+
|
|
50
|
+
- name: Set up Bun
|
|
51
|
+
uses: oven-sh/setup-bun@v2
|
|
52
|
+
with:
|
|
53
|
+
bun-version: 1.3.13
|
|
54
|
+
|
|
55
|
+
- name: Install dependencies
|
|
56
|
+
working-directory: web
|
|
57
|
+
run: bun install --frozen-lockfile
|
|
58
|
+
|
|
59
|
+
- name: Run tests
|
|
60
|
+
working-directory: web
|
|
61
|
+
run: bun run test
|
|
62
|
+
|
|
63
|
+
- name: Run typecheck
|
|
64
|
+
working-directory: web
|
|
65
|
+
run: bun run typecheck
|
|
66
|
+
|
|
67
|
+
- name: Build
|
|
68
|
+
working-directory: web
|
|
69
|
+
run: bun run build
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
.codex/
|
|
3
|
+
|
|
4
|
+
# Python
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.eggs/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.pyre/
|
|
13
|
+
.coverage
|
|
14
|
+
htmlcov/
|
|
15
|
+
|
|
16
|
+
# Virtual environments
|
|
17
|
+
.venv/
|
|
18
|
+
venv/
|
|
19
|
+
env/
|
|
20
|
+
|
|
21
|
+
# uv
|
|
22
|
+
.python-version
|
|
23
|
+
|
|
24
|
+
# Node / Bun / frontend
|
|
25
|
+
node_modules/
|
|
26
|
+
dist/
|
|
27
|
+
build/
|
|
28
|
+
web/dist/
|
|
29
|
+
.vite/
|
|
30
|
+
.next/
|
|
31
|
+
coverage/
|
|
32
|
+
bun.lockb
|
|
33
|
+
*.tsbuildinfo
|
|
34
|
+
|
|
35
|
+
# Local QA artifacts
|
|
36
|
+
.playwright-cli/
|
|
37
|
+
output/playwright/
|
|
38
|
+
tmp/
|
|
39
|
+
tmp/visual-renders/
|
|
40
|
+
|
|
41
|
+
# Local configuration
|
|
42
|
+
.env
|
|
43
|
+
.env.*
|
|
44
|
+
!.env.example
|
|
45
|
+
|
|
46
|
+
# Editor and OS noise
|
|
47
|
+
.idea/
|
|
48
|
+
.vscode/
|
|
49
|
+
*.swp
|
|
50
|
+
*.swo
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Project context managed under `docs/`. Navigate by `docs/index.md`.
|
|
2
|
+
|
|
3
|
+
Use `uv` for python and `bun` for web. See `docs/development.md`.
|
|
4
|
+
|
|
5
|
+
Do not use browser or Playwright testing unless explicitly asked.
|
|
6
|
+
|
|
7
|
+
Use conventional commits style, such as `feat: ...` or `fix: ...`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Feitong Song
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pretty-lattice
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local web GUI for rendering crystal structures as publication-ready figures.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: fastapi>=0.115.0
|
|
9
|
+
Requires-Dist: numpy>=2.0.0
|
|
10
|
+
Requires-Dist: pymatgen-core>=2026.5.18
|
|
11
|
+
Requires-Dist: typer>=0.16.0
|
|
12
|
+
Requires-Dist: uvicorn[standard]>=0.30.0
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
<h1 align="center"><img src="web/public/favicon.svg" alt="Pretty Lattice logo" width="30" height="30"> Pretty Lattice</h1>
|
|
16
|
+
|
|
17
|
+
<p align="center">
|
|
18
|
+
Pretty Lattice is a crystal visualization tool for creating beautiful, publication-ready figures.
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<a href="https://github.com/songfeitong/pretty-lattice/actions/workflows/ci.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/songfeitong/pretty-lattice/ci.yml?branch=main&label=build&style=flat-square"></a>
|
|
23
|
+
<img alt="Python 3.12+" src="https://img.shields.io/badge/python-3.12+-3776ab?style=flat-square">
|
|
24
|
+
<img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-green?style=flat-square">
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
- **Pretty**: tasteful defaults for colors, materials, lighting, and depth
|
|
28
|
+
- **Simple**: an intuitive browser GUI for loading, viewing, and exporting structures
|
|
29
|
+
- **Reliable**: structure parsing and analysis powered by the mature [pymatgen](https://github.com/materialsproject/pymatgen) package
|
|
30
|
+
- **Scalable**: smooth interaction with systems up to 10k atoms
|
|
31
|
+
- **Customizable**: tune colors, radii, materials, opacity, orientation, and export settings
|
|
32
|
+
|
|
33
|
+
<p align="center">
|
|
34
|
+
<img src="assets/demo.png" alt="Pretty Lattice interface preview" width="90%">
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## Why
|
|
39
|
+
|
|
40
|
+
I always find it harder than it should be to make a good-looking crystal figure.
|
|
41
|
+
|
|
42
|
+
Traditional crystallographic tools such as VESTA are powerful, but their visual defaults often feel outdated: harsh color palettes, low-quality 3D shading, and a lot of manual tweaking before the result looks acceptable. You could import the structure into professional 3D software such as Cinema 4D or Blender, but that feels like overkill and comes with a much steeper learning curve.
|
|
43
|
+
|
|
44
|
+
Pretty Lattice is my attempt to fill that gap. Built on [Three.js](https://github.com/mrdoob/three.js), it stays (relatively) lightweight without compromising visual quality. It offers a modern, intuitive interface with familiar controls researchers expect, and produces clean, aesthetically pleasing figures out of the box.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```shell
|
|
49
|
+
pip install pretty-lattice
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or install as an isolated tool with [uv](https://github.com/astral-sh/uv):
|
|
53
|
+
|
|
54
|
+
```shell
|
|
55
|
+
uv tool install pretty-lattice
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Requirements:
|
|
59
|
+
|
|
60
|
+
- Python 3.12+
|
|
61
|
+
- macOS, Linux, or Windows
|
|
62
|
+
- Any modern browser
|
|
63
|
+
|
|
64
|
+
## Quick start
|
|
65
|
+
|
|
66
|
+
```shell
|
|
67
|
+
prl gui
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Pretty Lattice starts a local server and opens the browser automatically.
|
|
71
|
+
|
|
72
|
+
Useful launch options:
|
|
73
|
+
|
|
74
|
+
```shell
|
|
75
|
+
prl gui --no-open # start the server without opening a browser
|
|
76
|
+
prl gui --port 8765 # use a specific port
|
|
77
|
+
prl gui -p 0 # choose any free port automatically
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Examples
|
|
81
|
+
|
|
82
|
+
### Material presets
|
|
83
|
+
|
|
84
|
+
<p align="center">
|
|
85
|
+
<img src="assets/SrTiO3-material-presets.png" alt="SrTiO3 material preset examples" width="75%">
|
|
86
|
+
</p>
|
|
87
|
+
|
|
88
|
+
### Color scheme presets
|
|
89
|
+
|
|
90
|
+
<p align="center">
|
|
91
|
+
<img src="assets/Ba2Ca2Cu3HgO8-color-schemes.png" alt="Ba2Ca2Cu3HgO8 color scheme examples" width="90%">
|
|
92
|
+
</p>
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
Pretty Lattice is released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<h1 align="center"><img src="web/public/favicon.svg" alt="Pretty Lattice logo" width="30" height="30"> Pretty Lattice</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
Pretty Lattice is a crystal visualization tool for creating beautiful, publication-ready figures.
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://github.com/songfeitong/pretty-lattice/actions/workflows/ci.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/songfeitong/pretty-lattice/ci.yml?branch=main&label=build&style=flat-square"></a>
|
|
9
|
+
<img alt="Python 3.12+" src="https://img.shields.io/badge/python-3.12+-3776ab?style=flat-square">
|
|
10
|
+
<img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-green?style=flat-square">
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
- **Pretty**: tasteful defaults for colors, materials, lighting, and depth
|
|
14
|
+
- **Simple**: an intuitive browser GUI for loading, viewing, and exporting structures
|
|
15
|
+
- **Reliable**: structure parsing and analysis powered by the mature [pymatgen](https://github.com/materialsproject/pymatgen) package
|
|
16
|
+
- **Scalable**: smooth interaction with systems up to 10k atoms
|
|
17
|
+
- **Customizable**: tune colors, radii, materials, opacity, orientation, and export settings
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="assets/demo.png" alt="Pretty Lattice interface preview" width="90%">
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Why
|
|
25
|
+
|
|
26
|
+
I always find it harder than it should be to make a good-looking crystal figure.
|
|
27
|
+
|
|
28
|
+
Traditional crystallographic tools such as VESTA are powerful, but their visual defaults often feel outdated: harsh color palettes, low-quality 3D shading, and a lot of manual tweaking before the result looks acceptable. You could import the structure into professional 3D software such as Cinema 4D or Blender, but that feels like overkill and comes with a much steeper learning curve.
|
|
29
|
+
|
|
30
|
+
Pretty Lattice is my attempt to fill that gap. Built on [Three.js](https://github.com/mrdoob/three.js), it stays (relatively) lightweight without compromising visual quality. It offers a modern, intuitive interface with familiar controls researchers expect, and produces clean, aesthetically pleasing figures out of the box.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```shell
|
|
35
|
+
pip install pretty-lattice
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or install as an isolated tool with [uv](https://github.com/astral-sh/uv):
|
|
39
|
+
|
|
40
|
+
```shell
|
|
41
|
+
uv tool install pretty-lattice
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Requirements:
|
|
45
|
+
|
|
46
|
+
- Python 3.12+
|
|
47
|
+
- macOS, Linux, or Windows
|
|
48
|
+
- Any modern browser
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
```shell
|
|
53
|
+
prl gui
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Pretty Lattice starts a local server and opens the browser automatically.
|
|
57
|
+
|
|
58
|
+
Useful launch options:
|
|
59
|
+
|
|
60
|
+
```shell
|
|
61
|
+
prl gui --no-open # start the server without opening a browser
|
|
62
|
+
prl gui --port 8765 # use a specific port
|
|
63
|
+
prl gui -p 0 # choose any free port automatically
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Examples
|
|
67
|
+
|
|
68
|
+
### Material presets
|
|
69
|
+
|
|
70
|
+
<p align="center">
|
|
71
|
+
<img src="assets/SrTiO3-material-presets.png" alt="SrTiO3 material preset examples" width="75%">
|
|
72
|
+
</p>
|
|
73
|
+
|
|
74
|
+
### Color scheme presets
|
|
75
|
+
|
|
76
|
+
<p align="center">
|
|
77
|
+
<img src="assets/Ba2Ca2Cu3HgO8-color-schemes.png" alt="Ba2Ca2Cu3HgO8 color scheme examples" width="90%">
|
|
78
|
+
</p>
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
Pretty Lattice is released under the [MIT License](LICENSE).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Project Constitution
|
|
2
|
+
|
|
3
|
+
## Product Definition
|
|
4
|
+
|
|
5
|
+
Pretty Lattice is a **local web GUI tool** for interactive preview and rendering crystal structures as pretty, publication-ready figures.
|
|
6
|
+
|
|
7
|
+
The workflow is simple:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
open a structure file
|
|
11
|
+
→ preview it interactively
|
|
12
|
+
→ adjust visual parameters and layouts
|
|
13
|
+
→ export a high-quality figure
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
Crystal workbenches like VESTA are useful and powerful, but on the visualization and figure side, they feel somewhat outdated. The figures often don’t really feel like they belong to the 2020s. You can tweak them, but it rarely feels enough.
|
|
19
|
+
|
|
20
|
+
For better visuals, people sometimes turn to C4D or Blender. But personally, I’m reluctant to learn these heavy industry tools just to make a nice crystal figure. It feels like overkill for the task.
|
|
21
|
+
|
|
22
|
+
So I decided to build a lightweight tool based on modern web tech (Three.js), that can generate pretty, publication-ready figures out of the box, while still being highly configurable and providing a smooth interactive preview.
|
|
23
|
+
|
|
24
|
+
## Principles
|
|
25
|
+
|
|
26
|
+
- Easy to use: WYSIWYG. Easy to navigate. Intuitive interaction.
|
|
27
|
+
- Yet powerful: more control and configurations (on the visual side) than VESTA.
|
|
28
|
+
- Publication-ready: this project is driven by my own need for publication-ready figures.
|
|
29
|
+
- Focused: Focused on viewing and rendering. Read-only on structure file. Prepare elsewhere.
|
|
30
|
+
- Modern: modern UI/UX, modern tech stack, modern asetetics.
|
|
31
|
+
|
|
32
|
+
## Release Shape
|
|
33
|
+
|
|
34
|
+
Developer side:
|
|
35
|
+
|
|
36
|
+
```mermaid
|
|
37
|
+
flowchart LR
|
|
38
|
+
A[Web source<br/>Bun + React + Three.js] --> B[Build static web assets]
|
|
39
|
+
B --> C[Bundle assets into Python package]
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Users install a Python package and launch a local GUI via the command `prl gui`. This starts a local Python server and opens a browser page. The browser runs the bundled web app and uses Three.js/WebGL for rendering.
|
|
44
|
+
|
|
45
|
+
```mermaid
|
|
46
|
+
flowchart LR
|
|
47
|
+
D[User installs pretty-lattice]
|
|
48
|
+
D --> E[prl gui]
|
|
49
|
+
E --> F[Local browser GUI]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Users should **not need JS developer tools** (Node, Bun, pnpm, Vite, etc.).
|
|
53
|
+
|
|
54
|
+
## Tech Stack
|
|
55
|
+
|
|
56
|
+
Pretty Lattice keeps a clear backend/frontend boundary. The Python backend owns structure IO, materials analysis, and scene generation. The Web frontend owns visual interaction and rendering. The browser may use lattice-derived scene data for camera and view controls, but crystallographic or materials-analysis decisions should stay in Python unless they are purely presentational.
|
|
57
|
+
|
|
58
|
+
### Backend: Python
|
|
59
|
+
|
|
60
|
+
Responsibilities:
|
|
61
|
+
|
|
62
|
+
- Read crystal structure files such as CIF and POSCAR.
|
|
63
|
+
- Run materials analysis, including bonding, polyhedra, and symmetry.
|
|
64
|
+
- Convert analyzed structures into scene specifications for the Web app.
|
|
65
|
+
- Serve the local GUI and API.
|
|
66
|
+
|
|
67
|
+
Stack:
|
|
68
|
+
|
|
69
|
+
- Dev tools: uv, pytest, ruff
|
|
70
|
+
- FastAPI + Uvicorn
|
|
71
|
+
- Typer
|
|
72
|
+
- Pymatgen
|
|
73
|
+
- Numpy
|
|
74
|
+
|
|
75
|
+
### Frontend: Web
|
|
76
|
+
|
|
77
|
+
Responsibilities:
|
|
78
|
+
|
|
79
|
+
- Render and interact with the scene returned by the backend.
|
|
80
|
+
- Handle camera, lighting, materials, visual parameters, and browser export.
|
|
81
|
+
- Keep UI state and controls close to the visual workflow.
|
|
82
|
+
|
|
83
|
+
Stack:
|
|
84
|
+
|
|
85
|
+
- Dev tools: Bun, Vite
|
|
86
|
+
- Front-end framework: React
|
|
87
|
+
- UI: Tailwind CSS, shadcn/ui, Radix, lucide
|
|
88
|
+
- 3D: Three.js + React Three Fiber
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Use Cyclic Crystal Orientation for Camera Roll Zero
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted 2026-06-30.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Crystal camera controls need two choices:
|
|
10
|
+
|
|
11
|
+
- which crystal direction is aligned to a screen axis;
|
|
12
|
+
- which roll angle is treated as zero around that aligned direction.
|
|
13
|
+
|
|
14
|
+
The first choice is explicit. The second choice is otherwise underdetermined:
|
|
15
|
+
after one direction is fixed, the camera can still rotate freely around that
|
|
16
|
+
direction.
|
|
17
|
+
|
|
18
|
+
VESTA-style direct-axis views appear to use a simple cyclic convention. When a
|
|
19
|
+
direct crystal axis points out of the screen, the next direct axis projects to
|
|
20
|
+
screen right:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
a -> outward means b projects right
|
|
24
|
+
b -> outward means c projects right
|
|
25
|
+
c -> outward means a projects right
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Pretty Lattice should use this as the default roll convention, and extend it to
|
|
29
|
+
arbitrary direct-lattice view directions.
|
|
30
|
+
|
|
31
|
+
## Decision
|
|
32
|
+
|
|
33
|
+
Use one cyclic rule for both discrete axis presets and continuous direct-lattice
|
|
34
|
+
directions.
|
|
35
|
+
|
|
36
|
+
Let the direct basis be
|
|
37
|
+
|
|
38
|
+
$$
|
|
39
|
+
\mathbf a,\quad \mathbf b,\quad \mathbf c
|
|
40
|
+
$$
|
|
41
|
+
|
|
42
|
+
and let the screen basis be
|
|
43
|
+
|
|
44
|
+
$$
|
|
45
|
+
X=\text{right},\qquad Y=\text{up},\qquad Z=\text{outward},
|
|
46
|
+
\qquad X\times Y=Z.
|
|
47
|
+
$$
|
|
48
|
+
|
|
49
|
+
Both bases are treated cyclically:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
a -> b -> c -> a
|
|
53
|
+
X -> Y -> Z -> X
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
For a requested direct-lattice direction
|
|
57
|
+
|
|
58
|
+
$$
|
|
59
|
+
\mathbf d = u\mathbf a + v\mathbf b + w\mathbf c,
|
|
60
|
+
$$
|
|
61
|
+
|
|
62
|
+
define the cyclic crystal transport
|
|
63
|
+
|
|
64
|
+
$$
|
|
65
|
+
T(\mathbf a)=\mathbf b,\qquad
|
|
66
|
+
T(\mathbf b)=\mathbf c,\qquad
|
|
67
|
+
T(\mathbf c)=\mathbf a.
|
|
68
|
+
$$
|
|
69
|
+
|
|
70
|
+
Therefore
|
|
71
|
+
|
|
72
|
+
$$
|
|
73
|
+
T(\mathbf d)=u\mathbf b+v\mathbf c+w\mathbf a
|
|
74
|
+
=w\mathbf a+u\mathbf b+v\mathbf c.
|
|
75
|
+
$$
|
|
76
|
+
|
|
77
|
+
If the user aligns $\mathbf d$ to screen axis $S$, first normalize
|
|
78
|
+
|
|
79
|
+
$$
|
|
80
|
+
\mathbf p = \frac{\mathbf d}{\lVert \mathbf d\rVert}.
|
|
81
|
+
$$
|
|
82
|
+
|
|
83
|
+
Then use $T(\mathbf d)$ to define the next screen axis. With
|
|
84
|
+
$S^+=\operatorname{next}(S)$, project the cyclic anchor into the screen plane:
|
|
85
|
+
|
|
86
|
+
$$
|
|
87
|
+
\Pi_{\mathbf p}(\mathbf q_0)
|
|
88
|
+
= \mathbf q_0 - (\mathbf q_0\cdot \mathbf p)\mathbf p,
|
|
89
|
+
\qquad
|
|
90
|
+
\mathbf q_0 = T(\mathbf d).
|
|
91
|
+
$$
|
|
92
|
+
|
|
93
|
+
If this projection is nonzero, normalize it:
|
|
94
|
+
|
|
95
|
+
$$
|
|
96
|
+
\mathbf q =
|
|
97
|
+
\frac{\Pi_{\mathbf p}(\mathbf q_0)}
|
|
98
|
+
{\lVert \Pi_{\mathbf p}(\mathbf q_0)\rVert}.
|
|
99
|
+
$$
|
|
100
|
+
|
|
101
|
+
The camera frame is then defined by
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
screen[S] = p
|
|
105
|
+
screen[next(S)] = q
|
|
106
|
+
screen[next2(S)] is completed by the right-hand rule
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
For example, if $S=Z$, then `next(S) = X`, so the cyclic anchor becomes the
|
|
110
|
+
screen-right direction. This gives:
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
a -> Z means b projects to X
|
|
114
|
+
b -> Z means c projects to X
|
|
115
|
+
c -> Z means a projects to X
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The same rule works for any target screen axis:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
a -> X means b projects to Y
|
|
122
|
+
a -> Y means b projects to Z
|
|
123
|
+
a -> Z means b projects to X
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Fallback
|
|
127
|
+
|
|
128
|
+
If the cyclic anchor is degenerate after projection, use the direct `c` axis as
|
|
129
|
+
the fallback roll reference:
|
|
130
|
+
|
|
131
|
+
$$
|
|
132
|
+
\mathbf q_0 = \mathbf c.
|
|
133
|
+
$$
|
|
134
|
+
|
|
135
|
+
If that is also degenerate, use the direct `a` axis:
|
|
136
|
+
|
|
137
|
+
$$
|
|
138
|
+
\mathbf q_0 = \mathbf a.
|
|
139
|
+
$$
|
|
140
|
+
|
|
141
|
+
Each fallback uses the same projection formula. This keeps the rule simple:
|
|
142
|
+
prefer cyclic transport, otherwise keep the special vertical role of `c`, and
|
|
143
|
+
only then fall back to `a`.
|
|
144
|
+
|
|
145
|
+
Zero-length requested directions are invalid input and should be rejected or
|
|
146
|
+
replaced by the current/default camera direction before this algorithm runs.
|
|
147
|
+
|
|
148
|
+
## Consequences
|
|
149
|
+
|
|
150
|
+
- Direct-axis presets and arbitrary `[u v w]` directions share one camera-roll
|
|
151
|
+
definition.
|
|
152
|
+
- The discrete `a`, `b`, and `c` views match the observed VESTA-like cyclic
|
|
153
|
+
direct-axis behavior.
|
|
154
|
+
- The algorithm is independent of cell angles because all projections are done
|
|
155
|
+
in Cartesian space after forming the real direct-lattice vectors.
|
|
156
|
+
- The fallback is intentionally small and explainable. It treats `c` as the
|
|
157
|
+
preferred structural reference when the cyclic transport cannot determine a
|
|
158
|
+
roll angle.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Decisions
|
|
2
|
+
|
|
3
|
+
Architecture and product decisions that should remain easy to find.
|
|
4
|
+
|
|
5
|
+
- [Keep Camera Interaction in Three.js](keep-camera-interaction-in-threejs.md)
|
|
6
|
+
- [Use Cyclic Crystal Orientation for Camera Roll Zero](cyclic-crystal-camera-orientation.md)
|
|
7
|
+
- [Use Pymatgen as the Python Structure Backend](use-pymatgen-backend.md)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Keep Camera Interaction in Three.js
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted. Updated 2026-06-26 to clarify the camera snapshot and command
|
|
6
|
+
boundary after the camera-direction UI work.
|
|
7
|
+
|
|
8
|
+
## Plain Version
|
|
9
|
+
|
|
10
|
+
We tried two mental models for preview zoom.
|
|
11
|
+
|
|
12
|
+
The first model was **React-owned zoom**:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
wheel event
|
|
16
|
+
-> compute a new viewScale in React
|
|
17
|
+
-> update React state
|
|
18
|
+
-> re-render the preview tree
|
|
19
|
+
-> write the new zoom back to the Three.js camera
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
That was easy to wire to the zoom slider and percent input, but it made a small
|
|
23
|
+
camera movement travel through a large React tree. With small structures this
|
|
24
|
+
felt fine. With structures containing thousands of atoms, one scroll gesture
|
|
25
|
+
could make React revisit many atoms, bonds, and polyhedra over and over.
|
|
26
|
+
|
|
27
|
+
The second model is **Three.js-owned camera interaction**:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
wheel or drag input
|
|
31
|
+
-> Three.js controls update the camera directly
|
|
32
|
+
-> small UI controls read snapshots of the resulting camera state
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This is closer to how rotation already works. The camera interaction stays
|
|
36
|
+
light, and the React UI follows it instead of driving every tiny input event.
|
|
37
|
+
|
|
38
|
+
## Context
|
|
39
|
+
|
|
40
|
+
Pretty Lattice uses React for application UI and React Three Fiber / Three.js
|
|
41
|
+
for the crystal preview. The preview can contain many renderable objects, so
|
|
42
|
+
high-frequency input should avoid unnecessary React work.
|
|
43
|
+
|
|
44
|
+
Rotation already felt smooth because TrackballControls changed the camera
|
|
45
|
+
directly. Zoom originally took a different route because the left rail needed a
|
|
46
|
+
shared value for the slider, percent input, reset, and clamp. That made the UI
|
|
47
|
+
state convenient, but it also made zoom heavier than rotation.
|
|
48
|
+
|
|
49
|
+
The performance issue became visible with larger structures: zoom could feel
|
|
50
|
+
like it was growing while trembling, while rotation remained smooth.
|
|
51
|
+
|
|
52
|
+
## Decision
|
|
53
|
+
|
|
54
|
+
High-frequency camera interaction belongs in Three.js controls.
|
|
55
|
+
|
|
56
|
+
TrackballControls and OrbitControls should own wheel, touch, and middle-button
|
|
57
|
+
zoom behavior. React may display camera values such as view scale, primary
|
|
58
|
+
direction, roll, and derived vectors, but those values are snapshots for UI, not
|
|
59
|
+
the ownership path for every controls event.
|
|
60
|
+
|
|
61
|
+
Bounds such as 20%-500% should be translated into Three.js camera zoom limits.
|
|
62
|
+
The rail slider, percent input, and reset button may still set a target view
|
|
63
|
+
scale, but after that the camera controller applies it to the camera.
|
|
64
|
+
|
|
65
|
+
For camera data that needs to cross the Three.js / React boundary, use two
|
|
66
|
+
separate channels:
|
|
67
|
+
|
|
68
|
+
- **Snapshots:** Three.js controls publish the current camera state to narrowly
|
|
69
|
+
subscribed UI widgets. These snapshots should not live in top-level app state
|
|
70
|
+
if changing them would re-render the crystal preview tree.
|
|
71
|
+
- **Commands:** UI widgets send target camera changes to the camera controller.
|
|
72
|
+
The controller applies the command directly to the camera and then publishes a
|
|
73
|
+
fresh snapshot.
|
|
74
|
+
|
|
75
|
+
This boundary is the fix. Frame coalescing, epsilon tuning, and idle-delay
|
|
76
|
+
adjustments can improve polish, but they should not be the primary performance
|
|
77
|
+
strategy.
|
|
78
|
+
|
|
79
|
+
Orientation-derived UI should follow the same rule. During an active drag,
|
|
80
|
+
inertial rotation, or camera animation, controls such as the roll wheel,
|
|
81
|
+
direction vectors, and linked aspect-ratio display may hold their last committed
|
|
82
|
+
snapshot. Once camera motion settles, they can jump to the new committed value.
|
|
83
|
+
That keeps the UI readable without moving the high-frequency camera loop into
|
|
84
|
+
React.
|
|
85
|
+
|
|
86
|
+
## Consequences
|
|
87
|
+
|
|
88
|
+
- Rotation and zoom use the same interaction ownership model: Three.js handles
|
|
89
|
+
the fast camera movement.
|
|
90
|
+
- React remains responsible for visible controls and user-facing state, but it
|
|
91
|
+
should not re-render the crystal object tree for every wheel tick.
|
|
92
|
+
- The zoom rail can stay synchronized by listening to camera snapshots, while
|
|
93
|
+
rail input sends commands back to the camera controller.
|
|
94
|
+
- App-level state should store low-frequency configuration and user intent, not
|
|
95
|
+
live camera snapshots that change every frame.
|
|
96
|
+
- Future camera features should first ask whether they are high-frequency
|
|
97
|
+
interaction or low-frequency UI configuration. High-frequency behavior should
|
|
98
|
+
stay near the Three.js camera layer; React should subscribe to snapshots or
|
|
99
|
+
send commands across that boundary.
|