diff --git a/docs/Makefile b/docs/Makefile index e84aadd33c..2f3c029db1 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -55,7 +55,8 @@ images: $(PDFS) $(PNGS) clean: -rm -rf $(BUILDDIR)/* - -rm $(PDFS) + -rm -rf $(PDFS) + -rm -rf source/pythonapi/generated/ html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html diff --git a/docs/source/usersguide/input.rst b/docs/source/usersguide/input.rst index 991c5912a1..4ce714b939 100644 --- a/docs/source/usersguide/input.rst +++ b/docs/source/usersguide/input.rst @@ -2178,7 +2178,7 @@ attributes or sub-elements. These are not used in "voxel" plots: cells or materials to plot. This overrides any ``color`` color specifications. - *Default*: None + *Default*: 255 255 255 (white) :meshlines: The ``meshlines`` sub-element allows for plotting the boundaries of a diff --git a/openmc/cell.py b/openmc/cell.py index e26af7f999..e83d01dcd7 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -224,6 +224,14 @@ class Cell(object): 'Call the Geometry.determine_paths() method.') return self._paths + @property + def bounding_box(self): + if self.region is not None: + return self.region.bounding_box + else: + return (np.array([-np.inf, -np.inf, -np.inf]), + np.array([np.inf, np.inf, np.inf])) + @property def num_instances(self): return len(self.paths) diff --git a/openmc/geometry.py b/openmc/geometry.py index 5cb7cf1679..4bb6e23e46 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -28,6 +28,9 @@ class Geometry(object): ---------- root_universe : openmc.Universe Root universe which contains all others + bounding_box : 2-tuple of numpy.array + Lower-left and upper-right coordinates of an axis-aligned bounding box + of the universe. """ @@ -41,6 +44,10 @@ class Geometry(object): def root_universe(self): return self._root_universe + @property + def bounding_box(self): + return self.root_universe.bounding_box + @root_universe.setter def root_universe(self, root_universe): check_type('root universe', root_universe, openmc.Universe) diff --git a/openmc/plots.py b/openmc/plots.py index 3da9960340..9e69bf7f89 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -23,12 +23,16 @@ def reset_auto_plot_id(): AUTO_PLOT_ID = 10000 -BASES = ['xy', 'xz', 'yz'] +_BASES = ['xy', 'xz', 'yz'] class Plot(object): - """Definition of a finite region of space to be plotted, either as a slice plot - in two dimensions or as a voxel plot in three dimensions. + """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 `_. Parameters ---------- @@ -58,15 +62,14 @@ class Plot(object): basis : {'xy', 'xz', 'yz'} The basis directions for the plot background : Iterable of int or str - Color of the background defined by RGB + Color of the background mask_components : Iterable of openmc.Cell or openmc.Material The cells or materials to plot - mask_background : Iterable of int + mask_background : Iterable of int or str Color to apply to all cells/materials not listed in mask_components - defined by RGB colors : dict Dictionary indicating that certain cells/materials (keys) should be - colored with a specific RGB (values) + displayed with a particular color. level : int Universe depth to plot at meshlines : dict @@ -80,7 +83,7 @@ class Plot(object): self.id = plot_id self.name = name self._width = [4.0, 4.0] - self._pixels = [1000, 1000] + self._pixels = [400, 400] self._origin = [0., 0., 0.] self._filename = None self._color_by = 'cell' @@ -206,7 +209,7 @@ class Plot(object): @basis.setter def basis(self, basis): - cv.check_value('plot basis', basis, ['xy', 'xz', 'yz']) + cv.check_value('plot basis', basis, _BASES) self._basis = basis @background.setter @@ -242,18 +245,21 @@ class Plot(object): @mask_components.setter def mask_components(self, mask_components): - cv.check_type('plot mask components', mask_components, Iterable, Integral) - for component in mask_components: - cv.check_greater_than('plot mask components', component, 0, True) + cv.check_type('plot mask components', mask_components, Iterable, + (openmc.Cell, openmc.Material)) self._mask_components = mask_components @mask_background.setter def mask_background(self, mask_background): - cv.check_type('plot mask background', mask_background, Iterable, Integral) - cv.check_length('plot mask background', mask_background, 3) - for rgb in mask_background: - cv.check_greater_than('plot mask background', rgb, 0, True) - cv.check_less_than('plot mask background', rgb, 256) + cv.check_type('plot mask background', mask_background, Iterable) + if isinstance(mask_background, string_types): + if not is_color_like(mask_background): + raise ValueError("'{}' is not a valid color.".format(mask_background)) + else: + cv.check_length('plot mask_background', mask_background, 3) + for rgb in mask_background: + cv.check_greater_than('plot mask background', rgb, 0, True) + cv.check_less_than('plot mask background', rgb, 256) self._mask_background = mask_background @level.setter @@ -316,6 +322,51 @@ class Plot(object): string += '{: <16}=\t{}\n'.format('\tMeshlines', self._meshlines) return string + @classmethod + def from_geometry(cls, geometry, basis='xy', slice_coord=0.): + """Return plot that encompasses a geometry. + + Parameters + ---------- + geometry : openmc.Geometry + The geometry the base the plot off of + basis : {'xy', 'xz', 'yz'} + The basis directions for the plot + slice_coord : float + The level at which the slice plot should be plotted. For example, if + the basis is 'xy', this would indicate at what z value is used in + the origin. + + """ + cv.check_type('geometry', geometry, openmc.Geometry) + cv.check_value('basis', basis, _BASES) + + # Decide which axes to keep + if basis == 'xy': + pick_index = (0, 1) + slice_index = 2 + elif basis == 'yz': + pick_index = (1, 2) + slice_index = 0 + elif basis == 'xz': + pick_index = (0, 2) + slice_index = 1 + + # Get lower-left and upper-right coordinates for desired axes + lower_left, upper_right = geometry.bounding_box + lower_left = lower_left[np.array(pick_index)] + upper_right = upper_right[np.array(pick_index)] + + if np.any(np.isinf((lower_left, upper_right))): + raise ValueError('The geometry does not appear to be bounded ' + 'in the {} plane.'.format(basis)) + + plot = cls() + plot.origin = np.insert((lower_left + upper_right)/2, + slice_index, slice_coord) + plot.width = upper_right - lower_left + return plot + def colorize(self, geometry, seed=1): """Generate a color scheme for each domain in the plot. @@ -446,10 +497,14 @@ class Plot(object): if self._mask_components is not None: subelement = ET.SubElement(element, "mask") - subelement.set("components", ' '.join(map( - str, self._mask_components))) - subelement.set("background", ' '.join(map( - str, self._mask_background))) + subelement.set("components", ' '.join( + str(d.id) for d in self._mask_components)) + color = self._mask_background + if color is not None: + if isinstance(color, string_types): + color = [int(255*x) for x in to_rgb(color)] + subelement.set("background", ' '.join( + str(x) for x in color)) if self._level is not None: subelement = ET.SubElement(element, "level") diff --git a/openmc/region.py b/openmc/region.py index 8fd7e5181d..4494a3f622 100644 --- a/openmc/region.py +++ b/openmc/region.py @@ -301,7 +301,7 @@ class Union(Region): ---------- nodes : tuple of openmc.Region Regions to take the union of - bounding_box : tuple of numpy.array + bounding_box : 2-tuple of numpy.array Lower-left and upper-right coordinates of an axis-aligned bounding box """ diff --git a/openmc/universe.py b/openmc/universe.py index 506bacef47..952f61dac1 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -46,6 +46,9 @@ class Universe(object): Volume of the universe in cm^3. This can either be set manually or calculated in a stochastic volume calculation and added via the :meth:`Universe.add_volume_information` method. + bounding_box : 2-tuple of numpy.array + Lower-left and upper-right coordinates of an axis-aligned bounding box + of the universe. """ @@ -105,6 +108,11 @@ class Universe(object): def volume(self): return self._volume + @property + def bounding_box(self): + regions = [c.region for c in self.cells.values()] + return openmc.Union(*regions).bounding_box + @id.setter def id(self, universe_id): if universe_id is None: diff --git a/openmc/volume.py b/openmc/volume.py index f7bad73b4c..c9e1a5afa6 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -80,9 +80,7 @@ class VolumeCalculation(object): # user-specified one is valid if self.domain_type == 'cell': for c in domains: - if c.region is None: - continue - ll, ur = c.region.bounding_box + ll, ur = c.bounding_box if np.any(np.isinf(ll)) or np.any(np.isinf(ur)): continue if (np.any(np.asarray(lower_left) > ll) or diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 3d8e327500..163dcf9013 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -4634,13 +4634,12 @@ contains end do ! Alter colors based on mask information - do j=1,size(pl % colors) - if (.not. any(j .eq. iarray)) then + do j = 1, size(pl % colors) + if (.not. any(j == iarray)) then if (check_for_node(node_mask, "background")) then call get_node_array(node_mask, "background", pl % colors(j) % rgb) else - call fatal_error("Missing in mask of plot " & - // trim(to_str(pl % id))) + pl % colors(j) % rgb(:) = [255, 255, 255] end if end if end do