예제 #1
0
htail = bodies['Htail']
other_htail = bodies['Htail.3']
vtail = bodies['Vtail']
jury = bodies['Jury']
other_jury = bodies['Jury.4']
strut = bodies['Strut']
other_strut = bodies['Strut.5']

gui = Viewer()
for body in [
        fuselage, wing, other_wing, gear, other_gear, htail, other_htail,
        vtail, jury, other_jury, strut, other_strut
]:
    body.set_color(0.5, 0.5, 0.5)
    body.set_transparency(0.5)
    gui.add(body)

# WING ------------------------------------------------------------------------
GroupAPI.create_group('wing group')

# Center wing structure will be based on the intersection between the wings
# and fuselage.
shape = IntersectShapes(fuselage.shape, wing.shape).shape

# Use the y-dimensions of the bounding box of the intersection curve.
bbox = BBox()
bbox.add_shape(shape)
ymax = bbox.ymax

# Center wing box
xz_plane = PlaneByAxes().plane
예제 #2
0
# Build a reference surface using chord lines of the cross sections. Make sure
# to use the same scaling and rotation parameters. Set the parametric domains
# to be between 0 and 1 for convenience.
chord1 = cs.build_chord(pln1, scale=c1)
chord2 = cs.build_chord(pln2, scale=c2)
chord3 = cs.build_chord(pln3, scale=c3, rotate=t3)
sref = NurbsSurfaceByInterp([chord1, chord2, chord3], 1).surface
sref.set_udomain(0., 1.)
sref.set_vdomain(0., 1.)

# Set the wing reference surface
wing.set_sref(sref)

# Show wing and reference surface
gui = Viewer()
gui.add(wing, wing.sref)
gui.start()

# Show the underlying shape of the reference surface
gui.clear()
gui.add(wing.sref_shape)
gui.start()

# Evaluate point
p = wing.sref.eval(0.5, 0.5)

# Extract a plane
pln = wing.extract_plane(0.5, 0., 0.5, 0.5)
face = FaceByPlane(pln, -10, 10, -10, 10).face

# Extract a trimmed curve
예제 #3
0
alg1d = Regular1D(the_gen)
edges = the_shape.edges
cmp = CompoundByShapes(edges).compound
the_mesh.add_hypothesis(hyp1d, cmp)
the_mesh.add_hypothesis(alg1d, cmp)

# Unstructured quad-dominant
hyp2d = NetgenSimple2D(the_gen, 4.)
alg2d = NetgenAlgo2D(the_gen)
the_mesh.add_hypothesis(hyp2d, the_shape)
the_mesh.add_hypothesis(alg2d, the_shape)

# Apply mapped quadrangle to internal structure
# mapped_hyp = QuadrangleHypo2D(the_gen)
# mapped_alg = QuadrangleAlgo2D(the_gen)
# for part_ in internal_parts + [skin]:
#     for face in part_.faces:
#         if mapped_alg.is_applicable(face, True):
#             the_mesh.add_hypothesis(mapped_hyp, face)
#             the_mesh.add_hypothesis(mapped_alg, face)

the_gen.compute(the_mesh, the_shape)

# View
# skin.set_transparency(0.5)
v = Viewer()
v.add(wingbox)
v.start()
v.add(the_mesh)
v.start()
예제 #4
0
skin.fuse(*parts)
skin.discard_by_dmin(htail.sref_shape, 1.)
skin.fix()
skin.set_transparency(0.5)

# JOIN
start = time.time()
print('Performing assembly join...')
bop = FuseGroups([wing_group, fuse_group, htail_group])
print('Joining complete in ', time.time() - start, ' seconds.')

shape = bop.shape
tool = ExploreFreeEdges(shape)

parts = GroupAPI.get_master().get_parts()

# Mesh
the_mesh = MeshVehicle(4.)
mesh_start = time.time()
print('Computing mesh...')
status = the_mesh.compute()
if not status:
    print('Failed to compute mesh')
else:
    print('Meshing complete in ', time.time() - mesh_start, ' seconds.')

gui = Viewer()
gui.add(*tool.free_edges)
gui.add(the_mesh)
gui.start()
예제 #5
0
                         fspar.shape,
                         rspar.shape,
                         wing,
                         d1=30,
                         d2=-30)
