Exemple #1
0
 def __init__(self, shape):
     tool = ShapeAnalysis_FreeBounds(shape.object)
     closed_wires = Compound(tool.GetClosedWires())
     open_wires = Compound(tool.GetOpenWires())
     self._closed_wires = closed_wires.wires
     self._open_wires = open_wires.wires
     self._edges = closed_wires.edges + open_wires.edges
Exemple #2
0
    def export_step(self, fn, label_solids=True, label_faces=False,
                    names=None):
        """
        Export the OpenVSP model as a STEP file using Extended Data Exchange.
        Each OpenVSP component will be a named product in the STEP file
        structure.

        :param str fn: The filename.
        :param bool label_solids: Option to label the solid bodies in the
            STEP entity. The name will be the same as the OpenVSP component.
        :param bool label_faces: Option to label the faces in each of the
            solids. Each face of the solid body will be labeled "Face 1",
            "Face 2", etc.
        :param names: List of Body names that will be included in export. If
            *None* then all are exported.
        :type names: collections.Sequence(str) or None

        :return: None.
        """
        # Initialize the document
        doc = XdeDocument()

        # Get bodies to include
        if names is None:
            bodies = self.all_bodies
        else:
            bodies = [self.get_body(name) for name in names]

        # Gather OpenVSP bodies and names and build single compound
        solids = []
        names = []
        for body in bodies:
            solids.append(body.shape)
            names.append(body.name)
        cmp = Compound.by_shapes(solids)

        # Add main shape and top-level assembly
        main = doc.add_shape(cmp, 'Vehicle')

        # Each body should be a product so names are transferred to other
        # CAD systems
        for name, solid in zip(names, solids):
            doc.add_subshape(main, solid, name)

        # Transfer the document and then modify the STEP item name directly
        # rather than using a label. This only applies when labeling
        # sub-shapes.
        doc.transfer_step()
        if label_solids or label_faces:
            for name, solid in zip(names, solids):
                if label_solids:
                    doc.set_shape_name(solid, name)
                if label_faces:
                    i = 1
                    for f in solid.faces:
                        name = ' '.join(['Face', str(i)])
                        doc.set_shape_name(f, name)
                        i += 1

        doc.write_step(fn)
Exemple #3
0
 def __init__(self, shapes):
     topods_compound = TopoDS_Compound()
     builder = BRep_Builder()
     builder.MakeCompound(topods_compound)
     for shape in shapes:
         shape = Shape.to_shape(shape)
         if isinstance(shape, Shape):
             builder.Add(topods_compound, shape.object)
     self._cp = Compound(topods_compound)
Exemple #4
0
    def __init__(self, wire, shape1, shape2=None):
        if wire.closed:
            raise TypeError('Closed wires are not supported.')

        shape1 = Shape.to_shape(shape1)
        shape2 = Shape.to_shape(shape2)

        # Split wire with shapes
        other_shape = Compound.by_shapes([shape1, shape2])
        split = SplitShapes(wire, other_shape)
        split_wire = split.shape.wires[0]

        # Get new vertices
        old_verts = ExploreWire(wire).ordered_vertices
        wire_exp = ExploreWire(split_wire)
        all_verts = wire_exp.ordered_vertices
        new_verts = [v for v in all_verts if v not in old_verts]

        # Find index of new vertices and use that to extract edges
        n = len(new_verts)
        if n == 2:
            i1 = all_verts.index(new_verts[0])
            i2 = all_verts.index(new_verts[1])
            ordered_edges = wire_exp.edges
            first_edges = ordered_edges[:i1]
            trimmed_edges = ordered_edges[i1:i2]
            last_edges = ordered_edges[i2:]
        elif n == 1:
            i1 = all_verts.index(new_verts[0])
            ordered_edges = wire_exp.edges
            first_edges = ordered_edges[:i1]
            trimmed_edges = []
            last_edges = ordered_edges[i1:]
        else:
            msg = 'Only one or two split locations are supported.'
            raise RuntimeError(msg)

        # Avoid circular imports
        from afem.topology.create import WireByEdges

        # Collect data and build trimmed wires
        self._first_wire = None
        self._trimmed_wire = None
        self._last_wire = None

        self._split_wire = split_wire
        if len(first_edges) > 0:
            self._first_wire = WireByEdges(*first_edges).wire
        if len(trimmed_edges) > 0:
            self._trimmed_wire = WireByEdges(*trimmed_edges).wire
        if len(last_edges) > 0:
            self._last_wire = WireByEdges(*last_edges).wire
        self._new_verts = new_verts
        self._verts = all_verts
