Skip to content

Commit 7e32463

Browse files
committed
Search Distance Fallback can now also be changed per element group
1 parent 75fa876 commit 7e32463

File tree

6 files changed

+78
-41
lines changed

6 files changed

+78
-41
lines changed

kk_bullet_constraints_builder.zip

303 Bytes
Binary file not shown.

kk_bullet_constraints_builder/builder.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,10 @@ def build():
9090
###### Calculate contact area for all connections
9191
### For now this is not used anymore as it is less safe than to derive an accurate contact area indirectly by using: volume /length
9292
if props.useAccurateArea:
93-
#connectsGeo, connectsLoc = calculateContactAreaBasedOnBooleansForAll(objs, connectsPair)
94-
connectsGeo, connectsLoc = calculateContactAreaBasedOnBoundaryBoxesForAll(objs, connectsPair, qAccurate=1)
93+
#connectsGeo, connectsLoc = calculateContactAreaBasedOnBooleansForAll(objs, objsEGrp, connectsPair)
94+
connectsGeo, connectsLoc = calculateContactAreaBasedOnBoundaryBoxesForAll(objs, objsEGrp, connectsPair, qAccurate=1)
9595
else:
96-
connectsGeo, connectsLoc = calculateContactAreaBasedOnBoundaryBoxesForAll(objs, connectsPair, qAccurate=0)
96+
connectsGeo, connectsLoc = calculateContactAreaBasedOnBoundaryBoxesForAll(objs, objsEGrp, connectsPair, qAccurate=0)
9797
###### Delete connections with zero contact area
9898
connectsPair, connectsGeo, connectsLoc = deleteConnectionsWithZeroContactArea(objs, objsEGrp, connectsPair, connectsGeo, connectsLoc)
9999
###### Delete connections with references from predefined constraints

kk_bullet_constraints_builder/builder_prep.py

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -751,9 +751,9 @@ def boundaryBoxFaces(obj, qGlobalSpace, selMin=None, selMax=None):
751751
else:
752752
return None, None, None, 0
753753

754-
################################################################################
754+
################################################################################
755755

756-
def calculateContactAreaBasedOnBoundaryBoxesForPair(objA, objB, qNonManifold=0, qAccurate=0):
756+
def calculateContactAreaBasedOnBoundaryBoxesForPair(objA, objB, sDistFallb, qNonManifold=0, qAccurate=0):
757757