internal_parts = wingbox.get_parts()
skin = SkinByBody('skin', wing).part
cref = wing.sref.u_iso(0.5)
skin.discard_by_dmin(cref, 1.0)

FuseSurfaceParts([skin], internal_parts)

group = GroupAPI.get_master()

v = Viewer()
v.add(group)
v.start()

# Save
print('\nSaving group...')
GroupAPI.save_model('structure.xbf')
print('done.\n')

# Load into new group
print('Loading group...')
new_group = GroupAPI.create_group('new model')
GroupAPI.load_model('structure.xbf', new_group)
print('done.')
v.clear()
v.add(new_group)
v.start()
예제 #6
0
CutParts(ribs, solid)

# Wing skin
skin = SkinByBody('skin', wing).part
skin.set_color(0.5, 0.5, 0.5)
skin.set_transparency(0.5)

# Discard end caps of skin
skin.discard_by_dmin(cref1, 0.5)

# Join all the parts
parts = GroupAPI.get_parts()
SplitParts(parts)

gui = Viewer()
gui.add(*parts)
gui.start()

# Mesh
print('Computing the mesh...')
the_shape = GroupAPI.prepare_shape_to_mesh()
the_gen = MeshGen()
the_mesh = MeshGen.create_mesh(the_gen)
the_mesh.shape_to_mesh(the_shape)

# 2-D mesh
hyp2d = NetgenSimple2D(the_gen, 1.)
alg2d = NetgenAlgo2D(the_gen)
the_mesh.add_hypothesis(hyp2d, the_shape)
the_mesh.add_hypothesis(alg2d, the_shape)
예제 #7
0
    # Uncomment this to export STEP file.
    # from afem.io import StepExport
    # step = StepExport()
    # step.transfer(shape_to_mesh)
    # step.write('wingbox.step')

    return GroupAPI.get_active()


if __name__ == '__main__':
    start = time.time()

    # Import model
    fname = '../models/777-200LR.xbf'
    bodies = Body.load_bodies(fname)
    wing_in = bodies['Wing']

    gui = Viewer()
    # Build wing box
    inputs = {
        'build aux': True,
        'mid spar rib': 10,
        'aux rib list': ['2', '5', '8']
    }
    group = build_wingbox(wing_in, inputs)

    gui.add(group)
    gui.start()

    print('Complete in ', time.time() - start, ' seconds.')
예제 #8
0
gui = Viewer()

# Create a point directly from the entity. Default is (0, 0, 0).
p1 = Point()

# Create a point by array-like
p2 = PointByArray([5, 0, 5]).point

# Create a point by x-, y-, and z-coordinates.
p3 = PointByXYZ(10, 0, 0).point

# Interpolate the points with a curve
c1 = NurbsCurveByInterp([p1, p2, p3]).curve

gui.add(p1, p2, p3, c1)
gui.start()

# Copy curve and translate
c2 = c1.copy()
c2.translate((0, 10, 0))

gui.add(c2)
gui.start()

# Copy and translate again
c3 = c2.copy()
c3.translate((0, 10, 10))

gui.add(c3)
gui.start()
예제 #9
0
    # A few notes about importing an OpenVSP STEP model:
    #   1) If the model comes from the modified version that includes metadata
    #      then the OpenVSP components can be identified by type and retrieved
    #      by name. Otherwise the import methods just tries to make solid
    #      bodies from the components and gives them a generic name.
    #   2) The import process attempts to sew the faces of the OpenVSP
    #      components together to form solids.
    #   3) If a surface is found to be planar, it is replaced with a plane
    #      before sewing. This helps eliminate some degenerated edges.
    #   4) Faces sharing the same domain are unified. If flat wing caps are
    #      used, this usually results in a single face for the entire cap
    #      rather than two faces split between the upper and lower surface.

    # View the model
    v = Viewer()
    v.add(*vsp_import.all_bodies)
    v.start()
    v.clear()

    # Retrieve relative components by name and set transparency for viewing
    wing_ = vsp_import.get_body('wing')
    fuse_ = vsp_import.get_body('fuse')
    wing_.set_transparency(0.5)
    fuse_.set_transparency(0.5)

    # OpenVSP 3.5 was modified by Laughlin Research to construct and export
    # metadata and reference geometry in the STEP file. For a wing component,
    # the reference geometry includes a surface that is lofted through the
    # chord lines at each wing station. This surface is used to define
    # structure in terms of percent chord and/or semispan. Currently, the
    # u-direction of the surface is in the chordwise and the v-direction is
