Salome HOME
022491: EDF 2249 SMESH: Integration of a small python library for quadrangle meshing
[modules/smesh.git] / src / Tools / MacMesh / MacMesh / CutnGroup.py
1 # This module allows cutting and grouping geometries by defining plane sections, with level of cutting as well as customizable Prefixes.
2 import geompy, math
3
4 def Go(GeoObj, CutPlnLst, OutLvlLst, PrefixLst, Publish):
5
6         """
7         This function cuts any geometry (with infinite trim !) into several subgeometries that are cleanly saved inside the navigation tree. (Check GoTrim for the same functionality with custom trim size)
8           - GeoObj is the geometrical object to be cut and grouped
9           - CutPlnLst is a list of plane definitions. Each plane is a 6-tuple (contains 6 values). The first three represent the coordinates of the origin point and the second three represent the coordinates of the normal vector to the plane
10                 Example 1: [(0,0,0,1,0,0)]: cut along a plane passing through the origin and normal to the x-axis
11                 Example 2: [(0,0,0,1,0,0),(50,0,0,0,1,0)]: in addition to the first plane cut, cut through a plane passing by (50,0,0) and normal to the y axis.
12                 Note that the plane size us determined automatically from the size of the geometry in question (using a very big trim size = 100 x length of geometry!)
13           - OutLvlLst is a list containing integers that represent the inner sectioning level with respect to the original geometry type
14                 A value of 1 means that the section will provide elements of one level lower than the original type. For exemple a solid sectioned at level 1 will produce faces. A Face sectioned at level 1 will produce edges.
15                 A value of 2 means that a deeper sectioning will be applied. A solid sectioned with level 2 will give faces and edges. A face will give edges and vertices. An edge will give only vertices
16                 The number of elements in this list should be (this is verified in the code) equal to the number of elements in the plane cut list. This is logical.
17                 Example 1: [1]
18                 Example 2: [1, 2], This means that the cut over the second plane will produce two types of elements unlike the first cut which will only output the first level objects.
19           - PrefixLst is a list of strings that contains the naming Prefixes that are used by the script to generate the subshape names. This is very useful for relating the results to the sectioning requested.
20                 Example 1: ['Entry']
21                 Example 2: ['Entry','Exit']     The resulting groups from the sectioning with plane no.1 will then be saved as "Entry_FACE" and/or "Entry_EDGE" according to the original geometry object type and the cutting level
22         
23         Imagine that we have a solid called ExampleSolid, an example command will be:
24         CutnGroup.Go(ExampleSolid,[(0,0,0,1,0,0),(50,0,0,0,1,0)],[1, 2],['Entry','Exit'])
25         """
26
27         NumCuts = CheckInput(CutPlnLst, OutLvlLst, PrefixLst, 1)
28         OrigType = FindStandType(GeoObj,0)
29         InvDictionary = dict((v,k) for k, v in geompy.ShapeType.iteritems())    # Give geometry type name as a function of standard type numbering, ex: 4=FACE, 6=EDGE, 7=VERTEX
30         TrimSize = geompy.BasicProperties(GeoObj)[0]*100
31         CutPlane = [] ; Sections = [] ; Parts = []
32         
33         if NumCuts:
34                 for i in range(0, NumCuts):             # Loop over the cutting planes to create them one by one
35                         CutPlane.append(CreatePlane(CutPlnLst[i],TrimSize))
36                 OutFather = geompy.MakePartition([GeoObj],CutPlane, [], [],FindStandType(GeoObj,1), 0, [], 0)   #Creating the partition object
37                 if Publish: geompy.addToStudy(OutFather,'SectionedObject')
38                 for i in range(0, NumCuts):
39                         for j in range(OrigType+1+2, OrigType+1+2*(OutLvlLst[i]+1),2):
40                                 if j == 8 : j = 7;      # Exception for the vertex case (=7)
41                                 PossSubShapesID = geompy.SubShapeAllIDs(OutFather,j)    # List of subshape IDs than correspond to the required cutting level (section type : face/wire/vertex)
42                                 PossSubShapes = geompy.ExtractShapes(OutFather,j)               # and the corresponding objects
43                                 Accepted = []
44                                 for k in range(0,len(PossSubShapesID)):         # Loop over all the subshapes checking if they belong to the cutting plane! if yes add them to current list
45                                         if  IsOnPlane(PossSubShapes[k], CutPlnLst[i], 1e-7):                    
46                                                 Accepted.append(PossSubShapesID[k])
47                                 if Accepted :                                   # If some element is found, save it as a group with the prescribed Prefix
48                                         dummyObj = geompy.CreateGroup(OutFather, j)
49                                         geompy.UnionIDs(dummyObj, Accepted)
50                                         Sections.append(dummyObj)
51                                         if Publish:geompy.addToStudyInFather(OutFather, dummyObj, PrefixLst[i]+"_"+InvDictionary[j][0:2])
52                                 else :
53                                         print "Warning: For the section no.", i, ", No intersection of type " + InvDictionary[j] + " was found. Hence, no corresponding groups were created"
54                 
55                 SubShapesID = geompy.SubShapeAllIDs(OutFather,OrigType+1)               # Saving also the groups corresponding to the sectioned item of the same type: ex. A solid into n sub-solids due to the sections
56                 for i in range(0,len(SubShapesID)):
57                         dummyObj = geompy.CreateGroup(OutFather, OrigType+1)
58                         geompy.UnionIDs(dummyObj, [SubShapesID[i]])
59                         Parts.append(dummyObj)
60                         if Publish: geompy.addToStudyInFather(OutFather, dummyObj, "SB"+"_"+InvDictionary[OrigType+1][0:3]+"_"+str(i+1))
61
62                 return OutFather, Sections, Parts
63         else:
64                 print("Fatal error, the routine cannot continue any further, check your input variables")
65                 return -1
66
67 def GoTrim(GeoObj, CutPlnLst, OutLvlLst, PrefixLst, Publish):
68
69         """
70         This function cuts any geometry into several subgeometries that are cleanly saved inside the navigation tree with a fully customizable trim size.
71           - GeoObj is the geometrical object to be cut and grouped
72           - CutPlnLst is a list of plane definitions. Each plane is a 7-tuple (contains 7 values). The first three represent the coordinates of the origin point and the second three represent the coordinates of the normal vector to the plane, the last value corresponds to the trim size of the planes
73                 Example 1: [(0,0,0,1,0,0,5)]: cut along a plane passing through the origin and normal to the x-axis with a trim size of 5
74                 Example 2: [(0,0,0,1,0,0,5),(50,0,0,0,1,0,10)]: in addition to the first plane cut, cut through a plane passing by (50,0,0) and normal to the y axis with a trim size of 10
75           - OutLvlLst is a list containing integers that represent the inner sectioning level with respect to the original geometry type
76                 A value of 1 means that the section will provide elements of one level lower than the original type. For exemple a solid sectioned at level 1 will produce faces. A Face sectioned at level 1 will produce edges.
77                 A value of 2 means that a deeper sectioning will be applied. A solid sectioned with level 2 will give faces and edges. A face will give edges and vertices. An edge will give only vertices
78                 The number of elements in this list should be (this is verified in the code) equal to the number of elements in the plane cut list. This is logical.
79                 Example 1: [1]
80                 Example 2: [1, 2], This means that the cut over the second plane will produce two types of elements unlike the first cut which will only output the first level objects.
81           - PrefixLst is a list of strings that contains the naming Prefixes that are used by the script to generate the subshape names. This is very useful for relating the results to the sectioning requested.
82                 Example 1: ['Entry']
83                 Example 2: ['Entry','Exit']     The resulting groups from the sectioning with plane no.1 will then be saved as "Entry_FACE" and/or "Entry_EDGE" according to the original geometry object type and the cutting level
84         
85         Imagine that we have a solid called ExampleSolid, an example command will be:
86         CutnGroup.Go(ExampleSolid,[(0,0,0,1,0,0,5),(50,0,0,0,1,0,10)],[1, 2],['Entry','Exit'])
87         """
88
89         NumCuts = CheckInput(CutPlnLst, OutLvlLst, PrefixLst, 0)
90         OrigType = FindStandType(GeoObj,0)
91         InvDictionary = dict((v,k) for k, v in geompy.ShapeType.iteritems())    # Give geometry type name as a function of standard type numbering, ex: 4=FACE, 6=EDGE, 7=VERTEX
92         CutPlane = [] ; Sections = [] ; Parts = []
93         if NumCuts:
94                 for i in range(0, NumCuts):             # Loop over the cutting planes to create them one by one
95                         CutPlane.append(CreatePlane(CutPlnLst[i][0:6],CutPlnLst[i][6]))
96                 OutFather = geompy.MakePartition([GeoObj],CutPlane, [], [],FindStandType(GeoObj,1), 0, [], 0)   #Creating the partition object
97                 if Publish: geompy.addToStudy(OutFather,'SectionedObject')
98                 for i in range(0, NumCuts):
99                         for j in range(OrigType+1+2, OrigType+1+2*(OutLvlLst[i]+1),2):
100                                 if j == 8 : j = 7;      # Exception for the vertex case (=7)
101                                 PossSubShapesID = geompy.SubShapeAllIDs(OutFather,j)    # List of subshape IDs than correspond to the required cutting level (section type : face/wire/vertex)
102                                 PossSubShapes = geompy.ExtractShapes(OutFather,j)               # and the corresponding objects
103                                 Accepted = []
104                                 for k in range(0,len(PossSubShapesID)):         # Loop over all the subshapes checking if they belong to the cutting plane WITH THE TRIM SIZE CONDITION! if yes add them to current list
105                                         if  IsOnPlane(PossSubShapes[k], CutPlnLst[i], 1e-7) and Distance2Pt(geompy.PointCoordinates(geompy.MakeCDG(PossSubShapes[k])),CutPlnLst[i][0:3])<=CutPlnLst[i][-1]:                     
106                                                 Accepted.append(PossSubShapesID[k])
107                                 if Accepted :                                   # If some element is found, save it as a group with the prescribed Prefix
108                                         dummyObj = geompy.CreateGroup(OutFather, j)
109                                         geompy.UnionIDs(dummyObj, Accepted)
110                                         Sections.append(dummyObj)
111                                         if Publish: geompy.addToStudyInFather(OutFather, dummyObj, PrefixLst[i]+"_"+InvDictionary[j][0:2])
112                                 else :
113                                         print "Warning: For the section no.", i, ", No intersection of type " + InvDictionary[j] + " was found. Hence, no corresponding groups were created"
114                 
115                 SubShapesID = geompy.SubShapeAllIDs(OutFather,OrigType+1)               # Saving also the groups corresponding to the sectioned item of the same type: ex. A solid into n sub-solids due to the sections
116                 for i in range(0,len(SubShapesID)):
117                         dummyObj = geompy.CreateGroup(OutFather, OrigType+1)
118                         geompy.UnionIDs(dummyObj, [SubShapesID[i]])
119                         Parts.append(dummyObj)
120                         if Publish: geompy.addToStudyInFather(OutFather, dummyObj, "SB"+"_"+InvDictionary[OrigType+1][0:3]+"_"+str(i+1))
121
122                 return OutFather, Sections, Parts
123         else:
124                 print("Fatal error, the routine cannot continue any further, check your input variables")
125                 return -1
126 def FindStandType(GeoObj, method):
127         """
128         Find the standard index for the Geometrical object/compound type input, see dictionary in geompy.ShapeType
129         """
130         TopType = GeoObj.GetMaxShapeType().__str__()
131         UnModType = geompy.ShapeType[TopType]
132         if method == 0 :
133                 StandType = UnModType-int(not(UnModType%2))             # So that wires and edges and considered the same, faces and shells, and so on
134         else :
135                 StandType = UnModType
136
137         return(StandType)
138
139 def CreatePlane(CutPlnVar,Trim):
140         """
141         Creates a temporary point and vector in salome in order to build the sectioning planes needed
142         """
143         Temp_Vtx = geompy.MakeVertex(CutPlnVar[0], CutPlnVar[1], CutPlnVar[2])
144         Temp_Vec = geompy.MakeVectorDXDYDZ(CutPlnVar[3], CutPlnVar[4], CutPlnVar[5])
145         CutPlane = geompy.MakePlane(Temp_Vtx, Temp_Vec, Trim)
146         return(CutPlane)
147
148 def CheckInput(CutPlnLst, OutLvlLst, PrefixLst, AutoTrim):
149         """
150         Checks the user input specifically if all needed parameters are provided
151         """
152         if not ((len(CutPlnLst) == len(OutLvlLst)) and (len(CutPlnLst) == len(PrefixLst))):
153                 print("Missing information about one or more of the cut planes") 
154                 return 0
155         elif not ((len(CutPlnLst[0]) == 6+int(not AutoTrim))):
156                 print("For each cutting plane you need to specify 6 parameters = 2 x 3 coordinates") 
157                 return 0
158         else:
159                 return len(CutPlnLst)
160
161 def IsOnPlane(GeoSubObj, CutPlnVar, tolerance):
162         """
163         Checks whether a geometry (vertex, segment, or face) belongs *completely* to the plane defined as a point and a normal vector
164         """
165         # lambda function that represents the plane equation, function = 0 <=> Pt defined with Coor belongs to plane
166         PlaneEq = lambda Coor: CutPlnVar[3]*(Coor[0]-CutPlnVar[0])+CutPlnVar[4]*(Coor[1]-CutPlnVar[1])+CutPlnVar[5]*(Coor[2]-CutPlnVar[2])
167         OrigType = FindStandType(GeoSubObj,0)
168         if (OrigType >= 7):             # Vertex
169                 NonTrimDecision = abs(PlaneEq(geompy.PointCoordinates(GeoSubObj))) < tolerance
170                 if len(CutPlnVar) == 6 : return NonTrimDecision # No trim condition used
171                 else : return (NonTrimDecision and Distance2Pt(CutPlnVar[0:3],geompy.PointCoordinates(GeoSubObj))<=CutPlnVar[6]/2)
172         elif (OrigType >= 5):           # Line, decompose into two points then call recursively IsOnPlane function!
173                 Verdict = True
174                 for i in range(0,2):
175                         Verdict = Verdict and IsOnPlane(geompy.GetVertexByIndex(GeoSubObj,i), CutPlnVar, tolerance)
176                 return Verdict
177         elif (OrigType >= 3):           # Face, decompose into three points then call recursively IsOnPlane function!
178                 if IsOnPlane(geompy.MakeCDG(GeoSubObj),CutPlnVar, tolerance) : # Center of gravity belongs to plane, check if normal is parallel to plane
179                         NormalP1Coor = geompy.PointCoordinates(geompy.GetVertexByIndex(geompy.GetNormal(GeoSubObj),0))
180                         NormalP2Coor = geompy.PointCoordinates(geompy.GetVertexByIndex(geompy.GetNormal(GeoSubObj),1))
181                         Normal = [NormalP1Coor[0]-NormalP2Coor[0], NormalP1Coor[1]-NormalP2Coor[1], NormalP1Coor[2]-NormalP2Coor[2]]
182                         CrossP = CrossProd(CutPlnVar[3:6],Normal)               # Checks whether normals (of section plane and of face) are parallel or not
183                         if (abs(CrossP[0])<tolerance and abs(CrossP[1])<tolerance and abs(CrossP[2])<tolerance):        # meaning zero cross product => parallel
184                                 return True
185                         else :
186                                 return False
187                 else :
188                         return False
189
190
191 def CrossProd(V1,V2):
192         """
193         Determines the cross product of two 3D vectors
194         """
195         return ([V1[1]*V2[2]-V1[2]*V2[1], V1[2]*V2[0]-V1[0]*V2[2], V1[0]*V2[1]-V1[1]*V2[0]])
196
197 def Distance2Pt(P1,P2):
198         """
199         Returns the distance between two points
200         """
201         return (math.sqrt((P1[0]-P2[0])**2+(P1[1]-P2[1])**2+(P1[2]-P2[2])**2))