Exemple #5
0
    def replace(self, old_shape, new_shapes):
        """
        Request to replace the old shape with a list of new shapes.

        :param afem.topology.entities.Shape old_shape: The old shape. This is
            usually a sub-shape of the original old shape.
        :param list(afem.topology.entities.Shape) new_shapes: The new shapes.

        :return: None.
        """

        compound = Compound.by_shapes(new_shapes)
        self._tool.Replace(old_shape.object, compound.object)
Exemple #6
0
    def rebuild_wing_solid(srfs, divide_closed=True, reloft=False, tol=0.01):
        """
        Rebuild a solid shape from the OpenVSP wing surface(s). If only one
        surface is provided then it is assumed that a single surface models the
        OML and it will be split and modified at the root, tip, and trailing
        edge. This single surface should have similar form and
        parametrization as the original OpenVSP surface. If more than once
        surface is provided then it is assumed that the surfaces were split
        during OpenVSP export and are simply sewn together to form the solid.

        :param srfs: The wing surface(s) used to rebuild the solid.
        :type srfs: collections.Sequence(afem.geometry.entities.Surface)
        :param bool divide_closed: Option to divide closed faces.
        :param bool reloft: For wings that are not split, this option will
            extract isocurves at each spanwise cross section, tessellate the
            curve using *tol*, and then approximate the section using a C1
            continuous curve. These curves are then used to generate a new
            wing surface. This method is experimental.
        :param float tol: Tolerance for approximation if
            *bspline_restrict* or *reloft* is *True*.

        :return: The new solid.
        :rtype: afem.topology.entities.Solid

        :raise ValueError: If no surfaces are provided.
        """
        faces = [Face.by_surface(s) for s in srfs]
        compound = Compound.by_shapes(faces)

        nsrfs = len(srfs)
        if nsrfs == 1:
            solid, _ = _process_unsplit_wing(compound, divide_closed, reloft,
                                             tol)
            return solid
        elif nsrfs > 1:
            solid, _ = _build_solid(compound, divide_closed)
            return solid
        else:
            raise ValueError('No surfaces provided.')
Exemple #7
0
    def __init__(self, old_shapes, tool):
        reshape = ShapeBuild_ReShape()

        self._new_shapes = TopTools_DataMapOfShapeShape()
        index_map = TopTools_IndexedMapOfShape()

        for old_shape in old_shapes:
            # Old shapes
            shapes = old_shape.faces
            if not shapes:
                shapes = old_shape.edges
            if not shapes:
                shapes = old_shape.vertices
            if not shapes:
                continue

            # Delete and replace
            for shape in shapes:
                # Deleted
                if tool.is_deleted(shape):
                    reshape.Remove(shape.object)
                    continue

                # Modified considering shapes already used
                mod_shapes = tool.modified(shape)
                replace_shapes = []
                for mod_shape in mod_shapes:
                    if index_map.Contains(mod_shape.object):
                        continue
                    replace_shapes.append(mod_shape)
                    index_map.Add(mod_shape.object)

                if replace_shapes:
                    new_shape = Compound.by_shapes(replace_shapes)
                    reshape.Replace(shape.object, new_shape.object)

            new_shape = Shape.wrap(reshape.Apply(old_shape.object))
            self._new_shapes.Bind(old_shape.object, new_shape.object)