예제 #10
0
from afem.geometry import *
from afem.graphics import Viewer
from afem.sketch import *
from afem.topology import *

# Create a new cross section
cs = Airfoil()

# Generate a 2-D profile by reading and approximating an airfoil file from the
# UIUC database. Close the trailing edge if necessary.
cs.read_uiuc('../models/clarky.dat', close=True)

# Define a plane at the root and scale
pln1 = PlaneByAxes(axes='xz').plane
cs.build(pln1, scale=5)
wire1 = cs.wires[0]

# Define plane at the tip and rotate
pln2 = PlaneByAxes((3, 15, 0), axes='xz').plane
cs.build(pln2, scale=1.5, rotate=3)
wire2 = cs.wires[0]

# Use the wires to loft a solid
shape = LoftShape([wire1, wire2], True).shape

gui = Viewer()
gui.add(wire1, wire2, shape)
gui.start()
예제 #11
0
from afem.config import Settings
from afem.exchange import ImportVSP
from afem.graphics import Viewer

Settings.log_to_console()

# This model has bad shapes. Look at the leading edge and you'll see the
# self-intersection.
fn = '../models/vsp_bad_geom.stp'

vsp_import = ImportVSP(fn)
htail = vsp_import['Htail']

gui = Viewer()
htail.set_color(0.5, 0.5, 0.5)
gui.add(htail)
for shape in vsp_import.invalid_shapes:
    shape.set_color(1, 0, 0)
    gui.add(shape)
gui.start()
예제 #12
0
from afem.topology import FixShape, FuseShapes

Settings.log_to_console()

# Import an OpenVSP STEP file. If generated using the modified version that
# includes metadata, each Body will be retrievable by its component name.
fn = './models/777-200LR.stp'

# Import the OpenVSP STEP file
vsp_import = ImportVSP(fn)

# View the bodies
gui = Viewer()
solids = []
for body in vsp_import.all_bodies:
    gui.add(body)
    solids.append(body.shape)
gui.start()
gui.clear()

# The Boolean fuse operation must have arguments and tools. Pick one solid to
# be an argument and the others tools. So far the choice of argument and tools
# has not impacted the final fused shape. If you're able to pick the arguments
# and tools, perhaps picking the largest/most connected solid is best (like
# a fuselage)?
args = [solids[0]]
tools = solids[1:]

# Fuse the shapes
fuse = FuseShapes()
fuse.set_args(args)
예제 #13
0
from afem.config import Settings
from afem.exchange import ImportVSP
from afem.graphics import Viewer

Settings.log_to_console()

fn = '../models/777-200LR.stp'

vsp_import = ImportVSP(fn)

v = Viewer()
v.add(*vsp_import.all_bodies)
v.start()
예제 #14
0
hyp2d_1 = NetgenSimple2D(gen, 1.)
hyp2d_2 = NetgenSimple2D(gen, 1., allow_quads=False)
alg1d = Regular1D(gen)
hyp1d = MaxLength1D(gen, 0.25)

# Add them to the mesh
mesh.add_hypotheses([alg2d, hyp2d_1])
mesh.add_hypothesis(hyp2d_2, faces[-1])
mesh.add_hypotheses([alg1d, hyp1d], edges[-1])

# Compute the mesh
gen.compute(mesh)

# View the mesh
gui = Viewer()
gui.add(mesh)
gui.start()
gui.clear()

# Get sub-meshes from sub-shapes
face_submesh = mesh.get_submesh(faces[0])
edge_submesh = mesh.get_submesh(edges[0])

# View the face sub-mesh (2-D elements)
gui.add(face_submesh)
gui.start()
gui.clear()

# View the edge sub-mesh (1-D elements)
gui.add(edge_submesh)
gui.start()
예제 #15
0
from afem.exchange import *
from afem.graphics import Viewer

fn = '../models/777-200LR_Onshape.step'

doc = XdeDocument()
shapes_label = doc.read_step(fn)

gui = Viewer()
for child_label in shapes_label.children_iter:
    gui.add(child_label.shape)
    print('Name: {}'.format(child_label.name))
gui.start()
예제 #16
0
    frame_pln_face = FaceBySurface(frame.sref).face
    shape = CommonShapes(below_cargo_floor, frame_pln_face).shape
    shape = CutShapes(shape, rev_cylinder).shape
    frame.merge(shape, True)
    i += 1

