Source code for openvsp.utilities

# 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