def _bspline_restrict(solid, tol): """ Attempt to re-fit OpenVSP surfaces with more continuity. """ # Use dmax=1 because that was only way to get the tool actually refit the # surfaces other than another surface where the multiplicity equaled the # degree. Not sure why the tool operates this way. logger.info('\tApplying ShapeBSplineRestriction tool...') tool = ShapeBSplineRestriction(solid, dmax=1, tol3d=tol) if not tool.is_done: logger.info('Method unsuccessful. Using original solid.') return solid # Get new shape and solid new_solid = tool.modified_shape(solid) # Limit/fix tolerance FixShape.limit_tolerance(new_solid) tol = new_solid.tol_avg if not CheckShape(new_solid).is_valid: logger.info('Shape invalid. Using original solid.') return solid logger.info('\tMethod successful with surface error: {}'.format( tool.error_surface)) logger.info('\tNew shape tolerance: {}'.format(tol)) return new_solid
def __init__(self, name, shape, cref=None, sref=None, group=None): super(Part, self).__init__(name) # Shape holder type_ = (Shape,) if isinstance(self, CurvePart): type_ = (Edge, Wire, Compound) elif isinstance(self, SurfacePart): type_ = (Face, Shell, Compound) ShapeHolder.__init__(self, type_, shape) # Random color self.random_color() # Unique ID self._id = Part._indx Part._indx += 1 # Geometry data self._cref, self._sref = None, None if cref is not None: self.set_cref(cref) if sref is not None: self.set_sref(sref) # Other data self._subparts = {} # Add to group GroupAPI.add_parts(group, self) # Log msg = ' '.join(['Creating part:', name]) logger.info(msg)
def log_errors(self): """ Log the errors at the "info" level. :return: None. """ for msg in self._errors: logger.info(msg)
def __init__(self, parts, tol=None): self._is_done = False for part in parts: if not isinstance(part, SurfacePart): msg = 'Part is not a surface part.' raise TypeError(msg) # Test all combinations of parts for intersection of reference curve join_parts = [] main_parts = [] nparts = len(parts) for i in range(0, nparts - 1): main = parts[i] other_parts = [] for j in range(i + 1, nparts): other = parts[j] if not main.has_cref or not other.has_cref: continue if tol is None: tol1 = main.shape.tol_max tol2 = other.shape.tol_max _tol = max(tol1, tol2) else: _tol = tol e1 = EdgeByCurve(main.cref).edge e2 = EdgeByCurve(other.cref).edge bop = IntersectShapes(e1, e2, fuzzy_val=_tol) if not bop.vertices: continue # Store potential join msg = 'Found joint between {} and {}.'.format( main.name, other.name) logger.info(msg) other_parts.append(other) if other_parts: main_parts.append(main) join_parts.append(other_parts) # Join the parts for main, other_parts in zip(main_parts, join_parts): main.fuse(*other_parts) self._is_done = True
def _reloft_wing_surface(srf, tol): """ Attempt to reloft an OpenVSP wing surface which was not split to achieve higher continuity. """ logger.info('\tAttempting to reloft the surface...') # Gather isocurves at each section, tessellate, and approximate crvs = [] for u in srf.uknots: c0 = srf.u_iso(u) adp_crv = AdaptorCurve.to_adaptor(c0) tool = GCPnts_QuasiUniformDeflection(adp_crv.object, tol) if not tool.IsDone(): logger.info('\tTessellation failed. Using original surface.') return srf pnts = [c0.eval(tool.Parameter(i)) for i in range(1, tool.NbPoints() + 1)] c = NurbsCurveByApprox(pnts, tol=tol, continuity=Geometry.C1).curve crvs.append(c) return NurbsSurfaceByInterp(crvs, 1).surface
def __init__(self, name, shape, cref=None, sref=None, group=None): types = (Shape, ) if isinstance(self, CurvePart): types = (Edge, Wire, Compound) elif isinstance(self, SurfacePart): types = (Face, Shell, Compound) super(Part, self).__init__(name, shape, cref, sref, types) # Unique ID self._id = Part._indx Part._indx += 1 # Add to group GroupAPI.add_parts(group, self) # Log msg = ' '.join(['Creating part:', name]) logger.info(msg) # Groups for meshing self._node_group = None self._edge_group = None self._face_group = None
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
def import_step(self, fn): """ Import a STEP file generated by the OpenVSP version that has been modified to include metadata. :param str fn: The full path to the file. :return: None. """ # Store data as dictionaries. bodies = {} indx = 0 # Dictionaries to attach wing reference surfaces to wing bodies using # reference surface ID as the key. wing_bodies = {} ref_surfs = {} # Data structures for fuselage reference surfaces fuselage_bodies = {} href_surfs = {} vref_surfs = {} # Read STEP file step_reader = StepRead(fn) master_shape = step_reader.shape # Iterate over master shape to find compounds for geometric sets. These # sets contain the metadata and the surfaces that make up the # component. for compound in master_shape.shape_iter: # Get the metadata name = step_reader.name_from_shape(compound) # Unnamed body if not name: indx += 1 comp_name = '.'.join(['Body', str(indx)]) msg = ' '.join(['---Processing OpenVSP component:', comp_name]) logger.info(msg) solid, invalid = _build_solid(compound, self._divide) self._invalid += invalid if solid is not None: body = Body(solid, comp_name) bodies[comp_name] = body continue metadata = json.loads(name) # Process reference surfaces and continue key = 'm_SurfType' if key in metadata and metadata[key] == 99: # Get surface sref = ImportVSP.process_sref(compound) # Get Sref ID sref_id = metadata['ID'] ref_surfs[sref_id] = sref continue elif key in metadata and metadata[key] == 100: # Fuselage horizontal sref f = compound.faces[0] sref = f.surface sref.set_udomain(-1., 1.) sref.set_vdomain(0., 1.) sref.object.ExchangeUV() sref.object.UReverse() sref_id = metadata['ID'] href_surfs[sref_id] = sref continue elif key in metadata and metadata[key] == 101: f = compound.faces[0] sref = f.surface sref.set_udomain(-1., 1.) sref.set_vdomain(0., 1.) sref.object.ExchangeUV() sref.object.UReverse() sref_id = metadata['ID'] vref_surfs[sref_id] = sref continue comp_name = metadata['m_Name'] if comp_name in bodies: indx += 1 comp_name = '.'.join([comp_name, str(indx)]) # Process component. msg = ' '.join(['---Processing OpenVSP component:', comp_name]) logger.info(msg) # Wing if metadata['m_Type'] == 5 and metadata['m_SurfType'] != 99: wing, invalid = _process_wing(compound, self._divide, self._restrict, self._tol, self._reloft, comp_name) self._invalid += invalid if wing is not None: bodies[comp_name] = wing sref_id = metadata['Sref ID'] wing_bodies[sref_id] = wing # Fuselage elif metadata['m_Type'] in [4, 9]: fuse, invalid = _process_fuse(compound, self._divide, comp_name) self._invalid += invalid if fuse is not None: bodies[comp_name] = fuse sref_id = metadata['Sref ID'] fuselage_bodies[sref_id] = fuse # Unknown else: solid, invalid = _build_solid(compound, self._divide) self._invalid += invalid if solid: body = Body(solid, comp_name) bodies[comp_name] = body # Attach wing reference surfaces to the bodies. for sref_id in wing_bodies: if sref_id not in ref_surfs: continue wing = wing_bodies[sref_id] sref = ref_surfs[sref_id] wing.set_sref(sref) # Attach fuselage reference surfaces to the bodies. for sref_id in fuselage_bodies: if sref_id in href_surfs: fuselage = fuselage_bodies[sref_id] sref = href_surfs[sref_id] fuselage.metadata.set('hsref', sref) if sref_id in vref_surfs: fuselage = fuselage_bodies[sref_id] sref = vref_surfs[sref_id] fuselage.metadata.set('vsref', sref) # Update self._bodies.update(bodies)