# Copyright (c) 2018-2020 Uber Technologies, Inc.
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from utilities import RunManager
import openvsp as vsp_module
from collections import namedtuple
import numpy as np
[docs]
class PropInfo():
def __init__(self, geom_id, thrust_vector, rotation_direction, hub_center, transmat, diameter):
self.geom_id = geom_id
self.thrust_vector = thrust_vector
self.rotation_direction = rotation_direction
self.hub_center = hub_center
self.transmat = transmat
self.diameter = diameter
@property
def area(self):
return np.pi * np.power(self.diameter/2, 2)
[docs]
def genX3d(file=None,set=vsp_module.SET_ALL, dims=[1000,400], vsp_instance=None, **kwargs):
vsp = vsp_module.get_instance(vsp_instance)
if file is not None:
vsp.ClearVSPModel()
vsp.ReadVSPFile(file)
vsp.Update()
with RunManager(**kwargs):
vsp.ExportFile("prop.x3d", set, vsp.EXPORT_X3D)
with open("prop.x3d", "r") as f:
x3d_str = f.read()
x3d_str = x3d_str[0:26] + " width=\"{}px\" height=\"{}px\"".format(dims[0],dims[1]) + x3d_str[26:-1]
return x3d_str
[docs]
def convert_vec3d_array_to_list_matrix(vec3d_array):
"""
Converts an array of vec3d objects into a list of x,y,z points
:param vec3d_array: array of vec3d objects
:return: list of x,y,z pairs
"""
return [[v3d.x(), v3d.y(), v3d.z()] for v3d in vec3d_array]
[docs]
def convert_double_tuple_to_list_matrix(tuple_matrix):
"""
Converts a double matrix of tuples into a maxtrix of lists
:param tuple_matrix: matrix of tuples
:return: matrix of lists
"""
return [list(row) for row in tuple_matrix]
[docs]
def parse_results_object(res_id, vsp_instance=None):
"""
Function to parse a generic results object from openvsp results manager
:param res_id: id of the results object to parse
:param vsp_instance: optional instance of vsp if using the multifacade
:return: named tuple of results values
"""
vsp = vsp_module.get_instance(vsp_instance)
names = vsp.GetAllDataNames(res_id)
name = vsp.GetResultsName(res_id)
res_tuple = namedtuple(name, names)
data = []
for name in names:
type = vsp.GetResultsType(res_id, name)
d = []
if type == vsp.INT_DATA:
d = list(vsp.GetIntResults(res_id, name))
elif type == vsp.STRING_DATA:
d = list(vsp.GetStringResults(res_id, name))
elif type == vsp.DOUBLE_DATA:
d = list(vsp.GetDoubleResults(res_id, name))
elif type == vsp.DOUBLE_MATRIX_DATA:
d = convert_double_tuple_to_list_matrix(vsp.GetDoubleMatResults(res_id, name))
elif type == vsp.VEC3D_DATA:
d = convert_vec3d_array_to_list_matrix(vsp.GetVec3dResults(res_id, name))
data.append(d)
return res_tuple(*data)
[docs]
def get_wing_reference_quantities(wing_name=None, wing_id=None, vsp_instance=None):
"""
Gets wing reference area, reference span, and reference chord (sref, bref, cref) from the TotalArea, TotalSpan, and
TotalChord properties of Wing component
:param wing_name: Name of the wing (will find the first wing with this name)
:param wing_id: ID of the wing object, if None wing name will be used to find the wing id. Wing ID takes precedence
over wing_name
:param vsp_instance: optional instance of vsp if using the multifacade
:return: sref, bref, and cref
"""
vsp = vsp_module.get_instance(vsp_instance)
# Check that the wing_name and wing_id are not both none
if wing_name is None and wing_id is None:
raise ValueError("wing_name and wing_id cannot both be None")
# If wing_id is None, then use wing name to find the wing id
if wing_id is None:
found_wing_id = vsp.FindGeom(wing_name, 0)
if not found_wing_id:
raise ValueError("could not find wing with name \"{}\"".format(wing_name))
wing_id = found_wing_id
# Get reference parameters
# TODO: Error check that the wing_id is actually the id to a wing component
bref = vsp.GetParmVal(wing_id, "TotalSpan", "WingGeom")
sref = vsp.GetParmVal(wing_id, "TotalArea", "WingGeom")
cref = vsp.GetParmVal(wing_id, "TotalChord", "WingGeom")
return sref, bref, cref
[docs]
def get_propeller_thrust_vectors(prop_set, alternate_rotation_direction=False, vsp_instance=None):
"""
Returns propeller thrust vector directions and rotation direction for all propellers in
the input set
:param prop_set: set containing all propellers (no other geometries should be included)
:param vsp_instance: optional instance of vsp if using the multifacade
:return: Named Tuple with (geom_id, thrust_vector, rotation_direction, hub_center)
"""
vsp = vsp_module.get_instance(vsp_instance)
import degen_geom as dg
# Run degen geom on the input set
degen_mgr = run_degen_geom(set_index=prop_set)
# loop over all geoms in degen_mgr
results = []
for geom_id, degens in degen_mgr.degen_objs.items():
for propeller in degens.copies:
# Only look at first blade
blade_0 = degens.copies[propeller][0]
results.append(get_single_propeller_info(blade_0))
if alternate_rotation_direction:
for i in range(len(results)):
if i % 2 == 1:
results[i].rotation_direction = -1
return results
[docs]
def get_single_propeller_info(prop_dg, vsp_instance=None):
"""
Gets propeller info for a single propeller degen geom object
:param prop_dg: propeller degen geom object
:param vsp_instance: optional instance of vsp if using the multifacade
:return: propeller info named tuple
"""
vsp = vsp_module.get_instance(vsp_instance)
# Define named tuple
#PropInfo = namedtuple("PropInfo", "geom_id thrust_vector rotation_direction hub_center transmat diameter")
reverse_flag = vsp.GetParmVal(prop_dg.geom_id, "ReverseFlag", "Design")
rotation_dir_geom = 1 if reverse_flag < 0.5 else -1
diameter = vsp.GetParmVal(prop_dg.geom_id, "Diameter", "Design")
# Use transformation matrix to get hub center and rotate default thrust vector into position
transmat = prop_dg.transmat
hub_center = transmat.get_translations()
thrust_vector = transmat.apply_rotations(np.array([-1.0, 0.0, 0.0]).reshape((3, 1)))
# Check the flip normal flag to determine if rotation direction should be reversed
rotation_dir_blade = rotation_dir_geom
if (prop_dg.flip_normal == 0 and not reverse_flag) or (prop_dg.flip_normal == 1 and reverse_flag):
rotation_dir_blade *= -1
return PropInfo(prop_dg.geom_id, thrust_vector, rotation_dir_blade, hub_center, transmat, diameter)
[docs]
def plot_propeller_info(prop_infos, vector_scale=10.0, markersize=2.0, mutation_scale=20.0):
"""
Plots an array of propeller info tuples to visually inspect thrust and rotation directions
:param prop_infos: array of prop info tuples
:param vector_scale: length of each thrust vector
:param markersize: size of marker for hub location
:param mutation_scale: mutation scale applied to arrow objects
:return: figure handle
"""
from matplotlib import pyplot as plt
from matplotlib.patches import FancyArrowPatch
from mpl_toolkits.mplot3d import proj3d
try:
if len(vector_scale) != len(prop_infos):
raise ValueError("vector_scale must be either a scalar or have the same length as prop_infos")
#else:
# maxScale = max(vector_scale)
# vector_scale = [20.*v/maxScale for v in vector_scale]
except TypeError:
vector_scale = [vector_scale for p in prop_infos]
class Arrow3D(FancyArrowPatch):
def __init__(self, xs, ys, zs, *args, **kwargs):
super().__init__((0, 0), (0, 0), *args, **kwargs)
self._verts3d = xs, ys, zs
def do_3d_projection(self, renderer=None):
xs3d, ys3d, zs3d = self._verts3d
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M)
self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
return np.min(zs)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for iprop, prop_info in enumerate(prop_infos):
color = 'blue'
if prop_info.rotation_direction == -1:
color = 'red'
ax.plot(prop_info.hub_center[0], prop_info.hub_center[1], prop_info.hub_center[2], 'o', markersize=markersize,
color='black')
a = Arrow3D([prop_info.hub_center[0][0], prop_info.hub_center[0][0]+prop_info.thrust_vector[0][0]*vector_scale[iprop]],
[prop_info.hub_center[1][0], prop_info.hub_center[1][0]+prop_info.thrust_vector[1][0]*vector_scale[iprop]],
[prop_info.hub_center[2][0], prop_info.hub_center[2][0]+prop_info.thrust_vector[2][0]*vector_scale[iprop]],
mutation_scale=mutation_scale, lw=2, arrowstyle="-|>", color=color)
ax.add_artist(a)
# add circle for rotor diameter
radius = prop_info.diameter/2.0
thetas = np.linspace(0.0, 2.0*np.pi, 50)
yprop = np.cos(thetas)*radius
zprop = np.sin(thetas)*radius
xprop = np.zeros(len(thetas))
prop_points = np.array([xprop, yprop, zprop])
prop_points_transformed = prop_info.transmat.apply_transformation(prop_points)
ax.plot(prop_points_transformed[0, :], prop_points_transformed[1, :], prop_points_transformed[2, :],
color='black')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
set_3d_axis_equal(ax)
return fig
[docs]
def run_degen_geom(set_index=None, set_name=None, vsp_instance=None):
"""
Runs degen geom on input set
:param set_index: set index, will take precedence if both set index and set name are specified
:param set_name: name of set, will be used if set index is not specified
:param vsp_instance: optional instance of vsp if using the multifacade
:return: degen geom manager object
"""
vsp = vsp_module.get_instance(vsp_instance)
import degen_geom as dg
if set_index is None and set_name is None:
raise ValueError("set_index and set_name cannot both be None")
# Get set index from name, if set index was not specified
if set_index is None:
set_index = vsp.GetSetIndex(set_name)
# Run degen geom on the input set
vsp.SetAnalysisInputDefaults("DegenGeom")
vsp.SetIntAnalysisInput("DegenGeom", "Set", [set_index], 0)
vsp.SetIntAnalysisInput("DegenGeom", "WriteCSVFlag", [0], 0)
vsp.SetIntAnalysisInput("DegenGeom", "WriteMFileFlag", [0], 0)
degen_res_id = vsp.ExecAnalysis("DegenGeom")
degen_objs = vsp.parse_degen_geom(degen_res_id)
degen_mgr = dg.DegenGeomMgr(degen_objs)
return degen_mgr
[docs]
def set_3d_axis_equal(ax):
"""
Sets axis aspect ratio to equal
:param ax: 3d axis object
:return:
"""
xlims = list(ax.get_xlim())
ylims = list(ax.get_ylim())
zlims = list(ax.get_zlim())
rx = xlims[1]-xlims[0]
ry = ylims[1]-ylims[0]
rz = zlims[1]-zlims[0]
rmax = max(rx, ry, rz)
if rx < rmax:
diff = abs(rx-rmax)
xlims[0] -= diff/2.0
xlims[1] += diff/2.0
if ry < rmax:
diff = abs(ry-rmax)
ylims[0] -= diff/2.0
ylims[1] += diff/2.0
if rz < rmax:
diff = abs(rz-rmax)
zlims[0] -= diff/2.0
zlims[1] += diff/2.0
ax.set_xlim(xlims[0], xlims[1])
ax.set_ylim(ylims[0], ylims[1])
ax.set_zlim(zlims[0], zlims[1])
[docs]
def export_airfoils(set=vsp_module.SET_ALL, vsp_instance=None, **kwargs):
vsp = vsp_module.get_instance(vsp_instance)
import vsp_airfoils
af_dict = dict()
with RunManager(**kwargs) as r:
output_file = r.wd + "/airfoils.csv"
vsp.ExportFile(output_file, set, vsp.EXPORT_SELIG_AIRFOIL)
#parse airfoils.csv
with open(output_file,"r") as f:
lines = f.readlines()
i = 2
strs = lines[i].split(",")
rootDir = strs[1].strip()
i = i+1
while i < len(lines):
if "Airfoil File Name" in lines[i]:
file = rootDir + lines[i].split(",")[1].strip()
af = vsp_airfoils.VspSeligExport(file)
i = i+1
af.geom_name = lines[i].split(",")[1].strip()
i = i+1
af.geom_id = lines[i].split(",")[1].strip()
i = i+1
af.airfoil_index = int(lines[i].split(",")[1])
i = i+1
af.xsec_flag = lines[i].split(",")[1].strip() == "1"
i = i+1
if af.xsec_flag:
af.xsec_index = int(lines[i].split(",")[1])
i = i+1
af.xsec_surf_id = lines[i].split(",")[1].strip()
i = i+1
af.foil_surf_u_value = float( lines[i].split(",")[1] )
i = i+1
af.global_u_value = float( lines[i].split(",")[1] )
i = i+1
af.le_point = np.array( lines[i].split(",")[1:]).astype(float)
i = i+1
af.te_point = np.array( lines[i].split(",")[1:] ).astype(float)
i = i+1
af.chord = float( lines[i].split(",")[1])
i = i+1
#af.findSegments()
try:
af_dict[af.geom_id].append( af )
except KeyError:
af_dict[af.geom_id] = []
af_dict[af.geom_id].append( af )
else:
i = i + 1
return af_dict