From dc6153ee125593ac31d8acb66da4457ce5c3893a Mon Sep 17 00:00:00 2001 From: Fabian Zills <46721498+PythonFZ@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:17:05 +0100 Subject: [PATCH] split settings (#737) * split settings * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * rename settings * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * configure selection and hover opactiy * remove unused code * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix tests --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- app/bun.lockb | Bin 344995 -> 304742 bytes app/src/App.tsx | 90 ++++----- app/src/components/cameraAndControls.tsx | 24 +-- app/src/components/floor.tsx | 12 +- app/src/components/particles.tsx | 52 +++-- app/src/components/sidebar.tsx | 14 +- tests/test_config.py | 20 +- zndraw/config.py | 241 +++++++++++++++-------- zndraw/tasks/__init__.py | 45 ++--- zndraw/zndraw.py | 47 ++--- 10 files changed, 312 insertions(+), 233 deletions(-) diff --git a/app/bun.lockb b/app/bun.lockb index fe123c80a22eab5d051ac1c401d2e667aace1344..bf51faaa25fcdb543542a6be4357514e1f4ababd 100755 GIT binary patch delta 46644 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�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 diff --git a/app/src/App.tsx b/app/src/App.tsx index 6ef94a03..acaa92f0 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -96,7 +96,7 @@ export default function App() { const [hoveredId, setHoveredId] = useState(-1); // UPDATE THESE using `vis.config` on the Python side const [roomConfig, setRoomConfig] = useState({ - arrows: { + Arrows: { colormap: [ [-0.5, 0.9, 0.5], [0.0, 0.9, 0.5], @@ -106,25 +106,28 @@ export default function App() { scale_vector_thickness: false, opacity: 1.0, }, - scene: { - fps: 30, - material: "MeshStandardMaterial", + Particle: { particle_size: 1.0, bond_size: 1.0, - animation_loop: false, - simulation_box: true, - vectorfield: true, - controls: "OrbitControls", - vectors: [], - vector_scale: 1.0, + material: "MeshStandardMaterial", selection_color: "#ffa500", + hover_opacity: 0.8, + selection_opacity: 0.5, + }, + Visualization: { + simulation_box: true, + floor: false, + frame_update: true, + animation_loop: false, + }, + Camera: { camera: "PerspectiveCamera", camera_near: 0.1, camera_far: 300, - frame_update: true, crosshair: false, - floor: false, synchronize_camera: true, + fps: 30, + controls: "OrbitControls", }, PathTracer: { enabled: false, @@ -134,6 +137,11 @@ export default function App() { clearcoat: 0.0, clearcoatRoughness: 0.0, }, + VectorDisplay: { + vectorfield: true, + vectors: "", + vector_scale: 1.0, + }, }); const [isAuthenticated, setIsAuthenticated] = useState(true); @@ -143,12 +151,6 @@ export default function App() { // TODO: fix const [modifierQueue, setModifierQueue] = useState(-1); - const cameraLightRef = useRef(null); - const controlsRef = useRef(null); - const cameraRef = useRef(null); - - const [cameraRoll, setCameraRoll] = useState(0); // or undefined - // extension UI elements const [tutorialURL, setTutorialURL] = useState(""); const [showSiMGen, setShowSiMGen] = useState(false); @@ -170,7 +172,7 @@ export default function App() { token, cameraAndControls, setCameraAndControls, - roomConfig.scene.synchronize_camera, + roomConfig.Camera.synchronize_camera, ); setupFrames( token, @@ -179,7 +181,7 @@ export default function App() { currentFrame, setLength, setStep, - roomConfig.scene.frame_update, + roomConfig.Visualization.frame_update, ); setupFigures(token, setUpdatedPlotsList); setupGeometries(token, setGeometries, geometries); @@ -448,7 +450,7 @@ export default function App() {
)} - {roomConfig.scene.floor ? ( + {roomConfig.Visualization.floor ? ( <> ) : ( )} - {roomConfig.scene.vectorfield && + {roomConfig.VectorDisplay.vectorfield && currentFrame.vectors !== undefined && ( )} @@ -500,7 +502,7 @@ export default function App() { isDrawing={isDrawing} setPoints={setPoints} setHoveredId={setHoveredId} - sceneSettings={roomConfig.scene} + sceneSettings={roomConfig.Particle} token={token} highlight="" visibleIndices={undefined} @@ -516,7 +518,7 @@ export default function App() { isDrawing={isDrawing} setPoints={setPoints} setHoveredId={setHoveredId} - sceneSettings={roomConfig.scene} + sceneSettings={roomConfig.Particle} token={token} visibleIndices={hoveredId} highlight={"backside"} @@ -529,7 +531,7 @@ export default function App() { isDrawing={isDrawing} setPoints={setPoints} setHoveredId={setHoveredId} - sceneSettings={roomConfig.scene} + sceneSettings={roomConfig.Particle} token={token} visibleIndices={selectedIds} highlight={"selection"} @@ -542,7 +544,7 @@ export default function App() { isDrawing={isDrawing} setPoints={setPoints} setHoveredId={setHoveredId} - sceneSettings={roomConfig.scene} + sceneSettings={roomConfig.Particle} token={token} visibleIndices={ new Set(currentFrame.constraints?.[0]?.indices) @@ -554,7 +556,7 @@ export default function App() { frame={currentFrame} visibleIndices={selectedIds} highlight="selection" - sceneSettings={roomConfig.scene} + sceneSettings={roomConfig.Particle} /> )} @@ -562,10 +564,10 @@ export default function App() { frame={currentFrame} visibleIndices={undefined} highlight="" - sceneSettings={roomConfig.scene} + sceneSettings={roomConfig.Particle} pathTracingSettings={roomConfig.PathTracer} /> - {roomConfig.scene.simulation_box && + {roomConfig.Visualization.simulation_box && !roomConfig.PathTracer.enabled && ( )} @@ -574,8 +576,8 @@ export default function App() { togglePlaying={setPlaying} step={step} setStep={setStep} - fps={roomConfig.scene.fps} - loop={roomConfig.scene.animation_loop} + fps={roomConfig.Camera.fps} + loop={roomConfig.Visualization.animation_loop} length={length} selectedFrames={selectedFrames} /> @@ -608,15 +610,15 @@ export default function App() { hoveredId={hoveredId} setHoveredId={setHoveredId} /> - {roomConfig.scene.vectors[0] && - roomConfig.scene.vectors.map((vector) => ( + {roomConfig.VectorDisplay.vectors[0] && + roomConfig.VectorDisplay.vectors.map((vector) => ( >; currentFrame: any; @@ -67,7 +67,7 @@ type CameraAndControlsProps = { }; const CameraAndControls: React.FC = ({ - roomConfig, + cameraConfig, cameraAndControls, setCameraAndControls, currentFrame, @@ -180,7 +180,7 @@ const CameraAndControls: React.FC = ({ if (resetCamera) { setCameraAndControls(resetCamera); } - }, [roomConfig.scene.camera]); + }, [cameraConfig.camera]); useEffect(() => { if (!cameraRef.current || !controlsRef.current) { @@ -239,28 +239,28 @@ const CameraAndControls: React.FC = ({ return ( <> - {roomConfig.scene.camera === "OrthographicCamera" && ( + {cameraConfig.camera === "OrthographicCamera" && ( )} - {roomConfig.scene.camera === "PerspectiveCamera" && ( + {cameraConfig.camera === "PerspectiveCamera" && ( )} - {roomConfig.scene.controls === "OrbitControls" && ( + {cameraConfig.controls === "OrbitControls" && ( = ({ ref={controlsRef} /> )} - {roomConfig.scene.controls === "TrackballControls" && ( + {cameraConfig.controls === "TrackballControls" && ( = ({ ref={controlsRef} /> )} - {roomConfig.scene.crosshair && controlsRef.current.target && ( + {cameraConfig.crosshair && controlsRef.current.target && ( )} diff --git a/app/src/components/floor.tsx b/app/src/components/floor.tsx index ba349b37..dfac0ca7 100644 --- a/app/src/components/floor.tsx +++ b/app/src/components/floor.tsx @@ -68,13 +68,13 @@ export const Floor: any = ({ colorMode, roomConfig }: any) => { { diff --git a/app/src/components/particles.tsx b/app/src/components/particles.tsx index 49479f0c..e7f03780 100644 --- a/app/src/components/particles.tsx +++ b/app/src/components/particles.tsx @@ -90,24 +90,32 @@ const ParticleBondMaterial = ({ highlight, material, color, + hover_opacity, + selection_opacity, }: { highlight: string; material: string; color?: string; + hover_opacity?: number; + selection_opacity?: number; }) => { if (highlight) { switch (highlight) { case "backside": return ( - - ); - case "frontside": - return ( - + ); case "selection": return ( - + ); case "constraint": return ( @@ -184,7 +192,13 @@ export const ParticleInstances = ({ const { colors, radii } = frame.arrays; const positions = frame.positions; - const { selection_color, material, particle_size } = sceneSettings; + const { + selection_color, + material, + particle_size, + hover_opacity, + selection_opacity, + } = sceneSettings; const geometry = useMemo(() => { const _geometry = new THREE.SphereGeometry(1, 32, 32); @@ -215,8 +229,6 @@ export const ParticleInstances = ({ let radius; if (highlight === "backside") { radius = radii[atomIdx] * 1.25; - } else if (highlight === "frontside") { - radius = radii[atomIdx] * 1.01; } else if (highlight === "selection") { radius = radii[atomIdx] * 1.01; } else { @@ -341,7 +353,12 @@ export const ParticleInstances = ({ castShadow frustumCulled={false} > - + ); @@ -362,7 +379,13 @@ export const BondInstances = ({ }) => { const meshRef = useRef(null); - const { material, selection_color, bond_size } = sceneSettings; + const { + material, + selection_color, + bond_size, + hover_opacity, + selection_opacity, + } = sceneSettings; const actualVisibleConnectivity = useMemo(() => { if (!visibleIndices) { @@ -475,7 +498,12 @@ export const BondInstances = ({ castShadow // receiveShadow > - + ); }; diff --git a/app/src/components/sidebar.tsx b/app/src/components/sidebar.tsx index 3471771d..cd148dd3 100644 --- a/app/src/components/sidebar.tsx +++ b/app/src/components/sidebar.tsx @@ -9,7 +9,7 @@ import { FaRegHandPointer, FaRegMap, } from "react-icons/fa"; -import { FaCircleNodes } from "react-icons/fa6"; +import { FaCircleNodes, FaGear } from "react-icons/fa6"; import { IoStop } from "react-icons/io5"; import Select from "react-select"; import * as znsocket from "znsocket"; @@ -312,15 +312,17 @@ function SideBar({ token }: { token: string }) { - + @@ -380,8 +382,8 @@ function SideBar({ token }: { token: string }) { sendImmediately={false} /> setVisibleOption("")} sendImmediately={true} diff --git a/tests/test_config.py b/tests/test_config.py index 65b291e6..f7877365 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -11,14 +11,14 @@ def test_config_modify_arrows(server): room = "test_config_arrows" vis = ZnDraw(url=server, token=room) - vis.config["arrows"]["normalize"] = False - assert vis.config["arrows"]["normalize"] is False + vis.config["Arrows"]["normalize"] = False + assert vis.config["Arrows"]["normalize"] is False - vis.config["arrows"]["normalize"] = True - assert vis.config["arrows"]["normalize"] is True + vis.config["Arrows"]["normalize"] = True + assert vis.config["Arrows"]["normalize"] is True # check other defaults - assert vis.config["arrows"]["opacity"] == 1.0 + assert vis.config["Arrows"]["opacity"] == 1.0 # def test_config_replace_znsocket(server): @@ -33,10 +33,10 @@ def test_config_modify_scene(server): room = "test_config_scene" vis = ZnDraw(url=server, token=room) - vis.config["scene"]["fps"] = 30 - assert vis.config["scene"]["fps"] == 30 - vis.config["scene"]["fps"] = 60 - assert vis.config["scene"]["fps"] == 60 + vis.config["Camera"]["fps"] = 30 + assert vis.config["Camera"]["fps"] == 30 + vis.config["Camera"]["fps"] = 60 + assert vis.config["Camera"]["fps"] == 60 # check other defaults - assert vis.config["scene"]["material"] == "MeshStandardMaterial" + assert vis.config["Camera"]["camera"] == "PerspectiveCamera" diff --git a/zndraw/config.py b/zndraw/config.py index acb4de54..ec692950 100644 --- a/zndraw/config.py +++ b/zndraw/config.py @@ -12,6 +12,12 @@ HSLColor = t.Tuple[float, float, float] +class SettingsBase(BaseModel): + @classmethod + def model_json_schema_from_atoms(cls, atoms: ase.Atoms) -> dict: + return cls.model_json_schema() + + class Material(str, enum.Enum): MeshBasicMaterial = "MeshBasicMaterial" # MeshLambertMaterial = "MeshLambertMaterial" @@ -27,71 +33,38 @@ class Controls(str, enum.Enum): TrackballControls = "TrackballControls" -class Camera(str, enum.Enum): +class CameraEnum(str, enum.Enum): PerspectiveCamera = "PerspectiveCamera" OrthographicCamera = "OrthographicCamera" -# create a class for the material, resolution, etc. -class Scene(BaseModel): - fps: int = Field(30, ge=1, le=120, description="Maximum frames per second") - material: Material = Field(Material.MeshStandardMaterial, description="Material") - # resolution: int = Field(10, ge=1, le=50, description="Resolution") +class Particle(SettingsBase): particle_size: float = Field(1.0, ge=0.1, le=5, description="Particle Size") bond_size: float = Field(1.0, ge=0.1, le=5, description="Bonds Size") - # wireframe: bool = Field(False, description="Wireframe") - animation_loop: bool = Field( - False, - description="Automatically restart animation when finished.", - ) - simulation_box: bool = Field( - True, - description="Show the simulation box.", - ) - vectorfield: bool = Field(True, description="Show vectorfield.") + material: Material = Field(Material.MeshStandardMaterial, description="Material") + selection_color: str = Field("#ffa500", description="Selection color") + hover_opacity: float = Field(0.8, ge=0.0, le=1.0, description="Hover opacity") + selection_opacity: float = Field(0.5, ge=0.0, le=1.0, description="Selection opacity") - controls: Controls = Field(Controls.OrbitControls, description="Controls") + @classmethod + def model_json_schema_from_atoms(cls, atoms: ase.Atoms) -> dict: + schema = cls.model_json_schema() + schema["properties"]["particle_size"]["format"] = "range" + schema["properties"]["particle_size"]["step"] = 0.1 + schema["properties"]["bond_size"]["format"] = "range" + schema["properties"]["bond_size"]["step"] = 0.1 + schema["properties"]["selection_color"]["format"] = "color" + schema["properties"]["hover_opacity"]["format"] = "range" + schema["properties"]["hover_opacity"]["step"] = 0.05 + schema["properties"]["selection_opacity"]["format"] = "range" + schema["properties"]["selection_opacity"]["step"] = 0.05 + return schema + +class VectorDisplay(SettingsBase): + vectorfield: bool = Field(True, description="Show vectorfield.") vectors: list = Field("", description="Visualize vectorial property") vector_scale: float = Field(1.0, ge=0.1, le=5, description="Rescale Vectors") - selection_color: str = Field("#ffa500", description="Selection color") - camera: Camera = Field(Camera.PerspectiveCamera, description="Camera") - camera_near: float = Field( - 0.1, ge=0, le=100, description="Camera near rendering plane" - ) - camera_far: float = Field( - 300, ge=1, le=1000, description="Camera far rendering plane" - ) - frame_update: bool = Field( - True, - description="Jump to updated frames.", - ) - crosshair: bool = Field( - False, - description="Show camera controls target.", - ) - floor: bool = Field( - False, - description="Show the floor.", - ) - synchronize_camera: bool = Field( - True, - description="Synchronize camera with other room members.", - ) - # bonds: bool = Field( - # True, - # description="Show bonds.", - # ) - # line_label: bool = Field( - # True, - # description="Show the length of the line.", - # ) - # label_offset: int = Field( - # 0, - # ge=-7, - # le=7, - # description="Move the label to the left or right (keypress i).", - # ) @classmethod def model_json_schema_from_atoms(cls, atoms: ase.Atoms) -> dict: @@ -114,43 +87,147 @@ def model_json_schema_from_atoms(cls, atoms: ase.Atoms) -> dict: array_props = [x for x in array_props if x != "positions"] schema["properties"]["vectors"]["items"] = {"type": "string", "enum": array_props} schema["properties"]["vectors"]["uniqueItems"] = True + schema["properties"]["vectorfield"]["format"] = "checkbox" + schema["properties"]["vector_scale"]["format"] = "range" + schema["properties"]["vector_scale"]["step"] = 0.05 + return schema - # schema["properties"]["wireframe"]["format"] = "checkbox" - schema["properties"]["animation_loop"]["format"] = "checkbox" + +class Visualization(SettingsBase): + simulation_box: bool = Field( + True, + description="Show the simulation box.", + ) + floor: bool = Field(False, description="Show the floor.") + frame_update: bool = Field( + True, + description="Jump to updated frames.", + ) + animation_loop: bool = Field( + False, + description="Automatically restart animation when finished.", + ) + + @classmethod + def model_json_schema_from_atoms(cls, atoms: ase.Atoms) -> dict: + schema = cls.model_json_schema() schema["properties"]["simulation_box"]["format"] = "checkbox" - schema["properties"]["vectorfield"]["format"] = "checkbox" schema["properties"]["frame_update"]["format"] = "checkbox" - schema["properties"]["crosshair"]["format"] = "checkbox" + schema["properties"]["animation_loop"]["format"] = "checkbox" schema["properties"]["floor"]["format"] = "checkbox" - schema["properties"]["synchronize_camera"]["format"] = "checkbox" - # schema["properties"]["resolution"]["format"] = "range" - # schema["properties"]["label_offset"]["format"] = "range" - schema["properties"]["particle_size"]["format"] = "range" - schema["properties"]["fps"]["format"] = "range" - schema["properties"]["selection_color"]["format"] = "color" - schema["properties"]["particle_size"]["step"] = 0.1 - schema["properties"]["bond_size"]["format"] = "range" - schema["properties"]["bond_size"]["step"] = 0.1 - schema["properties"]["vector_scale"]["format"] = "range" - schema["properties"]["vector_scale"]["step"] = 0.05 + return schema + + +class Camera(SettingsBase): + camera: CameraEnum = Field(CameraEnum.PerspectiveCamera) + camera_near: float = Field( + 0.1, ge=0, le=100, description="Camera near rendering plane" + ) + camera_far: float = Field( + 300, ge=1, le=1000, description="Camera far rendering plane" + ) + crosshair: bool = Field( + False, + description="Show camera controls target.", + ) + synchronize_camera: bool = Field( + True, + description="Synchronize camera with other room members.", + ) + fps: int = Field(30, ge=1, le=120, description="Maximum frames per second") + controls: Controls = Field(Controls.OrbitControls, description="Controls") + @classmethod + def model_json_schema_from_atoms(cls, atoms: ase.Atoms) -> dict: + schema = cls.model_json_schema() + schema["properties"]["fps"]["format"] = "range" + schema["properties"]["fps"]["step"] = 1 schema["properties"]["camera_near"]["format"] = "range" schema["properties"]["camera_near"]["step"] = 0.1 schema["properties"]["camera_far"]["format"] = "range" schema["properties"]["camera_far"]["step"] = 1 - # schema["properties"]["bonds"]["format"] = "checkbox" - # schema["properties"]["line_label"]["format"] = "checkbox" - + schema["properties"]["crosshair"]["format"] = "checkbox" + schema["properties"]["synchronize_camera"]["format"] = "checkbox" return schema -class Arrows(BaseModel): - colormap: list[HSLColor] = ((-0.5, 0.9, 0.5), (0.0, 0.9, 0.5)) +class Arrows(SettingsBase): + """Experimental vector color settings.""" + + colormap: list[HSLColor] = ((0.5, 0.9, 0.5), (1.0, 0.9, 0.5)) normalize: bool = True colorrange: tuple[float, float] = (0, 1.0) scale_vector_thickness: bool = False - opacity: float = 1.0 + opacity: float = Field(1.0, ge=0.0, le=1.0, description="Opacity") + + @classmethod + def model_json_schema_from_atoms(cls, atoms: ase.Atoms) -> dict: + schema = cls.model_json_schema() + schema["properties"]["colormap"] = { + "type": "array", + "description": "Defines the colormap as a list of HSL tuples (Hue, Saturation, Lightness).", + "items": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "headertemplate": "{{ i1 }}", + "items": [ + { + "type": "number", + "description": "Hue (0.0 - 1.0)", + "format": "range", + "step": 0.01, + "minimum": 0, + "maximum": 1, + }, + { + "type": "number", + "description": "Saturation (0.0 - 1.0)", + "format": "range", + "step": 0.01, + "minimum": 0, + "maximum": 1, + }, + { + "type": "number", + "description": "Lightness (0.0 - 1.0)", + "format": "range", + "step": 0.01, + "minimum": 0, + "maximum": 1, + }, + ], + }, + } + + # Enhance "colorrange" + schema["properties"]["colorrange"] = { + "type": "array", + "description": "Specifies the range of values for colors, defined as [min, max].", + "minItems": 2, + "maxItems": 2, + "items": [ + { + "type": "number", + "description": "Minimum value of the range.", + "format": "range", + "step": 0.01, + }, + { + "type": "number", + "description": "Maximum value of the range.", + "format": "range", + "step": 0.01, + }, + ], + } + + schema["properties"]["normalize"]["format"] = "checkbox" + schema["properties"]["scale_vector_thickness"]["format"] = "checkbox" + schema["properties"]["opacity"]["format"] = "range" + schema["properties"]["opacity"]["step"] = 0.05 + return schema class EnvironmentPreset(str, enum.Enum): @@ -167,7 +244,7 @@ class EnvironmentPreset(str, enum.Enum): warehouse = "warehouse" -class PathTracer(BaseModel): +class PathTracer(SettingsBase): """Experimental path tracer settings.""" enabled: bool = False @@ -194,3 +271,13 @@ def model_json_schema_from_atoms(cls, atoms: ase.Atoms) -> dict: schema["properties"]["clearcoat"]["step"] = 0.05 schema["properties"]["clearcoatRoughness"]["step"] = 0.05 return schema + + +SETTINGS = { + Particle.__name__: Particle, + Visualization.__name__: Visualization, + Camera.__name__: Camera, + PathTracer.__name__: PathTracer, + VectorDisplay.__name__: VectorDisplay, + Arrows.__name__: Arrows, +} diff --git a/zndraw/tasks/__init__.py b/zndraw/tasks/__init__.py index 17803779..6a63a07a 100644 --- a/zndraw/tasks/__init__.py +++ b/zndraw/tasks/__init__.py @@ -11,7 +11,7 @@ from zndraw.analyse import analyses from zndraw.base import FileIO from zndraw.bonds import ASEComputeBonds -from zndraw.config import PathTracer, Scene +from zndraw.config import SETTINGS from zndraw.draw import geometries from zndraw.modify import modifier from zndraw.queue import run_queued_task @@ -386,14 +386,11 @@ def run_room_worker(room): scene_queue = znsocket.Dict( r=current_app.extensions["redis"], socket=vis._refresh_client, - key=f"queue:{room}:scene", + key=f"queue:{room}:settings", ) for key, val in scene_queue.items(): - if key == "Scene": # hotfix - vis.config["scene"].update(val) - else: - vis.config[key].update(val) + vis.config[key].update(val) # TODO: also update the schema to update all other rooms vis.config["trigger_update"] = True @@ -498,34 +495,18 @@ def run_scene_dependent_schema(room) -> None: dct = znsocket.Dict( r=current_app.extensions["redis"], socket=vis._refresh_client, - key=f"schema:{room}:scene", + key=f"schema:{room}:settings", ) - scene_schema = Scene.model_json_schema_from_atoms(vis.atoms) - - # we also want to initialize the `vis.config` - # calling the config will set the default values - # but not overwrite the existing ones, if they are set - orig_scene_config = dict(vis.config["scene"]) - - for key, val in scene_schema["properties"].items(): - try: - scene_schema["properties"][key]["default"] = orig_scene_config[key] - except KeyError: - vis.log(f"KeyError: {key}") - - dct["Scene"] = scene_schema - - orig_path_tracer_config = dict(vis.config["PathTracer"]) - path_tracer_schema = PathTracer.model_json_schema_from_atoms(vis.atoms) - for key, val in path_tracer_schema["properties"].items(): - try: - path_tracer_schema["properties"][key]["default"] = orig_path_tracer_config[ - key - ] - except KeyError: - vis.log(f"KeyError: {key}") - dct["PathTracer"] = path_tracer_schema + for key, val in SETTINGS.items(): + config_values = dict(vis.config[key]) + schema = val.model_json_schema_from_atoms(vis.atoms) + for prop, val in schema["properties"].items(): + try: + schema["properties"][prop]["default"] = config_values[prop] + except KeyError: + vis.log(f"KeyError: {prop}") + dct[key] = schema vis.socket.sleep(1) vis.socket.disconnect() diff --git a/zndraw/zndraw.py b/zndraw/zndraw.py index 75cf5553..c689fd67 100644 --- a/zndraw/zndraw.py +++ b/zndraw/zndraw.py @@ -22,7 +22,7 @@ from zndraw.abc import Message from zndraw.base import Extension from zndraw.bonds import ASEComputeBonds -from zndraw.config import Arrows, PathTracer, Scene +from zndraw.config import SETTINGS from zndraw.converter import ASEConverter, Object3DConverter from zndraw.draw import Object3D from zndraw.figure import Figure, FigureConverter @@ -594,39 +594,18 @@ def config(self) -> znsocket.Dict: socket=self._refresh_client, ) if len(conf) == 0: - scene_conf = znsocket.Dict( - self.r, - f"room:{self.token}:config:scene", - repr_type="full", - socket=self._refresh_client, - ) - if len(scene_conf) == 0: - scene_conf.update(Scene().model_dump()) - arrows_conf = znsocket.Dict( - self.r, - f"room:{self.token}:config:arrows", - repr_type="full", - socket=self._refresh_client, - ) - if len(arrows_conf) == 0: - arrows_conf.update(Arrows().model_dump()) - - path_trace_conf = znsocket.Dict( - self.r, - f"room:{self.token}:config:path_tracer", - repr_type="full", - socket=self._refresh_client, - ) - if len(path_trace_conf) == 0: - path_trace_conf.update(PathTracer().model_dump()) - # - conf.update( - { - "scene": scene_conf, - "arrows": arrows_conf, - "PathTracer": path_trace_conf, - } - ) + conf_dicts = {} + for key, value in SETTINGS.items(): + conf_dicts[key] = znsocket.Dict( + self.r, + f"room:{self.token}:config:{key}", + repr_type="full", + socket=self._refresh_client, + ) + if len(conf_dicts[key]) == 0: + conf_dicts[key].update(value().model_dump()) + conf.update(conf_dicts) + # TODO: arrows return conf @property