Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ Press the keyboard shortcut, and move your mouse.

### Rotate selected atoms

To rotate around a custom axis, press ``r`` then ``a`` and click one or two atoms.
To rotate around a custom axis, press ``r`` then ``a`` and click one or two atoms, then press ``a`` again to exit axis picking.
Rotation defaults to the camera axis through the selection center.
One atom sets the rotation center (camera axis), two atoms define the bond axis.
The axis is shown with orange crosses and a long orange line (for two atoms).
Expand All @@ -118,6 +118,8 @@ Press ``r`` then ``x``, ``y``, or ``z`` to lock rotation to a world axis (press

### Translate along one axis
To translate along one axis, press ``g`` then ``x``, ``y``, or ``z`` to lock movement.
To translate along an atom-defined axis, press ``g`` then ``a`` and click two atoms, then press ``a`` again to exit axis picking.
To translate in an atom-defined plane, press ``g`` then ``a`` and click three atoms, press ``a`` again to exit axis picking, then press ``p`` for plane or ``n`` for normal.


### Delete selected atoms
Expand Down
4 changes: 3 additions & 1 deletion docs/source/edit.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ and click to confirm.
- ``s`` scale
- ``d`` duplicate and move
Rotation defaults to the camera axis through the selection center.
To rotate around a custom axis, press ``r`` to enter rotate mode, then press ``a`` and click one or two atoms.
To rotate around a custom axis, press ``r`` to enter rotate mode, then press ``a`` and click one or two atoms, then press ``a`` again to exit axis picking.
One atom sets the rotation center (camera axis), two atoms define the bond axis.
The axis is shown with orange crosses and a long orange line (for two atoms), and stays active until you redefine it.
Press ``a`` again to exit axis picking and rotate; click an axis atom again to deselect it.
Expand All @@ -43,6 +43,8 @@ Press ``r`` then ``x``, ``y``, or ``z`` to lock rotation to a world axis (press
Translate Axis Lock
=======================
Press ``g`` to translate, then press ``x``, ``y``, or ``z`` to lock movement to that axis.
To translate along an atom-defined axis, press ``g`` then ``a`` and click two atoms, then press ``a`` again to exit axis picking.
To translate in an atom-defined plane, press ``g`` then ``a`` and click three atoms, press ``a`` again to exit axis picking, then press ``p`` for plane or ``n`` for normal.

+-----------+----------+
| Operation | Shortcut |
Expand Down
7 changes: 4 additions & 3 deletions docs/source/fermi_surface.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"source": [
"# Fermi Surface\n",
"Generate and visualize Fermi surfaces from BXSF files.\n",
"The Fermi surface mesh is generated in the frontend (JavaScript marching cubes); Python prepares the volumetric data and optional Brillouin-zone mesh via seekpath.\n\n",
"\n",
"Requires the optional `fermi-surface` dependencies (includes `seekpath`):\n",
"\n",
Expand Down Expand Up @@ -44,21 +45,21 @@
"- `file_path`: Path to the `.bxsf` file.\n",
"- `fermi_energy`: Override the Fermi energy (default: use value in file).\n",
"- `drop_periodic`: Drop the duplicated periodic end points (default: `True`).\n",
"- `clip_bz`: Clip to the first Brillouin zone (default: `False`, requires `seekpath`).\n",
"- `clip_bz`: Clip to the first Brillouin zone (default: `True`, requires `seekpath`).\n",
"- `show_bz`: Add the Brillouin zone mesh (default: `True`).\n",
"- `show_reciprocal_axes`: Add reciprocal axes vectors (default: `True`).\n",
"- `band_index`: Render a single band by index (default: `None`).\n",
"- `combine_bands`: Merge all Fermi-crossing bands into one mesh (default: `True`).\n",
"- `combine_bands`: Merge all Fermi-crossing bands into one mesh (default: `False`).\n",
"- `name`: Mesh name override.\n",
"- `color`: RGB list for the mesh color or hex color string.\n",
"- `opacity`: Alpha channel applied to the mesh.\n",
"- `material_type`: Material type for the mesh (default: `\"Standard\"`).\n",
"- `supercell_size`: Tuple for band replication in reciprocal space (default: `(2, 2, 2)`).\n",
"- `brillouin_zone_options`: Extra keyword arguments forwarded to `add_brillouin_zone` (e.g., custom color/opacity).\n",
"- `reciprocal_axes_options`: Extra keyword arguments forwarded to `add_reciprocal_axes`.\n",
"\n",
"Notes:\n",
"\n",
"- The Fermi surface mesh is generated in the frontend (JavaScript marching cubes).\n",
"- If `band_index` is `None`, all Fermi-crossing bands are used.\n",
"- If `combine_bands=False`, each band is added as a separate mesh.\n",
"- A `ValueError` is raised when no Fermi-crossing bands are found.\n",
Expand Down
57 changes: 51 additions & 6 deletions js/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ function render({ model, el }) {
window.editor = editor; // for debugging
// volumetric data
setVolumetricData(editor, model.get("volumetricData"), atoms.cell);
// fermi surface data
setFermiSurfaceData(editor, model.get("fermiData"), atoms.cell);
editor.avr.transaction(() => {
const initialState = {
modelStyle: model.get("modelStyle"),
Expand Down Expand Up @@ -131,6 +133,7 @@ function render({ model, el }) {
const showOutBoundaryBonds = model.get("showOutBoundaryBonds");
const highlightSettings = model.get("highlightSettings") || {};
const isoSettings = model.get("isoSettings") || {};
const fermiSettings = model.get("fermiSettings") || {};
const sliceSettings = model.get("sliceSettings") || {};
const vectorField = model.get("vectorField") || {};
const showVectorField = model.get("showVectorField");
Expand All @@ -152,6 +155,7 @@ function render({ model, el }) {
vectorField: { settings: vectorField, show: showVectorField },
instancedMeshPrimitive: { settings: model.get("instancedMeshPrimitive") || [] },
anyMesh: { settings: model.get("anyMesh") || [] },
fermiSurface: { settings: fermiSettings },
text: { settings: model.get("text") || [] },
species: { settings: model.get("speciesSettings") || {} },
},
Expand Down Expand Up @@ -288,10 +292,20 @@ function render({ model, el }) {
setVolumetricData(editor, data, editor.avr.atoms.cell);
console.log("volumeData: ", editor.avr.volumetricData);
});
model.on("change:fermiData", () => {
const data = model.get("fermiData");
console.log("fermiData: ", data);
setFermiSurfaceData(editor, data, editor.avr.atoms.cell);
console.log("fermiSurfaceData: ", editor.avr.fermiSurfaceData);
});
model.on("change:isoSettings", () => {
const isoSettings = model.get("isoSettings") || {};
editor.state.set({ plugins: { isosurface: { settings: isoSettings } } });
});
model.on("change:fermiSettings", () => {
const fermiSettings = model.get("fermiSettings") || {};
editor.state.set({ plugins: { fermiSurface: { settings: fermiSettings } } });
});
// volume slice
model.on("change:sliceSettings", () => {
const data = model.get("sliceSettings") || {};
Expand Down Expand Up @@ -418,14 +432,24 @@ function render({ model, el }) {
});
}
function createVolumeData(data, cell=[[1, 0, 0], [0, 1, 0], [0, 0, 1]]) {
if (!data || !data.values || !Array.isArray(data.values) || data.values.length === 0) {
if (!data) {
return null;
}
// get the dimensions
const dims = [data.values.length, data.values[0].length, data.values[0][0].length];
// flatten the 3d data to 1d
const values = [].concat.apply([], [].concat.apply([], data.values));
return {dims, values, cell: cell, origin: [0, 0, 0]};
if (Array.isArray(data.values) && data.values.length === 0) {
return null;
}
if (Array.isArray(data.values) && Array.isArray(data.values[0]) && Array.isArray(data.values[0][0])) {
const dims = [data.values.length, data.values[0].length, data.values[0][0].length];
const values = [].concat.apply([], [].concat.apply([], data.values));
return {dims, values, cell: data.cell || cell, origin: data.origin || [0, 0, 0], bzPlanes: data.bzPlanes, bzMesh: data.bzMesh};
}
if (Array.isArray(data.values) && Array.isArray(data.dims)) {
return {dims: data.dims, values: data.values, cell: data.cell || cell, origin: data.origin || [0, 0, 0], bzPlanes: data.bzPlanes, bzMesh: data.bzMesh, datasets: data.datasets};
}
if (Array.isArray(data.datasets)) {
return {datasets: data.datasets, cell: data.cell || cell, origin: data.origin || [0, 0, 0], bzPlanes: data.bzPlanes, bzMesh: data.bzMesh};
}
return null;
}

function setVolumetricData(editor, data, cell) {
Expand All @@ -452,6 +476,27 @@ function setVolumetricData(editor, data, cell) {
}
}

function setFermiSurfaceData(editor, data, cell) {
if (!editor || !editor.avr) {
return;
}
const fermiData = createVolumeData(data, cell);
if (!fermiData) {
return;
}
if (typeof editor.avr.setFermiSurfaceData === "function") {
editor.avr.setFermiSurfaceData(fermiData);
return;
}
editor.avr.fermiSurfaceData = fermiData;
if (editor.avr.fermiSurfaceManager && typeof editor.avr.fermiSurfaceManager.drawFermiSurfaces === "function") {
editor.avr.fermiSurfaceManager.drawFermiSurfaces();
}
if (typeof editor.avr.requestRedraw === "function") {
editor.avr.requestRedraw("render");
}
}

function preventEventPropagation(element) {
const stopPropagation = (e) => e.stopPropagation();
["click", "keydown", "keyup", "keypress"].forEach((eventType) => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"dependencies": {
"dat.gui": "^0.7.9",
"three": "^0.169.0",
"weas": "^0.2.8"
"weas": "^0.2.10"
},
"devDependencies": {
"esbuild": "^0.20.0"
Expand Down
3 changes: 3 additions & 0 deletions src/weas_widget/atoms_viewer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .base_class import WidgetWrapper
from .plugins.vector_field import VectorField
from .plugins.isosurface import Isosurface
from .plugins.fermi_surface_plugin import FermiSurface
from .plugins.volume_slice import VolumeSlice
from .plugins.lattice_plane import LatticePlane
from .plugins.cell import CellManager
Expand Down Expand Up @@ -39,6 +40,7 @@ class AtomsViewer(WidgetWrapper):
"species",
"vf",
"iso",
"fermi",
"volume_slice",
"lp",
"atoms",
Expand All @@ -54,6 +56,7 @@ def __init__(self, _widget):
# Initialize plugins
setattr(self, "vf", VectorField(_widget))
setattr(self, "iso", Isosurface(_widget))
setattr(self, "fermi", FermiSurface(_widget))
setattr(self, "lp", LatticePlane(_widget))
setattr(self, "cell", CellManager(_widget))
setattr(self, "bond", BondManager(_widget))
Expand Down
2 changes: 2 additions & 0 deletions src/weas_widget/base_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class BaseWidget(anywidget.AnyWidget):
modelSticks = tl.List().tag(sync=True)
modelPolyhedras = tl.List().tag(sync=True)
volumetricData = tl.Dict({"values": [[[]]]}).tag(sync=True)
fermiData = tl.Dict().tag(sync=True)
imageData = tl.Unicode("").tag(sync=True)
vectorField = tl.Dict().tag(sync=True)
showVectorField = tl.Bool(True).tag(sync=True)
Expand Down Expand Up @@ -71,6 +72,7 @@ class BaseWidget(anywidget.AnyWidget):
bondSettings = tl.Dict().tag(sync=True)
# isosurface
isoSettings = tl.Dict().tag(sync=True)
fermiSettings = tl.Dict().tag(sync=True)
# volume slice
sliceSettings = tl.Dict().tag(sync=True)
# phonon
Expand Down
Loading
Loading