def blend(self, eps=1.0, show=True): ''' Blend all boundary and inner Curves to produce N smooth Surfaces. Parameters ---------- eps = the tolerance on geometric continuity (in degrees) show = whether or not to draw the blended Surfaces Returns ------- fig = a Figure ''' if not self.CIk: raise UndesignedFiller(self) self.BLRk = generate_inner_cross_deriv_nsided_region( self.CN.xyz, self.CIk, self.CLRk) self.nurbs = make_nsided_region(self.CLRk, self.DLRk, self.CIk, self.BLRk, eps) self.glue() O = self.O self.O = None if O is not None: self.C.translate(O) self.unglue() self.colorize() if show: return draw(self)
def _fit(self, func, p, xargs, show): ''' Fit a pth-degree Curve through the data Points using the approximation function func and the extra argument xargs. ''' lo, up = [points_to_obj_mat(self.data[si]) for si in ('lo', 'up')] Q = np.vstack((lo[-1:0:-1], up)) i = self.intersection if i: xyzw = i.xyzw[np.newaxis, :] Q = np.vstack((xyzw, Q, xyzw)) r = Q.shape[0] - 1 args = (r, Q, p) + xargs U, Pw = func(*args) nurbs = Curve(ControlPolygon(Pw=Pw), (p, ), (U, )) print('geom.airfoil.Airfoil.fit :: ' 'fit with {} control points'.format(Pw.shape[0])) self._halve(nurbs) if not self.issharp: print('geom.airfoil.Airfoil.fit :: ' 'warning, fit but blunt airfoil') if show: d = self.get_curvature_cplots() d += self.data['lo'] + self.data['up'] if i: d += [i] fig = draw(self, *d, stride=0.1) fig.c.setup_preset('xz') return fig
def trim(self, show=True): ''' ''' if not any(self.ICs): raise Exception for half, h, IC in zip((0, 1), self.wing.halves, self.ICs): if not self.half in (2, half): continue U, V = h.U if not self.tip: jnc1 = self.wing.junctions[1] if (not jnc1) or (jnc1 and not jnc1.ICs[half]): P0 = IC.cobj.cpts[ 0] P1 = IC.cobj.cpts[-1] P2 = Point(U[-1], V[-1]) P3 = Point(U[ 0], V[-1]) C0 = IC C1 = make_linear_curve(P1, P2) C2 = make_linear_curve(P2, P3) C3 = make_linear_curve(P3, P0) else: IC1 = jnc1.ICs[half] P0 = IC.cobj.cpts[ 0] P1 = IC.cobj.cpts[-1] P2 = IC1.cobj.cpts[-1] P3 = IC1.cobj.cpts[ 0] C0 = IC C1 = make_linear_curve(P1, P2) C2 = IC1.reverse() C3 = make_linear_curve(P3, P0) else: jnc0 = self.wing.junctions[0] if (not jnc0) or (jnc0 and not jnc0.ICs[half]): P0 = Point(U[ 0], V[0]) P1 = Point(U[-1], V[0]) P2 = IC.cobj.cpts[-1] P3 = IC.cobj.cpts[ 0] C0 = make_linear_curve(P0, P1) C1 = make_linear_curve(P1, P2) C2 = IC C3 = make_linear_curve(P3, P0) else: IC0 = jnc0.ICs[half] P0 = IC0.cobj.cpts[ 0] P1 = IC0.cobj.cpts[-1] P2 = IC.cobj.cpts[-1] P3 = IC.cobj.cpts[ 0] C0 = IC0 C1 = make_linear_curve(P1, P2) C2 = IC.reverse() C3 = make_linear_curve(P3, P0) IC = make_composite_curve([C0, C1, C2, C3], remove=False) h.trim(IC) if show: return draw(self.S, self.wing)
def finalize(self, d=1e-2, show=True): ''' Perform some post-processing steps on the OML Surface. This should only be called after having applied all molders (but obviously before creating other objects that depend on the final Fuselage's shape, such as Junctions). For now, this method is just a wrapper around nurbs.surface.Surface.removes, which removes as many control points as possible up to a given tolerance. Parameters ---------- d = the maximum deviation allowed from the current OML Surface show = whether or not to draw the new, simplified OML Surface Returns ------- fig = a Figure ''' if self._nurbs: n = self._nurbs else: n = self.nurbs self._nurbs = n.copy() e, n = n.removes(2, d=d) print('geom.fuselage.Fuselage.finalize :: ' '{} control points removed (u, v)'.format(e)) self.nurbs = n self.clamp() if show: return draw(self)
def intersect(self, CRT=5e-3, ANT=1e-2, show=True): ''' ''' P0 = find_LE_starting_point(self.wing.LE, self.S) print('geom.junction.TrimmedJunction.intersect :: ' 'LE starting point found: {}'.format(P0)) IP = [] for half, h in zip((0, 1), self.wing.halves): if self.half in (2, half): args = h, self.S, P0, CRT, ANT Q, stuv = surface_surface_intersect(*args) if show: Qw = obj_mat_to_4D(Q) IP += obj_mat_to_points(Qw).tolist() n = stuv.shape[0] - 1 z = np.zeros((n + 1, 1)) st = stuv[:,:2] Q = np.hstack((st, z)) U, Pw = local_curve_interp(n, Q) IC = Curve(ControlPolygon(Pw=Pw), (3,), (U,)) self.ICs[half] = IC if show: return draw(self.S, self.wing, *IP)
def revolve(self, D, ang=0.0, show=True): n = self.airfoil.nurbs.reverse() n.mirror(N=[0, 0, 1]) n.translate([0, 0, -D / 2.0]) S, T = [0, 0, 0], [-1, 0, 0] nurbs = make_revolved_surface_rat(n, S, T, 360) #nurbs = make_revolved_surface_nrat(n, S, T, 360) #if ang != 0.0: # rad = np.deg2rad(ang) # N = [np.cos(rad), 0, np.sin(rad)] # n, dummy = nurbs.cobj.n # for i in xrange(n + 1): # Pw = nurbs.cobj.Pw[i] # L0 = Pw[0,:3] # P = intersect_line_plane(L0, [1,0,0], [0,0,0], N) # sf = (L0[0] - P[0]) / L0[0] # scale(Pw, sf, L0, (1,0,0)) hs = nurbs.split(0.5, 1) self.nurbs = nurbs self.halves = hs self.D = D if show: return draw(self)
def read_patch(self, patch_file='patch.con', show=True): ''' Pre: Grid, Map ''' self.ptch = [] self.stch = [] with open(patch_file) as fh: fh.readline() fh.readline() nptch = int(fh.readline()) fh.readline() fh.readline() for ip in xrange(nptch): line = read_line_split(fh) im, side, dof = line[1:] map, udi = self.blk[im-1].map, self.EXTRACTMAP[side] ptch = map.extract(*udi) ptch = Patch(ptch.cobj, ptch.p, ptch.U) ptch.indx = ip + 1 ptch.map = im ptch.side = side ptch.dof = dof self.ptch.append(ptch) fh.readline() fh.readline() nstch = int(fh.readline()) fh.readline() fh.readline() for i in xrange(nstch): line = read_line_split(fh) ip, edge, di = (np.zeros(2, dtype='i'), np.zeros(2, dtype='i'), np.zeros(2, dtype='i')) typ, dof, conty, ip[0], edge[0], di[0] = line[1:] if typ == 0: line = read_line_split(fh) ip[1], edge[1], di[1] = line ptch, udi = self.ptch[ip[0]-1], self.EXTRACTMAP[edge[0]] stch = ptch.extract(*udi) stch = Stitch(stch.cobj, stch.p, stch.U) stch.indx = i + 1 stch.joined = True if typ == 0 else False stch.dof = dof stch.conty = conty stch.ptch = ip stch.edge = edge stch.dir = di self.stch.append(stch) self._colorize() if show: return draw(*(self.stch+self.ptch))
def orient(self, T, Tw=None, m=10, show=True): ''' Generate an orientation function to the trajectory Curve T. An orientation function is one that, as its name suggests, orients the root/tip Airfoils along T during the sweeping process (see nurbs.surface.make_swept_surface and Wing.fit). It is initialized via the projection normal method located in nurbs.surface.get_sweep_orient_func. Note that here this function is restricted in such a way that the Airfoil instances taken along T (once translated) are only allowed to rotate about the X axis (plus twist, if any). This is to ensure that ultimately each spanwise cross-section of a fitted Wing lies in its own plane whose normal has no X component. Parameters ---------- T = the trajectory Curve Tw = the B-spline twisting function, if any m = the number of points to construct the orientation function with show = whether or not to draw the orientation vectors Returns ------- fig = a Figure Examples -------- >>> T = nurbs.tb.make_linear_curve(Point(), Point(y=3)) >>> tw = BSplineFunctionCreator1(end=(0.0,90.0)).fit() >>> wi.orient(T, Tw=tw) ''' T = T.copy() if Tw: Tw = Tw.copy() U, = T.U; normalize_knot_vec(U) T0 = T.eval_derivatives(0, 1)[1]; T0[0] = 0 B0 = np.cross(T0, (-1,0,0)) Bv = get_sweep_orient_func(B0, T, (1,0,0), Tw, m) self.T, self.Tw, self.Bv = T, Tw, Bv if show: vb = np.linspace(0, 1, m) O, B = np.zeros((2, m, 3)) for i, v in enumerate(vb): O[i], B[i] = T.eval_point(v), Bv.eval_point(v) V = O + 0.2 * B # knob O = obj_mat_to_points(obj_mat_to_4D(O)).tolist() V = obj_mat_to_points(obj_mat_to_4D(V)).tolist() for v in V: v.color = (0, 255, 255, 255) return draw(T, *(O + V))
def read_connectivity(self, con_file='grid.con', show=True): ''' Pre: Grid ''' def read_line(query_type=True): line = read_line_split(fh) dit = np.array(line[-6:]).reshape((2,3)) typ = line[-9] if query_type else 0 blk, side = line[-8:-6] return typ, blk, side, dit self.iface = [] self.bcface = [] with open(con_file) as fh: fh.readline() fh.readline() nblk = int(fh.readline()); fh.readline() nsfc = int(fh.readline()); fh.readline() nifc = int(fh.readline()) fh.readline() fh.readline() assert nblk == len(self.blk) i0, i1 = 1, 1 for i in xrange(nsfc): typ, ib, side, dit = read_line() blk, udi = self.blk[ib-1], self.EXTRACTMAP[side] face = blk.extract(*udi) if typ > 0: bcface = Boundary(face.cobj, face.p, face.U) bcface.indx = i0; i0 += 1 bcface.type = typ bcface.blk = ib bcface.side = side bcface.dit = dit self.bcface.append(bcface) continue dummy, ib1, side1, dit1 = read_line(False) iface = Interface(face.cobj, face.p, face.U) iface.indx = i1; i1 += 1 iface.type = 0 iface.blk = np.array([ib, ib1]) iface.side = np.array([side, side1]) iface.dit1 = dit iface.dit2 = dit1 self.iface.append(iface) assert((nsfc == len(self.bcface) + len(self.iface)) and (nifc == len(self.iface))) self._colorize() if show: return draw(self)
def setup(self, nlcpt=50, cap=True, show=True): ''' Setup the NURBS representation of the initial bullet head cylinder. Analogous to modelling clay, it is this NURBS Surface that will be successively shaped by molders. Parameters ---------- nlcpt = the number of longitudinal control points used to represent the cylinder with cap = whether or not to close the rear-end gap with a rounded Surface; for geometries destined for inviscid flow simulations this flag should be set to False show = whether or not to draw the newly setup cylinder Returns ------- fig = a Figure ''' R, BL, NL = self.R, self.BL, self.NL O, X, Y = [0, 0, 0], [-1, 0, 0], [0, 0, -1] args = (O, X, Y, NL, R, 0, np.pi / 2.0) P0, P1 = Point(0, 0, -R), Point(BL, 0, -R) nose = make_ellipse(*args) body = make_linear_curve(P0, P1) for c in nose, body: reparam_arc_length_curve(c) n = make_composite_curve([nose, body]) n = refit_curve(n, nlcpt, num=50000) n2, = n.cobj.n for i in xrange(n2 + 1): Pw = n.cobj.Pw[i] if Pw[0] > 0 or Pw[2] < -R: Pw[2] = -R if cap: P2 = Point(BL, 0, 0) rear = make_linear_curve(P1, P2) reparam_arc_length_curve(rear) n = make_composite_curve([n, rear]) n = make_revolved_surface_nrat(n, O, [1, 0, 0], 180) if not cap: n.cobj.Pw[:, -1, 1] = 0.0 self.nurbs = n self.molders = [] self.clamp() if show: return draw(self)
def fill(self, l=5.0, dl=2.0, xb=0.35, ntip=20, show=True): ''' Fill the Wingtip with a Gordon Surface. The Gordon Surface (see nurbs.surface.make_gordon_surface) is interpolated from an automatically generated bi-directional Curve network. Aside from the number of v-directional Curves to compose the network with, ntip, the user is free to vary any of the other three shape parameters: l, dl and xb. \ / \ Wing / \ / \________ tip chord ________/_ \ \ \ | / / / | ntip = 7 \ \ \ Wingtip / / / | l \__\__\____|____/__/__/ v | dl xb <-----| v Parameters ---------- l = the length of the Wingtip extension, in tip chord percentage dl = the delta, in tip chord percentage, to supplement l with; this actually varies according to the smooth funtion: [ x ** 2*xb ] * [ (1.0 - x) ** (2.0 - 2*xb) ] xb = the location, between 0 and 1, where that function takes on a maximum ntip = the number of v-directional cross-sections used to construct the Curve network; in the u-direction, that number is fixed to 3 show = whether or not to draw the filled Wingtip Returns ------- fig = a Figure ''' ul = np.linspace(0, 1, ntip) vk = np.linspace(0, 1, 3) Ck, Cl = self._network(l, dl, xb, ul, vk) nurbs = make_gordon_surface(Ck, Cl, ul, vk) nurbs = nurbs.reverse(0) self._halve(nurbs) self._hs = [h.copy() for h in self.halves] self.Ck, self.Cl = Ck, Cl if show: return draw(self, self.wing, *(Ck + Cl))
def design(self): ''' Design the representation Curve interactively. The first and last control points are constrained to the (x = 0) and (x = 1) lines, respectively. ''' cpts = self.R.cobj.cpts cpt0, cpt1 = cpts[0], cpts[-1] cpt0.line = (0,0,0), (0,1,0) cpt1.line = (1,0,0), (0,1,0) fig = draw(self.R) fig.c.setup_preset(r=(0,0,0)) return fig
def attach(self): ''' ''' wing, S = self.wing, self.S U, V = S.U b0 = S.extract(U[ 0], 0) b1 = S.extract(U[-1], 0) b2 = S.extract(V[ 0], 1) b3 = S.extract(V[-1], 1) fig = draw(b0, b1, b2, b3, wing) fig.m.toggle_mode('_AttachWingJunctionMode') fig.m.mode.junction = self return fig
def extend(self, l=0.0, show=True): ''' ''' wing, nurbs = self.wing, self._nurbs if l != 0.0: u = arc_length_to_param(wing.QC, l) ne = nurbs.extend(u, 1, end=self.tip) nn = (ne, nurbs) if not self.tip else (nurbs, ne) nc = make_composite_surface(nn, reorient=False) normalize_knot_vec(nc.U[1]) wing._halve(nc) if show: return draw(self.S, self.wing)
def design(self): ''' Design a Wing's planform by manipulating the TE Curve in the XY plane (and/or the YZ plane if vertical scaling is desired). The LE Curve should remain untouched. ''' T0, T1 = self.Ts cpts = [cpt for T in (T0, T1) for cpt in T.cobj.cpts] for cpt in cpts: if hasattr(cpt, 'line'): del cpt.line cpt.plane = cpt.xyz, (0,1,0) T1.colorize() fig = draw(T0, T1) fig.c.setup_preset('xy') return fig
def design(self, show=True): ''' Design the Filler's default entities, i.e. its inner Curves, its central Point and/or normal vector. All of these will have a direct impact on the shape of the resultant blended NURBS patches. Choose them wisely! Parameters ---------- show = whether or not to interactively design the Filler Returns ------- fig = a Figure ''' self.nurbs = [] self.unglue() self._originate() if show: return draw(self, self.C, self.CN, *self.CIk)
def design(self): ''' Automatically create LE and TE Curve extensions attaching the two Wings. If not satisfied, the Wing objects can be repositioned before repeating the process. ''' w0, w1 = self.ws Q0, D0 = w0.LE.eval_derivatives(1, 1) Q1, D1 = w1.LE.eval_derivatives(0, 1) U, Pw = local_curve_interp(1, (Q0, Q1), D0, D1) T0 = Curve(ControlPolygon(Pw=Pw), (3,), (U,)) Q0, D0 = w0.TE.eval_derivatives(1, 1) Q1, D1 = w1.TE.eval_derivatives(0, 1) U, Pw = local_curve_interp(1, (Q0, Q1), D0, D1) T1 = Curve(ControlPolygon(Pw=Pw), (3,), (U,)) self.Ts = [T0, T1] return draw(w0, w1, T0, T1)
def merge(self, hf, reverse, l=None, show=True): ''' Similar to WingMerger. The idea here is however to merge only one half of each Wing, thus allowing the other half of a merged Wing to be reused in a subsequent merge with another HalfWingMerger. This design allows to build Wings of virtually any topology. By default each Wing's root is merged at the tip of the previous Wing's tip; if this is not possible or the present Wings' orientation do not allow for it, the user can specify so by means of the `reverse` list (see example below). Parameters ---------- hf = the half of the Wings to be merged (0 for lower or 1 for upper) reverse = the boolean list specifying whether or not a Wing's orientation should be temporarily reversed l = same as WingMerger show = same as WingMerger Returns ------- fig = a Figure Examples -------- >>> w1 = w0.copy() # w0 being a Wing object >>> w2 = w0.copy() >>> w1.glue(); w1.dihedral = 90; w1.unglue() >>> hwm = HalfWingMerger(w0, w1) >>> hwm.merge(0, [False, True], None) >>> w0m, w1m = hwm.merged_wings >>> hwm = HalfWingMerger(w1m, w2) >>> hwm.merge(0, [False, False], None) >>> w1m, w2m = hwm.merged_wings ''' self.merged_wings = [] Cas, hfo = [], np.mod(hf + 1, 2) for w1, r in zip(self.wings, reverse): h1s = w1.halves w1._halve(w1.nurbs) hf1 = hf if r else hfo w1.halves[hf1] = h1s[hf1] if not self.merged_wings: if r: self._reverse_half(w1, hfo if r else hf1) self.merged_wings.append(w1) continue w0 = self.merged_wings[-1] self.merged_wings.append(w1) afr0, aft0 = w0.airfoils afr1, aft1 = w1.airfoils af0 = aft0 if aft0 else afr0 af1 = afr1 if not (af0.issymmetric or af1.issymmetric): print('geom.wing.WingMerger.merge :: ' 'warning, tip airfoil(s) not symmetric') hf0 = hfo if (w1 is self.wings[1] and reverse[0]) else hf hf1 = hfo if r else hf if r: self._reverse_half(w1, hf1) # Step 1: translate w1, if necessary self._translate(w0, w1) # Step 2: force the approximation to stay within l (wrt the QC) Cas += self._segment(w0, w1, hf0, hf1, l) # Step 3: shear w0 and w1 equally, if necessary self._shear(w0, w1, hf0, hf1) if r: self._reverse_half(w1, hf1) Ns = [] for mw, r in zip(self.merged_wings, reverse): Ns.append(mw.halves[hfo if r else hf]) Ns = make_surfaces_compatible3(Ns, di=0) for mw, r, N in zip(self.merged_wings, reverse, Ns): mw.halves[hfo if r else hf] = N if reverse[0]: w0 = self.merged_wings[0] self._reverse_half(w0, hfo) if show: return draw(self, *Cas)
def merge(self, l=None, show=True): ''' Merge all Wings in sequential order. This is done by first forcing the to-be merged Wing to be spatially compatible with the last merged Wing, and then by finding the intersection Curve between the two. In general, such an intersection Curve does not exist, and thus an approximation to the Wing's Surfaces immediately before and after the intersection is built. Outside that interval (delimited by the Curves drawn if letting show=True) the Wing's Surfaces are kept intact. Note that the merged Wings are *copies* of those used to instantiate the WingMerger; they are however stored in the same order. Parameters ---------- l = if not None, the (approximate) length before and after which two merged Wings are approximated show = whether or not to draw the merged Wings with Curves delimiting their reapproximations Returns ------- fig = a Figure ''' self.merged_wings = [] Cas = [] for w1 in self.wings: w1._halve(w1.nurbs) if not self.merged_wings: self.merged_wings.append(w1) continue w0 = self.merged_wings[-1] self.merged_wings.append(w1) afr0, aft0 = w0.airfoils afr1, aft1 = w1.airfoils n0 = aft0.nurbs if aft0 else afr0.nurbs n1 = afr1.nurbs for hf in (0, 1): # Step 1: translate w1, if necessary self._translate(w0, w1) # Step 2: force the approximation to stay within l (wrt the QC) Cas += self._segment(w0, w1, hf, hf, l) # Step 3: shear w0 and w1 equally, if necessary self._shear(w0, w1, hf, hf) for hf in (0, 1): Ns = [mw.halves[hf] for mw in self.merged_wings] Ns = make_surfaces_compatible3(Ns, di=0) for mw, N in zip(self.merged_wings, Ns): mw.halves[hf] = N for mw in self.merged_wings: mw._size() if show: return draw(self, *Cas)
def fit(self, K=1, scs=(None, None, None), show=True): ''' Fit (sweep) the root/tip Airfoils along the Wing's trajectory Curve T with optional scaling. Use BSplineFunctionCreators if you do end up applying scaling. Recall that an Airfoil is defined in the XZ plane only, therefore applying Y-directional scaling won't affect the Wing. This method also uses the orientation function previously determined by Wing.orient. Note that for nonlinear Wings (in sweep, dihedral, twist, etc.), only an approximation is constructed. As explained in nurbs.surface.make_swept_surface, a better approximation can be obtained by increasing the value of K. As a rule of thumb, a fit is considered good when, while facing the YZ plane, each spanwise row of control points of the Wing falls on a straight line. Parameters ---------- K + 1 = the (minimum) number of Airfoil instances taken along T scs = the B-spline scaling functions, if any (a 3-tuple corresponding to scaling in X, Y and Z, respectively) show = whether or not to draw the swept Wing Returns ------- fig = a Figure Examples -------- >>> sc = BSplineFunctionCreator1(end=(2.0,1.0)).fit() >>> wi.fit(K=5, scs=(sc,None,sc)) ''' if not self.T: raise UnorientedWing() scs = [(sc.copy() if sc else None) for sc in scs] if any(scs): q, = self.T.p if q == 1: q = np.array([sc.p[0] for sc in scs if sc]) if (q > 1).any(): print('geom.wing.Wing.fit :: ' 'warning, linear sweep with nonlinear scaling') self.tip = None self.junctions = [None, None] self.structure = None afr, aft = self.airfoils args = afr.nurbs, self.T, self.Bv, K, scs if aft: args += aft.nurbs, nurbs = make_swept_surface(*args, local=get_sweep_local_sys_proj) self._halve(nurbs) self.scs = scs if show: return draw(self, self.T)