main_floor.set_transparency(0.5)
cargo_floor.set_transparency(0.5)

all_parts = GroupAPI.get_parts(order=True)

# Split all parts together
join = SplitParts(all_parts)

# Mesh
the_mesh = MeshVehicle(4.)

# Mapped quads applied to applicable faces
the_mesh.set_quadrangle_2d(the_mesh.shape)

print('Computing the mesh...')
the_mesh.compute()

# View
gui = Viewer()
gui.add(GroupAPI.get_master())
gui.start()
gui.clear()
gui.add(the_mesh)
gui.start()
import time

from afem.exchange import brep
from afem.graphics import Viewer
from afem.smesh import *

fn = 'wing_body.brep'
shape = brep.read_brep(fn)

# MeshGems
the_gen = MeshGen()
the_mesh = the_gen.create_mesh(shape)
alg2d = MeshGemsAlgo2D(the_gen)
hyp2d = MeshGemsHypo2D(the_gen, 4.)
the_mesh.add_hypotheses([alg2d, hyp2d])
print('Computing mesh with MeshGems...')
start = time.time()
the_gen.compute(the_mesh)
print('MeshGems complete in ', time.time() - start, ' seconds.')

gui = Viewer()
gui.add(the_mesh)
gui.start()
예제 #18
0
# Mesh
the_shape = GroupAPI.prepare_shape_to_mesh()
the_gen = MeshGen()
the_mesh = the_gen.create_mesh(the_shape)

# Unstructured quad-dominant
ngh = NetgenSimple2D(the_gen, 4.)
nga = NetgenAlgo2D(the_gen)
the_mesh.add_hypotheses([ngh, nga])

# Max edge length
hy1d = MaxLength1D(the_gen, 4.)
alg1d = Regular1D(the_gen)
the_mesh.add_hypotheses([hy1d, alg1d])

# Mapped quads applied to applicable faces
mapped_hyp = QuadrangleHypo2D(the_gen)
mapped_algo = QuadrangleAlgo2D(the_gen)

for face in the_shape.faces:
    if mapped_algo.is_applicable(face, True):
        the_mesh.add_hypotheses([mapped_hyp, mapped_algo], face)

the_gen.compute(the_mesh)

# View
v = Viewer()
v.add(GroupAPI.get_master())
v.add(the_mesh)
v.start()
예제 #19
0
p2 = Point(10, 0, 0)
p3 = Point(10, 10, 0)
p4 = Point(5, 10, 0)
p5 = Point(0, 10, 0)
wire = Wire.by_points([p1, p2, p3, p4, p5], True)
face = Face.by_wire(wire)

# Mesh using composite side algorithm to avoid making a vertex at edge
the_gen = MeshGen()
the_mesh = the_gen.create_mesh(face)
hyp1d = LocalLength1D(the_gen, 4)
alg1d = CompositeSide1D(the_gen)
the_mesh.add_hypotheses([hyp1d, alg1d], wire)
hyp2d = NetgenSimple2D(the_gen, 1)
alg2d = NetgenAlgo2D(the_gen)
the_mesh.add_hypotheses([hyp2d, alg2d], face)
the_gen.compute(the_mesh, face)

# Get the composite side
for e in wire.edges:
    fside = alg1d.get_face_side(the_mesh, e, face)
    if fside.num_nodes:
        break

gui = Viewer()
gui.view_top()
for vert in face.vertices:
    gui.add(vert)
gui.add(the_mesh)
gui.start()
예제 #20
0
    # A few notes about importing an OpenVSP STEP model:
    #   1) If the model comes from the modified version that includes metadata
    #      then the OpenVSP components can be identified by type and retrieved
    #      by name. Otherwise the import methods just tries to make solid
    #      bodies from the components and gives them a generic name.
    #   2) The import process attempts to sew the faces of the OpenVSP
    #      components together to form solids.
    #   3) If a surface is found to be planar, it is replaced with a plane
    #      before sewing. This helps eliminate some degenerated edges.
    #   4) Faces sharing the same domain are unified. If flat wing caps are
    #      used, this usually results in a single face for the entire cap
    #      rather than two faces split between the upper and lower surface.

    # View the model
    gui = Viewer()
    gui.add(*vsp_import.all_bodies)
    gui.start()
    gui.clear()

    # Retrieve relative components by name and set transparency for viewing
    wing_ = vsp_import['wing']
    fuse_ = vsp_import['fuse']
    wing_.set_transparency(0.5)
    fuse_.set_transparency(0.5)

    # OpenVSP 3.5 was modified by Laughlin Research to construct and export
    # metadata and reference geometry in the STEP file. For a wing component,
    # the reference geometry includes a surface that is lofted through the
    # chord lines at each wing station. This surface is used to define
    # structure in terms of percent chord and/or semispan. Currently, the
    # u-direction of the surface is in the chordwise and the v-direction is
