Skip to content

Commit

Permalink
Created tower class for use in TstMotionBasedCanting and associated t…
Browse files Browse the repository at this point in the history
…ests
  • Loading branch information
mhhwang1 committed Jun 21, 2024
1 parent 12f661c commit e74bfc9
Show file tree
Hide file tree
Showing 7 changed files with 359 additions and 7 deletions.
141 changes: 141 additions & 0 deletions opencsp/common/lib/csp/Tower.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
"""
Tower Class
Copyright (c) 2021 Sandia National Laboratories.
"""

import matplotlib.pyplot as plt
import numpy as np

from opencsp.common.lib.geometry.Pxyz import Pxyz
import opencsp.common.lib.render_control.RenderControlTower as rct
from opencsp.common.lib.csp.RayTraceable import RayTraceable
from opencsp.common.lib.render_control.RenderControlPointSeq import RenderControlPointSeq
from opencsp.common.lib.render_control.RenderControlTower import RenderControlTower
from opencsp.common.lib.render.View3d import View3d


class Tower(RayTraceable):
"""
Tower representation.
renders nsttf tower
"""

def __init__(
self,
name: str,
origin: np.ndarray,
parts: list[str] = ["whole tower"],
height: float = 100,
east: float = 8.8,
west: float = -8.8,
south: float = -8.8,
north: float = 8.8,
x_aim: float = 0,
y_aim: float = 8.8,
z_aim: float = 100,
):

# parameters used for control tower at NSTTF
# tower_control= Tower(name='Sandia NSTTF Control Tower',
# origin = np.array([0,0,0]),
# height=25,
# east = 8.8,
# west = -8.8,
# south = 284,
# north = 300)
"""Create a new Tower instance.
Parameters:
-----------
name The name of this Tower. Used for special styles given in the draw method.
origin The center of Tower, as a three vector xyz coordinate.
all measurements in meters using ENU coordinate system.
"""
super(Tower, self).__init__()
self.name = name
self.origin = origin
self.parts = parts
self.height = height
self.east = east
self.west = west
self.south = south
self.north = north
self.x_aim = x_aim
self.y_aim = y_aim
self.z_aim = z_aim
self.target_loc = Pxyz([x_aim, y_aim, z_aim])

# Tower faces, top, and bottom
self.top = [
[self.east, self.north, self.height],
[self.west, self.north, self.height],
[self.west, self.south, self.height],
[self.east, self.south, self.height],
]
self.northface = [
[self.east, self.north, self.height],
[self.west, self.north, self.height],
[self.west, self.north, 0],
[self.east, self.north, 0],
]
self.southface = [
[self.east, self.south, self.height],
[self.west, self.south, self.height],
[self.west, self.south, 0],
[self.east, self.south, 0],
]
self.bottom = [
[self.east, self.north, 0],
[self.west, self.north, 0],
[self.west, self.south, 0],
[self.east, self.south, 0],
]

self.point = [self.x_aim, self.y_aim, self.z_aim]

# Centroid
self.origin = np.array([origin[0], origin[1], origin[2]]) # Origin is at center of tower.

def walls(self):
"""Returns the list of walls in ul,ur,lr,ll order."""
# Assumes that Tower coordinates have been set, and the walls have been set.
# # Later we can add a more meaningful check for this.
return [self.top, self.northface, self.southface, self.bottom]

# RENDERING

def draw(self, view: View3d, tower_style: RenderControlTower) -> None:
# Assumes that heliostat configuration has already been set.

tower_style = tower_style.style(self.name)

# Whole tower
if 'whole tower' in self.parts:
self.parts += ['top', 'northface', 'southface', 'northface', 'bottom']

# Top of tower
if "top" in self.parts:
view.draw_xyz_list(self.top, close=True, style=tower_style.wire_frame)

# Northface of tower
if "northface" in self.parts:
view.draw_xyz_list(self.northface, close=True, style=tower_style.wire_frame)

# target on northface of tower
if "target" in self.parts:
view.draw_xyz(self.point, style=tower_style.target)

# Southface of tower
if "southface" in self.parts:
view.draw_xyz_list(self.southface, close=True, style=tower_style.wire_frame)

# Bottom of tower
if "bottom" in self.parts:
view.draw_xyz_list(self.bottom, close=True, style=tower_style.wire_frame)

return
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
"""


"""

