def face_normal(face): from OCC.Core.BRepTools import breptools_UVBounds umin, umax, vmin, vmax = breptools_UVBounds(face) surf = BRep_Tool().Surface(face) props = GeomLProp_SLProps(surf, (umin + umax) / 2., (vmin + vmax) / 2., 1, TOLERANCE) norm = props.Normal() if face.Orientation() == TopAbs_REVERSED: norm.Reverse() return norm
def face_normal(face: TopoDS_Face) -> gp_Dir: """ :param face: :return: """ umin, umax, vmin, vmax = breptools_UVBounds(face) surf = BRep_Tool().Surface(face) props = GeomLProp_SLProps(surf, (umin + umax) / 2., (vmin + vmax) / 2., 1, TOLERANCE) norm = props.Normal() if face.Orientation() == TopAbs_REVERSED: norm.Reverse() return norm
def evalcurvature(CADsurface, CADu, CADv, finetol, approxoutwardnormal=None): """ Evaluate the curvature of the specified CAD surface at the given CAD (u,v) coordinates, with the given tolerance. Returns two element vector of principal curvatures (in 1/mm), followed by corresponding axes (in 3D space). Returns all NaN if curvature is undefined at this point. WARNING: OpenCascade does not always select a normal in a consistent (i.e. u cross v pointing outside the object boundary) way. Set approxoutwardnormal to force the interpretation of the sense of the normal direction """ # Create props object with derivatives Curvature = GeomLProp_SLProps( CADsurface, CADu, CADv, 2, # Calculate 2 derivatives finetol) if not Curvature.IsCurvatureDefined() or not Curvature.IsTangentUDefined( ) or not Curvature.IsTangentVDefined(): # Return all NaN's. return (np.array((np.NaN, np.NaN)), np.ones( (3, 2), dtype='d') * np.NaN, np.ones(3) * np.NaN, np.ones(3) * np.NaN, np.ones(3) * np.NaN) Sigma_u = Curvature.D1U() # vector 1st derivative along U axis Sigma_v = Curvature.D1V() # along V axis Sigma_uu = Curvature.D2U() # vector 2nd derivative along U axis Sigma_vv = Curvature.D2V() # vector 2nd derivative along V axis Sigma_uv = Curvature.DUV() # Vector cross-derivative Normal = gp_Vec(Curvature.Normal()) NormalVec = np.array((Normal.X(), Normal.Y(), Normal.Z()), dtype='d') NormalVec = NormalVec / np.linalg.norm(NormalVec) if approxoutwardnormal is None: approxoutwardnormal = NormalVec pass TangentU_Dir = gp_Dir() Curvature.TangentU( TangentU_Dir) # Store tangent u direction in TangentU_Dir TangentU = gp_Vec(TangentU_Dir) TangentV_Dir = gp_Dir() Curvature.TangentV( TangentV_Dir) # Store tangent v direction in TangentV_Dir TangentV = gp_Vec(TangentV_Dir) ## if NormalVec is in the wrong direction we interchange U ## and V so as to correctly represent the desired right handedness ## of the frame with outward vector facing out. ## This also involves flipping NormalVec if np.inner(NormalVec, approxoutwardnormal) < 0.0: NormalVec = -NormalVec Normal = Normal.Reversed() temp1 = Sigma_u Sigma_u = Sigma_v Sigma_v = temp1 temp2 = Sigma_uu Sigma_uu = Sigma_vv Sigma_vv = temp2 temp3 = TangentU TangentU = TangentV TangentV = temp3 pass # First fundamental form: # See https://en.wikipedia.org/wiki/Differential_geometry_of_surfaces # https://en.wikipedia.org/wiki/First_fundamental_form # and http://www.cems.uvm.edu/~gswarrin/math295/Chap6.pdf E = Sigma_u.Dot(Sigma_u) F = Sigma_u.Dot(Sigma_v) G = Sigma_v.Dot(Sigma_v) # Second fundamental form: L = Sigma_uu.Dot(Normal) M = Sigma_uv.Dot(Normal) N = Sigma_vv.Dot(Normal) # Evaluate shape operator # https://en.wikipedia.org/wiki/Differential_geometry_of_surfaces#Shape_operator # where e-> L, f->M, g->N S = (1.0 / (E * G - F**2.0)) * np.array( ((L * G - M * F, M * G - N * F), (M * E - L * F, N * E - M * F)), dtype='d' ) # (see wikipedia + https://math.stackexchange.com/questions/536563/trouble-computing-the-shape-operator) http://mathworld.wolfram.com/WeingartenEquations.html # Shape operator in general will only be symmetric if (u,v) basis # is orthonormal, which it may not be #print(S) # Eigenvalue problem seems to be OK anyway so stuff below commented out # ## Apply Gram-Schmidt orthonormalization to find an ON basis Uvec = np.array((TangentU.X(), TangentU.Y(), TangentU.Z()), dtype='d') Uvec = Uvec / np.linalg.norm(Uvec) Vvec = np.array((TangentV.X(), TangentV.Y(), TangentV.Z()), dtype='d') Vvec = Vvec / np.linalg.norm(Vvec) # Check that Uvec cross Vvec = + Normal # Note that this verifies the handedness of the coordinate frame, # i.e. that U cross V gives an outward normal # and is thus important Wvec = np.cross(Uvec, Vvec) Wnormvec = Wvec / np.linalg.norm(Wvec) assert (np.abs( np.dot(Wnormvec, np.array((Normal.X(), Normal.Y(), Normal.Z()), dtype='d')) - 1.0) < 0.025) ## Inner product matrix, for converting between covariant ## and contravariant components #IPMat = np.array( ((np.inner(Uvec,Uvec),np.inner(Uvec,Vvec)), # (np.inner(Vvec,Uvec),np.inner(Vvec,Vvec))),dtype='d') # #IPMatInv=np.linalg.inv(IPMat) # ##def proj(direc,vec): # Projection operator ## return (np.inner(vec,direc)/np.inner(direc,direc))*direc # ## Vprimevec=Vvec-proj(Uvec,Vvec) # #Vprimevec=Vvec - (np.inner(Uvec,Vvec)/np.inner(Uvec,Uvec)) * Uvec # #Vprimenormfactor=1/np.linalg.norm(Vprimevec) #Vprimevec=Vprimevec*Vprimenormfactor # ## Covariant components of a 3D vector in (U,V) ## Upos_cov = Uvec dot 3dcoords ## Vpos_cov = Vvec dot 3dcoords # ## S matrix times contravariant components -> Contravariant components ## so S * IPMatInv * (Uvec;Vvec) * 3dcoords -> Contravariant components # ## Let (Uprimepos,Vprimepos) = (Uvec dot 3dcoords, Vprimevec dot 3dcoords) ## ON basis # ## [ Uprime ] = [ 1 0 ] [ Uvec ] ## [ Vprime ] [ -Vprimenormfactor*(Uvec dot Vvec)/(Uvec dot Uvec) Vprimenormfactor ] [ Vvec ] ## ## Therefore ## [ Uvec ] = [ 1 0 ]^-1 [ Uprime ] ## [ Vvec ] = [ -Vprimenormfactor*(Uvec dot Vvec)/(Uvec dot Uvec) Vprimenormfactor ] [ Vprime ] ## ## So a vector represented by some coefficient times Uprime plus ## some other coefficient * Vprime, multiplied ## on the left by the inverse matrix, gives rise to ## some new coefficient * Uvec plus some other new coefficient * Vvec ## These new coefficients are contravariant components. ## ## Let A = [ 1 0 ] ## = [ -(Uvec dot Vvec)/(Uvec dot Uvec) 1 ]*Vprimenormfactor # ## Now A*S*inv(A) provides a conversion of S to the Uprime,Vprime ON frame. ## ... Which should then be symmetric. #A = np.array(((1.0,0.0), # (-Vprimenormfactor*np.inner(Uvec,Vvec)/np.inner(Uvec,Uvec),Vprimenormfactor)),dtype='d') # #Stransformed=np.dot(A,np.dot(S,np.linalg.inv(A))) ## Confirm that Stransformed is indeed symmetric to reasonable ## precision... Use the trace (twice the mean curvature) ## as a reference metric #assert(abs(Stransformed[0,1]-Stransformed[1,0]) <= 1e-7*np.trace(Stransformed)) ## Force symmetry so our eigenvalue problem comes out real #Stransformed[0,1]=Stransformed[1,0] #(evals,evects) = np.linalg.eig(Stransformed) # Should be OK to just take eigenvalues of asymmetric matrix # per ShifrinDiffGeo.pdf page 51 (curvatures, evects) = np.linalg.eig(S) ## Now evects * diag(evals) * (evects.T) should be Stransformed ## Convert to 3D basis ## [ Uprime Vprime ] * evects * diag(evals) * evects.T * [ Uprime ; Vprime ] ## So store [ Uprime Vprime ] * evects as principal curvature tangent directions ## and evals as principal curvatures #curvaturetangents = np.dot(np.array((Uvec,Vprimevec),dtype='d').T,evects) curvaturetangents = np.dot(np.array((Uvec, Vvec)).T, evects) # per ShifrinDiffGeo.pdf page 52 confirm these tangents are orthogonal #if np.dot(curvaturetangents[:,0],curvaturetangents[:,1]) >= 1e-8: if np.dot(curvaturetangents[:, 0], curvaturetangents[:, 1]) >= 1e-2: raise GeometricInconsistency( "Curvature tangents are not orthogonal (inner product = %g)" % (np.dot(curvaturetangents[:, 0], curvaturetangents[:, 1]))) # We don't want the eigenframe to be mirrored relative to the (U,V) # frame, for consistency in interpreting positive vs. negative curvature. # ... so if the dot/inner product of (UxV) with (TANGENT0xTANGENT1) # is negative, that indicates mirroring # Negating one of the eigenvectors will un-mirror it. # The exception to this is if our normal and the desired approxoutwardnormal # are in opposite directions, then we DO want to mirror it, to # correct for OpenCascade's normal being in the wrong direction # Note: '^' here operating on booleans acts as a logical XOR operator #if not((np.inner(np.cross(Uvec,Vvec),np.cross(curvaturetangents[:,0],curvaturetangents[:,1])) < 0.0) ^ (np.inner(NormalVec,approxoutwardnormal) >= 0.0)): if np.inner(np.cross(Uvec, Vvec), np.cross(curvaturetangents[:, 0], curvaturetangents[:, 1])) < 0.0: curvaturetangents[:, 0] = -curvaturetangents[:, 0] pass ## Likewise, if NormalVec is in the wrong direction we flip it ## and UVec so as to correctly represent the desired right handedness ## of the frame with outward vector facing out. #if np.inner(NormalVec,approxoutwardnormal) < 0.0: # NormalVec=-NormalVec # Uvec=-Uvec # pass # transpose curvaturetangents so it is human readable in the serialization # axes: Which Vertex, tangent u or v, coord x,yz return (curvatures, curvaturetangents.transpose(1, 0), NormalVec, Uvec, Vvec)