Exemple #8
0
def _process_unsplit_wing(compound, divide_closed, reloft, tol):
    # Process a wing that was generated without "Split Surfs" option.

    faces = compound.faces
    if len(faces) != 1:
        return None, None
    face = faces[0]

    # Get the surface.
    master_surf = face.surface
    # master_surf = NurbsSurface(master_surf.object)
    uknots, vknots = master_surf.uknots, master_surf.vknots
    vsplit = master_surf.local_to_global_param('v', 0.5)

    # Segment off the end caps and the trailing edges.
    u1, u2 = uknots[1], uknots[-2]
    v1, v2 = vknots[1], vknots[-2]
    s1 = master_surf.copy()
    s1.segment(u1, u2, v1, v2)

    # Reloft the surface by tessellating a curve at each spanwise knot. This
    # enforces C1 continuity but assumes linear spanwise wing which may not
    # support blending wing sections in newer versions of OpenVSP. Also, since
    # the tessellated curves may not match up to the wing end caps making
    # sewing unreliable, flat end caps are assumed.
    if reloft:
        s1 = _reloft_wing_surface(s1, tol)

        # Generate new flat end caps using isocurves at the root and tip of
        # this new surface
        c0 = s1.v_iso(s1.v1)
        c1 = s1.v_iso(s1.v2)
        e0 = Edge.by_curve(c0)
        e1 = Edge.by_curve(c1)
        w0 = Wire.by_edge(e0)
        w1 = Wire.by_edge(e1)
        f0 = Face.by_wire(w0)
        f1 = Face.by_wire(w1)

        # Make faces of surfaces
        f = Face.by_surface(s1)
        new_faces = [f, f0, f1]
    else:
        # Reparamterize knots in spanwise direction to be chord length instead
        # of uniform. Use isocurve at quarter-chord to determine knot values.
        # This only works as long as surfaces are linear.
        c0 = s1.u_iso(s1.u1)
        c0.segment(vsplit, c0.u2)
        qc_u = PointFromParameter(c0, vsplit, 0.25 * c0.length).parameter
        c = s1.v_iso(qc_u)
        pnts = [c.eval(u) for u in c.knots]
        new_uknots = geom_utils.chord_parameters(pnts, 0., 1.)
        s1.set_uknots(new_uknots)

        # Segment off end caps
        u1, u2 = uknots[0], uknots[1]
        v1, v2 = vknots[1], vsplit
        s2 = master_surf.copy()
        s2.segment(u1, u2, v1, v2)

        u1, u2 = uknots[0], uknots[1]
        v1, v2 = vsplit, vknots[-2]
        s3 = master_surf.copy()
        s3.segment(u1, u2, v1, v2)

        u1, u2 = uknots[-2], uknots[-1]
        v1, v2 = vknots[1], vsplit
        s4 = master_surf.copy()
        s4.segment(u1, u2, v1, v2)

        u1, u2 = uknots[-2], uknots[-1]
        v1, v2 = vsplit, vknots[-2]
        s5 = master_surf.copy()
        s5.segment(u1, u2, v1, v2)

        # Make faces of surfaces
        new_faces = []
        for s in [s1, s2, s3, s4, s5]:
            f = Face.by_surface(s)
            new_faces.append(f)

    # Segment off TE.
    u1, u2 = uknots[0], uknots[-1]
    v1, v2 = vknots[0], vknots[1]
    s6 = master_surf.copy()
    s6.segment(u1, u2, v1, v2)

    u1, u2 = uknots[0], uknots[-1]
    v1, v2 = vknots[-2], vknots[-1]
    s7 = master_surf.copy()
    s7.segment(u1, u2, v1, v2)

    # Split the TE surface at each u-knot.
    usplits = occ_utils.to_tcolstd_hseq_real(uknots[1:-1])

    split = ShapeUpgrade_SplitSurface()
    split.Init(s6.object)
    split.SetUSplitValues(usplits)
    split.Perform()
    comp_surf1 = split.ResSurfaces()

    split = ShapeUpgrade_SplitSurface()
    split.Init(s7.object)
    split.SetUSplitValues(usplits)
    split.Perform()
    comp_surf2 = split.ResSurfaces()

    # For each patch in the composite surfaces create a face.
    for i in range(1, comp_surf1.NbUPatches() + 1):
        for j in range(1, comp_surf1.NbVPatches() + 1):
            hpatch = comp_surf1.Patch(i, j)
            f = BRepBuilderAPI_MakeFace(hpatch, 0.).Face()
            new_faces.append(f)

    for i in range(1, comp_surf2.NbUPatches() + 1):
        for j in range(1, comp_surf2.NbVPatches() + 1):
            hpatch = comp_surf2.Patch(i, j)
            f = BRepBuilderAPI_MakeFace(hpatch, 0.).Face()
            new_faces.append(f)

    # Put all faces into a compound a generate solid.
    new_compound = Compound.by_shapes(new_faces)

    return _build_solid(new_compound, divide_closed)
