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 second_derivative(h_surf, u=0, v=0): p1 = gp_Pnt() pu, pv = gp_Vec(), gp_Vec() puu, pvv = gp_Vec(), gp_Vec() puv = gp_Vec() prop = GeomLProp_SLProps(h_surf, u, v, 1, 1) GeomLProp_SurfaceTool.D2(h_surf, u, v, p1, pu, pv, puu, pvv, puv) e0 = pu.Crossed(pv) pu.Normalize() pv.Normalize() e0.Normalize() puu.Normalize() pvv.Normalize() puv.Normalize() print(p1) print("pu", pu) print("pv", pv) print("e0", e0) print("puu", puu) print("pvv", pvv) print("puv", puv) first_form = np.array([[pu.Dot(pu), pu.Dot(pv)], [pv.Dot(pu), pv.Dot(pv)]]) secnd_form = np.array([[e0.Dot(puu), e0.Dot(puv)], [e0.Dot(puv), e0.Dot(pvv)]]) print(first_form) print(secnd_form) print(prop.GaussianCurvature()) print(prop.MeanCurvature()) d1, d2 = gp_Dir(), gp_Dir() prop.CurvatureDirections(d1, d2) a1 = gp_Ax3() v1 = dir_to_vec(d1) v2 = dir_to_vec(d2) if pu.IsParallel(v1, 1 / 1000): c1 = prop.MaxCurvature() c2 = prop.MinCurvature() print(v1.Dot(pu), v1.Dot(pv)) print(v2.Dot(pu), v2.Dot(pv)) else: c1 = prop.MinCurvature() c2 = prop.MaxCurvature() print(v1.Dot(pu), v1.Dot(pv)) print(v2.Dot(pu), v2.Dot(pv)) print(c1, 1 / c1) print(c2, 1 / c2) px = np.linspace(-1, 1, 100) * 100 p1_y = px**2 / c1 p2_y = px**2 / c1 curv1 = curv_spl(px, p1_y) curv2 = curv_spl(px, p2_y)
def curvature(self, u,v): '''returns the curvature at the u parameter the curvature object can be returned too using curvatureType == curvatureType curvatureTypes are: gaussian minimum maximum mean curvatureType ''' if not self._curvature_initiated: from OCC.GeomLProp import GeomLProp_SLProps self._curvature = GeomLProp_SLProps(self.instance.h_srf, u, v, 1, 1e-6) else: self._curvature.SetParameters(u,v) self._curvature_initiated = True return self._curvature
def radius_at_uv(face, u, v): ''' returns the mean radius at a u,v coordinate @param face: surface input @param u,v: u,v coordinate ''' h_srf = BRep_Tool().Surface(face) uv_domain = GeomLProp_SurfaceTool().Bounds(h_srf) curvature = GeomLProp_SLProps(h_srf, u, v, 1, 1e-6) try: _crv_min = 1. / curvature.MinCurvature() except ZeroDivisionError: _crv_min = 0. try: _crv_max = 1. / curvature.MaxCurvature() except ZeroDivisionError: _crv_max = 0. return abs((_crv_min + _crv_max) / 2.)
def curvature(self, u, v): '''returns the curvature at the u parameter the curvature object can be returned too using curvatureType == curvatureType curvatureTypes are: gaussian minimum maximum mean curvatureType ''' if not self._curvature_initiated: self._curvature = GeomLProp_SLProps(self.instance.surface_handle, u, v, 2, 1e-7) _domain = self.instance.domain() if u in _domain or v in _domain: print('<<<CORRECTING DOMAIN...>>>') div = 1000 delta_u, delta_v = (_domain[0] - _domain[1]) / div, ( _domain[2] - _domain[3]) / div if u in _domain: low, hi = u - _domain[0], u - _domain[1] if low < hi: u = u - delta_u else: u = u + delta_u if v in _domain: low, hi = v - _domain[2], v - _domain[3] if low < hi: v = v - delta_v else: v = v + delta_v self._curvature.SetParameters(u, v) self._curvature_initiated = True return self._curvature
def testRemovedByRefFeature(self): ''' test that arguments returned by ref transormation is ok ''' from OCC.BRepPrimAPI import BRepPrimAPI_MakeSphere from OCC.BRep import BRep_Tool_Surface from OCC.GeomLProp import GeomLProp_SLProps from OCC.gp import gp_Pnt sphere_shape = BRepPrimAPI_MakeSphere(40.).Shape() # build a surface from this sphere from OCC.Utils.Topology import Topo t = Topo(sphere_shape) for f in t.faces(): face = f surf = BRep_Tool_Surface(face) lprop = GeomLProp_SLProps(0, 1e-12) lprop.SetSurface(surf) # evaluate_uv_coordinates coords = [] p = 0.0 # first point u, v = [0, 0] lprop.SetParameters(u, v) pnt = lprop.Value() print 'First point coords : ', pnt.Coord() print surf.GetObject().Value(u, v).Coord() # This one is [40.0,0.,0.] self.assertEqual(str(pnt.Coord()), '(40.0, 0.0, 0.0)') coords.append(pnt) #second point u, v = [0.5, 0.5] lprop.SetParameters(u, v) pnt2 = lprop.Value() # check then that the value has not changed (it does if returned by ref) self.assertEqual(str(pnt.Coord()), '(40.0, 0.0, 0.0)')
class DiffGeomSurface(object): def __init__(self, instance): self.instance = instance self._curvature = None self._curvature_initiated = False def curvature(self, u, v): '''returns the curvature at the u parameter the curvature object can be returned too using curvatureType == curvatureType curvatureTypes are: gaussian minimum maximum mean curvatureType ''' if not self._curvature_initiated: self._curvature = GeomLProp_SLProps(self.instance.surface_handle, u, v, 2, 1e-7) _domain = self.instance.domain() if u in _domain or v in _domain: print('<<<CORRECTING DOMAIN...>>>') div = 1000 delta_u, delta_v = (_domain[0] - _domain[1]) / div, ( _domain[2] - _domain[3]) / div if u in _domain: low, hi = u - _domain[0], u - _domain[1] if low < hi: u = u - delta_u else: u = u + delta_u if v in _domain: low, hi = v - _domain[2], v - _domain[3] if low < hi: v = v - delta_v else: v = v + delta_v self._curvature.SetParameters(u, v) self._curvature_initiated = True return self._curvature def gaussian_curvature(self, u, v): return self.curvature(u, v).GaussianCurvature() def min_curvature(self, u, v): return self.curvature(u, v).MinCurvature() def mean_curvature(self, u, v): return self.curvature(u, v).MeanCurvature() def max_curvature(self, u, v): return self.curvature(u, v).MaxCurvature() def normal(self, u, v): # TODO: should make this return a gp_Vec curv = self.curvature(u, v) if curv.IsNormalDefined(): return curv.Normal() else: raise ValueError('normal is not defined at u,v: {0}, {1}'.format( u, v)) def tangent(self, u, v): dU, dV = gp_Dir(), gp_Dir() curv = self.curvature(u, v) if curv.IsTangentUDefined() and curv.IsTangentVDefined(): curv.TangentU(dU), curv.TangentV(dV) return dU, dV else: return None, None def radius(self, u, v): '''returns the radius at u ''' # TODO: SHOULD WE RETURN A SIGNED RADIUS? ( get rid of abs() )? try: _crv_min = 1. / self.min_curvature(u, v) except ZeroDivisionError: _crv_min = 0. try: _crv_max = 1. / self.max_curvature(u, v) except ZeroDivisionError: _crv_max = 0. return abs((_crv_min + _crv_max) / 2.)
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)
class DiffGeomSurface(object): def __init__(self, instance): self.instance = instance self._curvature = None self._curvature_initiated = False def curvature(self, u,v): '''returns the curvature at the u parameter the curvature object can be returned too using curvatureType == curvatureType curvatureTypes are: gaussian minimum maximum mean curvatureType ''' if not self._curvature_initiated: from OCC.GeomLProp import GeomLProp_SLProps self._curvature = GeomLProp_SLProps(self.instance.h_srf, u, v, 1, 1e-6) else: self._curvature.SetParameters(u,v) self._curvature_initiated = True return self._curvature def gaussian_curvature(self,u,v): return self.curvature(u,v).GaussianCurvature() def min_curvature(self,u,v): return self.curvature(u,v).MinCurvature() def mean_curvature(self,u,v): return self.curvature(u,v).MeanCurvature() def max_curvature(self,u,v): return self.curvature(u,v).MaxCurvature() def normal(self,u,v): # TODO: should make this return a gp_Vec curv = self.curvature(u,v) if curv.IsNormalDefined(): return curv.Normal() else: raise ValueError('normal is not defined at u,v: {0}, {1}'.format(u,v)) def tangent(self,u,v, recurse=False): dU, dV = gp_Dir(), gp_Dir() curv = self.curvature(u,v) if curv.IsCurvatureDefined(): curv.TangentU(dU), curv.TangentV(dV) return dU, dV else: # most likely the curvature is just out of bounds... # move it a little close to the center of the surface... tol = 1e-2 print 'trying to fix shit...' domain = self.instance.domain() if u in domain: uorv = 'u' elif v in domain: uorv = 'v' else: raise ValueError('no curvature defined while sample does not lie on border...') if uorv is not None: indx = domain.index(u) if uorv == 'u' else domain.index(v) if indx in (1,3): # v v = v + tol if indx == 1 else v - tol else: u = u + tol if indx == 0 else u - tol print 'hopefully fixed it?' if not recurse: self.tangent(u,v, True) else: return None def radius(self, u, v ): '''returns the radius at u ''' # TODO: SHOULD WE RETURN A SIGNED RADIUS? ( get rid of abs() )? try: _crv_min = 1./self.min_curvature(u,v) except ZeroDivisionError: _crv_min = 0. try: _crv_max = 1./self.max_curvature(u,v) except ZeroDivisionError: _crv_max = 0. return abs((_crv_min+_crv_max)/2.) def frenet_frame(self, u, v): '''returns the frenet frame ( the 2 tangency directions + normal ) syntax sugar ''' pass def derivative_u(self, u, n): '''return n derivatives of u ''' pass def derivative_v(self, v, n): '''return n derivatives of v ''' pass def torsion(self, u, v): '''returns the torsion at the parameter http://en.wikipedia.org/wiki/Frenet-Serret_formulas ''' pass def continuity(self, face): '''returns continuity between self and another surface ''' # add dictionary mapping which G / C continuity it is... return self.surface.Continuity() def inflection_parameters(self): """ :return: a list of tuples (u,v) of parameters where there are inflection points on the edge returns None if no inflection parameters are found """ pass
class DiffGeomSurface(object): def __init__(self, instance): self.instance = instance self._curvature = None self._curvature_initiated = False def curvature(self, u, v): '''returns the curvature at the u parameter the curvature object can be returned too using curvatureType == curvatureType curvatureTypes are: gaussian minimum maximum mean curvatureType ''' if not self._curvature_initiated: self._curvature = GeomLProp_SLProps(self.instance.surface_handle, u, v, 1, 1e-6) _domain = self.instance.domain() if u in _domain or v in _domain: print '<<<CORRECTING DOMAIN...>>>' div = 1000 delta_u, delta_v = (_domain[0] - _domain[1]) / div, ( _domain[2] - _domain[3]) / div if u in _domain: low, hi = u - _domain[0], u - _domain[1] if low < hi: u = u - delta_u else: u = u + delta_u if v in _domain: low, hi = v - _domain[2], v - _domain[3] if low < hi: v = v - delta_v else: v = v + delta_v self._curvature.SetParameters(u, v) self._curvature_initiated = True return self._curvature def gaussian_curvature(self, u, v): return self.curvature(u, v).GaussianCurvature() def min_curvature(self, u, v): return self.curvature(u, v).MinCurvature() def mean_curvature(self, u, v): return self.curvature(u, v).MeanCurvature() def max_curvature(self, u, v): return self.curvature(u, v).MaxCurvature() def normal(self, u, v): # TODO: should make this return a gp_Vec curv = self.curvature(u, v) if curv.IsNormalDefined(): return curv.Normal() else: raise ValueError('normal is not defined at u,v: {0}, {1}'.format( u, v)) def tangent(self, u, v): dU, dV = gp_Dir(), gp_Dir() curv = self.curvature(u, v) if curv.IsTangentUDefined() and curv.IsTangentVDefined(): curv.TangentU(dU), curv.TangentV(dV) return dU, dV else: return None, None def radius(self, u, v): '''returns the radius at u ''' # TODO: SHOULD WE RETURN A SIGNED RADIUS? ( get rid of abs() )? try: _crv_min = 1. / self.min_curvature(u, v) except ZeroDivisionError: _crv_min = 0. try: _crv_max = 1. / self.max_curvature(u, v) except ZeroDivisionError: _crv_max = 0. return abs((_crv_min + _crv_max) / 2.) def frenet_frame(self, u, v): '''returns the frenet frame ( the 2 tangency directions + normal ) syntax sugar ''' pass def derivative_u(self, u, n): '''return n derivatives of u ''' pass def derivative_v(self, v, n): '''return n derivatives of v ''' pass def torsion(self, u, v): '''returns the torsion at the parameter http://en.wikipedia.org/wiki/Frenet-Serret_formulas ''' pass def continuity(self, face): '''returns continuity between self and another surface ''' # add dictionary mapping which G / C continuity it is... return self.surface.Continuity() def inflection_parameters(self): """ :return: a list of tuples (u,v) of parameters where there are inflection points on the edge returns None if no inflection parameters are found """ pass