matter-vis 0.0.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.
- matter_vis-0.0.0/.github/workflows/release.yml +151 -0
- matter_vis-0.0.0/.gitignore +31 -0
- matter_vis-0.0.0/AGENTS.md +104 -0
- matter_vis-0.0.0/PKG-INFO +28 -0
- matter_vis-0.0.0/README.md +366 -0
- matter_vis-0.0.0/agents/README.md +138 -0
- matter_vis-0.0.0/agents/atom_groups_api.md +257 -0
- matter_vis-0.0.0/agents/bond_groups_api.md +174 -0
- matter_vis-0.0.0/agents/compass_api.md +88 -0
- matter_vis-0.0.0/agents/config_api.md +68 -0
- matter_vis-0.0.0/agents/cube_api.md +188 -0
- matter_vis-0.0.0/agents/dash_service.md +536 -0
- matter_vis-0.0.0/agents/ortep_api.md +63 -0
- matter_vis-0.0.0/agents/polyhedron_api.md +315 -0
- matter_vis-0.0.0/agents/scene_api.md +166 -0
- matter_vis-0.0.0/agents/selection_api.md +39 -0
- matter_vis-0.0.0/agents/transforms_api.md +369 -0
- matter_vis-0.0.0/agents/tui_api.md +140 -0
- matter_vis-0.0.0/crystal_viewer/__init__.py +16 -0
- matter_vis-0.0.0/crystal_viewer/__main__.py +7 -0
- matter_vis-0.0.0/crystal_viewer/_version.py +24 -0
- matter_vis-0.0.0/crystal_viewer/api/__init__.py +43 -0
- matter_vis-0.0.0/crystal_viewer/api/shared.py +51 -0
- matter_vis-0.0.0/crystal_viewer/api/v1_legacy.py +128 -0
- matter_vis-0.0.0/crystal_viewer/api/v2_config.py +47 -0
- matter_vis-0.0.0/crystal_viewer/api/v2_export.py +80 -0
- matter_vis-0.0.0/crystal_viewer/api/v2_intent.py +18 -0
- matter_vis-0.0.0/crystal_viewer/api/v2_overlays.py +420 -0
- matter_vis-0.0.0/crystal_viewer/api/v2_perf.py +17 -0
- matter_vis-0.0.0/crystal_viewer/api/v2_scenes.py +64 -0
- matter_vis-0.0.0/crystal_viewer/api/v2_selection.py +87 -0
- matter_vis-0.0.0/crystal_viewer/api/v2_state.py +108 -0
- matter_vis-0.0.0/crystal_viewer/api/ws.py +77 -0
- matter_vis-0.0.0/crystal_viewer/app/__init__.py +17 -0
- matter_vis-0.0.0/crystal_viewer/app/backend.py +27 -0
- matter_vis-0.0.0/crystal_viewer/app/backend_analysis.py +381 -0
- matter_vis-0.0.0/crystal_viewer/app/backend_camera.py +533 -0
- matter_vis-0.0.0/crystal_viewer/app/backend_core.py +1308 -0
- matter_vis-0.0.0/crystal_viewer/app/backend_io.py +367 -0
- matter_vis-0.0.0/crystal_viewer/app/backend_operations.py +265 -0
- matter_vis-0.0.0/crystal_viewer/app/backend_overlays.py +338 -0
- matter_vis-0.0.0/crystal_viewer/app/backend_selection.py +188 -0
- matter_vis-0.0.0/crystal_viewer/app/backend_topology.py +842 -0
- matter_vis-0.0.0/crystal_viewer/app/callbacks_analysis.py +320 -0
- matter_vis-0.0.0/crystal_viewer/app/callbacks_disorder.py +328 -0
- matter_vis-0.0.0/crystal_viewer/app/callbacks_editors.py +315 -0
- matter_vis-0.0.0/crystal_viewer/app/callbacks_operations.py +298 -0
- matter_vis-0.0.0/crystal_viewer/app/callbacks_state.py +565 -0
- matter_vis-0.0.0/crystal_viewer/app/callbacks_view.py +823 -0
- matter_vis-0.0.0/crystal_viewer/app/camera_helpers.py +342 -0
- matter_vis-0.0.0/crystal_viewer/app/dash_impl.py +16 -0
- matter_vis-0.0.0/crystal_viewer/app/editor_operations.py +91 -0
- matter_vis-0.0.0/crystal_viewer/app/editor_tables.py +622 -0
- matter_vis-0.0.0/crystal_viewer/app/editor_transforms.py +328 -0
- matter_vis-0.0.0/crystal_viewer/app/factory.py +1114 -0
- matter_vis-0.0.0/crystal_viewer/app/normalizers.py +711 -0
- matter_vis-0.0.0/crystal_viewer/app/render_worker.py +218 -0
- matter_vis-0.0.0/crystal_viewer/app/rightclick.py +347 -0
- matter_vis-0.0.0/crystal_viewer/app/runtime.py +151 -0
- matter_vis-0.0.0/crystal_viewer/app/shared.py +76 -0
- matter_vis-0.0.0/crystal_viewer/app/status_helpers.py +63 -0
- matter_vis-0.0.0/crystal_viewer/app/style_helpers.py +187 -0
- matter_vis-0.0.0/crystal_viewer/atom_groups/__init__.py +10 -0
- matter_vis-0.0.0/crystal_viewer/bond_groups/__init__.py +10 -0
- matter_vis-0.0.0/crystal_viewer/bonds/__init__.py +10 -0
- matter_vis-0.0.0/crystal_viewer/cli.py +840 -0
- matter_vis-0.0.0/crystal_viewer/compass/__init__.py +15 -0
- matter_vis-0.0.0/crystal_viewer/compass/annotations.py +8 -0
- matter_vis-0.0.0/crystal_viewer/compass/basis.py +7 -0
- matter_vis-0.0.0/crystal_viewer/compass/core.py +296 -0
- matter_vis-0.0.0/crystal_viewer/compass/projection.py +7 -0
- matter_vis-0.0.0/crystal_viewer/config/__init__.py +120 -0
- matter_vis-0.0.0/crystal_viewer/config/colors.py +4 -0
- matter_vis-0.0.0/crystal_viewer/config/elements.py +294 -0
- matter_vis-0.0.0/crystal_viewer/config/loader.py +200 -0
- matter_vis-0.0.0/crystal_viewer/config/schema.py +202 -0
- matter_vis-0.0.0/crystal_viewer/cube/__init__.py +51 -0
- matter_vis-0.0.0/crystal_viewer/cube/bridge.py +206 -0
- matter_vis-0.0.0/crystal_viewer/cube/core.py +948 -0
- matter_vis-0.0.0/crystal_viewer/cube/export.py +9 -0
- matter_vis-0.0.0/crystal_viewer/cube/io.py +12 -0
- matter_vis-0.0.0/crystal_viewer/cube/meshes.py +9 -0
- matter_vis-0.0.0/crystal_viewer/cube/traces.py +13 -0
- matter_vis-0.0.0/crystal_viewer/depth_sort/__init__.py +10 -0
- matter_vis-0.0.0/crystal_viewer/depth_sort/core.py +160 -0
- matter_vis-0.0.0/crystal_viewer/legacy/__init__.py +1 -0
- matter_vis-0.0.0/crystal_viewer/legacy/crystal_scene.py +410 -0
- matter_vis-0.0.0/crystal_viewer/legacy/plot_crystal.py +223 -0
- matter_vis-0.0.0/crystal_viewer/legacy/publication_view.py +12 -0
- matter_vis-0.0.0/crystal_viewer/loader/__init__.py +39 -0
- matter_vis-0.0.0/crystal_viewer/loader/core.py +1224 -0
- matter_vis-0.0.0/crystal_viewer/loader/cube_adapter.py +199 -0
- matter_vis-0.0.0/crystal_viewer/loader/uploads.py +79 -0
- matter_vis-0.0.0/crystal_viewer/math/__init__.py +18 -0
- matter_vis-0.0.0/crystal_viewer/math/camera.py +426 -0
- matter_vis-0.0.0/crystal_viewer/math/ellipsoid.py +5 -0
- matter_vis-0.0.0/crystal_viewer/math/pbc.py +46 -0
- matter_vis-0.0.0/crystal_viewer/math/projection.py +5 -0
- matter_vis-0.0.0/crystal_viewer/math/rotation.py +144 -0
- matter_vis-0.0.0/crystal_viewer/operations/__init__.py +5 -0
- matter_vis-0.0.0/crystal_viewer/operations/disorder.py +174 -0
- matter_vis-0.0.0/crystal_viewer/ortep/__init__.py +12 -0
- matter_vis-0.0.0/crystal_viewer/ortep/billboard.py +8 -0
- matter_vis-0.0.0/crystal_viewer/ortep/core.py +952 -0
- matter_vis-0.0.0/crystal_viewer/ortep/flat_render.py +331 -0
- matter_vis-0.0.0/crystal_viewer/ortep/math.py +8 -0
- matter_vis-0.0.0/crystal_viewer/ortep/mesh.py +7 -0
- matter_vis-0.0.0/crystal_viewer/ortep/traces.py +12 -0
- matter_vis-0.0.0/crystal_viewer/perf/__init__.py +6 -0
- matter_vis-0.0.0/crystal_viewer/perf/bench.py +176 -0
- matter_vis-0.0.0/crystal_viewer/perf/bench_pipeline.py +223 -0
- matter_vis-0.0.0/crystal_viewer/perf/oracle.py +237 -0
- matter_vis-0.0.0/crystal_viewer/perf/profile_app.py +74 -0
- matter_vis-0.0.0/crystal_viewer/perf_log/__init__.py +10 -0
- matter_vis-0.0.0/crystal_viewer/perf_log/core.py +184 -0
- matter_vis-0.0.0/crystal_viewer/presets/__init__.py +10 -0
- matter_vis-0.0.0/crystal_viewer/presets/core.py +313 -0
- matter_vis-0.0.0/crystal_viewer/render/__init__.py +5 -0
- matter_vis-0.0.0/crystal_viewer/render/api.py +84 -0
- matter_vis-0.0.0/crystal_viewer/render/assembly.py +4 -0
- matter_vis-0.0.0/crystal_viewer/render/boundary_replicas.py +155 -0
- matter_vis-0.0.0/crystal_viewer/render/cache.py +341 -0
- matter_vis-0.0.0/crystal_viewer/render/common.py +30 -0
- matter_vis-0.0.0/crystal_viewer/render/compass.py +265 -0
- matter_vis-0.0.0/crystal_viewer/render/cube/__init__.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/cube/core.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/cube/export.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/cube/io.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/cube/meshes.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/cube/traces.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/display_modes.py +179 -0
- matter_vis-0.0.0/crystal_viewer/render/figures.py +306 -0
- matter_vis-0.0.0/crystal_viewer/render/meshes.py +143 -0
- matter_vis-0.0.0/crystal_viewer/render/morphology.py +95 -0
- matter_vis-0.0.0/crystal_viewer/render/ortep/__init__.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/ortep/billboard.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/ortep/core.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/ortep/math.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/ortep/mesh.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/ortep/traces.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/overlay/__init__.py +5 -0
- matter_vis-0.0.0/crystal_viewer/render/overlay/compass.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/overlay/labels.py +8 -0
- matter_vis-0.0.0/crystal_viewer/render/overlay/manual.py +26 -0
- matter_vis-0.0.0/crystal_viewer/render/scene_traces.py +25 -0
- matter_vis-0.0.0/crystal_viewer/render/selection.py +272 -0
- matter_vis-0.0.0/crystal_viewer/render/serialize.py +92 -0
- matter_vis-0.0.0/crystal_viewer/render/style/__init__.py +9 -0
- matter_vis-0.0.0/crystal_viewer/render/style/atom_groups.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/style/bond_groups.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/style/core.py +360 -0
- matter_vis-0.0.0/crystal_viewer/render/style/disorder.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/style/palette.py +3 -0
- matter_vis-0.0.0/crystal_viewer/render/style.py +292 -0
- matter_vis-0.0.0/crystal_viewer/render/topology.py +625 -0
- matter_vis-0.0.0/crystal_viewer/render/traces_atoms.py +642 -0
- matter_vis-0.0.0/crystal_viewer/render/traces_isosurface.py +461 -0
- matter_vis-0.0.0/crystal_viewer/render/traces_overlays.py +384 -0
- matter_vis-0.0.0/crystal_viewer/render/viewport.py +640 -0
- matter_vis-0.0.0/crystal_viewer/renderer/__init__.py +18 -0
- matter_vis-0.0.0/crystal_viewer/scene/__init__.py +36 -0
- matter_vis-0.0.0/crystal_viewer/scene/core.py +764 -0
- matter_vis-0.0.0/crystal_viewer/scene/serialize.py +56 -0
- matter_vis-0.0.0/crystal_viewer/scene/state.py +39 -0
- matter_vis-0.0.0/crystal_viewer/scene/store.py +3 -0
- matter_vis-0.0.0/crystal_viewer/scene/style.py +93 -0
- matter_vis-0.0.0/crystal_viewer/scene/types.py +203 -0
- matter_vis-0.0.0/crystal_viewer/scenes/__init__.py +10 -0
- matter_vis-0.0.0/crystal_viewer/scenes/core.py +323 -0
- matter_vis-0.0.0/crystal_viewer/structure/__init__.py +2 -0
- matter_vis-0.0.0/crystal_viewer/structure/bonds.py +505 -0
- matter_vis-0.0.0/crystal_viewer/structure/cif_parse.py +252 -0
- matter_vis-0.0.0/crystal_viewer/structure/disorder_index.py +117 -0
- matter_vis-0.0.0/crystal_viewer/structure/formula_unit.py +201 -0
- matter_vis-0.0.0/crystal_viewer/structure/geometry.py +37 -0
- matter_vis-0.0.0/crystal_viewer/structure/molcrys_bridge.py +465 -0
- matter_vis-0.0.0/crystal_viewer/structure/snapshot.py +23 -0
- matter_vis-0.0.0/crystal_viewer/style/__init__.py +2 -0
- matter_vis-0.0.0/crystal_viewer/style/atom_groups.py +258 -0
- matter_vis-0.0.0/crystal_viewer/style/bond_groups.py +230 -0
- matter_vis-0.0.0/crystal_viewer/style/disorder.py +113 -0
- matter_vis-0.0.0/crystal_viewer/style/palette.py +25 -0
- matter_vis-0.0.0/crystal_viewer/topology/__init__.py +36 -0
- matter_vis-0.0.0/crystal_viewer/topology/analysis.py +593 -0
- matter_vis-0.0.0/crystal_viewer/transforms/__init__.py +17 -0
- matter_vis-0.0.0/crystal_viewer/transforms/core.py +873 -0
- matter_vis-0.0.0/crystal_viewer/transforms/pipeline.py +263 -0
- matter_vis-0.0.0/crystal_viewer/tui/__init__.py +121 -0
- matter_vis-0.0.0/crystal_viewer/tui/_hull2d.py +57 -0
- matter_vis-0.0.0/crystal_viewer/tui/app.py +403 -0
- matter_vis-0.0.0/crystal_viewer/tui/braille.py +214 -0
- matter_vis-0.0.0/crystal_viewer/tui/compositor.py +965 -0
- matter_vis-0.0.0/crystal_viewer/tui/controller.py +693 -0
- matter_vis-0.0.0/crystal_viewer/tui/crystal_ir.py +171 -0
- matter_vis-0.0.0/crystal_viewer/tui/inspection.py +424 -0
- matter_vis-0.0.0/crystal_viewer/tui/loader_adapter.py +694 -0
- matter_vis-0.0.0/crystal_viewer/tui/observation.py +67 -0
- matter_vis-0.0.0/crystal_viewer/tui/renderer.py +372 -0
- matter_vis-0.0.0/crystal_viewer/tui/serializer.py +225 -0
- matter_vis-0.0.0/crystal_viewer/tui/state.py +180 -0
- matter_vis-0.0.0/crystal_viewer/tui/summary.py +66 -0
- matter_vis-0.0.0/crystal_viewer/tui/text.py +32 -0
- matter_vis-0.0.0/crystal_viewer/utils/__init__.py +3 -0
- matter_vis-0.0.0/crystal_viewer/utils/json_safe.py +5 -0
- matter_vis-0.0.0/crystal_viewer/viewpoint/__init__.py +33 -0
- matter_vis-0.0.0/crystal_viewer/viewpoint/core.py +605 -0
- matter_vis-0.0.0/docs/README.md +12 -0
- matter_vis-0.0.0/docs/build_images.py +195 -0
- matter_vis-0.0.0/docs/cli.md +289 -0
- matter_vis-0.0.0/docs/derivations/README.md +146 -0
- matter_vis-0.0.0/docs/derivations/camera.md +510 -0
- matter_vis-0.0.0/docs/derivations/display_modes.md +276 -0
- matter_vis-0.0.0/docs/derivations/lattice.md +149 -0
- matter_vis-0.0.0/docs/derivations/ortep.md +311 -0
- matter_vis-0.0.0/docs/derivations/transforms.md +335 -0
- matter_vis-0.0.0/docs/dev-notes.md +129 -0
- matter_vis-0.0.0/docs/images/banner.png +0 -0
- matter_vis-0.0.0/docs/images/feature_coordination.png +0 -0
- matter_vis-0.0.0/docs/images/feature_histogram.png +0 -0
- matter_vis-0.0.0/docs/images/feature_publication.png +0 -0
- matter_vis-0.0.0/docs/images/feature_three_modes.png +0 -0
- matter_vis-0.0.0/docs/images/feature_unit_cell.png +0 -0
- matter_vis-0.0.0/docs/perf.md +150 -0
- matter_vis-0.0.0/docs/postmortems.md +111 -0
- matter_vis-0.0.0/docs/redesign/README.md +153 -0
- matter_vis-0.0.0/docs/redesign/caches.md +185 -0
- matter_vis-0.0.0/docs/redesign/callbacks.md +156 -0
- matter_vis-0.0.0/docs/redesign/migration.md +173 -0
- matter_vis-0.0.0/docs/redesign/operations.md +193 -0
- matter_vis-0.0.0/docs/redesign/rendering.md +151 -0
- matter_vis-0.0.0/docs/redesign/state.md +174 -0
- matter_vis-0.0.0/docs/scores.md +272 -0
- matter_vis-0.0.0/frontend/README.md +8 -0
- matter_vis-0.0.0/frontend/assets/diagnostic.js +333 -0
- matter_vis-0.0.0/frontend/assets/mattervis.js +1110 -0
- matter_vis-0.0.0/frontend/assets/native_upload.js +285 -0
- matter_vis-0.0.0/frontend/assets/panel_resize.css +500 -0
- matter_vis-0.0.0/frontend/assets/panel_resize.js +105 -0
- matter_vis-0.0.0/frontend/assets/right_click_menu.css +150 -0
- matter_vis-0.0.0/frontend/assets/view_tools.css +57 -0
- matter_vis-0.0.0/matter_vis.egg-info/PKG-INFO +28 -0
- matter_vis-0.0.0/matter_vis.egg-info/SOURCES.txt +397 -0
- matter_vis-0.0.0/matter_vis.egg-info/dependency_links.txt +1 -0
- matter_vis-0.0.0/matter_vis.egg-info/entry_points.txt +2 -0
- matter_vis-0.0.0/matter_vis.egg-info/requires.txt +25 -0
- matter_vis-0.0.0/matter_vis.egg-info/scm_file_list.json +392 -0
- matter_vis-0.0.0/matter_vis.egg-info/scm_version.json +8 -0
- matter_vis-0.0.0/matter_vis.egg-info/top_level.txt +1 -0
- matter_vis-0.0.0/pyproject.toml +42 -0
- matter_vis-0.0.0/pytest.ini +36 -0
- matter_vis-0.0.0/requirements.txt +22 -0
- matter_vis-0.0.0/scripts/01_quick_render.py +59 -0
- matter_vis-0.0.0/scripts/02_coordination_analysis.py +221 -0
- matter_vis-0.0.0/scripts/03_display_modes_panel.py +94 -0
- matter_vis-0.0.0/scripts/04_static_publication.py +48 -0
- matter_vis-0.0.0/scripts/05_app_and_api.py +156 -0
- matter_vis-0.0.0/scripts/06_cp2k_cube_orbital.py +91 -0
- matter_vis-0.0.0/scripts/07_scene_tabs.py +32 -0
- matter_vis-0.0.0/scripts/08_ortep_interactive.py +39 -0
- matter_vis-0.0.0/scripts/09_ortep_publication.py +40 -0
- matter_vis-0.0.0/scripts/10_periodic_cube_isosurface.py +84 -0
- matter_vis-0.0.0/scripts/10_tui_controller_visuals.py +47 -0
- matter_vis-0.0.0/scripts/11_bond_scale_benchmark.py +41 -0
- matter_vis-0.0.0/scripts/README.md +13 -0
- matter_vis-0.0.0/scripts/data/DAP-4.cif +878 -0
- matter_vis-0.0.0/scripts/data/README.md +15 -0
- matter_vis-0.0.0/server.log +373 -0
- matter_vis-0.0.0/setup.cfg +4 -0
- matter_vis-0.0.0/skills/visualize-materials/SKILL.md +56 -0
- matter_vis-0.0.0/skills/visualize-materials/references/camera.md +45 -0
- matter_vis-0.0.0/skills/visualize-materials/references/diagnose-and-select.md +60 -0
- matter_vis-0.0.0/skills/visualize-materials/references/install.md +38 -0
- matter_vis-0.0.0/skills/visualize-materials/references/matplotlib-flat-ortep.md +37 -0
- matter_vis-0.0.0/skills/visualize-materials/references/molecule-highlight.md +32 -0
- matter_vis-0.0.0/skills/visualize-materials/references/plotly-render.md +50 -0
- matter_vis-0.0.0/skills/visualize-materials/references/publication-layout.md +69 -0
- matter_vis-0.0.0/skills/visualize-materials/references/trajectory-animation.md +52 -0
- matter_vis-0.0.0/skills/visualize-materials/references/tui.md +40 -0
- matter_vis-0.0.0/skills/visualize-materials/references/verification.md +46 -0
- matter_vis-0.0.0/tests/_layout_helpers.py +169 -0
- matter_vis-0.0.0/tests/app/test_app_state.py +28 -0
- matter_vis-0.0.0/tests/app/test_bfdh_analysis.py +64 -0
- matter_vis-0.0.0/tests/app/test_callback_dispatch_regression.py +163 -0
- matter_vis-0.0.0/tests/app/test_camera_capture_no_poll_echo.py +143 -0
- matter_vis-0.0.0/tests/app/test_camera_persistence_per_tab.py +50 -0
- matter_vis-0.0.0/tests/app/test_editor_builders.py +318 -0
- matter_vis-0.0.0/tests/app/test_frontend_scene_tab_routing.py +94 -0
- matter_vis-0.0.0/tests/app/test_inline_edit_emits_state.py +59 -0
- matter_vis-0.0.0/tests/app/test_layout_refresh_reflects_state.py +77 -0
- matter_vis-0.0.0/tests/app/test_minor_opacity_disable.py +24 -0
- matter_vis-0.0.0/tests/app/test_phase3_ui_layout.py +201 -0
- matter_vis-0.0.0/tests/app/test_preset_security.py +114 -0
- matter_vis-0.0.0/tests/app/test_scene_store_recovery.py +148 -0
- matter_vis-0.0.0/tests/app/test_scene_tabs_dispatcher.py +269 -0
- matter_vis-0.0.0/tests/app/test_status_banner.py +27 -0
- matter_vis-0.0.0/tests/app/test_surface_editor_error.py +250 -0
- matter_vis-0.0.0/tests/app/test_upload_security.py +205 -0
- matter_vis-0.0.0/tests/app/test_view_camera_callbacks.py +71 -0
- matter_vis-0.0.0/tests/app/test_visual_control_regressions.py +272 -0
- matter_vis-0.0.0/tests/config/test_config_loader.py +53 -0
- matter_vis-0.0.0/tests/config/test_config_mck_overrides_passthrough.py +38 -0
- matter_vis-0.0.0/tests/config/test_config_rest.py +51 -0
- matter_vis-0.0.0/tests/config/test_element_tables.py +89 -0
- matter_vis-0.0.0/tests/conftest.py +92 -0
- matter_vis-0.0.0/tests/cube/test_cube_bond_scale.py +185 -0
- matter_vis-0.0.0/tests/cube/test_periodic_isosurface.py +224 -0
- matter_vis-0.0.0/tests/loader/test_disorder_fast_path.py +59 -0
- matter_vis-0.0.0/tests/operations/test_resolve_disorder.py +19 -0
- matter_vis-0.0.0/tests/ortep/test_adp_propagation.py +33 -0
- matter_vis-0.0.0/tests/ortep/test_ortep_billboard.py +20 -0
- matter_vis-0.0.0/tests/ortep/test_ortep_integration.py +37 -0
- matter_vis-0.0.0/tests/ortep/test_ortep_math.py +30 -0
- matter_vis-0.0.0/tests/ortep/test_ortep_mesh.py +20 -0
- matter_vis-0.0.0/tests/ortep/test_ortep_mixed_modes.py +55 -0
- matter_vis-0.0.0/tests/ortep/test_ortep_traces.py +111 -0
- matter_vis-0.0.0/tests/perf/oracles/pipeline_v1.json +81 -0
- matter_vis-0.0.0/tests/perf/test_dap_o4_oracle.py +98 -0
- matter_vis-0.0.0/tests/perf/test_near_zero_latency.py +146 -0
- matter_vis-0.0.0/tests/perf/test_pipeline_oracle.py +130 -0
- matter_vis-0.0.0/tests/perf/test_topology_compute_offthread.py +50 -0
- matter_vis-0.0.0/tests/perf/test_update_view_under_50ms.py +72 -0
- matter_vis-0.0.0/tests/render/test_atom_groups_extra_selectors.py +122 -0
- matter_vis-0.0.0/tests/render/test_atom_groups_renderer.py +396 -0
- matter_vis-0.0.0/tests/render/test_bond_effective_opacity.py +78 -0
- matter_vis-0.0.0/tests/render/test_bond_groups.py +215 -0
- matter_vis-0.0.0/tests/render/test_boundary_replicas.py +824 -0
- matter_vis-0.0.0/tests/render/test_camera_aspect_persistence.py +86 -0
- matter_vis-0.0.0/tests/render/test_camera_projection.py +702 -0
- matter_vis-0.0.0/tests/render/test_canonical_boundary_bonds.py +224 -0
- matter_vis-0.0.0/tests/render/test_cif_symmetry_expansion.py +35 -0
- matter_vis-0.0.0/tests/render/test_compass_basis.py +18 -0
- matter_vis-0.0.0/tests/render/test_depth_sort.py +73 -0
- matter_vis-0.0.0/tests/render/test_disorder_modifiers.py +9 -0
- matter_vis-0.0.0/tests/render/test_disorder_preview_trace.py +31 -0
- matter_vis-0.0.0/tests/render/test_figure_cache_perf.py +233 -0
- matter_vis-0.0.0/tests/render/test_flat_projected_scale.py +100 -0
- matter_vis-0.0.0/tests/render/test_legacy_aliases.py +17 -0
- matter_vis-0.0.0/tests/render/test_ordered_structure_bond_opacity.py +72 -0
- matter_vis-0.0.0/tests/render/test_partial_occupancy_render_identity.py +199 -0
- matter_vis-0.0.0/tests/render/test_payload_size.py +87 -0
- matter_vis-0.0.0/tests/render/test_polyhedron_enabled_toggle_perf.py +485 -0
- matter_vis-0.0.0/tests/render/test_render_dispatch.py +312 -0
- matter_vis-0.0.0/tests/render/test_selection_outline_trace.py +26 -0
- matter_vis-0.0.0/tests/render/test_style_schema.py +39 -0
- matter_vis-0.0.0/tests/render/test_topology_with_styles.py +37 -0
- matter_vis-0.0.0/tests/render/test_unit_cell_box_aspectratio.py +665 -0
- matter_vis-0.0.0/tests/render/test_unwrap_modes.py +65 -0
- matter_vis-0.0.0/tests/render/test_view_alignment.py +237 -0
- matter_vis-0.0.0/tests/scenes/test_atom_groups_api.py +127 -0
- matter_vis-0.0.0/tests/scenes/test_atom_groups_backend.py +168 -0
- matter_vis-0.0.0/tests/scenes/test_healthz_api.py +57 -0
- matter_vis-0.0.0/tests/scenes/test_legacy_alias_v1.py +16 -0
- matter_vis-0.0.0/tests/scenes/test_monochrome_migration.py +97 -0
- matter_vis-0.0.0/tests/scenes/test_per_tab_close.py +39 -0
- matter_vis-0.0.0/tests/scenes/test_perf_log.py +111 -0
- matter_vis-0.0.0/tests/scenes/test_scenes_api.py +66 -0
- matter_vis-0.0.0/tests/scenes/test_scenes_resolver.py +16 -0
- matter_vis-0.0.0/tests/scenes/test_scenes_store.py +39 -0
- matter_vis-0.0.0/tests/scenes/test_v1_state_deprecation.py +59 -0
- matter_vis-0.0.0/tests/scenes/test_view_alignment_api.py +100 -0
- matter_vis-0.0.0/tests/scenes/test_ws_set_state_scene_id.py +76 -0
- matter_vis-0.0.0/tests/selection/test_box_select_projection.py +13 -0
- matter_vis-0.0.0/tests/selection/test_selection_dispatcher.py +41 -0
- matter_vis-0.0.0/tests/selection/test_selection_rest.py +42 -0
- matter_vis-0.0.0/tests/selection/test_selection_state_schema.py +27 -0
- matter_vis-0.0.0/tests/state_machine/test_no_lost_events.py +44 -0
- matter_vis-0.0.0/tests/state_machine/test_reducer_order.py +31 -0
- matter_vis-0.0.0/tests/structure/test_bonds_pbc_reference.py +106 -0
- matter_vis-0.0.0/tests/structure/test_disorder_index.py +53 -0
- matter_vis-0.0.0/tests/test_cli_camera.py +63 -0
- matter_vis-0.0.0/tests/test_cli_identity.py +13 -0
- matter_vis-0.0.0/tests/test_disorder_isolated_atoms.py +103 -0
- matter_vis-0.0.0/tests/test_module_organization.py +46 -0
- matter_vis-0.0.0/tests/test_public_import_contracts.py +53 -0
- matter_vis-0.0.0/tests/topology/test_disorder_bonds.py +64 -0
- matter_vis-0.0.0/tests/topology/test_loader_mol_indices.py +447 -0
- matter_vis-0.0.0/tests/topology/test_phase4_api.py +375 -0
- matter_vis-0.0.0/tests/topology/test_polyhedron_instance_overrides.py +207 -0
- matter_vis-0.0.0/tests/topology/test_polyhedron_specs_api.py +138 -0
- matter_vis-0.0.0/tests/topology/test_polyhedron_specs_backend.py +366 -0
- matter_vis-0.0.0/tests/topology/test_polyhedron_specs_mck04.py +278 -0
- matter_vis-0.0.0/tests/topology/test_polyhedron_specs_renderer.py +280 -0
- matter_vis-0.0.0/tests/topology/test_shape_classification.py +267 -0
- matter_vis-0.0.0/tests/topology/test_topology.py +72 -0
- matter_vis-0.0.0/tests/topology/test_topology_api_errors.py +80 -0
- matter_vis-0.0.0/tests/topology/test_transforms.py +501 -0
- matter_vis-0.0.0/tests/tui/fixtures/dirty_geometry.vasp +40 -0
- matter_vis-0.0.0/tests/tui/test_tui_camera_controls.py +112 -0
- matter_vis-0.0.0/tests/tui/test_tui_command_mode.py +183 -0
- matter_vis-0.0.0/tests/tui/test_tui_controller.py +217 -0
- matter_vis-0.0.0/tests/tui/test_tui_focus_and_inspection.py +150 -0
- matter_vis-0.0.0/tests/tui/test_tui_local_geometry.py +115 -0
- matter_vis-0.0.0/tests/tui/test_tui_usability.py +492 -0
- matter_vis-0.0.0/verification_screens/tui_controller/01_initial_mono.txt +20 -0
- matter_vis-0.0.0/verification_screens/tui_controller/02_orbit_mono.txt +21 -0
- matter_vis-0.0.0/verification_screens/tui_controller/03_focus_mono.txt +24 -0
- matter_vis-0.0.0/verification_screens/tui_controller/04_molecule_minor_mono.txt +24 -0
- matter_vis-0.0.0/verification_screens/tui_controller/05_molecule_minor_color.txt +21 -0
- matter_vis-0.0.0/verification_screens/tui_controller/README.md +12 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release-checks:
|
|
13
|
+
name: Release metadata checks
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
outputs:
|
|
16
|
+
version: ${{ steps.release.outputs.version }}
|
|
17
|
+
tag: ${{ steps.release.outputs.tag }}
|
|
18
|
+
steps:
|
|
19
|
+
- name: Check out repository
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
|
|
24
|
+
- name: Validate release tag
|
|
25
|
+
id: release
|
|
26
|
+
shell: bash
|
|
27
|
+
run: |
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
TAG="${GITHUB_REF_NAME}"
|
|
30
|
+
if [[ ! "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
31
|
+
echo "Release tag must match vX.Y.Z: ${TAG}" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
echo "tag=${TAG}" >> "${GITHUB_OUTPUT}"
|
|
36
|
+
echo "version=${TAG#v}" >> "${GITHUB_OUTPUT}"
|
|
37
|
+
|
|
38
|
+
tests:
|
|
39
|
+
name: Unit tests (Python ${{ matrix.python-version }})
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
needs: release-checks
|
|
42
|
+
strategy:
|
|
43
|
+
fail-fast: false
|
|
44
|
+
matrix:
|
|
45
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
46
|
+
steps:
|
|
47
|
+
- name: Check out repository
|
|
48
|
+
uses: actions/checkout@v4
|
|
49
|
+
with:
|
|
50
|
+
fetch-depth: 0
|
|
51
|
+
|
|
52
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
53
|
+
uses: actions/setup-python@v5
|
|
54
|
+
with:
|
|
55
|
+
python-version: ${{ matrix.python-version }}
|
|
56
|
+
cache: pip
|
|
57
|
+
|
|
58
|
+
- name: Install test dependencies
|
|
59
|
+
run: |
|
|
60
|
+
python -m pip install --upgrade pip
|
|
61
|
+
python -m pip install -e ".[test]"
|
|
62
|
+
|
|
63
|
+
- name: Run tests
|
|
64
|
+
run: pytest tests/
|
|
65
|
+
|
|
66
|
+
build-package:
|
|
67
|
+
name: Build package distributions
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
needs: [release-checks, tests]
|
|
70
|
+
steps:
|
|
71
|
+
- name: Check out repository
|
|
72
|
+
uses: actions/checkout@v4
|
|
73
|
+
with:
|
|
74
|
+
fetch-depth: 0
|
|
75
|
+
|
|
76
|
+
- name: Set up Python
|
|
77
|
+
uses: actions/setup-python@v5
|
|
78
|
+
with:
|
|
79
|
+
python-version: "3.10"
|
|
80
|
+
cache: pip
|
|
81
|
+
|
|
82
|
+
- name: Build source and wheel distributions
|
|
83
|
+
run: |
|
|
84
|
+
python -m pip install --upgrade pip build
|
|
85
|
+
python -m build
|
|
86
|
+
|
|
87
|
+
- name: Verify built version
|
|
88
|
+
env:
|
|
89
|
+
EXPECTED_VERSION: ${{ needs.release-checks.outputs.version }}
|
|
90
|
+
run: |
|
|
91
|
+
python -m pip install --no-deps dist/*.whl
|
|
92
|
+
python - <<'PY'
|
|
93
|
+
import importlib.metadata
|
|
94
|
+
import os
|
|
95
|
+
|
|
96
|
+
actual = importlib.metadata.version("matter-vis")
|
|
97
|
+
expected = os.environ["EXPECTED_VERSION"]
|
|
98
|
+
if actual != expected:
|
|
99
|
+
raise SystemExit(f"Built version {actual!r} does not match tag version {expected!r}")
|
|
100
|
+
print(f"matter-vis {actual}")
|
|
101
|
+
PY
|
|
102
|
+
|
|
103
|
+
- name: Upload distributions
|
|
104
|
+
uses: actions/upload-artifact@v4
|
|
105
|
+
with:
|
|
106
|
+
name: python-package-distributions
|
|
107
|
+
path: dist/
|
|
108
|
+
if-no-files-found: error
|
|
109
|
+
|
|
110
|
+
publish-pypi:
|
|
111
|
+
name: Publish to PyPI
|
|
112
|
+
runs-on: ubuntu-latest
|
|
113
|
+
needs: [release-checks, build-package]
|
|
114
|
+
environment:
|
|
115
|
+
name: pypi
|
|
116
|
+
url: https://pypi.org/project/matter-vis/
|
|
117
|
+
permissions:
|
|
118
|
+
contents: read
|
|
119
|
+
id-token: write
|
|
120
|
+
steps:
|
|
121
|
+
- name: Download distributions
|
|
122
|
+
uses: actions/download-artifact@v4
|
|
123
|
+
with:
|
|
124
|
+
name: python-package-distributions
|
|
125
|
+
path: dist/
|
|
126
|
+
|
|
127
|
+
- name: Publish to PyPI
|
|
128
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
129
|
+
with:
|
|
130
|
+
packages-dir: dist/
|
|
131
|
+
|
|
132
|
+
create-github-release:
|
|
133
|
+
name: Create GitHub Release
|
|
134
|
+
runs-on: ubuntu-latest
|
|
135
|
+
needs: [release-checks, publish-pypi]
|
|
136
|
+
permissions:
|
|
137
|
+
contents: write
|
|
138
|
+
steps:
|
|
139
|
+
- name: Download distributions
|
|
140
|
+
uses: actions/download-artifact@v4
|
|
141
|
+
with:
|
|
142
|
+
name: python-package-distributions
|
|
143
|
+
path: dist/
|
|
144
|
+
|
|
145
|
+
- name: Create GitHub Release
|
|
146
|
+
env:
|
|
147
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
148
|
+
run: |
|
|
149
|
+
gh release create "${GITHUB_REF_NAME}" dist/* \
|
|
150
|
+
--generate-notes \
|
|
151
|
+
--title "${GITHUB_REF_NAME}"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.pyo
|
|
4
|
+
*.pyd
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.mypy_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
env/
|
|
11
|
+
|
|
12
|
+
.local/
|
|
13
|
+
.exports/
|
|
14
|
+
catalog.local.json
|
|
15
|
+
|
|
16
|
+
uploads/
|
|
17
|
+
exports/
|
|
18
|
+
scripts/_outputs/
|
|
19
|
+
scripts/data/HPEP.cif
|
|
20
|
+
scripts/data/MPEP.cif
|
|
21
|
+
scripts/data/PEP.cif
|
|
22
|
+
scripts/data/SY.cif
|
|
23
|
+
scripts/_data/
|
|
24
|
+
|
|
25
|
+
.DS_Store
|
|
26
|
+
Thumbs.db
|
|
27
|
+
|
|
28
|
+
scripts/private/
|
|
29
|
+
verification_screens/
|
|
30
|
+
_tmp/
|
|
31
|
+
server.log
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# MatterVis — Developer Contract
|
|
2
|
+
|
|
3
|
+
This file is for agents and humans **modifying** MatterVis. It is the
|
|
4
|
+
top-level contract only; module/API details belong in linked docs.
|
|
5
|
+
Keep this file short, architectural, and free of function-specific rules.
|
|
6
|
+
|
|
7
|
+
## Read the right document first
|
|
8
|
+
|
|
9
|
+
- Caller-facing API contracts: [`agents/README.md`](agents/README.md).
|
|
10
|
+
- Detailed feature contracts: `agents/*_api.md` and
|
|
11
|
+
[`agents/dash_service.md`](agents/dash_service.md).
|
|
12
|
+
- Implementation notes and historical traps:
|
|
13
|
+
[`docs/dev-notes.md`](docs/dev-notes.md),
|
|
14
|
+
[`docs/postmortems.md`](docs/postmortems.md).
|
|
15
|
+
|
|
16
|
+
If a change touches a public feature, read the matching `agents/` file
|
|
17
|
+
before editing. If a change touches chemistry delegation, disorder,
|
|
18
|
+
fragmentation, topology, slabs, or loader/render boundaries, also read
|
|
19
|
+
`docs/dev-notes.md`.
|
|
20
|
+
|
|
21
|
+
## Repository ownership
|
|
22
|
+
|
|
23
|
+
| Path | Owns |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `crystal_viewer/` | Python library: structure, operations, analysis, render, scene, math, utilities |
|
|
26
|
+
| `api/` | REST/WebSocket service surface for scripts and agents |
|
|
27
|
+
| `app/` | Dash Python BFF, callbacks, layouts, `ViewerBackend` |
|
|
28
|
+
| `frontend/` | Browser-only JavaScript/CSS assets loaded by Dash |
|
|
29
|
+
| `agents/` | Public caller contracts and automation-facing API docs |
|
|
30
|
+
| `docs/` | Human docs, developer notes, postmortems, score tables |
|
|
31
|
+
| `scripts/` | Runnable examples and regression figure generators |
|
|
32
|
+
| `tests/` | Unit and integration tests |
|
|
33
|
+
|
|
34
|
+
New features should normally fit one of these owners. Avoid new
|
|
35
|
+
top-level modules unless the existing ownership model cannot stretch.
|
|
36
|
+
|
|
37
|
+
## Non-negotiable design principles
|
|
38
|
+
|
|
39
|
+
1. **Separate caller style from library capability.** No journal,
|
|
40
|
+
project, or paper-specific naming under `crystal_viewer/`. Defaults
|
|
41
|
+
are conveniences; styling must be caller-overridable.
|
|
42
|
+
2. **Keep APIs layered.** Prefer pure primitives at the bottom,
|
|
43
|
+
composable builders above them, and convenience wrappers at the top.
|
|
44
|
+
Callers must be able to drop down a layer without reimplementing math.
|
|
45
|
+
3. **Do not mutate shared module state.** Exposed palettes/configs are
|
|
46
|
+
readable defaults. Overrides must flow through explicit call/state data.
|
|
47
|
+
4. **Reuse before reinventing.** Before adding geometry, chemistry,
|
|
48
|
+
topology, PBC, shape, disorder, or slab logic, check upstream
|
|
49
|
+
`molcrys_kit` and existing MatterVis modules for the current primitive.
|
|
50
|
+
5. **Respect ownership boundaries.** Browser code stays in `frontend/`,
|
|
51
|
+
service code in `api/` or `app/`, reusable library code in
|
|
52
|
+
`crystal_viewer/`, and chemistry semantics in `molcrys_kit`.
|
|
53
|
+
6. **Keep operation paths distinct.** Source operations work on real
|
|
54
|
+
crystal objects and return through the loader. Display operations work
|
|
55
|
+
on manifested scene data and must not pretend to create source objects.
|
|
56
|
+
7. **Keep math domain-neutral.** Projection, rotation, ellipsoid, and PBC
|
|
57
|
+
primitives may live in math; coordination, labels, covalent radii,
|
|
58
|
+
fragments, and species semantics do not.
|
|
59
|
+
8. **Separate state, assembly, and snapshots.** Editable scene state,
|
|
60
|
+
render assembly, manual overlays, and scene-to-source snapshots have
|
|
61
|
+
different owners; do not blur them for convenience.
|
|
62
|
+
9. **Keep interactive work asynchronous.** Dash/Flask request callbacks
|
|
63
|
+
must not run expensive topology or figure assembly inline. Use the
|
|
64
|
+
established worker/intent/state flow.
|
|
65
|
+
10. **Visually verify rendering changes.** Static export can fail
|
|
66
|
+
silently; regenerate the relevant script output and inspect it.
|
|
67
|
+
|
|
68
|
+
## MolCrysKit boundary
|
|
69
|
+
|
|
70
|
+
`molcrys_kit` owns chemistry: molecule grouping, PBC unwrapping,
|
|
71
|
+
packing shells, shape classification, disorder resolution, slabs, and
|
|
72
|
+
related provenance. MatterVis owns loading adapters, scene state,
|
|
73
|
+
rendering, UI, and service surfaces.
|
|
74
|
+
|
|
75
|
+
When upstream already computes a chemistry quantity, consume it as the
|
|
76
|
+
single source of truth. Do not re-derive it locally “for safety”. When
|
|
77
|
+
upstream deprecates a primitive, read the replacement docs and update
|
|
78
|
+
the public contract in `agents/` if the payload changes.
|
|
79
|
+
|
|
80
|
+
## Public contracts
|
|
81
|
+
|
|
82
|
+
The caller-visible guarantees for cube rendering, compass overlays,
|
|
83
|
+
scene figures, ORTEP, atom/bond groups, transforms, polyhedra,
|
|
84
|
+
selection, config, REST, and WebSocket behavior live under `agents/`.
|
|
85
|
+
Breaking one of those contracts requires updating the matching doc;
|
|
86
|
+
back-incompatible changes require an API/version bump.
|
|
87
|
+
|
|
88
|
+
## Tests, lint, and examples
|
|
89
|
+
|
|
90
|
+
- Unit tests: `pytest tests/`.
|
|
91
|
+
- Lint: `ruff check crystal_viewer/`.
|
|
92
|
+
- Example regressions: run the relevant `python scripts/<n>_*.py` and
|
|
93
|
+
inspect the generated artifact under `scripts/_outputs/`.
|
|
94
|
+
|
|
95
|
+
## Adding or changing code
|
|
96
|
+
|
|
97
|
+
- Name modules and symbols for the data/operation, not a journal,
|
|
98
|
+
project, or one-off output.
|
|
99
|
+
- Add new capability at the lowest layer that fits, then wrap upward.
|
|
100
|
+
- Expose style choices as kwargs/state fields instead of constants.
|
|
101
|
+
- Update the matching `agents/` file for public API changes.
|
|
102
|
+
- Update examples and tests when behavior changes.
|
|
103
|
+
- Put deep implementation warnings in `docs/dev-notes.md` or local
|
|
104
|
+
code comments, not in this top-level contract.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: matter-vis
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Publication-quality crystal structure visualization + terminal viewer
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: dash
|
|
7
|
+
Requires-Dist: plotly
|
|
8
|
+
Requires-Dist: numpy
|
|
9
|
+
Requires-Dist: scipy
|
|
10
|
+
Requires-Dist: scikit-image
|
|
11
|
+
Requires-Dist: gemmi
|
|
12
|
+
Requires-Dist: pymatgen
|
|
13
|
+
Requires-Dist: kaleido
|
|
14
|
+
Requires-Dist: flask-sock
|
|
15
|
+
Requires-Dist: flask-compress
|
|
16
|
+
Requires-Dist: matplotlib
|
|
17
|
+
Requires-Dist: pillow
|
|
18
|
+
Requires-Dist: ase
|
|
19
|
+
Requires-Dist: networkx
|
|
20
|
+
Requires-Dist: textual
|
|
21
|
+
Requires-Dist: molcrys-kit>=0.6.1
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
24
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: matter-vis[test]; extra == "dev"
|
|
27
|
+
Requires-Dist: build; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff; extra == "dev"
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# MatterVis · crystal_viewer
|
|
2
|
+
|
|
3
|
+
Standalone Dash / Plotly frontend and automation toolkit for molecular
|
|
4
|
+
perchlorate crystals (**A**₂**B**(ClO₄)₄ and friends). CIF in — interactive 3D
|
|
5
|
+
viewer, coordination-topology scores, publication-quality Matplotlib export
|
|
6
|
+
and a REST + WebSocket API for other agents, all out.
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
Everything in this README is reproduced from the bundled
|
|
11
|
+
`scripts/data/DAP-4.cif` (a triclinic diammonium diperchlorate `P1` cell,
|
|
12
|
+
see [`scripts/data/README.md`](scripts/data/README.md)); swap it for your
|
|
13
|
+
own CIF with a single flag.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Highlights
|
|
18
|
+
|
|
19
|
+
- **Browser viewer** — Dash front-end, drag-and-drop CIF upload, formula /
|
|
20
|
+
unit-cell / asymmetric-unit / isolated-cluster display, `Mesh3d` atoms and
|
|
21
|
+
bonds with a fast `Scatter3d` fallback for large cells.
|
|
22
|
+
- **Coordination topology** — automatic CN detection via the nearest-neighbour
|
|
23
|
+
gap, angular RMSD vs 12 ideal polyhedra (CN 8-12), planarity RMS of any
|
|
24
|
+
5-atom subset and a prism / antiprism twist check. See
|
|
25
|
+
[`docs/scores.md`](docs/scores.md).
|
|
26
|
+
- **Publication export** — vendored ORTEP-style Matplotlib renderer with
|
|
27
|
+
correct depth ordering, two-colour bonds, smart label placement and
|
|
28
|
+
configurable presets. Plotly path shares radius-aware viewport bounds so
|
|
29
|
+
large halides never clip at the panel edge.
|
|
30
|
+
- **Multi-panel figures** — `uniform_viewport(scenes)` stamps a shared
|
|
31
|
+
world-cube on any list of scenes so every `build_figure` call emits at the
|
|
32
|
+
same physical length per pixel. Drop-in for N-up comparison figures.
|
|
33
|
+
- **Automation** — REST + WebSocket API on the same Flask server. Drive the
|
|
34
|
+
viewer from notebooks, agents or subprocesses (`GET /api/v1/state`,
|
|
35
|
+
`POST /api/v1/topology`, `GET /api/v1/screenshot`, ...).
|
|
36
|
+
- **Zero catalog required** — the package ships with a single public CIF so
|
|
37
|
+
`mat-vis serve --cif scripts/data/DAP-4.cif` just works.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/SchrodingersCattt/MatterVis.git
|
|
43
|
+
cd MatterVis
|
|
44
|
+
python -m pip install -r requirements.txt
|
|
45
|
+
python -m pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`molcrys_kit` is optional. When available the per-fragment **A / B / X**
|
|
49
|
+
heuristic falls back to its classifier; otherwise `crystal_viewer` uses its
|
|
50
|
+
built-in element / size heuristics. The browser UI doesn't expose A/B/X
|
|
51
|
+
labels directly -- it lists the stoichiometric formulas it detects (e.g.
|
|
52
|
+
`C6N2 ×2`, `ClO4 ×4`, `N ×1`) so the same controls work for non-perovskite
|
|
53
|
+
crystals.
|
|
54
|
+
|
|
55
|
+
## Quick render (CLI)
|
|
56
|
+
|
|
57
|
+
Generate publication-quality figures from CIF files without launching the
|
|
58
|
+
browser:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# PNG with default ball-and-stick style
|
|
62
|
+
mat-vis render structure.cif -o figure.png
|
|
63
|
+
|
|
64
|
+
# PDF, full unit cell, ORTEP hatch shading in greyscale
|
|
65
|
+
mat-vis render structure.cif -o figure.pdf \
|
|
66
|
+
--view unit_cell --style ortep --ortep-mode ortep_hatch --monochrome
|
|
67
|
+
|
|
68
|
+
# Interactive HTML for supplementary information
|
|
69
|
+
mat-vis render structure.cif -o si_figure.html \
|
|
70
|
+
--show-hydrogen --show-labels
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
See [`docs/cli.md`](docs/cli.md) for the full flag reference, common
|
|
74
|
+
recipes, and advanced `--config` JSON usage.
|
|
75
|
+
|
|
76
|
+
## Launch the browser viewer
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
mat-vis serve --cif scripts/data/DAP-4.cif
|
|
80
|
+
# Serving crystal viewer at http://0.0.0.0:50001
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Additional flags:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
mat-vis serve --port 8051 # pick a port
|
|
87
|
+
mat-vis serve --host 127.0.0.1 # bind to localhost only
|
|
88
|
+
mat-vis serve --structure DAP-4 # limit catalog to one name
|
|
89
|
+
mat-vis serve --cif a.cif --cif b.cif --cif c.cif
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`--cif` is `action="append"` -- repeat the flag once per CIF file you want to
|
|
93
|
+
preload. The viewer listens on `127.0.0.1` by default; pass `--host 0.0.0.0`
|
|
94
|
+
when the dev box is behind a port-forwarding proxy (see below).
|
|
95
|
+
|
|
96
|
+
### Remote dev boxes (Bohrium and friends)
|
|
97
|
+
|
|
98
|
+
Container platforms typically only expose a small range of ports. On a
|
|
99
|
+
**Bohrium tech development machine** the public proxy maps the URL
|
|
100
|
+
`http://<your-id>.bohrium.tech:5000X` straight through to container port
|
|
101
|
+
`5000X` for `X` in `1..5` -- nothing else is reachable from outside. Two
|
|
102
|
+
common gotchas:
|
|
103
|
+
|
|
104
|
+
1. **Bind to `0.0.0.0`.** Loopback-only listeners (the `--host 127.0.0.1`
|
|
105
|
+
default) never see traffic from the platform proxy.
|
|
106
|
+
2. **Use one of `50001`-`50005`.** Other ports on the container may be in
|
|
107
|
+
use by Jupyter / MCP servers; check with `ss -ltn | grep 5000` before
|
|
108
|
+
you pick one.
|
|
109
|
+
|
|
110
|
+
Putting it together for a Bohrium dev box:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
mat-vis serve --host 0.0.0.0 --port 50001 \
|
|
114
|
+
--cif scripts/data/DAP-4.cif --cif scripts/data/SY.cif
|
|
115
|
+
# Reachable at http://<your-id>.bohrium.tech:50001/
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
If the URL still ERR_CONNECTION_REFUSED, port `50001` is already taken on
|
|
119
|
+
that container -- pick another from the `50001-50005` window.
|
|
120
|
+
|
|
121
|
+
### Open the page in a real browser, not an embedded webview
|
|
122
|
+
|
|
123
|
+
Cursor's *Simple Browser*, VS Code's *preview panel* and similar
|
|
124
|
+
Electron-hosted webviews dispatch click events at the DOM level but
|
|
125
|
+
do not always propagate them through React 16's synthetic event
|
|
126
|
+
system that Dash binds onto. The visible failure mode is brutal:
|
|
127
|
+
**every checkbox / dropdown / slider appears to "click" but nothing
|
|
128
|
+
happens** -- the figure never updates, the network tab is silent
|
|
129
|
+
(no `/_dash-update-component` POST is ever sent), and the user
|
|
130
|
+
concludes the app is broken.
|
|
131
|
+
|
|
132
|
+
The viewer auto-detects this case by sniffing the
|
|
133
|
+
`User-Agent` and renders a sticky red banner at the top of the page
|
|
134
|
+
with a one-click *Copy URL* button. Paste the URL into Chrome, Edge,
|
|
135
|
+
Firefox or Safari and every control will start firing as expected.
|
|
136
|
+
|
|
137
|
+
A built-in wire-tap strip is also available behind the
|
|
138
|
+
`?diag=1` query parameter -- it shows live counts of clicks, Dash
|
|
139
|
+
POSTs and JS errors so you can tell at a glance whether a "no
|
|
140
|
+
response" report is a DOM problem (no clicks), an event-delegation
|
|
141
|
+
problem (clicks but no POST), or a server problem (POST but no OK).
|
|
142
|
+
|
|
143
|
+
For server-side debugging the same information is available via
|
|
144
|
+
`MATTERVIS_AUDIT=1 mat-vis serve ...`, which prints a
|
|
145
|
+
one-line summary of every non-poll callback (changedPropIds,
|
|
146
|
+
duration, payload size, originating IP / User-Agent).
|
|
147
|
+
|
|
148
|
+
See [`AGENTS.md`](AGENTS.md) for every REST / WebSocket endpoint and the full
|
|
149
|
+
set of stable UI element IDs.
|
|
150
|
+
|
|
151
|
+
## Headless scripts
|
|
152
|
+
|
|
153
|
+
Every script in [`scripts/`](scripts) can be run end-to-end without a
|
|
154
|
+
browser:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
python scripts/01_quick_render.py # CIF -> PNG + interactive HTML
|
|
158
|
+
python scripts/02_coordination_analysis.py # coordination shell + all scores
|
|
159
|
+
python scripts/03_display_modes_panel.py # formula / unit cell / shell
|
|
160
|
+
python scripts/04_static_publication.py # ORTEP-style PNG + PDF
|
|
161
|
+
python scripts/05_app_and_api.py # launch app + drive it via REST
|
|
162
|
+
python scripts/06_cp2k_cube_orbital.py --cube orbital.cube # CP2K/Gaussian cube isosurfaces
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Outputs land under `scripts/_outputs/` (gitignored). Regenerate the README
|
|
166
|
+
showcase images with `python docs/build_images.py`.
|
|
167
|
+
|
|
168
|
+
### CP2K / Gaussian cube orbitals
|
|
169
|
+
|
|
170
|
+
MatterVis can render CP2K or Gaussian `.cube` orbital files as paired positive
|
|
171
|
+
and negative Plotly isosurfaces, with atom positions overlaid from the cube
|
|
172
|
+
header:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
python scripts/06_cp2k_cube_orbital.py \
|
|
176
|
+
--cube /path/to/cp2k-WFN_00292_1-1_0.cube \
|
|
177
|
+
--output-prefix HOCO \
|
|
178
|
+
--stride 2 \
|
|
179
|
+
--percentile 98.5
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The reader converts cube coordinates from Bohr to Å and automatically chooses a
|
|
183
|
+
robust isovalue from the absolute-value distribution unless `--isovalue` is
|
|
184
|
+
provided. HTML output is always generated; PNG export is attempted when Kaleido
|
|
185
|
+
is available.
|
|
186
|
+
|
|
187
|
+
### 1. `01_quick_render.py` — CIF to unit cell in ten lines
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from crystal_viewer.loader import build_bundle_scene, build_loaded_crystal
|
|
191
|
+
from crystal_viewer.renderer import build_figure
|
|
192
|
+
from crystal_viewer.scene import scene_style
|
|
193
|
+
|
|
194
|
+
bundle = build_loaded_crystal(name="DAP-4", cif_path="scripts/data/DAP-4.cif")
|
|
195
|
+
scene = build_bundle_scene(bundle, display_mode="unit_cell")
|
|
196
|
+
style = scene_style(scene, {"show_unit_cell": True})
|
|
197
|
+
|
|
198
|
+
fig = build_figure(scene, style)
|
|
199
|
+
fig.write_image("dap4.png", width=900, height=720, scale=2)
|
|
200
|
+
fig.write_html("dap4.html", include_plotlyjs="cdn")
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+

|
|
204
|
+
|
|
205
|
+
### 2. `02_coordination_analysis.py` — topology scores with one function call
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from crystal_viewer.loader import build_loaded_crystal
|
|
209
|
+
from crystal_viewer.topology import analyze_topology
|
|
210
|
+
|
|
211
|
+
bundle = build_loaded_crystal(name="DAP-4", cif_path="scripts/data/DAP-4.cif")
|
|
212
|
+
# Pick the first DABCO ring -- in DAP-4 those have stoichiometry C6N2.
|
|
213
|
+
target = next(f for f in bundle.topology_fragment_table if f.get("formula") == "C6N2")
|
|
214
|
+
result = analyze_topology(bundle, center_index=target["index"], cutoff=8.0)
|
|
215
|
+
|
|
216
|
+
print(result["coordination_number"]) # 9
|
|
217
|
+
print(result["shape"]["primary_label"]) # tricapped_trigonal_prism
|
|
218
|
+
print(result["shape"]["label_modifier"]) # distorted
|
|
219
|
+
print(result["gap_info"]["gap_value"]) # 0.124 Å
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The example also dumps a tidy `02_coordination_summary.json` with every score
|
|
223
|
+
broken out — see [`docs/scores.md`](docs/scores.md) for a full reference.
|
|
224
|
+
|
|
225
|
+
<p>
|
|
226
|
+
<img src="docs/images/feature_coordination.png" alt="coordination hull" width="49%"/>
|
|
227
|
+
<img src="docs/images/feature_histogram.png" alt="distance histogram" width="49%"/>
|
|
228
|
+
</p>
|
|
229
|
+
|
|
230
|
+
### 3. `03_display_modes_panel.py` — side-by-side display modes
|
|
231
|
+
|
|
232
|
+
A single Plotly figure stitches formula unit, unit cell, and coordination
|
|
233
|
+
shell together so reviewers can switch between them without reloading:
|
|
234
|
+
|
|
235
|
+

|
|
236
|
+
|
|
237
|
+
### 4. `04_static_publication.py` — ORTEP-style Matplotlib export
|
|
238
|
+
|
|
239
|
+
Runs the vendored `crystal_viewer.static_publication.plot_crystal` renderer (same code
|
|
240
|
+
used by `POST /api/v1/export`) to produce high-DPI PNG + PDF suitable for
|
|
241
|
+
Nature-style figures:
|
|
242
|
+
|
|
243
|
+

|
|
244
|
+
|
|
245
|
+
### 5. `05_app_and_api.py` — drive the live viewer over HTTP
|
|
246
|
+
|
|
247
|
+
Starts `create_app()` in the background, preloads `DAP-4`, then hits
|
|
248
|
+
`POST /api/v1/topology` and `GET /api/v1/screenshot`. The call sequence is a
|
|
249
|
+
one-page recipe for wiring the viewer into a larger agent pipeline.
|
|
250
|
+
|
|
251
|
+
## Topology scores
|
|
252
|
+
|
|
253
|
+
`analyze_topology()` (and the REST `POST /api/v1/topology`) return five
|
|
254
|
+
named scores that together describe a coordination shell. One line summary:
|
|
255
|
+
|
|
256
|
+
| Score | Module field | What it measures |
|
|
257
|
+
| --- | --- | --- |
|
|
258
|
+
| Coordination number | `coordination_number` + `gap_info.gap_value` | Neighbours in the first shell, picked by the largest distance jump. |
|
|
259
|
+
| Shape classification | `shape.primary_label` / `label_modifier` / `cshm_value` | CShM-based polyhedron name + distortion tier (`clean` / `distorted` / `ambiguous`) for CN 4-12. |
|
|
260
|
+
| Planarity RMS | `planarity.best_rms` / `best_indices` | Best-fit plane through any 5 shell atoms (Å). |
|
|
261
|
+
| Prism / antiprism twist | `prism_analysis.twist_deg` / `classification` | Average inter-ring rotation; threshold 18°, only for CN ≥ 10. |
|
|
262
|
+
| Convex hull | `hull.vertices` / `simplices` / `edges` | Geometry the viewer draws as the purple polyhedron. |
|
|
263
|
+
|
|
264
|
+
See [`docs/scores.md`](docs/scores.md) for how each score is computed,
|
|
265
|
+
reasonable thresholds, and worked numbers for DAP-4.
|
|
266
|
+
|
|
267
|
+
## Rendering isolated clusters
|
|
268
|
+
|
|
269
|
+
Perchlorate-cluster CIFs (dummy 100 Å cell, atoms listed in P1, no periodic
|
|
270
|
+
imaging expected) go through a dedicated display mode that skips formula-unit
|
|
271
|
+
trimming and MIC bond search:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
from crystal_viewer.scene import build_scene_from_cif
|
|
275
|
+
from crystal_viewer.renderer import build_figure, uniform_viewport
|
|
276
|
+
from crystal_viewer.presets import DEFAULT_STYLE, deep_merge
|
|
277
|
+
|
|
278
|
+
# Palette override: add elements not in the vendored table (I, Na, K, Rb, …)
|
|
279
|
+
# or restyle existing ones for a specific figure.
|
|
280
|
+
style = deep_merge(DEFAULT_STYLE, {
|
|
281
|
+
"show_title": False,
|
|
282
|
+
"show_labels": False,
|
|
283
|
+
"show_axes": False,
|
|
284
|
+
"show_hydrogen": True,
|
|
285
|
+
"atom_scale": 0.9,
|
|
286
|
+
"bond_radius": 0.14,
|
|
287
|
+
"element_colors": {"I": "#940094", "Na": "#E6D11E", "K": "#AB82FF"},
|
|
288
|
+
})
|
|
289
|
+
preset = {"version": 1, "style": style, "structures": {}}
|
|
290
|
+
|
|
291
|
+
scenes = [
|
|
292
|
+
build_scene_from_cif(
|
|
293
|
+
name=name,
|
|
294
|
+
cif_path=f"clusters/{name}.cif",
|
|
295
|
+
title=name,
|
|
296
|
+
preset=preset,
|
|
297
|
+
show_hydrogen=True,
|
|
298
|
+
display_mode="cluster",
|
|
299
|
+
)
|
|
300
|
+
for name in ("DAI-1", "DAI-4", "DAP-2", "DAP-M4")
|
|
301
|
+
]
|
|
302
|
+
|
|
303
|
+
# Pin every panel to the same world cube so panel-to-panel scale is identical
|
|
304
|
+
# and no Cl / I atoms clip at the edge. The cube side = biggest scene span +
|
|
305
|
+
# padding (in Å).
|
|
306
|
+
uniform_viewport(scenes, padding=0.5)
|
|
307
|
+
|
|
308
|
+
for scene in scenes:
|
|
309
|
+
fig = build_figure(scene, style)
|
|
310
|
+
fig.write_image(f"{scene['name']}.png", width=600, height=600, scale=2)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Every scene now renders at the same Å-per-pixel, with radius-aware bounds so
|
|
314
|
+
even Cl / I / Br spheres are fully visible. `display_mode="cluster"` honours
|
|
315
|
+
the stored Cartesian coordinates exactly — useful when the CIF encodes a
|
|
316
|
+
hand-curated fragment rather than a crystallographic asymmetric unit.
|
|
317
|
+
|
|
318
|
+
## Package layout
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
crystal_viewer/
|
|
322
|
+
├── __init__.py # re-exports create_app
|
|
323
|
+
├── __main__.py # compatibility entry point; use `mat-vis`
|
|
324
|
+
├── app/ # Dash layout, callbacks, ViewerBackend internals
|
|
325
|
+
├── api/ # REST + WebSocket blueprints
|
|
326
|
+
├── render/ # Plotly viewport / traces / cache internals
|
|
327
|
+
├── structure/ # CIF parsing, bonds, formula units, MolCrysKit bridge
|
|
328
|
+
├── loader/ # CIF/fragment bundle loading facade + upload helpers
|
|
329
|
+
├── scene/ # cell/cluster scene builder facade
|
|
330
|
+
├── transforms/ # repeat/grow/slab transform primitives and pipeline
|
|
331
|
+
├── topology/ # coordination-shell extraction & shape analysis
|
|
332
|
+
├── style/ # atom/bond rules, palette, disorder render helpers
|
|
333
|
+
├── cube/ # cube/orbital I/O, meshes, traces, export facade
|
|
334
|
+
├── ortep/ # thermal ellipsoid math, billboards, traces
|
|
335
|
+
├── compass/ # camera-projected lattice direction annotations
|
|
336
|
+
├── renderer/ # public Plotly facade for atoms, bonds, hulls, axes
|
|
337
|
+
├── presets/ # preset / style / catalog IO
|
|
338
|
+
├── scenes/ # tab/session scene state
|
|
339
|
+
├── perf_log/ # lightweight server event log facade
|
|
340
|
+
├── depth_sort/ # matplotlib depth-order helpers
|
|
341
|
+
├── viewer_backend/ # public ViewerBackend compatibility facade
|
|
342
|
+
├── assets/ # Dash CSS + JS for the panel layout
|
|
343
|
+
└── static_publication/ # matplotlib publication exporter
|
|
344
|
+
scripts/ # runnable demo scripts (see section above)
|
|
345
|
+
docs/ # README showcase images + scores.md
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Notes
|
|
349
|
+
|
|
350
|
+
- Plotly screenshot export uses `kaleido`; the first call is slow because
|
|
351
|
+
it spins up a headless Chromium.
|
|
352
|
+
- Static publication export writes to `.exports/` (gitignored).
|
|
353
|
+
- Local presets default to `.local/crystal_view_preset.json`.
|
|
354
|
+
- Local catalog overrides can be supplied via `catalog.local.json` or
|
|
355
|
+
`.local/catalog.local.json`.
|
|
356
|
+
- The CIF parser handles Materials-Studio style loops that omit
|
|
357
|
+
`_atom_site_disorder_*` columns — no manual cleanup required.
|
|
358
|
+
- For automation details (every endpoint, stable UI IDs, WebSocket schema),
|
|
359
|
+
see [`AGENTS.md`](AGENTS.md).
|
|
360
|
+
|
|
361
|
+
## Citing
|
|
362
|
+
|
|
363
|
+
If you use the bundled example structure, please also credit the originating
|
|
364
|
+
publication that released it — see [`scripts/data/README.md`](scripts/data/README.md).
|
|
365
|
+
The `crystal_viewer` code itself is released under the repository's root
|
|
366
|
+
license file.
|