mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-28 06:05:58 -04:00
Merge pull request #1926 from gridley/projection_plots
Projection plots
This commit is contained in:
commit
e0d32e6a29
25 changed files with 1871 additions and 656 deletions
BIN
docs/source/_images/hexlat_anim.gif
Normal file
BIN
docs/source/_images/hexlat_anim.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
|
|
@ -121,3 +121,91 @@ will depend on the 3D viewer, but should be straightforward.
|
|||
program (Visit, ParaView, etc.) if the number of voxels is large (>10
|
||||
million or so). Thus if you want an accurate picture that renders
|
||||
smoothly, consider using only one voxel in a certain direction.
|
||||
|
||||
----------------
|
||||
Projection Plots
|
||||
----------------
|
||||
|
||||
.. image:: ../_images/hexlat_anim.gif
|
||||
:width: 200px
|
||||
|
||||
The :class:`openmc.ProjectionPlot` class presents an alternative method
|
||||
of producing 3D visualizations of OpenMC geometries. It was developed to
|
||||
overcome the primary shortcoming of voxel plots, that an enormous number
|
||||
of voxels must be employed to capture detailed geometric features.
|
||||
Projection plots perform volume rendering on material or
|
||||
cell volumes, with colors specified in the same manner as slice plots.
|
||||
This is done using the native ray tracing capabilities within OpenMC,
|
||||
so any geometry in which particles successfully run without overlaps
|
||||
or leaks will work with projection plots.
|
||||
|
||||
One drawback of projection plots is that particle tracks cannot be overlaid
|
||||
on them at present. Moreover, checking for overlap regions is not currently possible with projection plots. The image heading this section can
|
||||
be created by adding the following code to the hexagonal lattice example packaged
|
||||
with OpenMC, before exporting to plots.xml.
|
||||
|
||||
::
|
||||
|
||||
r = 5
|
||||
import numpy as np
|
||||
for i in range(100):
|
||||
phi = 2 * np.pi * i/100
|
||||
thisp = openmc.ProjectionPlot(plot_id = 4 + i)
|
||||
thisp.filename = 'frame%s'%(str(i).zfill(3))
|
||||
thisp.look_at = [0, 0, 0]
|
||||
thisp.camera_position = [r * np.cos(phi), r * np.sin(phi), 6 * np.sin(phi)]
|
||||
thisp.pixels = [200, 200]
|
||||
thisp.color_by = 'material'
|
||||
thisp.colorize(geometry)
|
||||
thisp.set_transparent(geometry)
|
||||
thisp.xs[fuel] = 1.0
|
||||
thisp.xs[iron] = 1.0
|
||||
thisp.wireframe_domains = [fuel]
|
||||
thisp.wireframe_thickness = 2
|
||||
|
||||
plot_file.append(thisp)
|
||||
|
||||
This generates a sequence of png files which can be joined to form a gif.
|
||||
Each image specifies a different camera position using some simple periodic
|
||||
functions to create a perfectly looped gif. :attr:`ProjectionPlot.look_at`
|
||||
defines where the camera's centerline should point at.
|
||||
:attr:`ProjectionPlot.camera_position` similarly defines where the camera
|
||||
is situated in the universe level we seek to plot. The other settings
|
||||
resemble those employed by :class:`openmc.Plot`, with the exception of
|
||||
the :class:`ProjectionPlot.set_transparent` method and :attr:`ProjectionPlot.xs`
|
||||
dictionary. These are used to control volume rendering of material
|
||||
volumes. "xs" here stands for cross section, and it defines material
|
||||
opacities in units of inverse centimeters. Setting this value to a
|
||||
large number would make a material or cell opaque, and setting it to
|
||||
zero makes a material transparent. Thus, the :class:`ProjectionPlot.set_transparent`
|
||||
can be used to make all materials in the geometry transparent. From there,
|
||||
individual material or cell opacities can be tuned to produce the
|
||||
desired result.
|
||||
|
||||
Two camera projections are available when using these plots, perspective
|
||||
and orthographic. The default, perspective projection,
|
||||
is a cone of rays passing through each pixel which radiate from the camera
|
||||
position and span the field of view in the x and y positions. The horizontal
|
||||
field of view can be set with the :attr: `ProjectionPlot.horizontal_field_of_view` attribute,
|
||||
which is to be specified in units of degrees. The field of view only influences
|
||||
behavior in perspective projection mode.
|
||||
|
||||
In the orthographic projection, rays follow the same angle but originate from
|
||||
different points. The horizontal width of this plane of ray starting points
|
||||
may be set with the :attr: `ProjectionPlot.orthographic_width` element. If this element
|
||||
is nonzero, the orthographic projection is employed. Left to its default value
|
||||
of zero, the perspective projection is employed.
|
||||
|
||||
Lastly, projection plots come packaged with wireframe generation that
|
||||
can target either all surface/cell/material boundaries in the geometry,
|
||||
or only wireframing around specific regions. In the above example, we
|
||||
have set only the fuel region from the hexagonal lattice example to have
|
||||
a wireframe drawn around it. This is accomplished by setting the
|
||||
:attr: `ProjectionPlot.wireframe_domains`, which may be set to either material
|
||||
IDs or cell IDs. The :attr:`ProjectionPlot.wireframe_thickness`
|
||||
attribute sets the wireframe thickness in units of pixels.
|
||||
|
||||
.. note:: When setting specific material or cell regions to have wireframes
|
||||
drawn around them, the plot must be colored by materials if wireframing
|
||||
around specific materials and similarly colored by cell instance if
|
||||
wireframing around specific cells.
|
||||
|
|
|
|||
|
|
@ -168,8 +168,7 @@ private:
|
|||
// Non-member functions
|
||||
//==============================================================================
|
||||
|
||||
int32_t next_cell(
|
||||
DAGUniverse* dag_univ, DAGCell* cur_cell, DAGSurface* surf_xed);
|
||||
int32_t next_cell(int32_t surf, int32_t curr_cell, int32_t univ);
|
||||
|
||||
} // namespace openmc
|
||||
|
||||
|
|
|
|||
|
|
@ -23,12 +23,13 @@ namespace openmc {
|
|||
// Global variables
|
||||
//===============================================================================
|
||||
|
||||
class Plot;
|
||||
class PlottableInterface;
|
||||
|
||||
namespace model {
|
||||
|
||||
extern std::unordered_map<int, int> plot_map; //!< map of plot ids to index
|
||||
extern vector<Plot> plots; //!< Plot instance container
|
||||
extern vector<std::unique_ptr<PlottableInterface>>
|
||||
plots; //!< Plot instance container
|
||||
|
||||
extern uint64_t plotter_seed; // Stream index used by the plotter
|
||||
|
||||
|
|
@ -66,6 +67,50 @@ struct RGBColor {
|
|||
// some default colors
|
||||
const RGBColor WHITE {255, 255, 255};
|
||||
const RGBColor RED {255, 0, 0};
|
||||
const RGBColor BLACK {0, 0, 0};
|
||||
|
||||
/*
|
||||
* PlottableInterface classes just have to have a unique ID in the plots.xml
|
||||
* file, and guarantee being able to create output in some way.
|
||||
*/
|
||||
class PlottableInterface {
|
||||
private:
|
||||
void set_id(pugi::xml_node plot_node);
|
||||
int id_; // unique plot ID
|
||||
|
||||
void set_bg_color(pugi::xml_node plot_node);
|
||||
void set_universe(pugi::xml_node plot_node);
|
||||
void set_default_colors(pugi::xml_node plot_node);
|
||||
void set_user_colors(pugi::xml_node plot_node);
|
||||
void set_overlap_color(pugi::xml_node plot_node);
|
||||
void set_mask(pugi::xml_node plot_node);
|
||||
|
||||
protected:
|
||||
// Plot output filename, derived classes have logic to set it
|
||||
std::string path_plot_;
|
||||
|
||||
public:
|
||||
enum class PlotColorBy { cells = 0, mats = 1 };
|
||||
|
||||
// Creates the output image named path_plot_
|
||||
virtual void create_output() const = 0;
|
||||
|
||||
// Print useful info to the terminal
|
||||
virtual void print_info() const = 0;
|
||||
|
||||
const std::string& path_plot() const { return path_plot_; }
|
||||
const int id() const { return id_; }
|
||||
const int level() const { return level_; }
|
||||
|
||||
// Public color-related data
|
||||
PlottableInterface(pugi::xml_node plot_node);
|
||||
int level_; // Universe level to plot
|
||||
bool color_overlaps_; // 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<RGBColor> colors_; // Plot colors
|
||||
};
|
||||
|
||||
typedef xt::xtensor<RGBColor, 2> ImageData;
|
||||
|
||||
|
|
@ -93,32 +138,30 @@ struct PropertyData {
|
|||
xt::xtensor<double, 3> data_; //!< 2D array of temperature & density data
|
||||
};
|
||||
|
||||
enum class PlotType { slice = 1, voxel = 2 };
|
||||
|
||||
enum class PlotBasis { xy = 1, xz = 2, yz = 3 };
|
||||
|
||||
enum class PlotColorBy { cells = 0, mats = 1 };
|
||||
|
||||
//===============================================================================
|
||||
// Plot class
|
||||
//===============================================================================
|
||||
class PlotBase {
|
||||
|
||||
class SlicePlotBase {
|
||||
public:
|
||||
template<class T>
|
||||
T get_map() const;
|
||||
|
||||
enum class PlotBasis { xy = 1, xz = 2, yz = 3 };
|
||||
|
||||
// Members
|
||||
public:
|
||||
Position origin_; //!< Plot origin in geometry
|
||||
Position width_; //!< Plot width in geometry
|
||||
PlotBasis basis_; //!< Plot basis (XY/XZ/YZ)
|
||||
array<size_t, 3> pixels_; //!< Plot size in pixels
|
||||
bool color_overlaps_; //!< Show overlapping cells?
|
||||
int level_; //!< Plot universe level
|
||||
bool slice_color_overlaps_; //!< Show overlapping cells?
|
||||
int slice_level_ {-1}; //!< Plot universe level
|
||||
private:
|
||||
};
|
||||
|
||||
template<class T>
|
||||
T PlotBase::get_map() const
|
||||
T SlicePlotBase::get_map() const
|
||||
{
|
||||
|
||||
size_t width = pixels_[0];
|
||||
|
|
@ -164,7 +207,7 @@ T PlotBase::get_map() const
|
|||
p.r() = xyz;
|
||||
p.u() = dir;
|
||||
p.coord(0).universe = model::root_universe;
|
||||
int level = level_;
|
||||
int level = slice_level_;
|
||||
int j {};
|
||||
|
||||
#pragma omp for
|
||||
|
|
@ -182,7 +225,7 @@ T PlotBase::get_map() const
|
|||
if (found_cell) {
|
||||
data.set_value(y, x, p, j);
|
||||
}
|
||||
if (color_overlaps_ && check_cell_overlap(p, false)) {
|
||||
if (slice_color_overlaps_ && check_cell_overlap(p, false)) {
|
||||
data.set_overlap(y, x);
|
||||
}
|
||||
} // inner for
|
||||
|
|
@ -192,61 +235,129 @@ T PlotBase::get_map() const
|
|||
return data;
|
||||
}
|
||||
|
||||
class Plot : public PlotBase {
|
||||
// Represents either a voxel or pixel plot
|
||||
class Plot : public PlottableInterface, public SlicePlotBase {
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Plot(pugi::xml_node plot);
|
||||
enum class PlotType { slice = 1, voxel = 2 };
|
||||
|
||||
Plot(pugi::xml_node plot, PlotType type);
|
||||
|
||||
// Methods
|
||||
private:
|
||||
void set_id(pugi::xml_node plot_node);
|
||||
void set_type(pugi::xml_node plot_node);
|
||||
void set_output_path(pugi::xml_node plot_node);
|
||||
void set_bg_color(pugi::xml_node plot_node);
|
||||
void set_basis(pugi::xml_node plot_node);
|
||||
void set_origin(pugi::xml_node plot_node);
|
||||
void set_width(pugi::xml_node plot_node);
|
||||
void set_universe(pugi::xml_node plot_node);
|
||||
void set_default_colors(pugi::xml_node plot_node);
|
||||
void set_user_colors(pugi::xml_node plot_node);
|
||||
void set_meshlines(pugi::xml_node plot_node);
|
||||
void set_mask(pugi::xml_node plot_node);
|
||||
void set_overlap_color(pugi::xml_node plot_node);
|
||||
|
||||
// Members
|
||||
public:
|
||||
int id_; //!< Plot ID
|
||||
// Add mesh lines to ImageData
|
||||
void draw_mesh_lines(ImageData& data) const;
|
||||
void create_image() const;
|
||||
void create_voxel() const;
|
||||
|
||||
virtual void create_output() const;
|
||||
virtual void print_info() const;
|
||||
|
||||
PlotType type_; //!< Plot type (Slice/Voxel)
|
||||
PlotColorBy color_by_; //!< Plot coloring (cell/material)
|
||||
int meshlines_width_; //!< Width of lines added to the plot
|
||||
int index_meshlines_mesh_ {-1}; //!< Index of the mesh to draw on the plot
|
||||
RGBColor meshlines_color_; //!< Color of meshlines on the plot
|
||||
RGBColor not_found_ {WHITE}; //!< Plot background color
|
||||
RGBColor overlap_color_ {RED}; //!< Plot overlap color
|
||||
vector<RGBColor> colors_; //!< Plot colors
|
||||
std::string path_plot_; //!< Plot output filename
|
||||
};
|
||||
|
||||
class ProjectionPlot : public PlottableInterface {
|
||||
|
||||
public:
|
||||
ProjectionPlot(pugi::xml_node plot);
|
||||
|
||||
virtual void create_output() const;
|
||||
virtual void print_info() const;
|
||||
|
||||
private:
|
||||
void set_output_path(pugi::xml_node plot_node);
|
||||
void set_look_at(pugi::xml_node node);
|
||||
void set_camera_position(pugi::xml_node node);
|
||||
void set_field_of_view(pugi::xml_node node);
|
||||
void set_pixels(pugi::xml_node node);
|
||||
void set_opacities(pugi::xml_node node);
|
||||
void set_orthographic_width(pugi::xml_node node);
|
||||
void set_wireframe_thickness(pugi::xml_node node);
|
||||
void set_wireframe_ids(pugi::xml_node node);
|
||||
void set_wireframe_color(pugi::xml_node node);
|
||||
|
||||
/* If starting the particle from outside the geometry, we have to
|
||||
* find a distance to the boundary in a non-standard surface intersection
|
||||
* check. It's an exhaustive search over surfaces in the top-level universe.
|
||||
*/
|
||||
static int advance_to_boundary_from_void(Particle& p);
|
||||
|
||||
/* Checks if a vector of two TrackSegments is equivalent. We define this
|
||||
* to mean not having matching intersection lengths, but rather having
|
||||
* a matching sequence of surface/cell/material intersections.
|
||||
*/
|
||||
struct TrackSegment;
|
||||
bool trackstack_equivalent(const vector<TrackSegment>& track1,
|
||||
const vector<TrackSegment>& track2) const;
|
||||
|
||||
/* Used for drawing wireframe and colors. We record the list of
|
||||
* surface/cell/material intersections and the corresponding lengths as a ray
|
||||
* traverses the geometry, then color by iterating in reverse.
|
||||
*/
|
||||
struct TrackSegment {
|
||||
int id; // material or cell ID (which is being colored)
|
||||
double length; // length of this track intersection
|
||||
|
||||
/* Recording this allows us to draw edges on the wireframe. For instance
|
||||
* if two surfaces bound a single cell, it allows drawing that sharp edge
|
||||
* where the surfaces intersect.
|
||||
*/
|
||||
int surface; // last surface ID intersected in this segment
|
||||
TrackSegment(int id_a, double length_a, int surface_a)
|
||||
: id(id_a), length(length_a), surface(surface_a)
|
||||
{}
|
||||
};
|
||||
|
||||
// Max intersections before we assume ray tracing is caught in an infinite
|
||||
// loop:
|
||||
static const int MAX_INTERSECTIONS = 1000000;
|
||||
|
||||
std::array<int, 2> pixels_; // pixel dimension of resulting image
|
||||
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
|
||||
Direction up_ {0.0, 0.0, 1.0}; // which way is up
|
||||
|
||||
// which color IDs should be wireframed. If empty, all cells are wireframed.
|
||||
vector<int> wireframe_ids_;
|
||||
|
||||
/* The horizontal thickness, if using an orthographic projection.
|
||||
* If set to zero, we assume using a perspective projection.
|
||||
*/
|
||||
double orthographic_width_ {0.0};
|
||||
|
||||
// Thickness of the wireframe lines. Can set to zero for no wireframe.
|
||||
int wireframe_thickness_ {1};
|
||||
|
||||
RGBColor wireframe_color_ {BLACK}; // wireframe color
|
||||
vector<double> xs_; // macro cross section values for cell volume rendering
|
||||
};
|
||||
|
||||
//===============================================================================
|
||||
// Non-member functions
|
||||
//===============================================================================
|
||||
|
||||
//! Add mesh lines to image data of a plot object
|
||||
//! \param[in] plot object
|
||||
//! \param[out] image data associated with the plot object
|
||||
void draw_mesh_lines(Plot const& pl, ImageData& data);
|
||||
|
||||
//! Write a PPM image using a plot object's image data
|
||||
//! \param[in] plot object
|
||||
//! \param[out] image data associated with the plot object
|
||||
void output_ppm(Plot const& pl, const ImageData& data);
|
||||
/* Write a PPM image
|
||||
* filename - name of output file
|
||||
* data - image data to write
|
||||
*/
|
||||
void output_ppm(const std::string& filename, const ImageData& data);
|
||||
|
||||
#ifdef USE_LIBPNG
|
||||
//! Write a PNG image using a plot object's image data
|
||||
//! \param[in] plot object
|
||||
//! \param[out] image data associated with the plot object
|
||||
void output_png(Plot const& pl, const ImageData& data);
|
||||
/* Write a PNG image
|
||||
* filename - name of output file
|
||||
* data - image data to write
|
||||
*/
|
||||
void output_png(const std::string& filename, const ImageData& data);
|
||||
#endif
|
||||
|
||||
//! Initialize a voxel file
|
||||
|
|
@ -286,14 +397,6 @@ void read_plots_xml(pugi::xml_node root);
|
|||
//! Clear memory
|
||||
void free_memory_plot();
|
||||
|
||||
//! Create an image for a plot object
|
||||
//! \param[in] plot object
|
||||
void create_image(Plot const& pl);
|
||||
|
||||
//! Create an hdf5 voxel file for a plot object
|
||||
//! \param[in] plot object
|
||||
void create_voxel(Plot const& pl);
|
||||
|
||||
//! Create a randomly generated RGB color
|
||||
//! \return RGBColor with random value
|
||||
RGBColor random_color();
|
||||
|
|
|
|||
|
|
@ -83,6 +83,11 @@ struct Position {
|
|||
return x * other.x + y * other.y + z * other.z;
|
||||
}
|
||||
inline double norm() const { return std::sqrt(x * x + y * y + z * z); }
|
||||
inline Position cross(Position other) const
|
||||
{
|
||||
return {y * other.z - z * other.y, z * other.x - x * other.z,
|
||||
x * other.y - y * other.x};
|
||||
}
|
||||
|
||||
//! Reflect a direction across a normal vector
|
||||
//! \param[in] other Vector to reflect across
|
||||
|
|
|
|||
|
|
@ -79,3 +79,25 @@ def reorder_attributes(root):
|
|||
attribs = sorted(attrib.items())
|
||||
attrib.clear()
|
||||
attrib.update(attribs)
|
||||
|
||||
|
||||
def get_elem_tuple(elem, name, dtype=int):
|
||||
'''Helper function to get a tuple of values from an elem
|
||||
|
||||
Parameters
|
||||
----------
|
||||
elem : xml.etree.ElementTree.Element
|
||||
XML element that should contain a tuple
|
||||
name : str
|
||||
Name of the subelement to obtain tuple from
|
||||
dtype : data-type
|
||||
The type of each element in the tuple
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple of dtype
|
||||
Data read from the tuple
|
||||
'''
|
||||
subelem = elem.find(name)
|
||||
if subelem is not None:
|
||||
return tuple([dtype(x) for x in subelem.text.split()])
|
||||
|
|
|
|||
732
openmc/plots.py
732
openmc/plots.py
|
|
@ -7,7 +7,7 @@ import numpy as np
|
|||
|
||||
import openmc
|
||||
import openmc.checkvalue as cv
|
||||
from ._xml import clean_indentation, reorder_attributes
|
||||
from ._xml import clean_indentation, reorder_attributes, get_elem_tuple
|
||||
from .mixin import IDManagerMixin
|
||||
|
||||
|
||||
|
|
@ -178,14 +178,8 @@ def _get_plot_image(plot, cwd):
|
|||
return Image(str(png_file))
|
||||
|
||||
|
||||
class Plot(IDManagerMixin):
|
||||
"""Definition of a finite region of space to be plotted.
|
||||
|
||||
OpenMC is capable of generating two-dimensional slice plots and
|
||||
three-dimensional voxel plots. Colors that are used in plots can be given as
|
||||
RGB tuples, e.g. (255, 255, 255) would be white, or by a string indicating a
|
||||
valid `SVG color <https://www.w3.org/TR/SVG11/types.html#ColorKeywords>`_.
|
||||
|
||||
class PlotBase(IDManagerMixin):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
plot_id : int
|
||||
|
|
@ -199,20 +193,12 @@ class Plot(IDManagerMixin):
|
|||
Unique identifier
|
||||
name : str
|
||||
Name of the plot
|
||||
width : Iterable of float
|
||||
Width of the plot in each basis direction
|
||||
pixels : Iterable of int
|
||||
Number of pixels to use in each basis direction
|
||||
origin : tuple or list of ndarray
|
||||
Origin (center) of the plot
|
||||
Number of pixels to use in each direction
|
||||
filename :
|
||||
Path to write the plot to
|
||||
color_by : {'cell', 'material'}
|
||||
Indicate whether the plot should be colored by cell or by material
|
||||
type : {'slice', 'voxel'}
|
||||
The type of the plot
|
||||
basis : {'xy', 'xz', 'yz'}
|
||||
The basis directions for the plot
|
||||
background : Iterable of int or str
|
||||
Color of the background
|
||||
mask_components : Iterable of openmc.Cell or openmc.Material or int
|
||||
|
|
@ -230,10 +216,6 @@ class Plot(IDManagerMixin):
|
|||
cell/material).
|
||||
level : int
|
||||
Universe depth to plot at
|
||||
meshlines : dict
|
||||
Dictionary defining type, id, linewidth and color of a mesh to be
|
||||
plotted on top of a plot
|
||||
|
||||
"""
|
||||
|
||||
next_id = 1
|
||||
|
|
@ -243,13 +225,9 @@ class Plot(IDManagerMixin):
|
|||
# Initialize Plot class attributes
|
||||
self.id = plot_id
|
||||
self.name = name
|
||||
self._width = [4.0, 4.0]
|
||||
self._pixels = [400, 400]
|
||||
self._origin = [0., 0., 0.]
|
||||
self._filename = None
|
||||
self._color_by = 'cell'
|
||||
self._type = 'slice'
|
||||
self._basis = 'xy'
|
||||
self._background = None
|
||||
self._mask_components = None
|
||||
self._mask_background = None
|
||||
|
|
@ -257,24 +235,15 @@ class Plot(IDManagerMixin):
|
|||
self._overlap_color = None
|
||||
self._colors = {}
|
||||
self._level = None
|
||||
self._meshlines = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
@property
|
||||
def pixels(self):
|
||||
return self._pixels
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
return self._origin
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return self._filename
|
||||
|
|
@ -283,14 +252,6 @@ class Plot(IDManagerMixin):
|
|||
def color_by(self):
|
||||
return self._color_by
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return self._type
|
||||
|
||||
@property
|
||||
def basis(self):
|
||||
return self._basis
|
||||
|
||||
@property
|
||||
def background(self):
|
||||
return self._background
|
||||
|
|
@ -319,27 +280,11 @@ class Plot(IDManagerMixin):
|
|||
def level(self):
|
||||
return self._level
|
||||
|
||||
@property
|
||||
def meshlines(self):
|
||||
return self._meshlines
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
cv.check_type('plot name', name, str)
|
||||
self._name = name
|
||||
|
||||
@width.setter
|
||||
def width(self, width):
|
||||
cv.check_type('plot width', width, Iterable, Real)
|
||||
cv.check_length('plot width', width, 2, 3)
|
||||
self._width = width
|
||||
|
||||
@origin.setter
|
||||
def origin(self, origin):
|
||||
cv.check_type('plot origin', origin, Iterable, Real)
|
||||
cv.check_length('plot origin', origin, 3)
|
||||
self._origin = origin
|
||||
|
||||
@pixels.setter
|
||||
def pixels(self, pixels):
|
||||
cv.check_type('plot pixels', pixels, Iterable, Integral)
|
||||
|
|
@ -358,16 +303,6 @@ class Plot(IDManagerMixin):
|
|||
cv.check_value('plot color_by', color_by, ['cell', 'material'])
|
||||
self._color_by = color_by
|
||||
|
||||
@type.setter
|
||||
def type(self, plottype):
|
||||
cv.check_value('plot type', plottype, ['slice', 'voxel'])
|
||||
self._type = plottype
|
||||
|
||||
@basis.setter
|
||||
def basis(self, basis):
|
||||
cv.check_value('plot basis', basis, _BASES)
|
||||
self._basis = basis
|
||||
|
||||
@background.setter
|
||||
def background(self, background):
|
||||
self._check_color('plot background', background)
|
||||
|
|
@ -410,6 +345,181 @@ class Plot(IDManagerMixin):
|
|||
cv.check_greater_than('plot level', plot_level, 0, equality=True)
|
||||
self._level = plot_level
|
||||
|
||||
@staticmethod
|
||||
def _check_color(err_string, color):
|
||||
cv.check_type(err_string, color, Iterable)
|
||||
if isinstance(color, str):
|
||||
if color.lower() not in _SVG_COLORS:
|
||||
raise ValueError(f"'{color}' is not a valid color.")
|
||||
else:
|
||||
cv.check_length(err_string, color, 3)
|
||||
for rgb in color:
|
||||
cv.check_type(err_string, rgb, Real)
|
||||
cv.check_greater_than('RGB component', rgb, 0, True)
|
||||
cv.check_less_than('RGB component', rgb, 256)
|
||||
|
||||
# Helper function that returns the domain ID given either a
|
||||
# Cell/Material object or the domain ID itself
|
||||
@staticmethod
|
||||
def _get_id(domain):
|
||||
return domain if isinstance(domain, Integral) else domain.id
|
||||
|
||||
def colorize(self, geometry, seed=1):
|
||||
"""Generate a color scheme for each domain in the plot.
|
||||
|
||||
This routine may be used to generate random, reproducible color schemes.
|
||||
The colors generated are based upon cell/material IDs in the geometry.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
geometry : openmc.Geometry
|
||||
The geometry for which the plot is defined
|
||||
seed : Integral
|
||||
The random number seed used to generate the color scheme
|
||||
|
||||
"""
|
||||
|
||||
cv.check_type('geometry', geometry, openmc.Geometry)
|
||||
cv.check_type('seed', seed, Integral)
|
||||
cv.check_greater_than('seed', seed, 1, equality=True)
|
||||
|
||||
# Get collections of the domains which will be plotted
|
||||
if self.color_by == 'material':
|
||||
domains = geometry.get_all_materials().values()
|
||||
else:
|
||||
domains = geometry.get_all_cells().values()
|
||||
|
||||
# Set the seed for the random number generator
|
||||
np.random.seed(seed)
|
||||
|
||||
# Generate random colors for each feature
|
||||
for domain in domains:
|
||||
self.colors[domain] = np.random.randint(0, 256, (3,))
|
||||
|
||||
def to_xml_element(self):
|
||||
"""Save common plot attributes to XML element
|
||||
|
||||
Returns
|
||||
-------
|
||||
element : xml.etree.ElementTree.Element
|
||||
XML element containing plot data
|
||||
|
||||
"""
|
||||
|
||||
element = ET.Element("plot")
|
||||
if self._filename is not None:
|
||||
element.set("filename", self._filename)
|
||||
element.set("color_by", self._color_by)
|
||||
|
||||
subelement = ET.SubElement(element, "pixels")
|
||||
subelement.text = ' '.join(map(str, self._pixels))
|
||||
|
||||
if self._background is not None:
|
||||
subelement = ET.SubElement(element, "background")
|
||||
color = self._background
|
||||
if isinstance(color, str):
|
||||
color = _SVG_COLORS[color.lower()]
|
||||
subelement.text = ' '.join(str(x) for x in color)
|
||||
|
||||
if self._mask_components is not None:
|
||||
subelement = ET.SubElement(element, "mask")
|
||||
subelement.set("components", ' '.join(
|
||||
str(PlotBase._get_id(d)) for d in self._mask_components))
|
||||
color = self._mask_background
|
||||
if color is not None:
|
||||
if isinstance(color, str):
|
||||
color = _SVG_COLORS[color.lower()]
|
||||
subelement.set("background", ' '.join(
|
||||
str(x) for x in color))
|
||||
|
||||
if self._level is not None:
|
||||
subelement = ET.SubElement(element, "level")
|
||||
subelement.text = str(self._level)
|
||||
|
||||
return element
|
||||
|
||||
|
||||
class Plot(PlotBase):
|
||||
'''Definition of a finite region of space to be plotted.
|
||||
|
||||
OpenMC is capable of generating two-dimensional slice plots, or
|
||||
three-dimensional voxel or projection plots. Colors that are used in plots can be given as
|
||||
RGB tuples, e.g. (255, 255, 255) would be white, or by a string indicating a
|
||||
valid `SVG color <https://www.w3.org/TR/SVG11/types.html#ColorKeywords>`_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
plot_id : int
|
||||
Unique identifier for the plot
|
||||
name : str
|
||||
Name of the plot
|
||||
|
||||
Attributes
|
||||
----------
|
||||
width : Iterable of float
|
||||
Width of the plot in each basis direction
|
||||
origin : tuple or list of ndarray
|
||||
Origin (center) of the plot
|
||||
type : {'slice', 'voxel'}
|
||||
The type of the plot
|
||||
basis : {'xy', 'xz', 'yz'}
|
||||
The basis directions for the plot
|
||||
meshlines : dict
|
||||
Dictionary defining type, id, linewidth and color of a mesh to be
|
||||
plotted on top of a plot
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, plot_id=None, name=''):
|
||||
super().__init__(plot_id, name)
|
||||
self._width = [4.0, 4.0]
|
||||
self._origin = [0., 0., 0.]
|
||||
self._type = 'slice'
|
||||
self._basis = 'xy'
|
||||
self._meshlines = None
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
return self._origin
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return self._type
|
||||
|
||||
@property
|
||||
def basis(self):
|
||||
return self._basis
|
||||
|
||||
@property
|
||||
def meshlines(self):
|
||||
return self._meshlines
|
||||
|
||||
@width.setter
|
||||
def width(self, width):
|
||||
cv.check_type('plot width', width, Iterable, Real)
|
||||
cv.check_length('plot width', width, 2, 3)
|
||||
self._width = width
|
||||
|
||||
@origin.setter
|
||||
def origin(self, origin):
|
||||
cv.check_type('plot origin', origin, Iterable, Real)
|
||||
cv.check_length('plot origin', origin, 3)
|
||||
self._origin = origin
|
||||
|
||||
@type.setter
|
||||
def type(self, plottype):
|
||||
cv.check_value('plot type', plottype, ['slice', 'voxel', 'projection'])
|
||||
self._type = plottype
|
||||
|
||||
@basis.setter
|
||||
def basis(self, basis):
|
||||
cv.check_value('plot basis', basis, _BASES)
|
||||
self._basis = basis
|
||||
|
||||
@meshlines.setter
|
||||
def meshlines(self, meshlines):
|
||||
cv.check_type('plot meshlines', meshlines, dict)
|
||||
|
|
@ -429,7 +539,8 @@ class Plot(IDManagerMixin):
|
|||
equality=True)
|
||||
|
||||
if 'linewidth' in meshlines:
|
||||
cv.check_type('plot mesh linewidth', meshlines['linewidth'], Integral)
|
||||
cv.check_type('plot mesh linewidth',
|
||||
meshlines['linewidth'], Integral)
|
||||
cv.check_greater_than('plot mesh linewidth', meshlines['linewidth'],
|
||||
0, equality=True)
|
||||
|
||||
|
|
@ -438,19 +549,6 @@ class Plot(IDManagerMixin):
|
|||
|
||||
self._meshlines = meshlines
|
||||
|
||||
@staticmethod
|
||||
def _check_color(err_string, color):
|
||||
cv.check_type(err_string, color, Iterable)
|
||||
if isinstance(color, str):
|
||||
if color.lower() not in _SVG_COLORS:
|
||||
raise ValueError(f"'{color}' is not a valid color.")
|
||||
else:
|
||||
cv.check_length(err_string, color, 3)
|
||||
for rgb in color:
|
||||
cv.check_type(err_string, rgb, Real)
|
||||
cv.check_greater_than('RGB component', rgb, 0, True)
|
||||
cv.check_less_than('RGB component', rgb, 256)
|
||||
|
||||
def __repr__(self):
|
||||
string = 'Plot\n'
|
||||
string += '{: <16}=\t{}\n'.format('\tID', self._id)
|
||||
|
|
@ -520,38 +618,6 @@ class Plot(IDManagerMixin):
|
|||
plot.basis = basis
|
||||
return plot
|
||||
|
||||
def colorize(self, geometry, seed=1):
|
||||
"""Generate a color scheme for each domain in the plot.
|
||||
|
||||
This routine may be used to generate random, reproducible color schemes.
|
||||
The colors generated are based upon cell/material IDs in the geometry.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
geometry : openmc.Geometry
|
||||
The geometry for which the plot is defined
|
||||
seed : Integral
|
||||
The random number seed used to generate the color scheme
|
||||
|
||||
"""
|
||||
|
||||
cv.check_type('geometry', geometry, openmc.Geometry)
|
||||
cv.check_type('seed', seed, Integral)
|
||||
cv.check_greater_than('seed', seed, 1, equality=True)
|
||||
|
||||
# Get collections of the domains which will be plotted
|
||||
if self.color_by == 'material':
|
||||
domains = geometry.get_all_materials().values()
|
||||
else:
|
||||
domains = geometry.get_all_cells().values()
|
||||
|
||||
# Set the seed for the random number generator
|
||||
np.random.seed(seed)
|
||||
|
||||
# Generate random colors for each feature
|
||||
for domain in domains:
|
||||
self.colors[domain] = np.random.randint(0, 256, (3,))
|
||||
|
||||
def highlight_domains(self, geometry, domains, seed=1,
|
||||
alpha=0.5, background='gray'):
|
||||
"""Use alpha compositing to highlight one or more domains in the plot.
|
||||
|
|
@ -604,7 +670,7 @@ class Plot(IDManagerMixin):
|
|||
self._colors[domain] = (r, g, b)
|
||||
|
||||
def to_xml_element(self):
|
||||
"""Return XML representation of the plot
|
||||
"""Return XML representation of the slice/voxel plot
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
|
@ -613,11 +679,8 @@ class Plot(IDManagerMixin):
|
|||
|
||||
"""
|
||||
|
||||
element = ET.Element("plot")
|
||||
element = super().to_xml_element()
|
||||
element.set("id", str(self._id))
|
||||
if self._filename is not None:
|
||||
element.set("filename", self._filename)
|
||||
element.set("color_by", self._color_by)
|
||||
element.set("type", self._type)
|
||||
|
||||
if self._type == 'slice':
|
||||
|
|
@ -629,41 +692,15 @@ class Plot(IDManagerMixin):
|
|||
subelement = ET.SubElement(element, "width")
|
||||
subelement.text = ' '.join(map(str, self._width))
|
||||
|
||||
subelement = ET.SubElement(element, "pixels")
|
||||
subelement.text = ' '.join(map(str, self._pixels))
|
||||
|
||||
if self._background is not None:
|
||||
subelement = ET.SubElement(element, "background")
|
||||
color = self._background
|
||||
if isinstance(color, str):
|
||||
color = _SVG_COLORS[color.lower()]
|
||||
subelement.text = ' '.join(str(x) for x in color)
|
||||
|
||||
# Helper function that returns the domain ID given either a
|
||||
# Cell/Material object or the domain ID itself
|
||||
def get_id(domain):
|
||||
return domain if isinstance(domain, Integral) else domain.id
|
||||
|
||||
if self._colors:
|
||||
for domain, color in sorted(self._colors.items(),
|
||||
key=lambda x: get_id(x[0])):
|
||||
key=lambda x: PlotBase._get_id(x[0])):
|
||||
subelement = ET.SubElement(element, "color")
|
||||
subelement.set("id", str(get_id(domain)))
|
||||
subelement.set("id", str(PlotBase._get_id(domain)))
|
||||
if isinstance(color, str):
|
||||
color = _SVG_COLORS[color.lower()]
|
||||
subelement.set("rgb", ' '.join(str(x) for x in color))
|
||||
|
||||
if self._mask_components is not None:
|
||||
subelement = ET.SubElement(element, "mask")
|
||||
subelement.set("components", ' '.join(
|
||||
str(get_id(d)) for d in self._mask_components))
|
||||
color = self._mask_background
|
||||
if color is not None:
|
||||
if isinstance(color, str):
|
||||
color = _SVG_COLORS[color.lower()]
|
||||
subelement.set("background", ' '.join(
|
||||
str(x) for x in color))
|
||||
|
||||
if self._show_overlaps:
|
||||
subelement = ET.SubElement(element, "show_overlaps")
|
||||
subelement.text = "true"
|
||||
|
|
@ -675,11 +712,6 @@ class Plot(IDManagerMixin):
|
|||
subelement = ET.SubElement(element, "overlap_color")
|
||||
subelement.text = ' '.join(str(x) for x in color)
|
||||
|
||||
|
||||
if self._level is not None:
|
||||
subelement = ET.SubElement(element, "level")
|
||||
subelement.text = str(self._level)
|
||||
|
||||
if self._meshlines is not None:
|
||||
subelement = ET.SubElement(element, "meshlines")
|
||||
subelement.set("meshtype", self._meshlines['type'])
|
||||
|
|
@ -716,37 +748,34 @@ class Plot(IDManagerMixin):
|
|||
plot.type = elem.get("type")
|
||||
plot.basis = elem.get("basis")
|
||||
|
||||
# Helper function to get a tuple of values
|
||||
def get_tuple(elem, name, dtype=int):
|
||||
subelem = elem.find(name)
|
||||
if subelem is not None:
|
||||
return tuple([dtype(x) for x in subelem.text.split()])
|
||||
|
||||
plot.origin = get_tuple(elem, "origin", float)
|
||||
plot.width = get_tuple(elem, "width", float)
|
||||
plot.pixels = get_tuple(elem, "pixels")
|
||||
plot._background = get_tuple(elem, "background")
|
||||
plot.origin = get_elem_tuple(elem, "origin", float)
|
||||
plot.width = get_elem_tuple(elem, "width", float)
|
||||
plot.pixels = get_elem_tuple(elem, "pixels")
|
||||
plot._background = get_elem_tuple(elem, "background")
|
||||
|
||||
# Set plot colors
|
||||
colors = {}
|
||||
for color_elem in elem.findall("color"):
|
||||
uid = int(color_elem.get("id"))
|
||||
colors[uid] = tuple([int(x) for x in color_elem.get("rgb").split()])
|
||||
colors[uid] = tuple([int(x)
|
||||
for x in color_elem.get("rgb").split()])
|
||||
plot.colors = colors
|
||||
|
||||
# Set masking information
|
||||
mask_elem = elem.find("mask")
|
||||
if mask_elem is not None:
|
||||
plot.mask_components = [int(x) for x in mask_elem.get("components").split()]
|
||||
plot.mask_components = [
|
||||
int(x) for x in mask_elem.get("components").split()]
|
||||
background = mask_elem.get("background")
|
||||
if background is not None:
|
||||
plot.mask_background = tuple([int(x) for x in background.split()])
|
||||
plot.mask_background = tuple(
|
||||
[int(x) for x in background.split()])
|
||||
|
||||
# show overlaps
|
||||
overlap_elem = elem.find("show_overlaps")
|
||||
if overlap_elem is not None:
|
||||
plot.show_overlaps = (overlap_elem.text in ('true', '1'))
|
||||
overlap_color = get_tuple(elem, "overlap_color")
|
||||
overlap_color = get_elem_tuple(elem, "overlap_color")
|
||||
if overlap_color is not None:
|
||||
plot.overlap_color = overlap_color
|
||||
|
||||
|
|
@ -803,12 +832,357 @@ class Plot(IDManagerMixin):
|
|||
return _get_plot_image(self, cwd)
|
||||
|
||||
|
||||
class ProjectionPlot(PlotBase):
|
||||
"""Definition of a camera's view of OpenMC geometry
|
||||
|
||||
Colors are defined in the same manner as the Plot class, but with the addition
|
||||
of a coloring parameter resembling a macroscopic cross section in units of inverse
|
||||
centimeters. The volume rendering technique is used to color regions of the model.
|
||||
An infinite cross section denotes a fully opaque region, and zero represents a
|
||||
transparent region which will expose the color of the regions behind it.
|
||||
|
||||
The camera projection may either by orthographic or perspective. Perspective
|
||||
projections are more similar to a pinhole camera, and orthographic projections
|
||||
preserve parallel lines and distances.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
plot_id : int
|
||||
Unique identifier for the plot
|
||||
name : str
|
||||
Name of the plot
|
||||
|
||||
Attributes
|
||||
----------
|
||||
horizontal_field_of_view : float
|
||||
Field of view horizontally, in units of degrees, defaults to 70.
|
||||
camera_position : tuple or list of ndarray
|
||||
Position of the camera in 3D space. Defaults to (1, 0, 0).
|
||||
look_at : tuple or list of ndarray
|
||||
The center of the camera's image points to this place in 3D space.
|
||||
Set to (0, 0, 0) by default.
|
||||
up : tuple or list of ndarray
|
||||
Which way is up for the camera. Must not be parallel to the
|
||||
line between look_at and camera_position. Set to (0, 0, 1) by default.
|
||||
orthographic_width : float
|
||||
If set to a nonzero value, an orthographic projection is used.
|
||||
All rays traced from the orthographic pixel array travel in the
|
||||
same direction. The width of the starting array must be specified,
|
||||
unlike with the default perspective projection. The height of the
|
||||
array is deduced from the ratio of pixel dimensions for the image.
|
||||
Defaults to zero, i.e. using perspective projection.
|
||||
wireframe_thickness : int
|
||||
Line thickness employed for drawing wireframes around cells or
|
||||
material regions. Can be set to zero for no wireframes at all.
|
||||
Defaults to one pixel.
|
||||
wireframe_color : tuple of ints
|
||||
RGB color of the wireframe lines. Defaults to black.
|
||||
wireframe_domains : iterable of either Material or Cells
|
||||
If provided, the wireframe is only drawn around these.
|
||||
If color_by is by material, it must be a list of materials, else cells.
|
||||
xs : dict
|
||||
A mapping from cell/material IDs to floats. The floating point values
|
||||
are macroscopic cross sections influencing the volume rendering opacity
|
||||
of each geometric region. Zero corresponds to perfect transparency, and
|
||||
infinity equivalent to opaque. These must be set by the user, but default
|
||||
values can be obtained using the set_transparent method.
|
||||
"""
|
||||
|
||||
def __init__(self, plot_id=None, name=''):
|
||||
# Initialize Plot class attributes
|
||||
super().__init__(plot_id, name)
|
||||
self._horizontal_field_of_view = 70.0
|
||||
self._camera_position = (1.0, 0.0, 0.0)
|
||||
self._look_at = (0.0, 0.0, 0.0)
|
||||
self._up = (0.0, 0.0, 1.0)
|
||||
self._orthographic_width = 0.0
|
||||
self._wireframe_thickness = 1
|
||||
self._wireframe_color = _SVG_COLORS['black']
|
||||
self._wireframe_domains = []
|
||||
self._xs = {}
|
||||
|
||||
@property
|
||||
def horizontal_field_of_view(self):
|
||||
return self._horizontal_field_of_view
|
||||
|
||||
@property
|
||||
def camera_position(self):
|
||||
return self._camera_position
|
||||
|
||||
@property
|
||||
def look_at(self):
|
||||
return self._look_at
|
||||
|
||||
@property
|
||||
def up(self):
|
||||
return self._up
|
||||
|
||||
@property
|
||||
def orthographic_width(self):
|
||||
return self._orthographic_width
|
||||
|
||||
@property
|
||||
def wireframe_thickness(self):
|
||||
return self._wireframe_thickness
|
||||
|
||||
@property
|
||||
def wireframe_color(self):
|
||||
return self._wireframe_color
|
||||
|
||||
@property
|
||||
def wireframe_domains(self):
|
||||
return self._wireframe_domains
|
||||
|
||||
@property
|
||||
def xs(self):
|
||||
return self._xs
|
||||
|
||||
@horizontal_field_of_view.setter
|
||||
def horizontal_field_of_view(self, horizontal_field_of_view):
|
||||
cv.check_type('plot horizontal field of view', horizontal_field_of_view,
|
||||
Real)
|
||||
assert horizontal_field_of_view > 0.0
|
||||
assert horizontal_field_of_view < 180.0
|
||||
self._horizontal_field_of_view = horizontal_field_of_view
|
||||
|
||||
@camera_position.setter
|
||||
def camera_position(self, camera_position):
|
||||
cv.check_type('plot camera position', camera_position, Iterable, Real)
|
||||
cv.check_length('plot camera position', camera_position, 3)
|
||||
self._camera_position = camera_position
|
||||
|
||||
@look_at.setter
|
||||
def look_at(self, look_at):
|
||||
cv.check_type('plot look at', look_at, Iterable, Real)
|
||||
cv.check_length('plot look at', look_at, 3)
|
||||
self._look_at = look_at
|
||||
|
||||
@up.setter
|
||||
def up(self, up):
|
||||
cv.check_type('plot up', up, Iterable, Real)
|
||||
cv.check_length('plot up', up, 3)
|
||||
self._up = up
|
||||
|
||||
@orthographic_width.setter
|
||||
def orthographic_width(self, orthographic_width):
|
||||
cv.check_type('plot orthographic width', orthographic_width, Real)
|
||||
assert orthographic_width >= 0.0
|
||||
self._orthographic_width = orthographic_width
|
||||
|
||||
@wireframe_thickness.setter
|
||||
def wireframe_thickness(self, wireframe_thickness):
|
||||
cv.check_type('plot wireframe thickness',
|
||||
wireframe_thickness, Integral)
|
||||
assert wireframe_thickness >= 0
|
||||
self._wireframe_thickness = wireframe_thickness
|
||||
|
||||
@wireframe_color.setter
|
||||
def wireframe_color(self, wireframe_color):
|
||||
self._check_color('plot wireframe color', wireframe_color)
|
||||
self._wireframe_color = wireframe_color
|
||||
|
||||
@wireframe_domains.setter
|
||||
def wireframe_domains(self, wireframe_domains):
|
||||
for region in wireframe_domains:
|
||||
if self._color_by == 'material':
|
||||
if not isinstance(region, openmc.Material):
|
||||
raise Exception('Must provide a list of materials for \
|
||||
wireframe_region if color_by=Material')
|
||||
else:
|
||||
if not isinstance(region, openmc.Cell):
|
||||
raise Exception('Must provide a list of cells for \
|
||||
wireframe_region if color_by=cell')
|
||||
self._wireframe_domains = wireframe_domains
|
||||
|
||||
@xs.setter
|
||||
def xs(self, xs):
|
||||
cv.check_type('plot xs', xs, Mapping)
|
||||
for key, value in xs.items():
|
||||
cv.check_type('plot xs key', key, (openmc.Cell, openmc.Material))
|
||||
cv.check_type('plot xs value', value, Real)
|
||||
assert value >= 0.0
|
||||
self._xs = xs
|
||||
|
||||
def set_transparent(self, geometry):
|
||||
"""Sets all volume rendering XS to zero for the model
|
||||
|
||||
Parameters
|
||||
----------
|
||||
geometry : openmc.Geometry
|
||||
The geometry for which the plot is defined
|
||||
"""
|
||||
|
||||
cv.check_type('geometry', geometry, openmc.Geometry)
|
||||
|
||||
# Get collections of the domains which will be plotted
|
||||
if self.color_by == 'material':
|
||||
domains = geometry.get_all_materials().values()
|
||||
else:
|
||||
domains = geometry.get_all_cells().values()
|
||||
|
||||
# Generate random colors for each feature
|
||||
for domain in domains:
|
||||
self.xs[domain] = 0.0
|
||||
|
||||
def to_xml_element(self):
|
||||
"""Return XML representation of the projection plot
|
||||
|
||||
Returns
|
||||
-------
|
||||
element : xml.etree.ElementTree.Element
|
||||
XML element containing plot data
|
||||
|
||||
"""
|
||||
|
||||
element = super().to_xml_element()
|
||||
element.set("id", str(self._id))
|
||||
element.set("type", "projection")
|
||||
|
||||
subelement = ET.SubElement(element, "camera_position")
|
||||
subelement.text = ' '.join(map(str, self._camera_position))
|
||||
|
||||
subelement = ET.SubElement(element, "look_at")
|
||||
subelement.text = ' '.join(map(str, self._look_at))
|
||||
|
||||
subelement = ET.SubElement(element, "wireframe_thickness")
|
||||
subelement.text = str(self._wireframe_thickness)
|
||||
|
||||
subelement = ET.SubElement(element, "wireframe_color")
|
||||
color = self._wireframe_color
|
||||
if isinstance(color, str):
|
||||
color = _SVG_COLORS[color.lower()]
|
||||
subelement.text = ' '.join(str(x) for x in color)
|
||||
|
||||
if self._wireframe_domains:
|
||||
id_list = [x.id for x in self._wireframe_domains]
|
||||
subelement = ET.SubElement(element, "wireframe_ids")
|
||||
subelement.text = ' '.join([str(x) for x in id_list])
|
||||
|
||||
# note that this differs from the slice plot colors
|
||||
# in that "xs" must also be specified
|
||||
if self._colors:
|
||||
for domain, color in sorted(self._colors.items(),
|
||||
key=lambda x: x[0].id):
|
||||
subelement = ET.SubElement(element, "color")
|
||||
subelement.set("id", str(domain.id))
|
||||
if isinstance(color, str):
|
||||
color = _SVG_COLORS[color.lower()]
|
||||
subelement.set("rgb", ' '.join(str(x) for x in color))
|
||||
subelement.set("xs", str(self._xs[domain]))
|
||||
|
||||
subelement = ET.SubElement(element, "horizontal_field_of_view")
|
||||
subelement.text = str(self._horizontal_field_of_view)
|
||||
|
||||
# do not need to write if orthographic_width == 0.0
|
||||
if self._orthographic_width > 0.0:
|
||||
subelement = ET.SubElement(element, "orthographic_width")
|
||||
subelement.text = str(self._orthographic_width)
|
||||
|
||||
return element
|
||||
|
||||
def __repr__(self):
|
||||
string = 'Projection Plot\n'
|
||||
string += '{: <16}=\t{}\n'.format('\tID', self._id)
|
||||
string += '{: <16}=\t{}\n'.format('\tName', self._name)
|
||||
string += '{: <16}=\t{}\n'.format('\tFilename', self._filename)
|
||||
string += '{: <16}=\t{}\n'.format('\tHorizontal FOV',
|
||||
self._horizontal_field_of_view)
|
||||
string += '{: <16}=\t{}\n'.format('\tOrthographic width',
|
||||
self._orthographic_width)
|
||||
string += '{: <16}=\t{}\n'.format('\tWireframe thickness',
|
||||
self._wireframe_thickness)
|
||||
string += '{: <16}=\t{}\n'.format('\tWireframe color',
|
||||
self._wireframe_color)
|
||||
string += '{: <16}=\t{}\n'.format('\tWireframe domains',
|
||||
self._wireframe_domains)
|
||||
string += '{: <16}=\t{}\n'.format('\tCamera position',
|
||||
self._camera_position)
|
||||
string += '{: <16}=\t{}\n'.format('\tLook at', self._look_at)
|
||||
string += '{: <16}=\t{}\n'.format('\tUp', self._up)
|
||||
string += '{: <16}=\t{}\n'.format('\tPixels', self._pixels)
|
||||
string += '{: <16}=\t{}\n'.format('\tColor by', self._color_by)
|
||||
string += '{: <16}=\t{}\n'.format('\tBackground', self._background)
|
||||
string += '{: <16}=\t{}\n'.format('\tColors', self._colors)
|
||||
string += '{: <16}=\t{}\n'.format('\tTransparencies', self._xs)
|
||||
string += '{: <16}=\t{}\n'.format('\tLevel', self._level)
|
||||
return string
|
||||
|
||||
@classmethod
|
||||
def from_xml_element(cls, elem):
|
||||
"""Generate plot object from an XML element
|
||||
|
||||
Parameters
|
||||
----------
|
||||
elem : xml.etree.ElementTree.Element
|
||||
XML element
|
||||
|
||||
Returns
|
||||
-------
|
||||
openmc.ProjectionPlot
|
||||
ProjectionPlot object
|
||||
|
||||
"""
|
||||
plot_id = int(elem.get("id"))
|
||||
plot = cls(plot_id)
|
||||
if "filename" in elem.keys():
|
||||
plot.filename = elem.get("filename")
|
||||
plot.color_by = elem.get("color_by")
|
||||
plot.type = "projection"
|
||||
|
||||
horizontal_fov = elem.find("horizontal_field_of_view")
|
||||
if horizontal_fov is not None:
|
||||
plot.horizontal_field_of_view = float(horizontal_fov.text)
|
||||
|
||||
tmp = elem.find("orthographic_width")
|
||||
if tmp is not None:
|
||||
self.orthographic_width = float(tmp)
|
||||
|
||||
plot.pixels = get_elem_tuple(elem, "pixels")
|
||||
plot.camera_position = get_elem_tuple(elem, "camera_position", float)
|
||||
plot.look_at = get_elem_tuple(elem, "look_at", float)
|
||||
|
||||
# Attempt to get wireframe thickness. May not be present
|
||||
wireframe_thickness = elem.get("wireframe_thickness")
|
||||
if wireframe_thickness:
|
||||
plot.wireframe_thickness = int(wireframe_thickness)
|
||||
wireframe_color = elem.get("wireframe_color")
|
||||
if wireframe_color:
|
||||
plot.wireframe_color = [int(item) for item in wireframe_color]
|
||||
|
||||
# Set plot colors
|
||||
colors = {}
|
||||
xs = {}
|
||||
for color_elem in elem.findall("color"):
|
||||
uid = color_elem.get("id")
|
||||
colors[uid] = get_elem_tuple(color_elem, "rgb")
|
||||
xs[uid] = float(color_elem.get("xs"))
|
||||
|
||||
# Set masking information
|
||||
mask_elem = elem.find("mask")
|
||||
if mask_elem is not None:
|
||||
mask_components = [int(x)
|
||||
for x in mask_elem.get("components").split()]
|
||||
# TODO: set mask components (needs geometry information)
|
||||
background = mask_elem.get("background")
|
||||
if background is not None:
|
||||
plot.mask_background = tuple(
|
||||
[int(x) for x in background.split()])
|
||||
|
||||
# Set universe level
|
||||
level = elem.find("level")
|
||||
if level is not None:
|
||||
plot.level = int(level.text)
|
||||
|
||||
return plot
|
||||
|
||||
|
||||
class Plots(cv.CheckedList):
|
||||
"""Collection of Plots used for an OpenMC simulation.
|
||||
|
||||
This class corresponds directly to the plots.xml input file. It can be
|
||||
thought of as a normal Python list where each member is a :class:`Plot`. It
|
||||
behaves like a list as the following example demonstrates:
|
||||
thought of as a normal Python list where each member is inherits from
|
||||
:class:`PlotBase`. It behaves like a list as the following example
|
||||
demonstrates:
|
||||
|
||||
>>> xz_plot = openmc.Plot()
|
||||
>>> big_plot = openmc.Plot()
|
||||
|
|
@ -819,13 +1193,13 @@ class Plots(cv.CheckedList):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
plots : Iterable of openmc.Plot
|
||||
Plots to add to the collection
|
||||
plots : Iterable of openmc.Plot or openmc.ProjectionPlot
|
||||
plots to add to the collection
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, plots=None):
|
||||
super().__init__(Plot, 'plots collection')
|
||||
super().__init__((Plot, ProjectionPlot), 'plots collection')
|
||||
self._plots_file = ET.Element("plots")
|
||||
if plots is not None:
|
||||
self += plots
|
||||
|
|
@ -835,7 +1209,7 @@ class Plots(cv.CheckedList):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
plot : openmc.Plot
|
||||
plot : openmc.Plot or openmc.ProjectionPlot
|
||||
Plot to append
|
||||
|
||||
"""
|
||||
|
|
@ -873,7 +1247,6 @@ class Plots(cv.CheckedList):
|
|||
for plot in self:
|
||||
plot.colorize(geometry, seed)
|
||||
|
||||
|
||||
def highlight_domains(self, geometry, domains, seed=1,
|
||||
alpha=0.5, background='gray'):
|
||||
"""Use alpha compositing to highlight one or more domains in the plot.
|
||||
|
|
@ -925,7 +1298,8 @@ class Plots(cv.CheckedList):
|
|||
|
||||
# Clean the indentation in the file to be user-readable
|
||||
clean_indentation(self._plots_file)
|
||||
reorder_attributes(self._plots_file) # TODO: Remove when support is Python 3.8+
|
||||
# TODO: Remove when support is Python 3.8+
|
||||
reorder_attributes(self._plots_file)
|
||||
|
||||
return self._plots_file
|
||||
|
||||
|
|
@ -966,7 +1340,11 @@ class Plots(cv.CheckedList):
|
|||
# Generate each plot
|
||||
plots = cls()
|
||||
for e in elem.findall('plot'):
|
||||
plots.append(Plot.from_xml_element(e))
|
||||
plot_type = e.get('type')
|
||||
if plot_type == 'projection':
|
||||
plots.append(ProjectionPlot.from_xml_element(e))
|
||||
else:
|
||||
plots.append(Plot.from_xml_element(e))
|
||||
return plots
|
||||
|
||||
@classmethod
|
||||
|
|
@ -987,5 +1365,3 @@ class Plots(cv.CheckedList):
|
|||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
return cls.from_xml_element(root)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -603,6 +603,7 @@ std::pair<double, int32_t> DAGCell::distance(
|
|||
// indicate that particle is lost
|
||||
surf_idx = -1;
|
||||
dist = INFINITY;
|
||||
if (settings::run_mode == RunMode::PLOTTING) return {dist, surf_idx};
|
||||
if (!dagmc_ptr_->is_implicit_complement(vol) ||
|
||||
model::universe_map[dag_univ->id_] == model::root_universe) {
|
||||
std::string material_id = p->material() == MATERIAL_VOID
|
||||
|
|
@ -728,19 +729,22 @@ void check_dagmc_root_univ()
|
|||
}
|
||||
}
|
||||
|
||||
int32_t next_cell(
|
||||
DAGUniverse* dag_univ, DAGCell* cur_cell, DAGSurface* surf_xed)
|
||||
{
|
||||
moab::EntityHandle surf =
|
||||
surf_xed->dagmc_ptr()->entity_by_index(2, surf_xed->dag_index());
|
||||
moab::EntityHandle vol =
|
||||
cur_cell->dagmc_ptr()->entity_by_index(3, cur_cell->dag_index());
|
||||
int32_t next_cell(int32_t surf, int32_t curr_cell, int32_t univ) {
|
||||
auto surfp = dynamic_cast<DAGSurface*>(model::surfaces[surf - 1].get());
|
||||
auto cellp = dynamic_cast<DAGCell*>(model::cells[curr_cell].get());
|
||||
auto univp = static_cast<DAGUniverse*>(model::universes[univ].get());
|
||||
|
||||
moab::EntityHandle surf_handle =
|
||||
surfp->dagmc_ptr()->entity_by_index(2, surfp->dag_index());
|
||||
moab::EntityHandle curr_vol =
|
||||
cellp->dagmc_ptr()->entity_by_index(3, cellp->dag_index());
|
||||
|
||||
moab::EntityHandle new_vol;
|
||||
cur_cell->dagmc_ptr()->next_vol(surf, vol, new_vol);
|
||||
moab::ErrorCode rval = cellp->dagmc_ptr()->next_vol(surf_handle, curr_vol, new_vol);
|
||||
if (rval != moab::MB_SUCCESS) return -1;
|
||||
|
||||
return cur_cell->dagmc_ptr()->index_by_handle(new_vol) +
|
||||
dag_univ->cell_idx_offset_;
|
||||
return cellp->dagmc_ptr()->index_by_handle(new_vol) +
|
||||
univp->cell_idx_offset_;
|
||||
}
|
||||
|
||||
} // namespace openmc
|
||||
|
|
@ -756,8 +760,11 @@ void read_dagmc_universes(pugi::xml_node node)
|
|||
"with DAGMC");
|
||||
}
|
||||
};
|
||||
|
||||
void check_dagmc_root_univ() {};
|
||||
|
||||
int32_t next_cell(int32_t surf, int32_t curr_cell, int32_t univ);
|
||||
|
||||
} // namespace openmc
|
||||
|
||||
#endif // DAGMC
|
||||
|
|
|
|||
|
|
@ -221,56 +221,11 @@ void print_plot()
|
|||
if (settings::verbosity < 5)
|
||||
return;
|
||||
|
||||
for (auto pl : model::plots) {
|
||||
// Plot id
|
||||
fmt::print("Plot ID: {}\n", pl.id_);
|
||||
// Plot filename
|
||||
fmt::print("Plot file: {}\n", pl.path_plot_);
|
||||
// Plot level
|
||||
fmt::print("Universe depth: {}\n", pl.level_);
|
||||
|
||||
// Plot type
|
||||
if (PlotType::slice == pl.type_) {
|
||||
fmt::print("Plot Type: Slice\n");
|
||||
} else if (PlotType::voxel == pl.type_) {
|
||||
fmt::print("Plot Type: Voxel\n");
|
||||
}
|
||||
|
||||
// Plot parameters
|
||||
fmt::print(
|
||||
"Origin: {} {} {}\n", pl.origin_[0], pl.origin_[1], pl.origin_[2]);
|
||||
|
||||
if (PlotType::slice == pl.type_) {
|
||||
fmt::print("Width: {:4} {:4}\n", pl.width_[0], pl.width_[1]);
|
||||
} else if (PlotType::voxel == pl.type_) {
|
||||
fmt::print(
|
||||
"Width: {:4} {:4} {:4}\n", pl.width_[0], pl.width_[1], pl.width_[2]);
|
||||
}
|
||||
|
||||
if (PlotColorBy::cells == pl.color_by_) {
|
||||
fmt::print("Coloring: Cells\n");
|
||||
} else if (PlotColorBy::mats == pl.color_by_) {
|
||||
fmt::print("Coloring: Materials\n");
|
||||
}
|
||||
|
||||
if (PlotType::slice == pl.type_) {
|
||||
switch (pl.basis_) {
|
||||
case PlotBasis::xy:
|
||||
fmt::print("Basis: XY\n");
|
||||
break;
|
||||
case PlotBasis::xz:
|
||||
fmt::print("Basis: XZ\n");
|
||||
break;
|
||||
case PlotBasis::yz:
|
||||
fmt::print("Basis: YZ\n");
|
||||
break;
|
||||
}
|
||||
fmt::print("Pixels: {} {}\n", pl.pixels_[0], pl.pixels_[1]);
|
||||
} else if (PlotType::voxel == pl.type_) {
|
||||
fmt::print(
|
||||
"Voxels: {} {} {}\n", pl.pixels_[0], pl.pixels_[1], pl.pixels_[2]);
|
||||
}
|
||||
|
||||
for (const auto& pl : model::plots) {
|
||||
fmt::print("Plot ID: {}\n", pl->id());
|
||||
fmt::print("Plot file: {}\n", pl->path_plot());
|
||||
fmt::print("Universe depth: {}\n", pl->level());
|
||||
pl->print_info(); // prints type-specific plot info
|
||||
fmt::print("\n");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -433,13 +433,7 @@ void Particle::cross_surface()
|
|||
#ifdef DAGMC
|
||||
// in DAGMC, we know what the next cell should be
|
||||
if (surf->geom_type_ == GeometryType::DAG) {
|
||||
auto surfp = dynamic_cast<DAGSurface*>(surf);
|
||||
auto cellp =
|
||||
dynamic_cast<DAGCell*>(model::cells[cell_last(n_coord() - 1)].get());
|
||||
auto univp = static_cast<DAGUniverse*>(
|
||||
model::universes[coord(n_coord() - 1).universe].get());
|
||||
// determine the next cell for this crossing
|
||||
int32_t i_cell = next_cell(univp, cellp, surfp) - 1;
|
||||
int32_t i_cell = next_cell(i_surface, cell_last(n_coord() - 1), lowest_coord().universe) - 1;
|
||||
// save material and temp
|
||||
material_last() = material();
|
||||
sqrtkT_last() = sqrtkT();
|
||||
|
|
|
|||
972
src/plot.cpp
972
src/plot.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -48,14 +48,14 @@
|
|||
</settings>
|
||||
<plots>
|
||||
<plot basis="xy" color_by="cell" filename="cellplot" id="1" type="slice">
|
||||
<pixels>400 400</pixels>
|
||||
<origin>0 0 0</origin>
|
||||
<width>7 7</width>
|
||||
<pixels>400 400</pixels>
|
||||
</plot>
|
||||
<plot basis="xy" color_by="material" filename="matplot" id="2" type="slice">
|
||||
<pixels>400 400</pixels>
|
||||
<origin>0 0 0</origin>
|
||||
<width>7 7</width>
|
||||
<pixels>400 400</pixels>
|
||||
</plot>
|
||||
</plots>
|
||||
</model>
|
||||
|
|
|
|||
|
|
@ -1,60 +1,7 @@
|
|||
import glob
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
import h5py
|
||||
import openmc
|
||||
|
||||
from tests.testing_harness import TestHarness
|
||||
from tests.testing_harness import PlotTestHarness
|
||||
from tests.regression_tests import config
|
||||
|
||||
|
||||
class PlotTestHarness(TestHarness):
|
||||
"""Specialized TestHarness for running OpenMC plotting tests."""
|
||||
def __init__(self, plot_names):
|
||||
super().__init__(None)
|
||||
self._plot_names = plot_names
|
||||
|
||||
def _run_openmc(self):
|
||||
openmc.plot_geometry(openmc_exec=config['exe'])
|
||||
|
||||
def _test_output_created(self):
|
||||
"""Make sure *.png has been created."""
|
||||
for fname in self._plot_names:
|
||||
assert os.path.exists(fname), 'Plot output file does not exist.'
|
||||
|
||||
def _cleanup(self):
|
||||
super()._cleanup()
|
||||
for fname in self._plot_names:
|
||||
if os.path.exists(fname):
|
||||
os.remove(fname)
|
||||
|
||||
def _get_results(self):
|
||||
"""Return a string hash of the plot files."""
|
||||
outstr = bytes()
|
||||
|
||||
for fname in self._plot_names:
|
||||
if fname.endswith('.png'):
|
||||
# Add PNG output to results
|
||||
with open(fname, 'rb') as fh:
|
||||
outstr += fh.read()
|
||||
elif fname.endswith('.h5'):
|
||||
# Add voxel data to results
|
||||
with h5py.File(fname, 'r') as fh:
|
||||
outstr += fh.attrs['filetype']
|
||||
outstr += fh.attrs['num_voxels'].tobytes()
|
||||
outstr += fh.attrs['lower_left'].tobytes()
|
||||
outstr += fh.attrs['voxel_width'].tobytes()
|
||||
outstr += fh['data'][()].tobytes()
|
||||
|
||||
# Hash the information and return.
|
||||
sha512 = hashlib.sha512()
|
||||
sha512.update(outstr)
|
||||
outstr = sha512.hexdigest()
|
||||
|
||||
return outstr
|
||||
|
||||
|
||||
def test_plot():
|
||||
harness = PlotTestHarness(('plot_1.png', 'plot_2.png', 'plot_3.png',
|
||||
'plot_4.h5'))
|
||||
|
|
|
|||
|
|
@ -1,60 +1,7 @@
|
|||
import glob
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
import h5py
|
||||
import openmc
|
||||
|
||||
from tests.testing_harness import TestHarness
|
||||
from tests.testing_harness import PlotTestHarness
|
||||
from tests.regression_tests import config
|
||||
|
||||
|
||||
class PlotTestHarness(TestHarness):
|
||||
"""Specialized TestHarness for running OpenMC plotting tests."""
|
||||
def __init__(self, plot_names):
|
||||
super().__init__(None)
|
||||
self._plot_names = plot_names
|
||||
|
||||
def _run_openmc(self):
|
||||
openmc.plot_geometry(openmc_exec=config['exe'])
|
||||
|
||||
def _test_output_created(self):
|
||||
"""Make sure *.png has been created."""
|
||||
for fname in self._plot_names:
|
||||
assert os.path.exists(fname), 'Plot output file does not exist.'
|
||||
|
||||
def _cleanup(self):
|
||||
super()._cleanup()
|
||||
for fname in self._plot_names:
|
||||
if os.path.exists(fname):
|
||||
os.remove(fname)
|
||||
|
||||
def _get_results(self):
|
||||
"""Return a string hash of the plot files."""
|
||||
outstr = bytes()
|
||||
|
||||
for fname in self._plot_names:
|
||||
if fname.endswith('.png'):
|
||||
# Add PNG output to results
|
||||
with open(fname, 'rb') as fh:
|
||||
outstr += fh.read()
|
||||
elif fname.endswith('.h5'):
|
||||
# Add voxel data to results
|
||||
with h5py.File(fname, 'r') as fh:
|
||||
outstr += fh.attrs['filetype']
|
||||
outstr += fh.attrs['num_voxels'].tobytes()
|
||||
outstr += fh.attrs['lower_left'].tobytes()
|
||||
outstr += fh.attrs['voxel_width'].tobytes()
|
||||
outstr += fh['data'][()].tobytes()
|
||||
|
||||
# Hash the information and return.
|
||||
sha512 = hashlib.sha512()
|
||||
sha512.update(outstr)
|
||||
outstr = sha512.hexdigest()
|
||||
|
||||
return outstr
|
||||
|
||||
|
||||
def test_plot_overlap():
|
||||
harness = PlotTestHarness(('plot_1.png', 'plot_2.png', 'plot_3.png',
|
||||
'plot_4.h5'))
|
||||
|
|
|
|||
0
tests/regression_tests/plot_projections/__init__.py
Normal file
0
tests/regression_tests/plot_projections/__init__.py
Normal file
15
tests/regression_tests/plot_projections/geometry.xml
Normal file
15
tests/regression_tests/plot_projections/geometry.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0"?>
|
||||
<geometry>
|
||||
|
||||
<surface id="1" type="z-cylinder" coeffs="0 0 2" />
|
||||
<surface id="2" type="z-cylinder" coeffs="0 0 5" />
|
||||
<surface id="3" type="z-cylinder" coeffs="0 0 10" boundary="vacuum" />
|
||||
<surface id="4" type="y-plane" coeffs="5.001" boundary="vacuum" />
|
||||
<surface id="5" type="z-plane" coeffs="-5" boundary="vacuum" />
|
||||
<surface id="6" type="z-plane" coeffs="5" boundary="vacuum" />
|
||||
|
||||
<cell id="1" material="1" region=" -1 -4 5 -6" />
|
||||
<cell id="2" material="2" region="1 -2 -4 5 -6" />
|
||||
<cell id="3" material="3" region="2 -3 -4 5 -6" />
|
||||
|
||||
</geometry>
|
||||
19
tests/regression_tests/plot_projections/materials.xml
Normal file
19
tests/regression_tests/plot_projections/materials.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0"?>
|
||||
<materials>
|
||||
|
||||
<material id="1">
|
||||
<density value="4.5" units="g/cc" />
|
||||
<nuclide name="U235" ao="1.0" />
|
||||
</material>
|
||||
|
||||
<material id="2">
|
||||
<density value="4.5" units="g/cc" />
|
||||
<nuclide name="U235" ao="2.0" />
|
||||
</material>
|
||||
|
||||
<material id="3">
|
||||
<density value="4.5" units="g/cc" />
|
||||
<nuclide name="U235" ao="3.0" />
|
||||
</material>
|
||||
|
||||
</materials>
|
||||
55
tests/regression_tests/plot_projections/plots.xml
Normal file
55
tests/regression_tests/plot_projections/plots.xml
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0"?>
|
||||
<plots>
|
||||
|
||||
<plot id="1" type="projection">
|
||||
<look_at>0. 0. 0.</look_at>
|
||||
<camera_position>20. 20. 20.</camera_position>
|
||||
<pixels>200 200</pixels>
|
||||
<color id="1" rgb="0 0 255" xs="10"/>
|
||||
<color id="2" rgb="0 255 0" xs="0.1"/>
|
||||
<color id="3" rgb="255 0 0" xs="1.0"/>
|
||||
<field_of_view>70</field_of_view>
|
||||
</plot>
|
||||
|
||||
<plot id="2" type="projection">
|
||||
<look_at>0. 0. 0.</look_at>
|
||||
<camera_position>10. 10. 0.</camera_position>
|
||||
<width>25 25</width>
|
||||
<pixels>200 200</pixels>
|
||||
<mask components="1 3" background="255 255 255" />
|
||||
<field_of_view>90</field_of_view>
|
||||
<wireframe_thickness>4</wireframe_thickness>
|
||||
<filename>example1</filename>
|
||||
</plot>
|
||||
|
||||
<plot id="3" color_by="material" type="projection">
|
||||
<look_at>0. 0. 0.</look_at>
|
||||
<camera_position>20. 20. 20.</camera_position>
|
||||
<pixels>200 200</pixels>
|
||||
<background>240 240 240</background>
|
||||
<filename>example2.png</filename>
|
||||
<wireframe_ids>2</wireframe_ids>
|
||||
</plot>
|
||||
|
||||
<plot id="4" color_by="material" type="projection">
|
||||
<look_at>0. 0. 0.</look_at>
|
||||
<camera_position>0. 10.0 20.</camera_position>
|
||||
<pixels>200 200</pixels>
|
||||
<background>110 240 240</background>
|
||||
<filename>example3.png</filename>
|
||||
</plot>
|
||||
|
||||
<plot id="5" type="projection">
|
||||
<look_at>0. 0. 0.</look_at>
|
||||
<camera_position>10. 10. 10.</camera_position>
|
||||
<width>25 25</width>
|
||||
<pixels>200 200</pixels>
|
||||
<orthographic_width>25.0</orthographic_width>
|
||||
<wireframe_thickness>2</wireframe_thickness>
|
||||
<filename>orthographic_example1</filename>
|
||||
<color id="1" rgb="0 0 255" xs="10"/>
|
||||
<color id="2" rgb="0 255 0" xs="0.1"/>
|
||||
<color id="3" rgb="255 0 0" xs="1.0"/>
|
||||
</plot>
|
||||
|
||||
</plots>
|
||||
1
tests/regression_tests/plot_projections/results_true.dat
Normal file
1
tests/regression_tests/plot_projections/results_true.dat
Normal file
|
|
@ -0,0 +1 @@
|
|||
6448093f8acaa2af02452ec1b29461ffaf89628bb002cc8873a3a6831fe5d613645a197033597e5a064896388f19d0782f05e78ce474354ae2a76c35ff4551d7
|
||||
13
tests/regression_tests/plot_projections/settings.xml
Normal file
13
tests/regression_tests/plot_projections/settings.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0"?>
|
||||
<settings>
|
||||
|
||||
<run_mode>plot</run_mode>
|
||||
|
||||
<mesh id="1">
|
||||
<dimension>5 4 3</dimension>
|
||||
<lower_left>-10 -10 -10</lower_left>
|
||||
<upper_right>10 10 10</upper_right>
|
||||
</mesh>
|
||||
<entropy_mesh>1</entropy_mesh>
|
||||
|
||||
</settings>
|
||||
20
tests/regression_tests/plot_projections/tallies.xml
Normal file
20
tests/regression_tests/plot_projections/tallies.xml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0"?>
|
||||
<tallies>
|
||||
|
||||
<mesh id="2" type="rectilinear">
|
||||
<x_grid>-10 10</x_grid>
|
||||
<y_grid>-10 10</y_grid>
|
||||
<z_grid>-10 0 5 7.5 8.75 10</z_grid>
|
||||
</mesh>
|
||||
|
||||
<filter id="1">
|
||||
<type>mesh</type>
|
||||
<bins>2</bins>
|
||||
</filter>
|
||||
|
||||
<tally id="1">
|
||||
<filters>1</filters>
|
||||
<scores>total</scores>
|
||||
</tally>
|
||||
|
||||
</tallies>
|
||||
6
tests/regression_tests/plot_projections/test.py
Normal file
6
tests/regression_tests/plot_projections/test.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from tests.testing_harness import PlotTestHarness
|
||||
from tests.regression_tests import config
|
||||
|
||||
def test_plot():
|
||||
harness = PlotTestHarness(('plot_1.png', 'example1.png', 'example2.png', 'example3.png', 'orthographic_example1.png'))
|
||||
harness.main()
|
||||
|
|
@ -1,62 +1,11 @@
|
|||
import glob
|
||||
import hashlib
|
||||
import os
|
||||
from subprocess import check_call
|
||||
|
||||
import h5py
|
||||
import openmc
|
||||
import pytest
|
||||
|
||||
from tests.testing_harness import TestHarness
|
||||
from tests.testing_harness import PlotTestHarness
|
||||
from tests.regression_tests import config
|
||||
|
||||
|
||||
vtk = pytest.importorskip('vtk')
|
||||
class PlotVoxelTestHarness(TestHarness):
|
||||
"""Specialized TestHarness for running OpenMC voxel plot tests."""
|
||||
def __init__(self, plot_names):
|
||||
super().__init__(None)
|
||||
self._plot_names = plot_names
|
||||
|
||||
def _run_openmc(self):
|
||||
openmc.plot_geometry(openmc_exec=config['exe'])
|
||||
|
||||
check_call(['../../../scripts/openmc-voxel-to-vtk'] +
|
||||
glob.glob('plot_4.h5'))
|
||||
|
||||
def _test_output_created(self):
|
||||
"""Make sure plots have been created."""
|
||||
for fname in self._plot_names:
|
||||
assert os.path.exists(fname), 'Plot output file does not exist.'
|
||||
|
||||
def _cleanup(self):
|
||||
super()._cleanup()
|
||||
for fname in self._plot_names:
|
||||
if os.path.exists(fname):
|
||||
os.remove(fname)
|
||||
|
||||
def _get_results(self):
|
||||
"""Return a string hash of the plot files."""
|
||||
outstr = bytes()
|
||||
|
||||
for fname in self._plot_names:
|
||||
if fname.endswith('.h5'):
|
||||
# Add voxel data to results
|
||||
with h5py.File(fname, 'r') as fh:
|
||||
outstr += fh.attrs['filetype']
|
||||
outstr += fh.attrs['num_voxels'].tobytes()
|
||||
outstr += fh.attrs['lower_left'].tobytes()
|
||||
outstr += fh.attrs['voxel_width'].tobytes()
|
||||
outstr += fh['data'][()].tobytes()
|
||||
|
||||
# Hash the information and return.
|
||||
sha512 = hashlib.sha512()
|
||||
sha512.update(outstr)
|
||||
outstr = sha512.hexdigest()
|
||||
|
||||
return outstr
|
||||
|
||||
|
||||
def test_plot_voxel():
|
||||
harness = PlotVoxelTestHarness(('plot_4.h5', 'plot.vti'))
|
||||
harness = PlotTestHarness(('plot_4.h5', 'plot.vti'), voxel_convert_checks=['plot_4.h5'])
|
||||
harness.main()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
from difflib import unified_diff
|
||||
from subprocess import check_call
|
||||
import filecmp
|
||||
import glob
|
||||
import h5py
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
|
|
@ -380,3 +382,55 @@ class HashedPyAPITestHarness(PyAPITestHarness):
|
|||
def _get_results(self):
|
||||
"""Digest info in the statepoint and return as a string."""
|
||||
return super()._get_results(True)
|
||||
|
||||
|
||||
class PlotTestHarness(TestHarness):
|
||||
"""Specialized TestHarness for running OpenMC plotting tests."""
|
||||
def __init__(self, plot_names, voxel_convert_checks=[]):
|
||||
super().__init__(None)
|
||||
self._plot_names = plot_names
|
||||
self._voxel_convert_checks = voxel_convert_checks
|
||||
|
||||
def _run_openmc(self):
|
||||
openmc.plot_geometry(openmc_exec=config['exe'])
|
||||
|
||||
# Check that voxel h5 can be converted to vtk
|
||||
for voxel_h5_filename in self._voxel_convert_checks:
|
||||
check_call(['../../../scripts/openmc-voxel-to-vtk'] +
|
||||
glob.glob(voxel_h5_filename))
|
||||
|
||||
def _test_output_created(self):
|
||||
"""Make sure *.png has been created."""
|
||||
for fname in self._plot_names:
|
||||
assert os.path.exists(fname), 'Plot output file does not exist.'
|
||||
|
||||
def _cleanup(self):
|
||||
super()._cleanup()
|
||||
for fname in self._plot_names:
|
||||
if os.path.exists(fname):
|
||||
os.remove(fname)
|
||||
|
||||
def _get_results(self):
|
||||
"""Return a string hash of the plot files."""
|
||||
outstr = bytes()
|
||||
|
||||
for fname in self._plot_names:
|
||||
if fname.endswith('.png'):
|
||||
# Add PNG output to results
|
||||
with open(fname, 'rb') as fh:
|
||||
outstr += fh.read()
|
||||
elif fname.endswith('.h5'):
|
||||
# Add voxel data to results
|
||||
with h5py.File(fname, 'r') as fh:
|
||||
outstr += fh.attrs['filetype']
|
||||
outstr += fh.attrs['num_voxels'].tobytes()
|
||||
outstr += fh.attrs['lower_left'].tobytes()
|
||||
outstr += fh.attrs['voxel_width'].tobytes()
|
||||
outstr += fh['data'][()].tobytes()
|
||||
|
||||
# Hash the information and return.
|
||||
sha512 = hashlib.sha512()
|
||||
sha512.update(outstr)
|
||||
outstr = sha512.hexdigest()
|
||||
|
||||
return outstr
|
||||
|
|
|
|||
|
|
@ -37,15 +37,47 @@ def myplot():
|
|||
}
|
||||
return plot
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def myprojectionplot():
|
||||
plot = openmc.ProjectionPlot(name='myprojectionplot')
|
||||
plot.look_at = (0.0, 0.0, 0.0)
|
||||
plot.camera_position = (4.0, 3.0, 0.0)
|
||||
plot.pixels = (500, 500)
|
||||
plot.filename = 'myprojectionplot'
|
||||
plot.background = (0, 0, 0)
|
||||
plot.background = 'black'
|
||||
|
||||
plot.color_by = 'material'
|
||||
m1, m2 = openmc.Material(), openmc.Material()
|
||||
plot.colors = {m1: (0, 255, 0), m2: (0, 0, 255)}
|
||||
plot.colors = {m1: 'green', m2: 'blue'}
|
||||
plot.xs = {m1: 1.0, m2: 0.01}
|
||||
|
||||
plot.mask_components = [openmc.Material()]
|
||||
plot.mask_background = (255, 255, 255)
|
||||
plot.mask_background = 'white'
|
||||
|
||||
plot.overlap_color = (255, 211, 0)
|
||||
plot.overlap_color = 'yellow'
|
||||
|
||||
plot.wireframe_thickness = 2
|
||||
|
||||
plot.level = 1
|
||||
return plot
|
||||
|
||||
def test_attributes(myplot):
|
||||
assert myplot.name == 'myplot'
|
||||
|
||||
def test_attributes_proj(myprojectionplot):
|
||||
assert myprojectionplot.name == 'myprojectionplot'
|
||||
|
||||
def test_repr(myplot):
|
||||
r = repr(myplot)
|
||||
assert isinstance(r, str)
|
||||
|
||||
def test_repr_proj(myprojectionplot):
|
||||
r = repr(myprojectionplot)
|
||||
assert isinstance(r, str)
|
||||
|
||||
def test_from_geometry():
|
||||
width = 25.
|
||||
|
|
@ -89,6 +121,18 @@ def test_xml_element(myplot):
|
|||
assert getattr(newplot, attr) == getattr(myplot, attr), attr
|
||||
|
||||
|
||||
def test_to_xml_element_proj(myprojectionplot):
|
||||
elem = myprojectionplot.to_xml_element()
|
||||
assert 'id' in elem.attrib
|
||||
assert 'color_by' in elem.attrib
|
||||
assert 'type' in elem.attrib
|
||||
assert elem.find('camera_position') is not None
|
||||
assert elem.find('wireframe_thickness') is not None
|
||||
assert elem.find('look_at') is not None
|
||||
assert elem.find('pixels') is not None
|
||||
assert elem.find('background').text == '0 0 0'
|
||||
|
||||
|
||||
def test_plots(run_in_tmpdir):
|
||||
p1 = openmc.Plot(name='plot1')
|
||||
p1.origin = (5., 5., 5.)
|
||||
|
|
@ -99,10 +143,14 @@ def test_plots(run_in_tmpdir):
|
|||
plots = openmc.Plots([p1, p2])
|
||||
assert len(plots) == 2
|
||||
|
||||
p3 = openmc.Plot(name='plot3')
|
||||
plots.append(p3)
|
||||
p3 = openmc.ProjectionPlot(name='plot3')
|
||||
plots = openmc.Plots([p1, p2, p3])
|
||||
assert len(plots) == 3
|
||||
|
||||
p4 = openmc.Plot(name='plot4')
|
||||
plots.append(p4)
|
||||
assert len(plots) == 4
|
||||
|
||||
plots.export_to_xml()
|
||||
|
||||
# from_xml
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue