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.
Files changed (324) hide show
  1. pretty_lattice-0.1.0/.github/workflows/ci.yml +69 -0
  2. pretty_lattice-0.1.0/.gitignore +50 -0
  3. pretty_lattice-0.1.0/AGENTS.md +7 -0
  4. pretty_lattice-0.1.0/LICENSE +21 -0
  5. pretty_lattice-0.1.0/PKG-INFO +96 -0
  6. pretty_lattice-0.1.0/README.md +82 -0
  7. pretty_lattice-0.1.0/assets/Ba2Ca2Cu3HgO8-color-schemes.png +0 -0
  8. pretty_lattice-0.1.0/assets/SrTiO3-material-presets.png +0 -0
  9. pretty_lattice-0.1.0/assets/demo.png +0 -0
  10. pretty_lattice-0.1.0/docs/constitution.md +88 -0
  11. pretty_lattice-0.1.0/docs/decisions/cyclic-crystal-camera-orientation.md +158 -0
  12. pretty_lattice-0.1.0/docs/decisions/index.md +7 -0
  13. pretty_lattice-0.1.0/docs/decisions/keep-camera-interaction-in-threejs.md +99 -0
  14. pretty_lattice-0.1.0/docs/decisions/use-pymatgen-backend.md +69 -0
  15. pretty_lattice-0.1.0/docs/development.md +59 -0
  16. pretty_lattice-0.1.0/docs/front-end.md +38 -0
  17. pretty_lattice-0.1.0/docs/index.md +18 -0
  18. 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
  19. pretty_lattice-0.1.0/docs/notes/ChatGPT-VESTA standard view/350/247/243/346/236/220.md" +96 -0
  20. 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
  21. 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
  22. 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
  23. pretty_lattice-0.1.0/docs/notes/backend_architecture_scan_2026-06-27.md +169 -0
  24. pretty_lattice-0.1.0/docs/notes/camera_orientation_math.md +143 -0
  25. pretty_lattice-0.1.0/docs/notes/crystal_toolkit_boundary_report.md +119 -0
  26. pretty_lattice-0.1.0/docs/notes/frontend_architecture_scan_2026-06-27.md +183 -0
  27. pretty_lattice-0.1.0/docs/notes/index.md +27 -0
  28. pretty_lattice-0.1.0/docs/notes/naumann_standard_view_algorithm.md +346 -0
  29. pretty_lattice-0.1.0/docs/notes/polyhedra_rendering_research.md +136 -0
  30. pretty_lattice-0.1.0/docs/notes/pymatgen_prl_report.md +277 -0
  31. pretty_lattice-0.1.0/docs/notes/thermo_nuclear_code_quality_review_2026-06-30.md +295 -0
  32. pretty_lattice-0.1.0/docs/notes/threejs-best-practices-100-tips.md +1404 -0
  33. pretty_lattice-0.1.0/docs/notes/threejs_preview_performance_handoff_2026-06-27.md +114 -0
  34. pretty_lattice-0.1.0/docs/notes/vercel_design.md +499 -0
  35. pretty_lattice-0.1.0/docs/notes/vesta_standard_view_notes.md +335 -0
  36. pretty_lattice-0.1.0/docs/notes/webgpu-threejs-migration-guide.md +834 -0
  37. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-21-load-structure-preview/.openspec.yaml +2 -0
  38. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-21-load-structure-preview/design.md +107 -0
  39. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-21-load-structure-preview/proposal.md +41 -0
  40. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-21-load-structure-preview/specs/structure-preview/spec.md +146 -0
  41. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-21-load-structure-preview/tasks.md +26 -0
  42. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-element-legend/.openspec.yaml +2 -0
  43. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-element-legend/design.md +116 -0
  44. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-element-legend/proposal.md +39 -0
  45. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-element-legend/specs/structure-preview/spec.md +71 -0
  46. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-element-legend/tasks.md +17 -0
  47. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-periodic-boundary-display/.openspec.yaml +2 -0
  48. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-periodic-boundary-display/design.md +67 -0
  49. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-periodic-boundary-display/proposal.md +26 -0
  50. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-periodic-boundary-display/specs/structure-preview/spec.md +118 -0
  51. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-22-add-periodic-boundary-display/tasks.md +17 -0
  52. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-add-interactive-view-controls/.openspec.yaml +2 -0
  53. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-add-interactive-view-controls/design.md +83 -0
  54. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-add-interactive-view-controls/proposal.md +32 -0
  55. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-add-interactive-view-controls/specs/structure-preview/spec.md +151 -0
  56. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-add-interactive-view-controls/tasks.md +26 -0
  57. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-migrate-to-pymatgen-backend/.openspec.yaml +2 -0
  58. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-migrate-to-pymatgen-backend/design.md +87 -0
  59. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-migrate-to-pymatgen-backend/proposal.md +46 -0
  60. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-migrate-to-pymatgen-backend/specs/structure-backend/spec.md +88 -0
  61. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-migrate-to-pymatgen-backend/specs/structure-preview/spec.md +84 -0
  62. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-23-migrate-to-pymatgen-backend/tasks.md +32 -0
  63. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-bonds-components-panel/.openspec.yaml +2 -0
  64. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-bonds-components-panel/design.md +81 -0
  65. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-bonds-components-panel/proposal.md +34 -0
  66. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-bonds-components-panel/specs/structure-backend/spec.md +74 -0
  67. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-bonds-components-panel/specs/structure-preview/spec.md +249 -0
  68. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-bonds-components-panel/tasks.md +31 -0
  69. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-crystal-toolkit-polyhedra/.openspec.yaml +2 -0
  70. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-crystal-toolkit-polyhedra/design.md +82 -0
  71. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-crystal-toolkit-polyhedra/proposal.md +32 -0
  72. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-crystal-toolkit-polyhedra/specs/structure-backend/spec.md +70 -0
  73. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-crystal-toolkit-polyhedra/specs/structure-preview/spec.md +144 -0
  74. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-24-add-crystal-toolkit-polyhedra/tasks.md +28 -0
  75. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-25-add-figure-export/.openspec.yaml +2 -0
  76. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-25-add-figure-export/design.md +149 -0
  77. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-25-add-figure-export/proposal.md +52 -0
  78. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-25-add-figure-export/specs/structure-preview/spec.md +169 -0
  79. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-25-add-figure-export/tasks.md +38 -0
  80. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-26-add-crystal-camera-controls/.openspec.yaml +2 -0
  81. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-26-add-crystal-camera-controls/design.md +76 -0
  82. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-26-add-crystal-camera-controls/proposal.md +31 -0
  83. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-26-add-crystal-camera-controls/specs/structure-preview/spec.md +187 -0
  84. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-26-add-crystal-camera-controls/tasks.md +27 -0
  85. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-27-add-material-presets/.openspec.yaml +2 -0
  86. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-27-add-material-presets/design.md +71 -0
  87. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-27-add-material-presets/proposal.md +32 -0
  88. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-27-add-material-presets/specs/structure-preview/spec.md +96 -0
  89. pretty_lattice-0.1.0/openspec/changes/archive/2026-06-27-add-material-presets/tasks.md +33 -0
  90. pretty_lattice-0.1.0/openspec/config.yaml +16 -0
  91. pretty_lattice-0.1.0/openspec/specs/structure-backend/spec.md +234 -0
  92. pretty_lattice-0.1.0/openspec/specs/structure-preview/spec.md +887 -0
  93. pretty_lattice-0.1.0/pyproject.toml +40 -0
  94. pretty_lattice-0.1.0/scripts/build_release.py +141 -0
  95. pretty_lattice-0.1.0/scripts/generate_static_scene.py +24 -0
  96. pretty_lattice-0.1.0/scripts/soften_colormap.py +307 -0
  97. pretty_lattice-0.1.0/src/pretty_lattice/__init__.py +3 -0
  98. pretty_lattice-0.1.0/src/pretty_lattice/cli.py +91 -0
  99. pretty_lattice-0.1.0/src/pretty_lattice/server/__init__.py +1 -0
  100. pretty_lattice-0.1.0/src/pretty_lattice/server/app.py +175 -0
  101. pretty_lattice-0.1.0/src/pretty_lattice/server/routes.py +66 -0
  102. pretty_lattice-0.1.0/src/pretty_lattice/structures/__init__.py +1 -0
  103. pretty_lattice-0.1.0/src/pretty_lattice/structures/connectivity.py +260 -0
  104. pretty_lattice-0.1.0/src/pretty_lattice/structures/periodic_images.py +296 -0
  105. pretty_lattice-0.1.0/src/pretty_lattice/structures/polyhedra.py +349 -0
  106. pretty_lattice-0.1.0/src/pretty_lattice/structures/readers.py +55 -0
  107. pretty_lattice-0.1.0/src/pretty_lattice/structures/scene.py +50 -0
  108. pretty_lattice-0.1.0/src/pretty_lattice/structures/scene_builder.py +138 -0
  109. pretty_lattice-0.1.0/src/pretty_lattice/structures/scene_contract.json +30 -0
  110. pretty_lattice-0.1.0/src/pretty_lattice/structures/schema.py +131 -0
  111. pretty_lattice-0.1.0/src/pretty_lattice/structures/summary.py +100 -0
  112. pretty_lattice-0.1.0/src/pretty_lattice/structures/symmetry.py +50 -0
  113. pretty_lattice-0.1.0/src/pretty_lattice/structures/visibility.py +83 -0
  114. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/Geist-MediumItalic-tzJ114MP.ttf +0 -0
  115. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/Geist-Regular-BhgZPHH5.ttf +0 -0
  116. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/exportRenderer-CZpjPvzO.js +1 -0
  117. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/fontkit.es-B4yDa3WR.js +47 -0
  118. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-400-normal-B40WzpMT.woff2 +0 -0
  119. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-400-normal-cWY99Cna.woff +0 -0
  120. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-500-italic-C61nW-2l.woff +0 -0
  121. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-500-italic-U3jG8IXN.woff2 +0 -0
  122. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-500-normal-CHEM4JuE.woff +0 -0
  123. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-500-normal-CTWBw9NS.woff2 +0 -0
  124. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-600-italic-BAmgU4ZR.woff +0 -0
  125. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-600-italic-VAfbuvIv.woff2 +0 -0
  126. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-600-normal-BeQEdSAO.woff +0 -0
  127. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-600-normal-CSETrqM2.woff2 +0 -0
  128. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-700-italic-B0LJ-tM0.woff +0 -0
  129. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-700-italic-CiSW-G5G.woff2 +0 -0
  130. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-700-normal-CFi8mLqe.woff2 +0 -0
  131. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-latin-700-normal-RGxhsL9r.woff +0 -0
  132. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-mono-latin-400-normal-BXAprPdR.woff +0 -0
  133. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-mono-latin-400-normal-DKaoCDn5.woff2 +0 -0
  134. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-mono-latin-500-normal-C3sF8Y1B.woff2 +0 -0
  135. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-mono-latin-500-normal-YfPbDI_o.woff +0 -0
  136. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-mono-latin-600-normal-BHzjB6_C.woff2 +0 -0
  137. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/geist-mono-latin-600-normal-C5fp8g0r.woff +0 -0
  138. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/index-ClX6i6DT.css +1 -0
  139. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/index-CwkjM2YR.js +51 -0
  140. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/index-xllU6-g1.js +4442 -0
  141. pretty_lattice-0.1.0/src/pretty_lattice/web_static/assets/index-ztFMT2Ax.js +1 -0
  142. pretty_lattice-0.1.0/src/pretty_lattice/web_static/examples/Al2O3.scene.json +1 -0
  143. pretty_lattice-0.1.0/src/pretty_lattice/web_static/examples/LiFePO4.scene.json +6524 -0
  144. pretty_lattice-0.1.0/src/pretty_lattice/web_static/favicon.svg +16 -0
  145. pretty_lattice-0.1.0/src/pretty_lattice/web_static/index.html +14 -0
  146. pretty_lattice-0.1.0/tests/fixtures/structures/Al2O3.cif +68 -0
  147. pretty_lattice-0.1.0/tests/fixtures/structures/Ba2Ca2Cu3HgO8.cif +42 -0
  148. pretty_lattice-0.1.0/tests/fixtures/structures/Hg3Cl4O.cif +58 -0
  149. pretty_lattice-0.1.0/tests/fixtures/structures/LiFePO4.cif +61 -0
  150. pretty_lattice-0.1.0/tests/fixtures/structures/MoS2.cif +37 -0
  151. pretty_lattice-0.1.0/tests/fixtures/structures/NaCl.cif +39 -0
  152. pretty_lattice-0.1.0/tests/fixtures/structures/SOURCE_NOTES.md +38 -0
  153. pretty_lattice-0.1.0/tests/fixtures/structures/Si.cif +38 -0
  154. pretty_lattice-0.1.0/tests/fixtures/structures/Sm(Mo3S4)2.cif +77 -0
  155. pretty_lattice-0.1.0/tests/fixtures/structures/SrTiO3.cif +37 -0
  156. pretty_lattice-0.1.0/tests/fixtures/structures/TiO2.cif +32 -0
  157. pretty_lattice-0.1.0/tests/test_api.py +246 -0
  158. pretty_lattice-0.1.0/tests/test_cli.py +88 -0
  159. pretty_lattice-0.1.0/tests/test_structures.py +710 -0
  160. pretty_lattice-0.1.0/uv.lock +1669 -0
  161. pretty_lattice-0.1.0/vercel.json +8 -0
  162. pretty_lattice-0.1.0/web/bun.lock +632 -0
  163. pretty_lattice-0.1.0/web/bunfig.toml +2 -0
  164. pretty_lattice-0.1.0/web/components.json +21 -0
  165. pretty_lattice-0.1.0/web/doctor.config.json +23 -0
  166. pretty_lattice-0.1.0/web/index.html +13 -0
  167. pretty_lattice-0.1.0/web/package.json +53 -0
  168. pretty_lattice-0.1.0/web/public/examples/Al2O3.scene.json +1 -0
  169. pretty_lattice-0.1.0/web/public/examples/LiFePO4.scene.json +6524 -0
  170. pretty_lattice-0.1.0/web/public/favicon.svg +16 -0
  171. pretty_lattice-0.1.0/web/src/api/scene.ts +232 -0
  172. pretty_lattice-0.1.0/web/src/app/App.tsx +659 -0
  173. pretty_lattice-0.1.0/web/src/app/AtomInspectorCard.tsx +112 -0
  174. pretty_lattice-0.1.0/web/src/app/atomInspector.ts +70 -0
  175. pretty_lattice-0.1.0/web/src/app/cameraInteractionStore.ts +1 -0
  176. pretty_lattice-0.1.0/web/src/app/colorSchemes.ts +1 -0
  177. pretty_lattice-0.1.0/web/src/app/controls/CommonControlsPanel.tsx +4 -0
  178. pretty_lattice-0.1.0/web/src/app/controls/ViewControlRail.tsx +337 -0
  179. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/CommonControlsPanel.tsx +361 -0
  180. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/DisplayTab.tsx +377 -0
  181. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/ExportTab.tsx +733 -0
  182. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/MaterialPresetToken3D.tsx +202 -0
  183. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/OrientationTab.tsx +52 -0
  184. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/StyleTab.tsx +755 -0
  185. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/controlFeedback.ts +2 -0
  186. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/orientation/PrimaryAxisRollSection.tsx +105 -0
  187. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/orientation/RollControl.tsx +264 -0
  188. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/orientation/ScreenAxisChooser.tsx +387 -0
  189. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/orientation/VectorEditor.tsx +382 -0
  190. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/orientation/orientationControlMath.ts +71 -0
  191. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/orientation/vectorEditorModel.ts +117 -0
  192. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/sharedControls.tsx +269 -0
  193. pretty_lattice-0.1.0/web/src/app/controls/commonPanel/styles.ts +5 -0
  194. pretty_lattice-0.1.0/web/src/app/elementLegend.ts +40 -0
  195. pretty_lattice-0.1.0/web/src/app/elementRadii.ts +1 -0
  196. pretty_lattice-0.1.0/web/src/app/exportFigure.ts +173 -0
  197. pretty_lattice-0.1.0/web/src/app/hooks/useFigureExportController.ts +186 -0
  198. pretty_lattice-0.1.0/web/src/app/hooks/useLockedInteractionFeedback.ts +189 -0
  199. pretty_lattice-0.1.0/web/src/app/hooks/usePreviewCameraCommands.ts +411 -0
  200. pretty_lattice-0.1.0/web/src/app/hooks/useStructurePreview.ts +248 -0
  201. pretty_lattice-0.1.0/web/src/app/inspector/InspectorSidebar.tsx +648 -0
  202. pretty_lattice-0.1.0/web/src/app/layout/overlayLayout.ts +63 -0
  203. pretty_lattice-0.1.0/web/src/app/legend/ElementLegend.tsx +162 -0
  204. pretty_lattice-0.1.0/web/src/app/materialPresets.ts +1 -0
  205. pretty_lattice-0.1.0/web/src/app/panels/StructureSummaryCard.tsx +237 -0
  206. pretty_lattice-0.1.0/web/src/app/panels/structureSummaryFormatting.tsx +155 -0
  207. pretty_lattice-0.1.0/web/src/app/previewState.ts +23 -0
  208. pretty_lattice-0.1.0/web/src/app/surface.ts +19 -0
  209. pretty_lattice-0.1.0/web/src/app/symmetryNotation.tsx +54 -0
  210. pretty_lattice-0.1.0/web/src/app/viewState.ts +178 -0
  211. pretty_lattice-0.1.0/web/src/assets/fonts/Geist-MediumItalic.ttf +0 -0
  212. pretty_lattice-0.1.0/web/src/assets/fonts/Geist-Regular.ttf +0 -0
  213. pretty_lattice-0.1.0/web/src/components/ui/alert.tsx +87 -0
  214. pretty_lattice-0.1.0/web/src/components/ui/angle-slider.tsx +233 -0
  215. pretty_lattice-0.1.0/web/src/components/ui/badge.tsx +33 -0
  216. pretty_lattice-0.1.0/web/src/components/ui/button.tsx +55 -0
  217. pretty_lattice-0.1.0/web/src/components/ui/checkbox.tsx +37 -0
  218. pretty_lattice-0.1.0/web/src/components/ui/context-menu.tsx +215 -0
  219. pretty_lattice-0.1.0/web/src/components/ui/input.tsx +21 -0
  220. pretty_lattice-0.1.0/web/src/components/ui/popover.tsx +40 -0
  221. pretty_lattice-0.1.0/web/src/components/ui/select.tsx +188 -0
  222. pretty_lattice-0.1.0/web/src/components/ui/separator.tsx +26 -0
  223. pretty_lattice-0.1.0/web/src/components/ui/switch.tsx +59 -0
  224. pretty_lattice-0.1.0/web/src/components/ui/tabs.tsx +89 -0
  225. pretty_lattice-0.1.0/web/src/components/ui/toggle-group.tsx +83 -0
  226. pretty_lattice-0.1.0/web/src/components/ui/toggle.tsx +45 -0
  227. pretty_lattice-0.1.0/web/src/components/ui/tooltip.tsx +51 -0
  228. pretty_lattice-0.1.0/web/src/data/colormaps/catalog.json +30 -0
  229. pretty_lattice-0.1.0/web/src/data/colormaps/presets/jmol-soft.json +116 -0
  230. pretty_lattice-0.1.0/web/src/data/colormaps/presets/jmol.json +116 -0
  231. pretty_lattice-0.1.0/web/src/data/colormaps/presets/vesta-soft.json +102 -0
  232. pretty_lattice-0.1.0/web/src/data/colormaps/presets/vesta.json +102 -0
  233. pretty_lattice-0.1.0/web/src/data/element-radii.json +586 -0
  234. pretty_lattice-0.1.0/web/src/data/material-presets/catalog.json +5 -0
  235. pretty_lattice-0.1.0/web/src/data/material-presets/material-preset-template.jsonc +87 -0
  236. pretty_lattice-0.1.0/web/src/data/material-presets/presets/2-5d.json +42 -0
  237. pretty_lattice-0.1.0/web/src/data/material-presets/presets/2d.json +17 -0
  238. pretty_lattice-0.1.0/web/src/data/material-presets/presets/classic-matte.json +38 -0
  239. pretty_lattice-0.1.0/web/src/data/material-presets/presets/glossy.json +40 -0
  240. pretty_lattice-0.1.0/web/src/data/material-presets/presets/metallic.json +40 -0
  241. pretty_lattice-0.1.0/web/src/data/material-presets/presets/modern-matte.json +40 -0
  242. pretty_lattice-0.1.0/web/src/export/combinedExportFile.ts +54 -0
  243. pretty_lattice-0.1.0/web/src/export/combinedExportRaster.ts +302 -0
  244. pretty_lattice-0.1.0/web/src/export/crystalAxesExport.ts +103 -0
  245. pretty_lattice-0.1.0/web/src/export/fileNames.ts +9 -0
  246. pretty_lattice-0.1.0/web/src/export/legendExport.ts +252 -0
  247. pretty_lattice-0.1.0/web/src/export/pdfTextExport.ts +131 -0
  248. pretty_lattice-0.1.0/web/src/export/rasterCanvas.ts +142 -0
  249. pretty_lattice-0.1.0/web/src/export/structureExportFile.ts +60 -0
  250. pretty_lattice-0.1.0/web/src/export/structureRasterExport.ts +57 -0
  251. pretty_lattice-0.1.0/web/src/export/types.ts +31 -0
  252. pretty_lattice-0.1.0/web/src/export/zipExport.ts +188 -0
  253. pretty_lattice-0.1.0/web/src/lib/utils.ts +6 -0
  254. pretty_lattice-0.1.0/web/src/main.tsx +11 -0
  255. pretty_lattice-0.1.0/web/src/model/appearance.ts +123 -0
  256. pretty_lattice-0.1.0/web/src/model/cameraInteractionStore.ts +97 -0
  257. pretty_lattice-0.1.0/web/src/model/colorSchemes/autoDistinct.ts +432 -0
  258. pretty_lattice-0.1.0/web/src/model/colorSchemes/oklch.ts +143 -0
  259. pretty_lattice-0.1.0/web/src/model/colorSchemes.ts +387 -0
  260. pretty_lattice-0.1.0/web/src/model/crystalCameraState.ts +13 -0
  261. pretty_lattice-0.1.0/web/src/model/displayState.ts +181 -0
  262. pretty_lattice-0.1.0/web/src/model/elementRadii.ts +28 -0
  263. pretty_lattice-0.1.0/web/src/model/exportSettings.ts +501 -0
  264. pretty_lattice-0.1.0/web/src/model/index.ts +9 -0
  265. pretty_lattice-0.1.0/web/src/model/layout.ts +30 -0
  266. pretty_lattice-0.1.0/web/src/model/materialPresets.ts +472 -0
  267. pretty_lattice-0.1.0/web/src/model/previewFpsStore.ts +27 -0
  268. pretty_lattice-0.1.0/web/src/model/rendering.ts +26 -0
  269. pretty_lattice-0.1.0/web/src/model/structureLimits.ts +4 -0
  270. pretty_lattice-0.1.0/web/src/model/vector.ts +1 -0
  271. pretty_lattice-0.1.0/web/src/model/viewState.ts +193 -0
  272. pretty_lattice-0.1.0/web/src/scene/AtomSelectionRing.tsx +112 -0
  273. pretty_lattice-0.1.0/web/src/scene/BatchedBonds.tsx +260 -0
  274. pretty_lattice-0.1.0/web/src/scene/BatchedPolyhedra.tsx +496 -0
  275. pretty_lattice-0.1.0/web/src/scene/BondRenderItems.ts +81 -0
  276. pretty_lattice-0.1.0/web/src/scene/CameraHeadlight.tsx +63 -0
  277. pretty_lattice-0.1.0/web/src/scene/CellFrame.tsx +66 -0
  278. pretty_lattice-0.1.0/web/src/scene/ExportSceneContent.tsx +81 -0
  279. pretty_lattice-0.1.0/web/src/scene/InstancedAtoms.tsx +418 -0
  280. pretty_lattice-0.1.0/web/src/scene/LatticeScene.tsx +347 -0
  281. pretty_lattice-0.1.0/web/src/scene/MaterialPresetLights.tsx +132 -0
  282. pretty_lattice-0.1.0/web/src/scene/OrientationGizmo.tsx +564 -0
  283. pretty_lattice-0.1.0/web/src/scene/PreviewCameraController.tsx +745 -0
  284. pretty_lattice-0.1.0/web/src/scene/StructureMaterial.tsx +139 -0
  285. pretty_lattice-0.1.0/web/src/scene/StructureSceneObjects.tsx +355 -0
  286. pretty_lattice-0.1.0/web/src/scene/atomHighlight.ts +21 -0
  287. pretty_lattice-0.1.0/web/src/scene/cameraPose.ts +56 -0
  288. pretty_lattice-0.1.0/web/src/scene/crystalCamera.ts +622 -0
  289. pretty_lattice-0.1.0/web/src/scene/exportFrame.ts +269 -0
  290. pretty_lattice-0.1.0/web/src/scene/exportRenderer.tsx +650 -0
  291. pretty_lattice-0.1.0/web/src/scene/materialPresetResolver.ts +82 -0
  292. pretty_lattice-0.1.0/web/src/scene/orientationGizmoHitTesting.ts +137 -0
  293. pretty_lattice-0.1.0/web/src/scene/orientationGizmoMath.ts +58 -0
  294. pretty_lattice-0.1.0/web/src/scene/renderAppearance.ts +6 -0
  295. pretty_lattice-0.1.0/web/src/scene/rendererParameters.ts +5 -0
  296. pretty_lattice-0.1.0/web/src/scene/sceneGeometry.ts +75 -0
  297. pretty_lattice-0.1.0/web/src/scene/sceneLayout.ts +178 -0
  298. pretty_lattice-0.1.0/web/src/scene/structureGeometry.ts +118 -0
  299. pretty_lattice-0.1.0/web/src/scene/viewMath.ts +203 -0
  300. pretty_lattice-0.1.0/web/src/styles/global.css +557 -0
  301. pretty_lattice-0.1.0/web/src/vite-env.d.ts +10 -0
  302. pretty_lattice-0.1.0/web/tests/00-exportFigure.test.ts +58 -0
  303. pretty_lattice-0.1.0/web/tests/App.test.tsx +2301 -0
  304. pretty_lattice-0.1.0/web/tests/angleSlider.test.tsx +134 -0
  305. pretty_lattice-0.1.0/web/tests/atomInspector.test.ts +131 -0
  306. pretty_lattice-0.1.0/web/tests/colorSchemes.test.ts +210 -0
  307. pretty_lattice-0.1.0/web/tests/crystalCamera.test.ts +253 -0
  308. pretty_lattice-0.1.0/web/tests/elementLegend.test.ts +130 -0
  309. pretty_lattice-0.1.0/web/tests/elementRadii.test.ts +29 -0
  310. pretty_lattice-0.1.0/web/tests/exportLayout.test.ts +80 -0
  311. pretty_lattice-0.1.0/web/tests/helpers/appHarness.tsx +106 -0
  312. pretty_lattice-0.1.0/web/tests/latticeScene.test.ts +919 -0
  313. pretty_lattice-0.1.0/web/tests/latticeSceneCameraController.test.tsx +723 -0
  314. pretty_lattice-0.1.0/web/tests/materialPresets.test.ts +292 -0
  315. pretty_lattice-0.1.0/web/tests/orientationControlMath.test.ts +50 -0
  316. pretty_lattice-0.1.0/web/tests/orientationGizmoHitTesting.test.ts +87 -0
  317. pretty_lattice-0.1.0/web/tests/previewFpsStore.test.ts +33 -0
  318. pretty_lattice-0.1.0/web/tests/settings.test.ts +617 -0
  319. pretty_lattice-0.1.0/web/tests/setup.ts +12 -0
  320. pretty_lattice-0.1.0/web/tests/symmetryNotation.test.tsx +13 -0
  321. pretty_lattice-0.1.0/web/tests/vectorEditorModel.test.ts +99 -0
  322. pretty_lattice-0.1.0/web/tests/viewState.test.ts +157 -0
  323. pretty_lattice-0.1.0/web/tsconfig.json +26 -0
  324. 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
@@ -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.