Exemple #9
0
def _build_solid(compound, divide_closed):
    """
    Try to build a solid from the OpenVSP compound of faces.

    :param afem.topology.entities.Compound compound: The compound.
    :param bool divide_closed: Option to divide closed faces.

    :return: The solid.
    :rtype: afem.topology.entities.Solid
    """
    # Get all the faces in the compound. The surfaces must be split. Discard
    # any with zero area.
    faces = []
    for face in compound.faces:
        area = SurfaceProps(face).area
        if area > 1.0e-7:
            faces.append(face)

    # Replace any planar B-Spline surfaces with planes.
    non_planar_faces = []
    planar_faces = []
    for f in faces:
        srf = f.surface
        try:
            pln = srf.as_plane()
            if pln:
                w = f.outer_wire
                # Fix the wire because they are usually degenerate edges in
                # the planar end caps.
                builder = BRepBuilderAPI_MakeWire()
                for e in w.edges:
                    if LinearProps(e).length > 1.0e-7:
                        builder.Add(e.object)
                w = builder.Wire()
                fix = ShapeFix_Wire()
                fix.Load(w)
                fix.SetSurface(pln.object)
                fix.FixReorder()
                fix.FixConnected()
                fix.FixEdgeCurves()
                fix.FixDegenerated()
                w = Wire(fix.WireAPIMake())
                fnew = Face.by_wire(w)
                planar_faces.append(fnew)
            else:
                non_planar_faces.append(f)
        except RuntimeError:
            logger.info('Failed to check for planar face...')
            non_planar_faces.append(f)

    # Make a compound of the faces
    shape = Compound.by_shapes(non_planar_faces + planar_faces)

    # Split closed faces
    if divide_closed:
        shape = DivideClosedShape(shape).shape

    # Sew shape
    sewn_shape = SewShape(shape).sewed_shape
    if isinstance(sewn_shape, Face):
        sewn_shape = sewn_shape.to_shell()

    # Attempt to unify planar domains
    shell = UnifyShape(sewn_shape).shape

    # Make solid
    if not isinstance(shell, Shell):
        logger.info('\tA valid shell was not able to be generated.')
        check = CheckShape(shell)
        if not check.is_valid:
            logger.info('\tShape errors:')
            check.log_errors()
        return shell, check.invalid_shapes

    solid = Solid.by_shell(shell)

    # Limit tolerance
    FixShape.limit_tolerance(solid)

    # Check the solid and attempt to fix
    invalid = []
    check = CheckShape(solid)
    if not check.is_valid:
        logger.info('\tFixing the solid...')
        solid = FixShape(solid).shape
        check = CheckShape(solid)
        if not check.is_valid:
            logger.info('\t...solid could not be fixed.')
            logger.info('\tShape errors:')
            check.log_errors()
            failed = check.invalid_shapes
            invalid += failed
    else:
        tol = solid.tol_avg
        logger.info(
            '\tSuccessfully generated solid with tolerance={}'.format(tol))

    return solid, invalid