예제 #21
0
"""
Test case from: https://github.com/tpaviot/pythonocc-core/issues/522
"""
from afem.exchange import StepRead
from afem.graphics import Viewer

# Read the file
reader = StepRead('../models/geometry_names.step')
shape = reader.shape

# Traverse all the faces and find the desired ones based on their name
faces = []
for f in shape.faces:
    name = reader.name_from_shape(f)
    if name == 'CYLINDER_TOP':
        f.set_color(1, 0, 0)
    elif name == 'FACE_UP':
        f.set_color(0, 0, 1)
    else:
        f.set_color(0.5, 0.5, 0.5)
    faces.append(f)

# Show the model with named faces as different colors
gui = Viewer()
gui.add(*faces)
gui.start()
예제 #22
0
Settings.log_to_console()

# Set units to inch.
Settings.set_units('in')

# Initialize a viewer
gui = Viewer()

# Import an OpenVSP model that includes OML reference geometry
fn = r'../models/simple_wing.stp'
vsp_import = ImportVSP(fn)
wing = vsp_import['WingGeom']
wing.set_color(1., 0., 0.)
wing.set_transparency(0.75)

gui.add(wing.sref, wing)
gui.start()
gui.clear()

# Define a group to put the structure in
wingbox = GroupAPI.create_group('wing box')
spars_assy = wingbox.create_subgroup('spar assy', active=True)
ribs_assy = wingbox.create_subgroup('rib assy', active=False)

# Define a front spar between parameters on the wing reference surface
fspar = SparByParameters('fspar', 0.15, 0.1, 0.15, 0.98, wing).part

gui.add(wing.sref, fspar, fspar.cref, fspar.cref.p1, fspar.cref.p2)
gui.start()

# Define a rear spar between parameters on the wing reference surface
예제 #23
0
DiscardByCref(internal_parts)

skin = SkinByBody('skin', wing).part
skin.fuse(*internal_parts)
skin.set_transparency(0.5)

# Vtail structure
GroupAPI.create_group('vtail group')

fspar = SparByParameters('vtail fspar', 0.15, 0.01, 0.15, 0.99, vtail).part
rspar = SparByParameters('vtail rspar', 0.70, 0.01, 0.70, 0.99, vtail).part
RibByPoints('vtail root rib', fspar.p1, rspar.p1, vtail)
RibByPoints('vtail tip rib', fspar.p2, rspar.p2, vtail)
RibsAlongCurveByDistance('vtail rib', rspar.cref, 18, fspar.shape, rspar.shape,
                         vtail, d1=18, d2=-30)

internal_parts = GroupAPI.get_parts()
FuseSurfacePartsByCref(internal_parts)
DiscardByCref(internal_parts)

skin = SkinByBody('vtail skin', vtail).part
skin.fuse(*internal_parts)
skin.discard_by_dmin(vtail.sref_shape, 1.)
skin.fix()

# View
skin.set_transparency(0.5)
v = Viewer()
v.add(GroupAPI.get_master())
v.start()
예제 #24
0
                         fspar.shape,
                         rspar.shape,
                         wing,
                         d1=30,
                         d2=-30)
internal_parts = wingbox.get_parts()
skin = SkinByBody('skin', wing).part
cref = wing.sref.u_iso(0.5)
skin.discard_by_dmin(cref, 1.0)

FuseSurfaceParts([skin], internal_parts)

group = GroupAPI.get_master()

gui = Viewer()
gui.add(group)
gui.start()

# Save
print('\nSaving group...')
GroupAPI.save_model('structure.xbf')
print('done.\n')

