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
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)
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)
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
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)
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.')
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)
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)
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