import opencsp.common.lib.render_control.RenderControlPointSeq as rcps
import opencsp.common.lib.render_control.RenderControlText as rctxt
from opencsp.common.lib.render_control.RenderControlPointSeq import RenderControlPointSeq
Expand Down Expand Up @@ -39,8 +34,7 @@ def style(self, any):
We add this method here so that RenderControlHeliostat can be used similarly to RenderControlEnsemble."""
return self


# Common Configurations
# Common Configurations


def normal_tower():
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Metadata:
Figure number: 1
Name: tto001_Single Tower
Title: Single Tower
Code tag: TestTowerOutput.test_single_tower()
View spec: 3d

Title:
Single Tower

Caption:
A single Sandia NSTTF tower.

Comments:
Demonstration of single 3d tower drawing.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Metadata:
Figure number: 1
Name: tto002_Multiple Towers
Title: Multiple Towers
Code tag: TestTowerOutput.test_multiple_towers()
View spec: 3d

Title:
Multiple Towers

Caption:
Sandia NSTTF reciever and control tower.

Comments:
Demonstration of single 3d tower drawing.
Black tower is NSTTF receiver tower.
Green tower is NSTTF control tower.

183 changes: 183 additions & 0 deletions opencsp/common/lib/test/test_TowerOutput.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
"""
Demonstrate Tower Plotting Routines
Copyright (c) 2021 Sandia National Laboratories.
"""

from datetime import datetime
import matplotlib.pyplot as plt
import numpy as np
import os

import opencsp.common.lib.csp.HeliostatConfiguration as hc
from opencsp.common.lib.csp.SolarField import SolarField
import opencsp.common.lib.csp.SolarField as sf
import opencsp.common.lib.csp.sun_track as sun_track # "st" is taken by string_tools.
from opencsp.common.lib.csp.Tower import Tower
import opencsp.common.lib.opencsp_path.data_path_for_test as dpft
import opencsp.common.lib.opencsp_path.opencsp_root_path as orp
import opencsp.common.lib.render.figure_management as fm
import opencsp.common.lib.render.view_spec as vs
import opencsp.common.lib.render_control.RenderControlTower as rct
import opencsp.common.lib.test.support_test as stest
import opencsp.common.lib.test.TestOutput as to
import opencsp.common.lib.tool.file_tools as ft
import opencsp.common.lib.tool.log_tools as lt


class TestTowerOutput(to.TestOutput):

@classmethod
def setUpClass(
self,
source_file_body: str = 'TestTowerOutput', # Set these here, because pytest calls
figure_prefix_root: str = 'tto', # setup_class() with no arguments.
interactive: bool = False,
verify: bool = True,
):

# Save input.
# Interactive mode flag.
# This has two effects:
# 1. If interactive mode, plots stay displayed.
# 2. If interactive mode, after several plots have been displayed,
# plot contents might change due to Matplotlib memory issues.
# This may in turn cause misleading failures in comparing actual
# output to expected output.
# Thus:
# - Run pytest in non-interactive mode.
# - If viewing run output interactively, be advised that figure content might
# change (e.g., garbled plot titles, etc), and actual-vs-expected comparison
# might erroneously fail (meaning they might report an error which is not
# actually a code error).
#
super(TestTowerOutput, self).setUpClass(
source_file_body=source_file_body,
figure_prefix_root=figure_prefix_root,
interactive=interactive,
verify=verify,
)

# Note: It is tempting to put the "Reset rendering" code lines here, to avoid redundant
# computation and keep all the plots up. Don't do this, because the plotting system
# will run low/out of memory, causing adverse effectes on the plots.

def test_single_tower(self) -> None:
"""
Draws one tower.
"""
# Initialize test.
self.start_test()

# View setup
title = 'Single Tower'
caption = 'A single Sandia NSTTF tower.'
comments = []

# Configuration setup
tower = Tower(name='Sandia NSTTF', origin=np.array([0, 0, 0]), parts=["whole tower", "target"])

# Setup render control.
# Style setup
tower_control = rct.normal_tower()

# comments\
comments.append("Demonstration of single 3d tower drawing.")

# Draw.
fig_record = fm.setup_figure_for_3d_data(
self.figure_control,
self.axis_control_m,
vs.view_spec_3d(),
number_in_name=False,
input_prefix=self.figure_prefix(
1
), # Figure numbers needed because titles may be identical. Hard-code number because test order is unpredictable.
title=title,
caption=caption,
comments=comments,
code_tag=self.code_tag,
)
tower.draw(fig_record.view, tower_control)

# Output.
self.show_save_and_check_figure(fig_record)

def test_multiple_towers(self) -> None:
"""
Draws one tower.
"""
# Initialize test.
self.start_test()

# View setup
title = 'Multiple Towers'
caption = 'Sandia NSTTF reciever and control tower.'
comments = []

# Configuration setup
tower_receiver = Tower(name='Sandia NSTTF', origin=np.array([0, 0, 0]), parts=["whole tower", "target"])
tower_control = Tower(
name='Sandia NSTTF Control Tower',
origin=np.array([0, 0, 0]),
height=50,
east=8.8,
west=-8.8,
south=332.4,
north=350,
)

# Setup render control.
# Style setup
tower_control_rec = rct.normal_tower()
tower_control_con = rct.no_target()

# comments\
comments.append("Demonstration of single 3d tower drawing.")
comments.append("Black tower is NSTTF receiver tower.")
comments.append("Green tower is NSTTF control tower.")

# Draw.
fig_record = fm.setup_figure_for_3d_data(
self.figure_control,
self.axis_control_m,
vs.view_spec_3d(),
number_in_name=False,
input_prefix=self.figure_prefix(
2
), # Figure numbers needed because titles may be identical. Hard-code number because test order is unpredictable.
title=title,
caption=caption,
comments=comments,
code_tag=self.code_tag,
)
tower_receiver.draw(fig_record.view, tower_control_rec)
tower_control.draw(fig_record.view, tower_control_con)

# Output.
self.show_save_and_check_figure(fig_record)


# MAIN EXECUTION

if __name__ == "__main__":
# Control flags.
interactive = False
# Set verify to False when you want to generate all figures and then copy
# them into the expected_output directory.
# (Does not affect pytest, which uses default value.)
verify = True # False
# Setup.py
test_object = TestTowerOutput()
test_object.setUpClass(interactive=interactive, verify=verify)
# Tests.
lt.info('Beginning tests...')
test_object.test_single_tower()
test_object.test_multiple_towers()

lt.info('All tests complete.')
# Cleanup.
if interactive:
input("Press Enter...")
test_object.tearDown()

0 comments on commit e74bfc9

Please sign in to comment.