# Load into new group
print('Loading group...')
new_group = GroupAPI.create_group('new model')
GroupAPI.load_model('structure.xbf', new_group)
print('done.')
gui.clear()
gui.add(new_group)
gui.start()
예제 #25
0
doc = XdeDocument(False)

# Add label for the main shape
main = doc.add_shape(solid, 'The Box')

# Add labels for the sub-shapes
doc.add_subshape(main, f1, 'top face')
doc.add_subshape(main, f2, 'bottom face')
doc.add_subshape(main, f3, 'front face')
doc.add_subshape(main, f4, 'back face')
doc.add_subshape(main, f5, 'left face')
doc.add_subshape(main, f6, 'right face')

# Save document
doc.save_as('named_box')

# Close and then reopen the document
doc.close()
doc.open('named_box')

# Get the top-level shape
label = doc.get_shape_by_name('The Box')

# Print the child labels
for child in label.children_iter:
    print('Sub-label name:', child.name)

gui = Viewer()
gui.add(label.shape)
gui.start()
예제 #26
0
DiscardByCref(internal_parts)

skin = SkinByBody('skin', wing).part
skin.fuse(*internal_parts)
skin.set_transparency(0.5)

# Vtail structure
GroupAPI.create_group('vtail group')

fspar = SparByParameters('vtail fspar', 0.15, 0.01, 0.15, 0.99, vtail).part
rspar = SparByParameters('vtail rspar', 0.70, 0.01, 0.70, 0.99, vtail).part
RibByPoints('vtail root rib', fspar.cref.p1, rspar.cref.p1, vtail)
RibByPoints('vtail tip rib', fspar.cref.p2, rspar.cref.p2, vtail)
RibsAlongCurveByDistance('vtail rib', rspar.cref, 18, fspar.shape, rspar.shape,
                         vtail, d1=18, d2=-30)

internal_parts = GroupAPI.get_parts()
FuseSurfacePartsByCref(internal_parts)
DiscardByCref(internal_parts)

skin = SkinByBody('vtail skin', vtail).part
skin.fuse(*internal_parts)
skin.discard_by_dmin(vtail.sref_shape, 1.)
skin.fix()

# View
skin.set_transparency(0.5)
gui = Viewer()
gui.add(GroupAPI.get_master())
gui.start()
예제 #27
0
def show_shapes(*shapes):
    gui = Viewer()
    gui.add(*shapes)
    gui.start()
예제 #28
0
beam2 = Beam1DByCurve('beam 2', cref2).part

# Create a solid to cut the ribs with
face = FaceByPlanarWire(circle1).face
solid = SweepShape(cref1, face).shape
CutParts(ribs, solid)

# Wing skin
skin = SkinByBody('skin', wing).part
skin.set_color(0.5, 0.5, 0.5)
skin.set_transparency(0.5)

# Discard end caps of skin
skin.discard_by_dmin(cref1, 0.5)

# Join all the parts
parts = GroupAPI.get_parts()
SplitParts(parts)

gui = Viewer()
gui.add(*parts)
gui.start()

# Mesh
print('Computing the mesh...')
mesh = MeshVehicle(1.)
mesh.compute()
gui.clear()
gui.add(mesh)
gui.start()
예제 #29
0
from afem.config import Settings
from afem.exchange import ImportVSP
from afem.graphics import Viewer

Settings.log_to_console()

fn = '../models/777-200LR.stp'

vsp_import = ImportVSP(fn)

gui = Viewer()
gui.add(*vsp_import.all_bodies)
gui.start()
# Tail bulkhead
bbox = BBox()
bbox.add_shape(fuselage.shape)
xmax = bbox.xmax
pln = PlaneByAxes((xmax - 36, 0, 0), 'yz').plane
tail_bh = BulkheadByShape('tail bh', pln, fuselage).part

# Aft frames
plns = [rear_pressure_bh_pln, tail_bh.plane]
frames = []
for pln1, pln2 in misc_utils.pairwise(plns):
    builder = FramesBetweenPlanesByDistance('frame',
                                            pln1,
                                            pln2,
                                            24.,
                                            fuselage,
                                            8,
                                            36.,
                                            -36.,
                                            first_index=next_index)
    frames += builder.parts
    next_index = builder.next_index

# Join
# FuseSurfaceParts([fwd_bh, rear_bh, aft_bh, floor, fskin], frames)

master = GroupAPI.get_master()
v = Viewer()
v.add(master)
v.start()