From bcb93952078b632f2d97238143c3e1b74b527e2d Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 12 Feb 2026 23:54:27 -0600 Subject: [PATCH] SolidRayTracePlot CAPI (#3789) Co-authored-by: GuySten <62616591+GuySten@users.noreply.github.com> --- docs/source/capi/index.rst | 273 ++++++++++++++ docs/source/pythonapi/capi.rst | 7 + include/openmc/capi.h | 44 +++ include/openmc/plot.h | 30 +- openmc/lib/plot.py | 386 +++++++++++++++++++- src/plot.cpp | 636 ++++++++++++++++++++++++++++++++- tests/unit_tests/test_lib.py | 54 +++ 7 files changed, 1402 insertions(+), 28 deletions(-) diff --git a/docs/source/capi/index.rst b/docs/source/capi/index.rst index 0a1962f77..1777e795a 100644 --- a/docs/source/capi/index.rst +++ b/docs/source/capi/index.rst @@ -580,6 +580,279 @@ Functions :return: Return status (negative if an error occurs) :rtype: int +.. c:function:: int openmc_get_plot_index(int32_t id, int32_t* index) + + Get the index in the plots array for a plot with a given ID. + + :param int32_t id: Plot ID + :param int32_t* index: Index in the plots array + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_plot_get_id(int32_t index, int32_t* id) + + Get the ID of a plot. + + :param int32_t index: Index in the plots array + :param int32_t* id: Plot ID + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_plot_set_id(int32_t index, int32_t id) + + Set the ID of a plot. + + :param int32_t index: Index in the plots array + :param int32_t id: Plot ID + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: size_t openmc_plots_size() + + Number of plots currently allocated. + + :return: Number of plots in the plots array + :rtype: size_t + +.. c:function:: int openmc_solidraytrace_plot_create(int32_t* index) + + Create a new solid raytrace plot. + + :param int32_t* index: Index of the newly created plot + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_get_pixels(int32_t index, int32_t* width, int32_t* height) + + Get output pixel dimensions for a solid raytrace plot. + + :param int32_t index: Index in the plots array + :param int32_t* width: Image width in pixels + :param int32_t* height: Image height in pixels + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_set_pixels(int32_t index, int32_t width, int32_t height) + + Set output pixel dimensions for a solid raytrace plot. + + :param int32_t index: Index in the plots array + :param int32_t width: Image width in pixels + :param int32_t height: Image height in pixels + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_get_color_by(int32_t index, int32_t* color_by) + + Get the domain type used for coloring (0=materials, 1=cells). + + :param int32_t index: Index in the plots array + :param int32_t* color_by: Coloring mode + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_set_color_by(int32_t index, int32_t color_by) + + Set the domain type used for coloring (0=materials, 1=cells). + + :param int32_t index: Index in the plots array + :param int32_t color_by: Coloring mode + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_set_default_colors(int32_t index) + + Set default random colors for the current ``color_by`` mode. + + :param int32_t index: Index in the plots array + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_set_all_opaque(int32_t index) + + Mark all domains in the current ``color_by`` mode as opaque. + + :param int32_t index: Index in the plots array + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_set_opaque(int32_t index, int32_t id, bool visible) + + Set whether a specific domain ID is opaque (visible) in the rendered image. + + :param int32_t index: Index in the plots array + :param int32_t id: Cell/material ID (based on ``color_by``) + :param bool visible: Whether the domain is opaque + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_set_color(int32_t index, int32_t id, uint8_t r, uint8_t g, uint8_t b) + + Set RGB color for a specific domain ID. + + :param int32_t index: Index in the plots array + :param int32_t id: Cell/material ID (based on ``color_by``) + :param uint8_t r: Red channel + :param uint8_t g: Green channel + :param uint8_t b: Blue channel + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_get_color(int32_t index, int32_t id, uint8_t* r, uint8_t* g, uint8_t* b) + + Get RGB color for a specific domain ID. + + :param int32_t index: Index in the plots array + :param int32_t id: Cell/material ID (based on ``color_by``) + :param uint8_t* r: Red channel + :param uint8_t* g: Green channel + :param uint8_t* b: Blue channel + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_get_camera_position(int32_t index, double* x, double* y, double* z) + + Get camera position. + + :param int32_t index: Index in the plots array + :param double* x: X coordinate + :param double* y: Y coordinate + :param double* z: Z coordinate + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_set_camera_position(int32_t index, double x, double y, double z) + + Set camera position. + + :param int32_t index: Index in the plots array + :param double x: X coordinate + :param double y: Y coordinate + :param double z: Z coordinate + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_get_look_at(int32_t index, double* x, double* y, double* z) + + Get camera target point. + + :param int32_t index: Index in the plots array + :param double* x: X coordinate + :param double* y: Y coordinate + :param double* z: Z coordinate + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_set_look_at(int32_t index, double x, double y, double z) + + Set camera target point. + + :param int32_t index: Index in the plots array + :param double x: X coordinate + :param double y: Y coordinate + :param double z: Z coordinate + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_get_up(int32_t index, double* x, double* y, double* z) + + Get the camera up vector. + + :param int32_t index: Index in the plots array + :param double* x: X component + :param double* y: Y component + :param double* z: Z component + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_set_up(int32_t index, double x, double y, double z) + + Set the camera up vector. + + :param int32_t index: Index in the plots array + :param double x: X component + :param double y: Y component + :param double z: Z component + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_get_light_position(int32_t index, double* x, double* y, double* z) + + Get light source position. + + :param int32_t index: Index in the plots array + :param double* x: X coordinate + :param double* y: Y coordinate + :param double* z: Z coordinate + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_set_light_position(int32_t index, double x, double y, double z) + + Set light source position. + + :param int32_t index: Index in the plots array + :param double x: X coordinate + :param double y: Y coordinate + :param double z: Z coordinate + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_get_fov(int32_t index, double* fov) + + Get horizontal field of view in degrees. + + :param int32_t index: Index in the plots array + :param double* fov: Field of view in degrees + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_set_fov(int32_t index, double fov) + + Set horizontal field of view in degrees. + + :param int32_t index: Index in the plots array + :param double fov: Field of view in degrees + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_get_diffuse_fraction(int32_t index, double* diffuse_fraction) + + Get diffuse-light fraction. + + :param int32_t index: Index in the plots array + :param double* diffuse_fraction: Diffuse fraction in [0, 1] + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_set_diffuse_fraction(int32_t index, double diffuse_fraction) + + Set diffuse-light fraction. + + :param int32_t index: Index in the plots array + :param double diffuse_fraction: Diffuse fraction in [0, 1] + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_update_view(int32_t index) + + Recompute internal camera/view transforms after camera changes. + + :param int32_t index: Index in the plots array + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_solidraytrace_plot_create_image(int32_t index, uint8_t* data_out, int32_t width, int32_t height) + + Render the plot to an RGB image buffer. + + :param int32_t index: Index in the plots array + :param uint8_t* data_out: Output buffer of shape ``height*width*3`` + :param int32_t width: Image width in pixels + :param int32_t height: Image height in pixels + :return: Return status (negative if an error occurred) + :rtype: int + .. c:function:: int openmc_reset() Resets all tally scores diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index 6f18db56f..465df14d8 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -88,6 +88,7 @@ Classes SpatialLegendreFilter SphericalHarmonicsFilter SphericalMesh + SolidRayTracePlot SurfaceFilter Tally TemporarySession @@ -125,6 +126,12 @@ Data :type: dict +.. data:: plots + + Mapping of plot ID to :class:`openmc.lib.SolidRayTracePlot` instances. + + :type: dict + .. data:: nuclides Mapping of nuclide name to :class:`openmc.lib.Nuclide` instances. diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 533ebb65a..911654d31 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -125,6 +125,49 @@ int openmc_nuclide_name(int index, const char** name); int openmc_plot_geometry(); int openmc_id_map(const void* slice, int32_t* data_out); int openmc_property_map(const void* slice, double* data_out); +int openmc_get_plot_index(int32_t id, int32_t* index); +int openmc_plot_get_id(int32_t index, int32_t* id); +int openmc_plot_set_id(int32_t index, int32_t id); +int openmc_solidraytrace_plot_create(int32_t* index); +int openmc_solidraytrace_plot_get_pixels( + int32_t index, int32_t* width, int32_t* height); +int openmc_solidraytrace_plot_set_pixels( + int32_t index, int32_t width, int32_t height); +int openmc_solidraytrace_plot_get_color_by(int32_t index, int32_t* color_by); +int openmc_solidraytrace_plot_set_color_by(int32_t index, int32_t color_by); +int openmc_solidraytrace_plot_set_default_colors(int32_t index); +int openmc_solidraytrace_plot_set_all_opaque(int32_t index); +int openmc_solidraytrace_plot_set_opaque( + int32_t index, int32_t id, bool visible); +int openmc_solidraytrace_plot_set_color( + int32_t index, int32_t id, uint8_t r, uint8_t g, uint8_t b); +int openmc_solidraytrace_plot_get_camera_position( + int32_t index, double* x, double* y, double* z); +int openmc_solidraytrace_plot_set_camera_position( + int32_t index, double x, double y, double z); +int openmc_solidraytrace_plot_get_look_at( + int32_t index, double* x, double* y, double* z); +int openmc_solidraytrace_plot_set_look_at( + int32_t index, double x, double y, double z); +int openmc_solidraytrace_plot_get_up( + int32_t index, double* x, double* y, double* z); +int openmc_solidraytrace_plot_set_up( + int32_t index, double x, double y, double z); +int openmc_solidraytrace_plot_get_light_position( + int32_t index, double* x, double* y, double* z); +int openmc_solidraytrace_plot_set_light_position( + int32_t index, double x, double y, double z); +int openmc_solidraytrace_plot_get_fov(int32_t index, double* fov); +int openmc_solidraytrace_plot_set_fov(int32_t index, double fov); +int openmc_solidraytrace_plot_update_view(int32_t index); +int openmc_solidraytrace_plot_create_image( + int32_t index, uint8_t* data_out, int32_t width, int32_t height); +int openmc_solidraytrace_plot_get_color( + int32_t index, int32_t id, uint8_t* r, uint8_t* g, uint8_t* b); +int openmc_solidraytrace_plot_get_diffuse_fraction( + int32_t index, double* diffuse_fraction); +int openmc_solidraytrace_plot_set_diffuse_fraction( + int32_t index, double diffuse_fraction); int openmc_rectilinear_mesh_get_grid(int32_t index, double** grid_x, int* nx, double** grid_y, int* ny, double** grid_z, int* nz); int openmc_rectilinear_mesh_set_grid(int32_t index, const double* grid_x, @@ -219,6 +262,7 @@ int openmc_weight_windows_set_weight_cutoff(int32_t index, double cutoff); int openmc_weight_windows_get_max_split(int32_t index, int* max_split); int openmc_weight_windows_set_max_split(int32_t index, int max_split); size_t openmc_weight_windows_size(); +size_t openmc_plots_size(); int openmc_weight_windows_export(const char* filename = nullptr); int openmc_weight_windows_import(const char* filename = nullptr); int openmc_zernike_filter_get_order(int32_t index, int* order); diff --git a/include/openmc/plot.h b/include/openmc/plot.h index 02ff2848d..3813101c6 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -83,10 +83,11 @@ const RGBColor BLACK {0, 0, 0}; * \class PlottableInterface * \brief Interface for plottable objects. * - * PlottableInterface classes must have a unique ID in the plots.xml file. - * They guarantee the ability to create output in some form. This interface - * is designed to be implemented by classes that produce plot-relevant data - * which can be visualized. + * PlottableInterface classes must have unique IDs. If no ID (or -1) is + * provided, the next available ID is assigned automatically. They guarantee + * the ability to create output in some form. This interface is designed to be + * implemented by classes that produce plot-relevant data which can be + * visualized. */ typedef xt::xtensor ImageData; @@ -98,7 +99,7 @@ public: private: void set_id(pugi::xml_node plot_node); - int id_; // unique plot ID + int id_ {C_NONE}; // unique plot ID void set_bg_color(pugi::xml_node plot_node); void set_universe(pugi::xml_node plot_node); @@ -129,18 +130,19 @@ public: const std::string& path_plot() const { return path_plot_; } std::string& path_plot() { return path_plot_; } int id() const { return id_; } + void set_id(int id = C_NONE); int level() const { return level_; } PlotColorBy color_by() const { return color_by_; } // Public color-related data PlottableInterface(pugi::xml_node plot_node); virtual ~PlottableInterface() = default; - int level_ {-1}; // Universe level to plot - bool color_overlaps_ {false}; // Show overlapping cells? - PlotColorBy color_by_; // Plot coloring (cell/material) - RGBColor not_found_ {WHITE}; // Plot background color - RGBColor overlap_color_ {RED}; // Plot overlap color - vector colors_; // Plot colors + int level_ {-1}; // Universe level to plot + bool color_overlaps_ {false}; // Show overlapping cells? + PlotColorBy color_by_ {PlotColorBy::mats}; // Plot coloring (cell/material) + RGBColor not_found_ {WHITE}; // Plot background color + RGBColor overlap_color_ {RED}; // Plot overlap color + vector colors_; // Plot colors }; struct IdData { @@ -371,9 +373,9 @@ private: double horizontal_field_of_view_ {70.0}; // horiz. f.o.v. in degrees Position camera_position_; // where camera is - Position look_at_; // point camera is centered looking at - std::array pixels_; // pixel dimension of resulting image - Direction up_ {0.0, 0.0, 1.0}; // which way is up + Position look_at_; // point camera is centered looking at + std::array pixels_ {100, 100}; // pixel dimension of resulting image + Direction up_ {0.0, 0.0, 1.0}; // which way is up /* The horizontal thickness, if using an orthographic projection. * If set to zero, we assume using a perspective projection. diff --git a/openmc/lib/plot.py b/openmc/lib/plot.py index 68f61821c..90af80d5b 100644 --- a/openmc/lib/plot.py +++ b/openmc/lib/plot.py @@ -1,7 +1,11 @@ +from collections.abc import Mapping from ctypes import (c_bool, c_int, c_size_t, c_int32, - c_double, Structure, POINTER) + c_double, c_uint8, Structure, POINTER) +from weakref import WeakValueDictionary +from ..exceptions import AllocationError, InvalidIDError from . import _dll +from .core import _FortranObjectWithID from .error import _error_handler import numpy as np @@ -258,3 +262,383 @@ def property_map(plot): prop_data = np.zeros((plot.v_res, plot.h_res, 2)) _dll.openmc_property_map(plot, prop_data.ctypes.data_as(POINTER(c_double))) return prop_data + +_dll.openmc_get_plot_index.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_get_plot_index.restype = c_int +_dll.openmc_get_plot_index.errcheck = _error_handler + +_dll.openmc_plot_get_id.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_plot_get_id.restype = c_int +_dll.openmc_plot_get_id.errcheck = _error_handler + +_dll.openmc_plot_set_id.argtypes = [c_int32, c_int32] +_dll.openmc_plot_set_id.restype = c_int +_dll.openmc_plot_set_id.errcheck = _error_handler + +_dll.openmc_plots_size.restype = c_size_t + +_dll.openmc_solidraytrace_plot_create.argtypes = [POINTER(c_int32)] +_dll.openmc_solidraytrace_plot_create.restype = c_int +_dll.openmc_solidraytrace_plot_create.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_get_pixels.argtypes = [ + c_int32, POINTER(c_int32), POINTER(c_int32)] +_dll.openmc_solidraytrace_plot_get_pixels.restype = c_int +_dll.openmc_solidraytrace_plot_get_pixels.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_set_pixels.argtypes = [c_int32, c_int32, c_int32] +_dll.openmc_solidraytrace_plot_set_pixels.restype = c_int +_dll.openmc_solidraytrace_plot_set_pixels.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_get_color_by.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_solidraytrace_plot_get_color_by.restype = c_int +_dll.openmc_solidraytrace_plot_get_color_by.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_set_color_by.argtypes = [c_int32, c_int32] +_dll.openmc_solidraytrace_plot_set_color_by.restype = c_int +_dll.openmc_solidraytrace_plot_set_color_by.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_set_default_colors.argtypes = [c_int32] +_dll.openmc_solidraytrace_plot_set_default_colors.restype = c_int +_dll.openmc_solidraytrace_plot_set_default_colors.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_set_all_opaque.argtypes = [c_int32] +_dll.openmc_solidraytrace_plot_set_all_opaque.restype = c_int +_dll.openmc_solidraytrace_plot_set_all_opaque.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_set_opaque.argtypes = [c_int32, c_int32, c_bool] +_dll.openmc_solidraytrace_plot_set_opaque.restype = c_int +_dll.openmc_solidraytrace_plot_set_opaque.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_set_color.argtypes = [c_int32, c_int32, c_uint8, c_uint8, c_uint8] +_dll.openmc_solidraytrace_plot_set_color.restype = c_int +_dll.openmc_solidraytrace_plot_set_color.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_get_camera_position.argtypes = [ + c_int32, POINTER(c_double), POINTER(c_double), POINTER(c_double)] +_dll.openmc_solidraytrace_plot_get_camera_position.restype = c_int +_dll.openmc_solidraytrace_plot_get_camera_position.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_set_camera_position.argtypes = [c_int32, c_double, c_double, c_double] +_dll.openmc_solidraytrace_plot_set_camera_position.restype = c_int +_dll.openmc_solidraytrace_plot_set_camera_position.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_get_look_at.argtypes = [ + c_int32, POINTER(c_double), POINTER(c_double), POINTER(c_double)] +_dll.openmc_solidraytrace_plot_get_look_at.restype = c_int +_dll.openmc_solidraytrace_plot_get_look_at.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_set_look_at.argtypes = [c_int32, c_double, c_double, c_double] +_dll.openmc_solidraytrace_plot_set_look_at.restype = c_int +_dll.openmc_solidraytrace_plot_set_look_at.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_get_up.argtypes = [ + c_int32, POINTER(c_double), POINTER(c_double), POINTER(c_double)] +_dll.openmc_solidraytrace_plot_get_up.restype = c_int +_dll.openmc_solidraytrace_plot_get_up.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_set_up.argtypes = [c_int32, c_double, c_double, c_double] +_dll.openmc_solidraytrace_plot_set_up.restype = c_int +_dll.openmc_solidraytrace_plot_set_up.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_get_light_position.argtypes = [ + c_int32, POINTER(c_double), POINTER(c_double), POINTER(c_double)] +_dll.openmc_solidraytrace_plot_get_light_position.restype = c_int +_dll.openmc_solidraytrace_plot_get_light_position.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_set_light_position.argtypes = [c_int32, c_double, c_double, c_double] +_dll.openmc_solidraytrace_plot_set_light_position.restype = c_int +_dll.openmc_solidraytrace_plot_set_light_position.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_get_fov.argtypes = [c_int32, POINTER(c_double)] +_dll.openmc_solidraytrace_plot_get_fov.restype = c_int +_dll.openmc_solidraytrace_plot_get_fov.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_set_fov.argtypes = [c_int32, c_double] +_dll.openmc_solidraytrace_plot_set_fov.restype = c_int +_dll.openmc_solidraytrace_plot_set_fov.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_update_view.argtypes = [c_int32] +_dll.openmc_solidraytrace_plot_update_view.restype = c_int +_dll.openmc_solidraytrace_plot_update_view.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_create_image.argtypes = [c_int32, POINTER(c_uint8), c_int32, c_int32] +_dll.openmc_solidraytrace_plot_create_image.restype = c_int +_dll.openmc_solidraytrace_plot_create_image.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_get_color.argtypes = [c_int32, c_int32, + POINTER(c_uint8), POINTER(c_uint8), POINTER(c_uint8)] +_dll.openmc_solidraytrace_plot_get_color.restype = c_int +_dll.openmc_solidraytrace_plot_get_color.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_get_diffuse_fraction.argtypes = [ + c_int32, POINTER(c_double)] +_dll.openmc_solidraytrace_plot_get_diffuse_fraction.restype = c_int +_dll.openmc_solidraytrace_plot_get_diffuse_fraction.errcheck = _error_handler + +_dll.openmc_solidraytrace_plot_set_diffuse_fraction.argtypes = [c_int32, c_double] +_dll.openmc_solidraytrace_plot_set_diffuse_fraction.restype = c_int +_dll.openmc_solidraytrace_plot_set_diffuse_fraction.errcheck = _error_handler + + +class SolidRayTracePlot(_FortranObjectWithID): + """Solid ray-traced plot stored internally. + + This class exposes a solid ray-traced plot that is stored internally in + the OpenMC library. To obtain a view of an existing plot with a given ID, + use the :data:`openmc.lib.plots` mapping. + + Parameters + ---------- + uid : int or None + Unique ID of the plot + new : bool + When `index` is None, this argument controls whether a new object is + created or a view of an existing object is returned. + index : int or None + Index in the internal plots array. + + Attributes + ---------- + id : int + Unique ID of the plot. + pixels : tuple of int + Plot image dimensions as ``(width, height)``. + color_by : int + Coloring mode. Use :attr:`COLOR_BY_MATERIAL` or + :attr:`COLOR_BY_CELL`. + camera_position : tuple of float + Camera position as ``(x, y, z)``. + look_at : tuple of float + Point the camera is aimed at as ``(x, y, z)``. + up : tuple of float + Up direction as ``(x, y, z)``. + light_position : tuple of float + Position of the light source as ``(x, y, z)``. + fov : float + Horizontal field-of-view angle in degrees. + diffuse_fraction : float + Fraction of reflected light treated as diffuse (0 to 1). + """ + + COLOR_BY_MATERIAL = 0 + COLOR_BY_CELL = 1 + __instances = WeakValueDictionary() + + def __new__(cls, uid=None, new=True, index=None): + mapping = plots + if index is None: + if new: + if uid is not None and uid in mapping: + raise AllocationError( + f'A plot with ID={uid} has already been allocated.' + ) + index = c_int32() + _dll.openmc_solidraytrace_plot_create(index) + index = index.value + else: + index = mapping[uid]._index + + if index not in cls.__instances: + instance = super().__new__(cls) + instance._index = index + if uid is not None: + instance.id = uid + cls.__instances[index] = instance + + return cls.__instances[index] + + def __init__(self, uid=None, new=True, index=None): + super().__init__(uid, new, index) + + @property + def id(self): + plot_id = c_int32() + _dll.openmc_plot_get_id(self._index, plot_id) + return plot_id.value + + @id.setter + def id(self, plot_id): + _dll.openmc_plot_set_id(self._index, plot_id) + + @staticmethod + def _get_xyz(getter, index): + x = c_double() + y = c_double() + z = c_double() + getter(index, x, y, z) + return (x.value, y.value, z.value) + + @staticmethod + def _set_xyz(setter, index, xyz): + x, y, z = xyz + setter(index, float(x), float(y), float(z)) + + @property + def pixels(self): + width = c_int32() + height = c_int32() + _dll.openmc_solidraytrace_plot_get_pixels(self._index, width, height) + return (width.value, height.value) + + @pixels.setter + def pixels(self, pixels): + width, height = pixels + _dll.openmc_solidraytrace_plot_set_pixels( + self._index, int(width), int(height)) + + @property + def color_by(self): + color_by = c_int32() + _dll.openmc_solidraytrace_plot_get_color_by(self._index, color_by) + return color_by.value + + @color_by.setter + def color_by(self, color_by): + _dll.openmc_solidraytrace_plot_set_color_by(self._index, int(color_by)) + + def set_default_colors(self): + _dll.openmc_solidraytrace_plot_set_default_colors(self._index) + + def set_all_opaque(self): + _dll.openmc_solidraytrace_plot_set_all_opaque(self._index) + + def set_visibility(self, domain_id, visible): + _dll.openmc_solidraytrace_plot_set_opaque( + self._index, int(domain_id), bool(visible) + ) + + def set_color(self, domain_id, color): + r, g, b = [int(c) for c in color] + _dll.openmc_solidraytrace_plot_set_color( + self._index, int(domain_id), r, g, b) + + @property + def camera_position(self): + return self._get_xyz(_dll.openmc_solidraytrace_plot_get_camera_position, + self._index) + + @camera_position.setter + def camera_position(self, position): + self._set_xyz(_dll.openmc_solidraytrace_plot_set_camera_position, + self._index, position) + + @property + def look_at(self): + return self._get_xyz(_dll.openmc_solidraytrace_plot_get_look_at, + self._index) + + @look_at.setter + def look_at(self, position): + self._set_xyz(_dll.openmc_solidraytrace_plot_set_look_at, + self._index, position) + + @property + def up(self): + return self._get_xyz(_dll.openmc_solidraytrace_plot_get_up, self._index) + + @up.setter + def up(self, direction): + self._set_xyz(_dll.openmc_solidraytrace_plot_set_up, self._index, + direction) + + @property + def light_position(self): + return self._get_xyz(_dll.openmc_solidraytrace_plot_get_light_position, + self._index) + + @light_position.setter + def light_position(self, position): + self._set_xyz(_dll.openmc_solidraytrace_plot_set_light_position, + self._index, position) + + @property + def fov(self): + fov = c_double() + _dll.openmc_solidraytrace_plot_get_fov(self._index, fov) + return fov.value + + @fov.setter + def fov(self, fov): + _dll.openmc_solidraytrace_plot_set_fov(self._index, float(fov)) + + def update_view(self): + _dll.openmc_solidraytrace_plot_update_view(self._index) + + def create_image(self): + width, height = self.pixels + image = np.zeros((height, width, 3), dtype=np.uint8) + _dll.openmc_solidraytrace_plot_create_image( + self._index, + image.ctypes.data_as(POINTER(c_uint8)), + width, + height + ) + return image + + def get_color(self, domain_id): + r = c_uint8() + g = c_uint8() + b = c_uint8() + _dll.openmc_solidraytrace_plot_get_color( + self._index, int(domain_id), r, g, b) + return int(r.value), int(g.value), int(b.value) + + @property + def diffuse_fraction(self): + value = c_double() + _dll.openmc_solidraytrace_plot_get_diffuse_fraction(self._index, value) + return value.value + + @diffuse_fraction.setter + def diffuse_fraction(self, value): + _dll.openmc_solidraytrace_plot_set_diffuse_fraction( + self._index, float(value)) + + # Backward-compatible setter aliases + def set_pixels(self, width, height): + self.pixels = (width, height) + + def set_color_by(self, color_by): + self.color_by = color_by + + def set_camera_position(self, x, y, z): + self.camera_position = (x, y, z) + + def set_look_at(self, x, y, z): + self.look_at = (x, y, z) + + def set_up(self, x, y, z): + self.up = (x, y, z) + + def set_light_position(self, x, y, z): + self.light_position = (x, y, z) + + def set_fov(self, fov): + self.fov = fov + + def set_diffuse_fraction(self, value): + self.diffuse_fraction = value + + +class _PlotMapping(Mapping): + def __getitem__(self, key): + index = c_int32() + try: + _dll.openmc_get_plot_index(key, index) + except (AllocationError, InvalidIDError) as e: + raise KeyError(str(e)) + return SolidRayTracePlot(index=index.value) + + def __iter__(self): + for i in range(len(self)): + yield SolidRayTracePlot(index=i).id + + def __len__(self): + return _dll.openmc_plots_size() + + def __repr__(self): + return repr(dict(self)) + + +plots = _PlotMapping() diff --git a/src/plot.cpp b/src/plot.cpp index daeb0bde7..e3b1e84c8 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -211,8 +211,11 @@ void read_plots_xml() void read_plots_xml(pugi::xml_node root) { for (auto node : root.children("plot")) { - std::string id_string = get_node_value(node, "id", true); - int id = std::stoi(id_string); + std::string plot_desc = ""; + if (check_for_node(node, "id")) { + plot_desc = get_node_value(node, "id", true); + } + if (check_for_node(node, "type")) { std::string type_str = get_node_value(node, "type", true); if (type_str == "slice") { @@ -227,12 +230,12 @@ void read_plots_xml(pugi::xml_node root) } else if (type_str == "solid_raytrace") { model::plots.emplace_back(std::make_unique(node)); } else { - fatal_error( - fmt::format("Unsupported plot type '{}' in plot {}", type_str, id)); + fatal_error(fmt::format( + "Unsupported plot type '{}' in plot {}", type_str, plot_desc)); } model::plot_map[model::plots.back()->id()] = model::plots.size() - 1; } else { - fatal_error(fmt::format("Must specify plot type in plot {}", id)); + fatal_error(fmt::format("Must specify plot type in plot {}", plot_desc)); } } } @@ -290,20 +293,43 @@ ImageData Plot::create_image() const void PlottableInterface::set_id(pugi::xml_node plot_node) { - // Copy data into plots + int id {C_NONE}; if (check_for_node(plot_node, "id")) { - id_ = std::stoi(get_node_value(plot_node, "id")); - } else { - fatal_error("Must specify plot id in plots XML file."); + id = std::stoi(get_node_value(plot_node, "id")); } - // Check to make sure 'id' hasn't been used - if (model::plot_map.find(id_) != model::plot_map.end()) { - fatal_error( - fmt::format("Two or more plots use the same unique ID: {}", id_)); + try { + set_id(id); + } catch (const std::runtime_error& e) { + fatal_error(e.what()); } } +void PlottableInterface::set_id(int id) +{ + if (id < 0 && id != C_NONE) { + throw std::runtime_error {fmt::format("Invalid plot ID: {}", id)}; + } + + if (id == C_NONE) { + id = 1; + for (const auto& p : model::plots) { + id = std::max(id, p->id() + 1); + } + } + + if (id_ == id) + return; + + // Check to make sure this ID doesn't already exist + if (model::plot_map.find(id) != model::plot_map.end()) { + throw std::runtime_error { + fmt::format("Two or more plots use the same unique ID: {}", id)}; + } + + id_ = id; +} + // Checks if png or ppm is already present bool file_extension_present( const std::string& filename, const std::string& extension) @@ -1927,4 +1953,588 @@ extern "C" int openmc_property_map(const void* plot, double* data_out) return 0; } +extern "C" int openmc_get_plot_index(int32_t id, int32_t* index) +{ + auto it = model::plot_map.find(id); + if (it == model::plot_map.end()) { + set_errmsg("No plot exists with ID=" + std::to_string(id) + "."); + return OPENMC_E_INVALID_ID; + } + + *index = it->second; + return 0; +} + +extern "C" int openmc_plot_get_id(int32_t index, int32_t* id) +{ + if (index < 0 || index >= model::plots.size()) { + set_errmsg("Index in plots array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + *id = model::plots[index]->id(); + return 0; +} + +extern "C" int openmc_plot_set_id(int32_t index, int32_t id) +{ + if (index < 0 || index >= model::plots.size()) { + set_errmsg("Index in plots array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + if (id < 0 && id != C_NONE) { + set_errmsg("Invalid plot ID."); + return OPENMC_E_INVALID_ARGUMENT; + } + + auto* plot = model::plots[index].get(); + int32_t old_id = plot->id(); + if (id == old_id) + return 0; + + model::plot_map.erase(old_id); + try { + plot->set_id(id); + } catch (const std::runtime_error& e) { + model::plot_map[old_id] = index; + set_errmsg(e.what()); + return OPENMC_E_INVALID_ID; + } + model::plot_map[plot->id()] = index; + return 0; +} + +extern "C" size_t openmc_plots_size() +{ + return model::plots.size(); +} + +int map_phong_domain_id( + const SolidRayTracePlot* plot, int32_t id, int32_t* index_out) +{ + if (!plot || !index_out) { + set_errmsg("Invalid plot pointer passed to map_phong_domain_id"); + return OPENMC_E_INVALID_ARGUMENT; + } + + if (plot->color_by_ == PlottableInterface::PlotColorBy::mats) { + auto it = model::material_map.find(id); + if (it == model::material_map.end()) { + set_errmsg("Invalid material ID for SolidRayTracePlot"); + return OPENMC_E_INVALID_ID; + } + *index_out = it->second; + return 0; + } + + if (plot->color_by_ == PlottableInterface::PlotColorBy::cells) { + auto it = model::cell_map.find(id); + if (it == model::cell_map.end()) { + set_errmsg("Invalid cell ID for SolidRayTracePlot"); + return OPENMC_E_INVALID_ID; + } + *index_out = it->second; + return 0; + } + + set_errmsg("Unsupported color_by for SolidRayTracePlot"); + return OPENMC_E_INVALID_TYPE; +} + +int get_solidraytrace_plot_by_index(int32_t index, SolidRayTracePlot** plot) +{ + if (!plot) { + set_errmsg("Null output pointer passed to get_solidraytrace_plot_by_index"); + return OPENMC_E_INVALID_ARGUMENT; + } + + if (index < 0 || index >= model::plots.size()) { + set_errmsg("Index in plots array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + auto* plottable = model::plots[index].get(); + auto* solid_plot = dynamic_cast(plottable); + if (!solid_plot) { + set_errmsg("Plot at index=" + std::to_string(index) + + " is not a solid raytrace plot."); + return OPENMC_E_INVALID_TYPE; + } + + *plot = solid_plot; + return 0; +} + +extern "C" int openmc_solidraytrace_plot_create(int32_t* index) +{ + if (!index) { + set_errmsg( + "Null output pointer passed to openmc_solidraytrace_plot_create"); + return OPENMC_E_INVALID_ARGUMENT; + } + + try { + auto new_plot = std::make_unique(); + new_plot->set_id(); + int32_t new_plot_id = new_plot->id(); +#ifdef USE_LIBPNG + new_plot->path_plot() = fmt::format("plot_{}.png", new_plot_id); +#else + new_plot->path_plot() = fmt::format("plot_{}.ppm", new_plot_id); +#endif + int32_t new_plot_index = model::plots.size(); + model::plots.emplace_back(std::move(new_plot)); + model::plot_map[new_plot_id] = new_plot_index; + *index = new_plot_index; + } catch (const std::exception& e) { + set_errmsg(e.what()); + return OPENMC_E_ALLOCATE; + } + + return 0; +} + +extern "C" int openmc_solidraytrace_plot_get_pixels( + int32_t index, int32_t* width, int32_t* height) +{ + if (!width || !height) { + set_errmsg( + "Invalid arguments passed to openmc_solidraytrace_plot_get_pixels"); + return OPENMC_E_INVALID_ARGUMENT; + } + + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + *width = plt->pixels()[0]; + *height = plt->pixels()[1]; + return 0; +} + +extern "C" int openmc_solidraytrace_plot_set_pixels( + int32_t index, int32_t width, int32_t height) +{ + if (width <= 0 || height <= 0) { + set_errmsg( + "Invalid arguments passed to openmc_solidraytrace_plot_set_pixels"); + return OPENMC_E_INVALID_ARGUMENT; + } + + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + plt->pixels()[0] = width; + plt->pixels()[1] = height; + return 0; +} + +extern "C" int openmc_solidraytrace_plot_get_color_by( + int32_t index, int32_t* color_by) +{ + if (!color_by) { + set_errmsg( + "Invalid arguments passed to openmc_solidraytrace_plot_get_color_by"); + return OPENMC_E_INVALID_ARGUMENT; + } + + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + if (plt->color_by_ == PlottableInterface::PlotColorBy::mats) { + *color_by = 0; + } else if (plt->color_by_ == PlottableInterface::PlotColorBy::cells) { + *color_by = 1; + } else { + set_errmsg("Unsupported color_by for SolidRayTracePlot"); + return OPENMC_E_INVALID_TYPE; + } + + return 0; +} + +extern "C" int openmc_solidraytrace_plot_set_color_by( + int32_t index, int32_t color_by) +{ + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + if (color_by == 0) { + plt->color_by_ = PlottableInterface::PlotColorBy::mats; + } else if (color_by == 1) { + plt->color_by_ = PlottableInterface::PlotColorBy::cells; + } else { + set_errmsg("Invalid color_by value for SolidRayTracePlot"); + return OPENMC_E_INVALID_ARGUMENT; + } + + return 0; +} + +extern "C" int openmc_solidraytrace_plot_set_default_colors(int32_t index) +{ + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + plt->set_default_colors(); + return 0; +} + +extern "C" int openmc_solidraytrace_plot_set_all_opaque(int32_t index) +{ + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + plt->opaque_ids().clear(); + if (plt->color_by_ == PlottableInterface::PlotColorBy::mats) { + for (int32_t i = 0; i < model::materials.size(); ++i) { + plt->opaque_ids().insert(i); + } + return 0; + } + + if (plt->color_by_ == PlottableInterface::PlotColorBy::cells) { + for (int32_t i = 0; i < model::cells.size(); ++i) { + plt->opaque_ids().insert(i); + } + return 0; + } + + set_errmsg("Unsupported color_by for SolidRayTracePlot"); + return OPENMC_E_INVALID_TYPE; +} + +extern "C" int openmc_solidraytrace_plot_set_opaque( + int32_t index, int32_t id, bool visible) +{ + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + int32_t domain_index = -1; + err = map_phong_domain_id(plt, id, &domain_index); + if (err) + return err; + + if (visible) { + plt->opaque_ids().insert(domain_index); + } else { + plt->opaque_ids().erase(domain_index); + } + + return 0; +} + +extern "C" int openmc_solidraytrace_plot_set_color( + int32_t index, int32_t id, uint8_t r, uint8_t g, uint8_t b) +{ + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + int32_t domain_index = -1; + err = map_phong_domain_id(plt, id, &domain_index); + if (err) + return err; + + if (domain_index < 0 || + static_cast(domain_index) >= plt->colors_.size()) { + set_errmsg("Color index out of range for SolidRayTracePlot"); + return OPENMC_E_OUT_OF_BOUNDS; + } + + plt->colors_[domain_index] = RGBColor(r, g, b); + return 0; +} + +extern "C" int openmc_solidraytrace_plot_get_camera_position( + int32_t index, double* x, double* y, double* z) +{ + if (!x || !y || !z) { + set_errmsg("Invalid arguments passed to " + "openmc_solidraytrace_plot_get_camera_position"); + return OPENMC_E_INVALID_ARGUMENT; + } + + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + const auto& camera_position = plt->camera_position(); + *x = camera_position.x; + *y = camera_position.y; + *z = camera_position.z; + return 0; +} + +extern "C" int openmc_solidraytrace_plot_set_camera_position( + int32_t index, double x, double y, double z) +{ + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + plt->camera_position() = {x, y, z}; + return 0; +} + +extern "C" int openmc_solidraytrace_plot_get_look_at( + int32_t index, double* x, double* y, double* z) +{ + if (!x || !y || !z) { + set_errmsg( + "Invalid arguments passed to openmc_solidraytrace_plot_get_look_at"); + return OPENMC_E_INVALID_ARGUMENT; + } + + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + const auto& look_at = plt->look_at(); + *x = look_at.x; + *y = look_at.y; + *z = look_at.z; + return 0; +} + +extern "C" int openmc_solidraytrace_plot_set_look_at( + int32_t index, double x, double y, double z) +{ + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + plt->look_at() = {x, y, z}; + return 0; +} + +extern "C" int openmc_solidraytrace_plot_get_up( + int32_t index, double* x, double* y, double* z) +{ + if (!x || !y || !z) { + set_errmsg("Invalid arguments passed to openmc_solidraytrace_plot_get_up"); + return OPENMC_E_INVALID_ARGUMENT; + } + + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + const auto& up = plt->up(); + *x = up.x; + *y = up.y; + *z = up.z; + return 0; +} + +extern "C" int openmc_solidraytrace_plot_set_up( + int32_t index, double x, double y, double z) +{ + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + plt->up() = {x, y, z}; + return 0; +} + +extern "C" int openmc_solidraytrace_plot_get_light_position( + int32_t index, double* x, double* y, double* z) +{ + if (!x || !y || !z) { + set_errmsg("Invalid arguments passed to " + "openmc_solidraytrace_plot_get_light_position"); + return OPENMC_E_INVALID_ARGUMENT; + } + + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + const auto& light_position = plt->light_location(); + *x = light_position.x; + *y = light_position.y; + *z = light_position.z; + return 0; +} + +extern "C" int openmc_solidraytrace_plot_set_light_position( + int32_t index, double x, double y, double z) +{ + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + plt->light_location() = {x, y, z}; + return 0; +} + +extern "C" int openmc_solidraytrace_plot_get_fov(int32_t index, double* fov) +{ + if (!fov) { + set_errmsg("Invalid arguments passed to openmc_solidraytrace_plot_get_fov"); + return OPENMC_E_INVALID_ARGUMENT; + } + + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + *fov = plt->horizontal_field_of_view(); + return 0; +} + +extern "C" int openmc_solidraytrace_plot_set_fov(int32_t index, double fov) +{ + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + plt->horizontal_field_of_view() = fov; + return 0; +} + +extern "C" int openmc_solidraytrace_plot_update_view(int32_t index) +{ + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + plt->update_view(); + return 0; +} + +extern "C" int openmc_solidraytrace_plot_create_image( + int32_t index, uint8_t* data_out, int32_t width, int32_t height) +{ + if (!data_out || width <= 0 || height <= 0) { + set_errmsg( + "Invalid arguments passed to openmc_solidraytrace_plot_create_image"); + return OPENMC_E_INVALID_ARGUMENT; + } + + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + if (plt->pixels()[0] != width || plt->pixels()[1] != height) { + set_errmsg( + "Requested image size does not match SolidRayTracePlot pixel settings"); + return OPENMC_E_INVALID_SIZE; + } + + ImageData data = plt->create_image(); + if (static_cast(data.shape()[0]) != width || + static_cast(data.shape()[1]) != height) { + set_errmsg("Unexpected image size from SolidRayTracePlot create_image"); + return OPENMC_E_INVALID_SIZE; + } + + for (int32_t y = 0; y < height; ++y) { + for (int32_t x = 0; x < width; ++x) { + const auto& color = data(x, y); + size_t idx = (static_cast(y) * width + x) * 3; + data_out[idx + 0] = color.red; + data_out[idx + 1] = color.green; + data_out[idx + 2] = color.blue; + } + } + + return 0; +} + +extern "C" int openmc_solidraytrace_plot_get_color( + int32_t index, int32_t id, uint8_t* r, uint8_t* g, uint8_t* b) +{ + if (!r || !g || !b) { + set_errmsg( + "Invalid arguments passed to openmc_solidraytrace_plot_get_color"); + return OPENMC_E_INVALID_ARGUMENT; + } + + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + int32_t domain_index = -1; + err = map_phong_domain_id(plt, id, &domain_index); + if (err) + return err; + + if (domain_index < 0 || + static_cast(domain_index) >= plt->colors_.size()) { + set_errmsg("Color index out of range for SolidRayTracePlot"); + return OPENMC_E_OUT_OF_BOUNDS; + } + + const auto& color = plt->colors_[domain_index]; + *r = color.red; + *g = color.green; + *b = color.blue; + return 0; +} + +extern "C" int openmc_solidraytrace_plot_get_diffuse_fraction( + int32_t index, double* diffuse_fraction) +{ + if (!diffuse_fraction) { + set_errmsg("Invalid arguments passed to " + "openmc_solidraytrace_plot_get_diffuse_fraction"); + return OPENMC_E_INVALID_ARGUMENT; + } + + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + *diffuse_fraction = plt->diffuse_fraction(); + return 0; +} + +extern "C" int openmc_solidraytrace_plot_set_diffuse_fraction( + int32_t index, double diffuse_fraction) +{ + SolidRayTracePlot* plt = nullptr; + int err = get_solidraytrace_plot_by_index(index, &plt); + if (err) + return err; + + if (diffuse_fraction < 0.0 || diffuse_fraction > 1.0) { + set_errmsg("Diffuse fraction must be between 0 and 1"); + return OPENMC_E_INVALID_ARGUMENT; + } + + plt->diffuse_fraction() = diffuse_fraction; + return 0; +} + } // namespace openmc diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index 3ec2a9ae4..8ef8d5927 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -934,6 +934,60 @@ def test_property_map(lib_init): assert np.allclose(expected_properties, properties, atol=1e-04) +def test_solid_raytrace_plot(lib_init, pincell_model): + # Ensure plot mapping can be accessed and grows after allocation + n0 = len(openmc.lib.plots) + plot = openmc.lib.SolidRayTracePlot() + assert len(openmc.lib.plots) == n0 + 1 + assert plot.id in openmc.lib.plots + assert openmc.lib.plots[plot.id] is plot + + # Exercise plot property getters/setters + plot.pixels = (8, 6) + assert plot.pixels == (8, 6) + + plot.color_by = openmc.lib.SolidRayTracePlot.COLOR_BY_MATERIAL + assert plot.color_by == openmc.lib.SolidRayTracePlot.COLOR_BY_MATERIAL + + plot.camera_position = (2.0, 0.0, 1.0) + plot.look_at = (0.0, 0.0, 0.0) + plot.up = (0.0, 0.0, 1.0) + plot.light_position = (3.0, 2.0, 4.0) + plot.fov = 60.0 + plot.diffuse_fraction = 0.4 + assert plot.camera_position == pytest.approx((2.0, 0.0, 1.0)) + assert plot.look_at == pytest.approx((0.0, 0.0, 0.0)) + assert plot.up == pytest.approx((0.0, 0.0, 1.0)) + assert plot.light_position == pytest.approx((3.0, 2.0, 4.0)) + assert plot.fov == pytest.approx(60.0) + assert plot.diffuse_fraction == pytest.approx(0.4) + + # Exercise color/visibility CAPI wrappers + plot.set_default_colors() + plot.set_color(1, (12, 34, 56)) + assert plot.get_color(1) == (12, 34, 56) + plot.set_visibility(1, False) + plot.set_visibility(1, True) + + # Confirm image creation path works and dimensions match pixels + plot.update_view() + image = plot.create_image() + assert image.shape == (6, 8, 3) + assert image.dtype == np.uint8 + + # Change some properties and confirm image changes + plot.set_color(1, (255, 0, 0)) + plot.update_view() + image2 = plot.create_image() + assert not np.array_equal(image, image2) + + # Solid raytrace uses Phong/diffuse shading, so rendered RGB values are + # generally modulated and need not exactly match the assigned palette. + changed = np.any(image != image2, axis=2) + assert np.any(changed) + assert np.mean(image2[..., 0][changed]) > np.mean(image[..., 0][changed]) + + def test_position(lib_init): pos = openmc.lib.plot._Position(1.0, 2.0, 3.0)