758758
###### Calculate contact area for a single pair of objects
759759
props = bpy.context.window_manager.bcb
@@ -813,15 +813,15 @@ def calculateContactAreaBasedOnBoundaryBoxesForPair(objA, objB, qNonManifold=0,
813813
bbBMin, bbBMax, bbBCenter = bbBMinF, bbBMaxF, bbBCenterF
814814
### Alternatively use regular search distance face areas to derive locations from (but not for contact area as it may be too inaccurate)
815815
# Use the smallest detected area of both objects as contact area
816-
elif props.searchDistanceFallback and areaA2 > 0 and areaB2 > 0:
816+
elif sDistFallb and areaA2 > 0 and areaB2 > 0:
817817
bbAMin, bbAMax, bbACenter = bbAMinF2, bbAMaxF2, bbACenterF2
818818
bbBMin, bbBMax, bbBCenter = bbBMinF2, bbBMaxF2, bbBCenterF2
819819
# Or if only one area is greater zero then use that one
820-
elif props.searchDistanceFallback and areaA2 > 0:
820+
elif sDistFallb and areaA2 > 0:
821821
bbAMin, bbAMax, bbACenter = bbAMinF2, bbAMaxF2, bbACenterF2
822-
elif props.searchDistanceFallback and areaB2 > 0:
822+
elif sDistFallb and areaB2 > 0:
823823
bbBMin, bbBMax, bbBCenter = bbBMinF2, bbBMaxF2, bbBCenterF2
824-
elif not props.searchDistanceFallback: qSkipConnect = 1
824+
elif not sDistFallb: qSkipConnect = 1
825825

826826
### Calculate overlap of face based boundary boxes rather than simple boundary box overlap
827827
if areaA > 0 and areaB > 0:
@@ -849,33 +849,33 @@ def calculateContactAreaBasedOnBoundaryBoxesForPair(objA, objB, qNonManifold=0,
849849
qSkipConnect = 1
850850
else: overlapX, overlapY, overlapZ = max(0,overlapXF), max(0,overlapYF), max(0,overlapZF)
851851
### Alternatively use overlap of face based boundary boxes based on regular search distance
852-
elif props.searchDistanceFallback and areaA2 > 0 and areaB2 > 0:
852+
elif sDistFallb and areaA2 > 0 and areaB2 > 0:
853853
overlapXF = min(bbAMaxF2[0],bbBMaxF2[0]) -max(bbAMinF2[0],bbBMinF2[0])
854854
overlapYF = min(bbAMaxF2[1],bbBMaxF2[1]) -max(bbAMinF2[1],bbBMinF2[1])
855855
overlapZF = min(bbAMaxF2[2],bbBMaxF2[2]) -max(bbAMinF2[2],bbBMinF2[2])
856856
# If one axis is out of search distance range then fall back to simple boundary box based overlap
857857
if overlapXF < -searchDist or overlapYF < -searchDist or overlapZF < -searchDist:
858858
qSkipConnect = 1
859859
else: overlapX, overlapY, overlapZ = max(0,overlapXF), max(0,overlapYF), max(0,overlapZF)
860-
elif props.searchDistanceFallback and areaA2 > 0:
860+
elif sDistFallb and areaA2 > 0:
861861
overlapXF = min(bbAMaxF2[0],bbBMax[0]) -max(bbAMinF2[0],bbBMin[0])
862862
overlapYF = min(bbAMaxF2[1],bbBMax[1]) -max(bbAMinF2[1],bbBMin[1])
863863
overlapZF = min(bbAMaxF2[2],bbBMax[2]) -max(bbAMinF2[2],bbBMin[2])
864864
# If one axis is out of search distance range then fall back to simple boundary box based overlap
865865
if overlapXF < -searchDist or overlapYF < -searchDist or overlapZF < -searchDist:
866866
qSkipConnect = 1
867867
else: overlapX, overlapY, overlapZ = max(0,overlapXF), max(0,overlapYF), max(0,overlapZF)
868-
elif props.searchDistanceFallback and areaB2 > 0:
868+
elif sDistFallb and areaB2 > 0:
869869
overlapXF = min(bbAMax[0],bbBMaxF2[0]) -max(bbAMin[0],bbBMinF2[0])
870870
overlapYF = min(bbAMax[1],bbBMaxF2[1]) -max(bbAMin[1],bbBMinF2[1])
871871
overlapZF = min(bbAMax[2],bbBMaxF2[2]) -max(bbAMin[2],bbBMinF2[2])
872872
# If one axis is out of search distance range then fall back to simple boundary box based overlap
873873
if overlapXF < -searchDist or overlapYF < -searchDist or overlapZF < -searchDist:
874874
qSkipConnect = 1
875875
else: overlapX, overlapY, overlapZ = max(0,overlapXF), max(0,overlapYF), max(0,overlapZF)
876-
elif not props.searchDistanceFallback: qSkipConnect = 1
876+
elif not sDistFallb: qSkipConnect = 1
877877

878-
if not qAccurate or qSkipConnect or props.searchDistanceFallback:
878+
if not qAccurate or qSkipConnect or sDistFallb:
879879
### Calculate simple overlap of boundary boxes for contact area calculation (project along all axis')
880880
overlapX = max(0, min(bbAMax[0],bbBMax[0]) -max(bbAMin[0],bbBMin[0]))
881881
overlapY = max(0, min(bbAMax[1],bbBMax[1]) -max(bbAMin[1],bbBMin[1]))
@@ -944,16 +944,45 @@ def calculateContactAreaBasedOnBoundaryBoxesForPair(objA, objB, qNonManifold=0,
944944

945945
########################################
946946

947-
def calculateContactAreaBasedOnBoundaryBoxesForAll(objs, connectsPair, qAccurate):
947+
def calculateContactAreaBasedOnBoundaryBoxesForAll(objs, objsEGrp, connectsPair, qAccurate):
948948

949949
### Calculate contact area for all connections
950950
print("Calculating contact area for connections...")
951951

952+
props = bpy.context.window_manager.bcb
953+
elemGrps = global_vars.elemGrps
954+
955+
### Prepare dictionary of element indices for faster item search (optimization)
956+
objsDict = {}
957+
for i in range(len(objs)):
958+
objsDict[objs[i]] = i
959+
952960
connectsGeo = []
953961
connectsLoc = []
954-
for k in range(len(connectsPair)):
955-
objA = objs[connectsPair[k][0]]
956-
objB = objs[connectsPair[k][1]]
962+
connectsPair_iter = iter(connectsPair)
963+
for i in range(len(connectsPair)):
964+
pair = next(connectsPair_iter)
965+
objA = objs[pair[0]]
966+
objB = objs[pair[1]]
967+
968+
### Get Search Distance Fallback setting
969+
if not props.searchDistanceFallback:
970+
elemGrpA = objsEGrp[objsDict[objA]]
971+
elemGrpB = objsEGrp[objsDict[objB]]
972+
elemGrps_elemGrpA = elemGrps[elemGrpA]
973+
elemGrps_elemGrpB = elemGrps[elemGrpB]
974+
Prio_A = elemGrps_elemGrpA[EGSidxPrio]
975+
Prio_B = elemGrps_elemGrpB[EGSidxPrio]
976+
if Prio_A == Prio_B: # Priority is the same for both element groups
977+
sDistFallb_A = elemGrps_elemGrpA[EGSidxSDFl]
978+
sDistFallb_B = elemGrps_elemGrpB[EGSidxSDFl]
979+
sDistFallb = sDistFallb_A or sDistFallb_B
980+
if Prio_A > Prio_B: # Priority is higher for A
981+
sDistFallb = elemGrps_elemGrpA[EGSidxSDFl]
982+
elif Prio_A < Prio_B: # Priority is higher for B
983+
sDistFallb = elemGrps_elemGrpB[EGSidxSDFl]
984+
else:
985+
sDistFallb = 1
957986

958987
### Check if meshes are water tight (non-manifold)
959988
nonManifolds = []
@@ -967,7 +996,7 @@ def calculateContactAreaBasedOnBoundaryBoxesForAll(objs, connectsPair, qAccurate
967996
bm.free()
968997

969998
###### Calculate contact area for a single pair of objects
970-
geoContactArea, geoHeight, geoWidth, center, geoAxis, qVolCorrect = calculateContactAreaBasedOnBoundaryBoxesForPair(objA, objB, qAccurate=qAccurate, qNonManifold=len(nonManifolds))
999+
geoContactArea, geoHeight, geoWidth, center, geoAxis, qVolCorrect = calculateContactAreaBasedOnBoundaryBoxesForPair(objA, objB, sDistFallb, qAccurate=qAccurate, qNonManifold=len(nonManifolds))
9711000

9721001
# Geometry array: [area, height, width, axisNormal, axisHeight, axisWidth, qVolCorrect]
9731002
connectsGeo.append([geoContactArea, geoHeight, geoWidth, geoAxis[0], geoAxis[1], geoAxis[2], qVolCorrect])
@@ -977,7 +1006,7 @@ def calculateContactAreaBasedOnBoundaryBoxesForAll(objs, connectsPair, qAccurate
9771006

9781007
################################################################################
9791008

980-
def calculateContactAreaBasedOnBooleansForAll(objs, connectsPair):
1009+
def calculateContactAreaBasedOnBooleansForAll(objs, objsEGrp, connectsPair):
9811010

9821011
### Calculate contact area for all connections
9831012
print("Calculating contact area for connections... (%d)" %len(connectsPair))
@@ -1035,7 +1064,7 @@ def calculateContactAreaBasedOnBooleansForAll(objs, connectsPair):
10351064
#print('Warning: Mesh not water tight, non-manifolds found:', obj.name)
10361065

10371066
###### Calculate contact area for a single pair of objects
1038-
geoContactArea, geoHeight, geoWidth, center, geoAxis, qVolCorrect = calculateContactAreaBasedOnBoundaryBoxesForPair(objA, objB, qNonManifold=1)
1067+
geoContactArea, geoHeight, geoWidth, center, geoAxis, qVolCorrect = calculateContactAreaBasedOnBoundaryBoxesForPair(objA, objB, sDistFallb, qNonManifold=1)
10391068

10401069
# Geometry array: [area, height, width, axisNormal, axisHeight, axisWidth]
10411070
connectsGeo.append([geoContactArea, geoHeight, geoWidth, geoAxis[0], geoAxis[1], geoAxis[2], qVolCorrect])

kk_bullet_constraints_builder/global_props.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ class bcb_props(bpy.types.PropertyGroup):
230230
exec("elemGrp_%d_EGSidxTl2R" %i +" = float_(name='2nd Rot. Tol.', default=presets[j][EGSidxTl2R], min=-1.0, max=pi, update=updGlob, description='Second deformation tolerance limit for angular change in radian for connection removal. The Formula Assistant might set this to 0 which means that this tolerance will be calculated later during the constraint building phase individually for each connection using Formula Assistant settings, there is no need to change it back then')")
231231
exec("elemGrp_%d_EGSidxPrio" %i +" = int_(name='Connection Priority', default=presets[j][EGSidxPrio], min=1, max=9, update=updGlob, description='Changes the connection priority for this element group which will override that the weaker breaking threshold of two elements is preferred for an connection. Lower Strength Priority has similar functionality but works on all groups, however, it is ignored if the priority here is different for a particular connection')")
232232
exec("elemGrp_%d_EGSidxFric" %i +" = float_(name='Friction', default=presets[j][EGSidxFric], min=0.0, max=100000, update=updGlob, description='Coefficient of friction for the given material (dimensionless)')")
233+
exec("elemGrp_%d_EGSidxSDFl" %i +" = bool_(name='Search Distance Fallback', default=presets[j][EGSidxSDFl], update=updGlob, description='In case no geometry could be detected within mesh search distance while the neighbor element´s boundary box is still within range this enables a fallback using the intersection of the boundary boxes as contact area instead of the mesh surface. If disabled contact area will remain zero and no connection will be created in that case')")
233234
exec("elemGrp_%d_EGSidxMCTh" %i +" = bool_(name='Mohr-Coulomb Theory', default=presets[j][EGSidxMCTh], update=updGlob, description='Enables the calculation of shear and bending strength using the Mohr-Coulomb theory and makes it stress-related. This method is recommended for masonry structures in earthquake scenarios. Note that the Multiplier setting is also applied to the strength increase')")
234235
exec("elemGrp_%d_EGSidxScal" %i +" = float_(name='Rescale Factor', default=presets[j][EGSidxScal], min=0.0, max=10.0, update=updGlob, description='Applies scaling factor on elements to avoid `Jenga´ effect (undesired stability increase caused by incompressible rigid bodies). This has no influence on breaking threshold and mass calculations')")
235236
exec("elemGrp_%d_EGSidxNoHo" %i +" = bool_(name='No Horizontal Connections', default=presets[j][EGSidxNoHo], update=updGlob, description='Removes horizontal connections between elements of different element groups. This can be useful for masonry walls touching a framing structure without a particular fixation')")
@@ -283,6 +284,7 @@ def props_update_menu(self):
283284
exec("self.elemGrp_%d_EGSidxTl2R" %i +" = elemGrps[i][EGSidxTl2R]")
284285
exec("self.elemGrp_%d_EGSidxPrio" %i +" = elemGrps[i][EGSidxPrio]")
285286
exec("self.elemGrp_%d_EGSidxFric" %i +" = elemGrps[i][EGSidxFric]")
287+
exec("self.elemGrp_%d_EGSidxSDFl" %i +" = elemGrps[i][EGSidxSDFl]")
286288
exec("self.elemGrp_%d_EGSidxMCTh" %i +" = elemGrps[i][EGSidxMCTh]")
287289
exec("self.elemGrp_%d_EGSidxScal" %i +" = elemGrps[i][EGSidxScal]")
288290
exec("self.elemGrp_%d_EGSidxNoHo" %i +" = elemGrps[i][EGSidxNoHo]")
@@ -352,6 +354,7 @@ def props_update_globals(self):
352354
elemGrps[i][EGSidxTl2R] = eval("self.elemGrp_%d_EGSidxTl2R" %i)
353355
elemGrps[i][EGSidxPrio] = eval("self.elemGrp_%d_EGSidxPrio" %i)
354356
elemGrps[i][EGSidxFric] = eval("self.elemGrp_%d_EGSidxFric" %i)
357+
elemGrps[i][EGSidxSDFl] = eval("self.elemGrp_%d_EGSidxSDFl" %i)
355358
lastValue = elemGrps[i][EGSidxMCTh]
356359
elemGrps[i][EGSidxMCTh] = eval("self.elemGrp_%d_EGSidxMCTh" %i)
357360
if elemGrps[i][EGSidxMCTh] != lastValue: qFMonlySettingModified = 1

0 commit comments

Comments
 (0)