import gmsh
import random
import math
class SpherePacking:
def __init__(self, number, min_radius, max_radius, box, k_move, operation):
self.number = number
self.min_radius = min_radius
self.max_radius = max_radius
self.box = box # [x_min, y_min, z_min, x_max, y_max, z_max]
self.k_move = k_move
self.operation = operation
self.objects = []
def generate_random_positions(self):
for _ in range(self.number):
radius = random.uniform(self.min_radius, self.max_radius)
# Generate random position in cylindrical coordinates
r = random.uniform(0, self.box[3]) # Assume max radius from the bounding box
theta = random.uniform(0, 2 * math.pi)
z = random.uniform(self.box[2] + radius, self.box[5] - radius)
# Convert cylindrical to Cartesian coordinates
x = r * math.cos(theta)
y = r * math.sin(theta)
# Ensure the particle stays within the cylindrical boundary
if r + radius <= self.box[3]:
self.objects.append({"x": x, "y": y, "z": z, "radius": radius})
def validate_positions(self):
for obj in self.objects:
# Check box boundary
obj["x"] = max(self.box[0] + obj["radius"], min(self.box[3] - obj["radius"], obj["x"]))
obj["y"] = max(self.box[1] + obj["radius"], min(self.box[4] - obj["radius"], obj["y"]))
obj["z"] = max(self.box[2] + obj["radius"], min(self.box[5] - obj["radius"], obj["z"]))
def resolve_collisions(self):
for _ in range(500): # Increased iteration limit for more thorough resolution
stabilized = True
for i in range(len(self.objects)):
for j in range(i + 1, len(self.objects)):
obj_i, obj_j = self.objects[i], self.objects[j]
dx = obj_i["x"] - obj_j["x"]
dy = obj_i["y"] - obj_j["y"]
dz = obj_i["z"] - obj_j["z"]
dist = math.sqrt(dx**2 + dy**2 + dz**2)
# Check if particles overlap
if dist < obj_i["radius"] + obj_j["radius"]:
stabilized = False
overlap = (obj_i["radius"] + obj_j["radius"] - dist) / 2
move_dist = overlap / dist
# Apply displacement in both directions along the line of collision
obj_i["x"] += dx * move_dist
obj_i["y"] += dy * move_dist
obj_i["z"] += dz * move_dist
obj_j["x"] -= dx * move_dist
obj_j["y"] -= dy * move_dist
obj_j["z"] -= dz * move_dist
if stabilized:
break
self.validate_positions() # Validate positions after collision resolution
def generate_geometry(self):
gmsh.initialize()
gmsh.option.setNumber("General.Terminal", 1)
gmsh.model.add("Sphere Packing in Cylinder")
# Add spheres
for i, obj in enumerate(self.objects):
gmsh.model.occ.addSphere(obj["x"], obj["y"], obj["z"], obj["radius"], tag=i + 1)
gmsh.model.occ.synchronize()
gmsh.option.setNumber("Mesh.CharacteristicLengthMin", 0.01)
gmsh.option.setNumber("Mesh.CharacteristicLengthMax", 0.05)
try:
gmsh.model.mesh.generate(3)
except Exception as e:
print(f"Error during mesh generation: {e}")
gmsh.write("sphere_packing_cylinder.msh")
gmsh.write("sphere_packing_cylinder.step")
gmsh.finalize()
def run(self):
print("Generating random positions...")
self.generate_random_positions()
print("Resolving collisions...")
self.resolve_collisions()
print("Generating Gmsh geometry...")
self.generate_geometry()
print("Done! Files saved as 'sphere_packing_cylinder.msh' and 'sphere_packing_cylinder.step'.")
# Parameters
number_of_spheres = 3000
min_particle_radius = 0.05
max_particle_radius = 0.1
bounding_box = [-1, -1, 0, 1, 1, 2] # [x_min, y_min, z_min, x_max, y_max, z_max]
k_move = 0.0001
operation = "Fragments"
sphere_packing = SpherePacking(number_of_spheres, min_particle_radius, max_particle_radius, bounding_box, k_move, operation)
sphere_packing.run()
Hoping will get some ideas to implement using FEniCS.