Python_occ 学习记录 | 细观建模(2)
骨料建模
可以在挖孔的基础上,做一个混凝土随机骨料模型
import random
import mathfrom OCC.Core.gp import gp, gp_Pnt, gp_Circ
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire, BRepBuilderAPI_MakeFace
from OCC.Display.SimpleGui import init_display# ---------- 参数 ----------
W, H = 100.0, 100.0 # 板尺寸
target_area_frac = 0.30 # 目标骨料面积分数(2D)
r_min, r_max = 2.0, 8.0 # 半径范围
margin = 2.0 # 距离边界的保护层
gap = 0.5 # 骨料之间的最小净距
max_attempts = 20000 # 拒绝采样最大尝试次数
use_power_law = True # 使用幂律近似 Fuller;False 则为均匀# ---------- 分布采样函数 ----------
def sample_radius():if not use_power_law:# 简单:均匀半径return random.uniform(r_min, r_max)# 幂律/Fuller 近似:r^q 均匀 -> r# q 取 0.5~0.7 常见;q 越小越偏向小粒径(更接近 Fuller 曲线)q = 0.6u = random.random()r = ((u * (r_max**q - r_min**q)) + r_min**q) ** (1.0/q)return r# ---------- 外环矩形 ----------
def make_rect_wire(x0, y0, x1, y1):p00, p10, p11, p01 = gp_Pnt(x0, y0, 0), gp_Pnt(x1, y0, 0), gp_Pnt(x1, y1, 0), gp_Pnt(x0, y1, 0)w = BRepBuilderAPI_MakeWire(BRepBuilderAPI_MakeEdge(p00, p10).Edge(),BRepBuilderAPI_MakeEdge(p10, p11).Edge(),BRepBuilderAPI_MakeEdge(p11, p01).Edge(),BRepBuilderAPI_MakeEdge(p01, p00).Edge()).Wire()return w# ---------- 不重叠布置 ----------
def pack_circles():circles = [] # [(cx, cy, r), ...]placed_area = 0.0target_area = target_area_frac * W * Hattempts = 0while placed_area < target_area and attempts < max_attempts:attempts += 1r = sample_radius()cx = random.uniform(margin + r, W - margin - r)cy = random.uniform(margin + r, H - margin - r)ok = Truefor (x, y, rr) in circles:dx, dy = cx - x, cy - yif (dx*dx + dy*dy) < (r + rr + gap)**2:ok = Falsebreakif ok:circles.append((cx, cy, r))placed_area += math.pi * r * rreturn circles, placed_area / (W * H)# ---------- 构造 2D 面 ----------
def build_2d_faces(circles):# 基体(先作为无孔面)outer = make_rect_wire(0, 0, W, H)matrix_mk = BRepBuilderAPI_MakeFace(outer)# 骨料 faces + 同时把它们作为 "孔" 加进基体(反向)ax2 = gp.XOY()aggregate_faces = []for (cx, cy, r) in circles:circ = gp_Circ(ax2, r)circ.SetLocation(gp_Pnt(cx, cy, 0.0))edge = BRepBuilderAPI_MakeEdge(circ).Edge()wire = BRepBuilderAPI_MakeWire(edge).Wire()# 骨料自身的实心面(用于单独着色/挤出)agg_face = BRepBuilderAPI_MakeFace(wire).Face()aggregate_faces.append(agg_face)# 同时把线框反向后加入基体,使基体出现“孔”wire.Reverse()matrix_mk.Add(wire)if not matrix_mk.IsDone():raise RuntimeError("Matrix face build failed")matrix_face = matrix_mk.Face()return matrix_face, aggregate_faces# ---------- 主流程 ----------
display, start_display, add_menu, add_function_to_menu = init_display()circles, real_frac = pack_circles()print(f"Placed {len(circles)} aggregates, actual area fraction = {real_frac*100:.1f}%")matrix_face, aggregate_faces = build_2d_faces(circles)# 显示:基体 + 骨料(不同颜色)
display.DisplayShape(matrix_face, update=False) # 基体:默认色(有孔)
for f in aggregate_faces:display.DisplayShape(f, color="BLUE1", update=False) # 骨料:蓝色
display.View_Top()
display.FitAll()
start_display()
进阶建模
考虑界面过渡区、孔洞
meso2d.py
# -------------------------------------------------------------
# 2D 混凝土细观骨料生成
# - 多级粒径(≤5),Fuller/均匀分布
# - ITZ(界面过渡区)+ ITZ 安全间距(避免 ITZ 互相接触)
# - 更致密堆积:大粒径优先 + shrink-to-fit
# - 圆形气孔:默认作为“孔洞”(Reverse wire 加入基体),若气孔允许截断则退回布尔
# - 布尔运算采用 fuzzy_*(模糊容差+可并行)
# - 返回英文键名的 stats
# -------------------------------------------------------------import math
import random
from typing import List, Dict, Tuple, Optionalfrom OCC.Core.gp import gp, gp_Pnt, gp_Circ
from OCC.Core.BRepBuilderAPI import (BRepBuilderAPI_MakeEdge,BRepBuilderAPI_MakeWire,BRepBuilderAPI_MakeFace,
)
from OCC.Core.BRepAlgoAPI import (BRepAlgoAPI_Fuse,BRepAlgoAPI_Cut,BRepAlgoAPI_Common,
)
from OCC.Core.TopTools import TopTools_ListOfShape
from OCC.Core.GProp import GProp_GProps
from OCC.Core.BRepGProp import brepgprop_SurfaceProperties
from OCC.Core.TopoDS import TopoDS_Face, TopoDS_Shape# --------------------------
# “模糊布尔”封装(并行可选)
# --------------------------def fuzzy_cut(shape_A: TopoDS_Shape, shape_B: TopoDS_Shape, tol: float = 5e-5, parallel: bool = False) -> TopoDS_Shape:"""返回 A - B(带模糊容差),更稳健"""op = BRepAlgoAPI_Cut()L1 = TopTools_ListOfShape(); L1.Append(shape_A)L2 = TopTools_ListOfShape(); L2.Append(shape_B)op.SetArguments(L1); op.SetTools(L2)op.SetFuzzyValue(tol); op.SetRunParallel(parallel)op.Build()if op.HasErrors():msg = "BRepAlgoAPI_Cut failed"if hasattr(op, "DumpErrorsToString"):msg = op.DumpErrorsToString()raise AssertionError(msg)return op.Shape()def fuzzy_fuse(shape_a: TopoDS_Shape, shape_b: TopoDS_Shape, tol: float = 1e-4, parallel: bool = False) -> TopoDS_Shape:"""返回 a ∪ b"""op = BRepAlgoAPI_Fuse()la = TopTools_ListOfShape(); la.Append(shape_a)lb = TopTools_ListOfShape(); lb.Append(shape_b)op.SetArguments(la); op.SetTools(lb)op.SetFuzzyValue(tol); op.SetRunParallel(parallel)op.Build()return op.Shape()def fuzzy_common(shape_a: TopoDS_Shape, shape_b: TopoDS_Shape, tol: float = 1e-4, parallel: bool = False) -> TopoDS_Shape:"""返回 a ∩ b"""op = BRepAlgoAPI_Common()la = TopTools_ListOfShape(); la.Append(shape_a)lb = TopTools_ListOfShape(); lb.Append(shape_b)op.SetArguments(la); op.SetTools(lb)op.SetFuzzyValue(tol); op.SetRunParallel(parallel)op.Build()return op.Shape()# --------------------------
# 几何构造小工具
# --------------------------def _make_rect_wire(x0: float, y0: float, x1: float, y1: float):"""矩形外环 Wire"""p00, p10, p11, p01 = gp_Pnt(x0, y0, 0), gp_Pnt(x1, y0, 0), gp_Pnt(x1, y1, 0), gp_Pnt(x0, y1, 0)return BRepBuilderAPI_MakeWire(BRepBuilderAPI_MakeEdge(p00, p10).Edge(),BRepBuilderAPI_MakeEdge(p10, p11).Edge(),BRepBuilderAPI_MakeEdge(p11, p01).Edge(),BRepBuilderAPI_MakeEdge(p01, p00).Edge(),).Wire()def _make_circle_edge(cx: float, cy: float, r: float):"""圆 -> 边"""ax2 = gp.XOY()c = gp_Circ(ax2, r)c.SetLocation(gp_Pnt(cx, cy, 0.0))return BRepBuilderAPI_MakeEdge(c).Edge()def _make_circle_wire(cx: float, cy: float, r: float):"""圆 -> 线框"""return BRepBuilderAPI_MakeWire(_make_circle_edge(cx, cy, r)).Wire()def _make_circle_face(cx: float, cy: float, r: float) -> TopoDS_Face:"""圆 -> 面(实心圆盘)"""return BRepBuilderAPI_MakeFace(_make_circle_wire(cx, cy, r)).Face()def _fuse_many(shapes: List[TopoDS_Shape]) -> Optional[TopoDS_Shape]:"""多形体融合(链式 fuzzy_fuse)"""if not shapes:return Noneacc = shapes[0]for s in shapes[1:]:acc = fuzzy_fuse(acc, s)return accdef _face_area(face: TopoDS_Face) -> float:"""面面积(OCCT 真值)"""props = GProp_GProps()brepgprop_SurfaceProperties(face, props)return props.Mass()# --------------------------
# 取样与几何检查
# --------------------------def _sample_radius(level: Dict) -> float:"""按层级分布采样半径:uniform / fuller(q)"""rmin, rmax = float(level["r_min"]), float(level["r_max"])dist = level.get("dist", "fuller").lower()if dist == "uniform":return random.uniform(rmin, rmax)# Fuller 近似:r^q 均匀 -> rq = float(level.get("q", 0.6))u = random.random()return ((u * (rmax**q - rmin**q) + rmin**q) ** (1.0 / q))def _dist2(x1, y1, x2, y2) -> float:dx, dy = x1 - x2, y1 - y2return dx * dx + dy * dydef _min_dist_to_boundary(cx, cy, W, H) -> float:"""点到矩形边界的最小距离"""return min(cx, cy, W - cx, H - cy)# --------------------------
# shrink-to-fit(考虑 ITZ+安全间距)
# --------------------------def _allowed_radius_shrink_to_fit(cx: float, cy: float, r_try: float,placed: List[Tuple[float, float, float]], # 已放骨料 (x,y,r)gap_base: float, W: float, H: float,clip_boundary: bool, margin: float,itz_thickness: float, itz_clearance: float
) -> float:"""在不接触“其他骨料 ITZ”的前提下,尽量缩小半径保留。有效半径 = r + itz_thickness;另外再加 itz_clearance 作为缓冲。"""r_allowed = r_tryfor (x, y, rj) in placed:d = math.sqrt(_dist2(cx, cy, x, y))r_allowed = min(r_allowed,max(0.0, d - (rj + itz_thickness) - gap_base - itz_clearance - itz_thickness))if r_allowed <= 0.0:return 0.0if not clip_boundary:r_allowed = min(r_allowed, _min_dist_to_boundary(cx, cy, W, H) - margin)return max(0.0, r_allowed)# --------------------------
# 逐层放置(大粒径优先)
# --------------------------def _place_level(W: float, H: float,level: Dict,already: List[Tuple[float, float, float]], # 已放骨料 (x,y,r)target_area_abs: float,margin: float,gap: float,max_attempts: int,densify_mode: str,itz_thickness: float,itz_clearance: float,
) -> Tuple[List[Tuple[float, float, float]], float]:"""在一个层级内放置若干圆形骨料。返回 (本层新增骨料列表, 近似新增面积)- gap:骨料本体之间最小净距- ITZ 判定:按有效半径 r + itz_thickness,并额外加入 itz_clearance"""placed = []area_sum = 0.0attempts = 0rmin = float(level["r_min"])while area_sum < target_area_abs and attempts < max_attempts:attempts += 1r = _sample_radius(level)# 采样中心cx = random.uniform(margin + r, W - margin - r)cy = random.uniform(margin + r, H - margin - r)# 快速可行性(考虑 ITZ + 安全间距)ok = Truefor (x, y, rj) in (already + placed):if _dist2(cx, cy, x, y) < ((r + itz_thickness) + (rj + itz_thickness) + gap + itz_clearance) ** 2:ok = Falsebreakif ok:placed.append((cx, cy, r))area_sum += math.pi * r * rcontinue# shrink-to-fitif densify_mode.lower() == "salvage":r_allow = _allowed_radius_shrink_to_fit(cx, cy, r, already + placed, gap, W, H, clip_boundary, margin, itz_thickness, itz_clearance)if r_allow >= rmin:r_acc = max(rmin, min(r, r_allow))placed.append((cx, cy, r_acc))area_sum += math.pi * r_acc * r_accreturn placed, area_sum# --------------------------
# 公开主函数
# --------------------------def generate_meso_2d(width: float,height: float,levels: List[Dict],*,margin: float = 0.0, # 与边界的保护层gap: float = 0.2, # 骨料本体净距(不含 ITZ)itz_thickness: float = 0.0, # ITZ 厚度(=0 不生成 ITZ)itz_clearance: float = 0.0, # ITZ 之间的额外安全间距max_attempts_per_level: int = 40000,densify_mode: str = "salvage", # "rsa" 或 "salvage"seed: Optional[int] = None,holes: Optional[Dict] = None, # 例如:{"frac":0.01,"r_min":1,"r_max":3,"dist":"uniform","gap":0.3,"clip_boundary":False}fuzzy_tol: float = 1e-4, # 模糊布尔的容差(全局)parallel_bool: bool = False, # 模糊布尔是否并行
) -> Dict:"""生成 2D 细观结构(圆形骨料 + 可选气孔 + 可选 ITZ),并返回几何与统计信息。返回 dict:- "domain_face": 域面- "matrix_face": 基体面(已减去骨料与孔)- "aggregate_faces": 骨料面列表(截断后)- "itz_faces": ITZ 面列表- "hole_faces": 气孔面列表(若作为孔洞 Reverse 方式加入基体,此列表仍给出独立面供可视化)- "circles": [(cx, cy, r, level_index)]- "holes_circles": [(cx, cy, r)]- "stats": 英文键名统计"""if seed is not None:random.seed(seed)if len(levels) == 0:raise ValueError("levels must be non-empty")if len(levels) > 5:raise ValueError("At most 5 levels are supported")# 大粒径优先levels_sorted = sorted(levels, key=lambda lv: float(lv["r_max"]), reverse=True)W, H = float(width), float(height)outer = _make_rect_wire(0.0, 0.0, W, H)domain_face = BRepBuilderAPI_MakeFace(outer).Face()domain_area = W * H# ---------- 放置骨料 ----------all_circles: List[Tuple[float, float, float, int]] = []approx_area_sum = 0.0for idx, lv in enumerate(levels_sorted):frac = float(lv["frac"])if frac <= 0.0:continuetarget_area_abs = frac * domain_areaplaced, area_added = _place_level(W, H, lv, [(x, y, r) for (x, y, r, _) in all_circles],target_area_abs, clip_boundary, margin, gap,max_attempts_per_level, densify_mode,itz_thickness, itz_clearance)approx_area_sum += area_addedall_circles.extend([(x, y, r, idx) for (x, y, r) in placed])# 骨料面 & 并集aggregate_faces: List[TopoDS_Face] = []for (cx, cy, r, _) in all_circles:f = _make_circle_face(cx, cy, r)if clip_boundary:f = fuzzy_common(f, domain_face, tol=fuzzy_tol, parallel=parallel_bool)aggregate_faces.append(f)agg_union = _fuse_many(aggregate_faces)# ---------- 气孔(作为“孔洞”优先) ----------hole_faces: List[TopoDS_Face] = []holes_circles: List[Tuple[float, float, float]] = []holes_as_holes = Falseif holes is not None and (holes.get("frac") or holes.get("count")):h_gap = float(holes.get("gap", gap))h_clip = Falseholes_as_holes = not h_clip # 不截断=>可用 Reverse 方式target_h_area = Noneif "frac" in holes and holes["frac"] is not None:target_h_area = float(holes["frac"]) * domain_areatarget_h_count = int(holes.get("count", 0))placed_h = []area_h = 0.0attempts = 0max_attempts_h = int(holes.get("max_attempts", max_attempts_per_level))# 用于采样半径h_level = dict(r_min=float(holes["r_min"]),r_max=float(holes["r_max"]),dist=str(holes.get("dist", "uniform")).lower(),q=float(holes.get("q", 0.6)),)# 气孔不能接触“骨料 ITZ”,因此按 r_eff_agg = r_agg + itz_thickness + itz_clearance 判定base_aggs = [(x, y, r) for (x, y, r, _) in all_circles]def _stop():if target_h_area is not None and area_h >= target_h_area:return Trueif target_h_count and len(placed_h) >= target_h_count:return Truereturn Falsewhile not _stop() and attempts < max_attempts_h:attempts += 1r = _sample_radius(h_level)# 不截断:整体入域cx = random.uniform(margin + r, W - margin - r)cy = random.uniform(margin + r, H - margin - r)ok = True# 1) 与骨料 ITZ 的距离限制for (x, y, rj) in base_aggs:r_eff_agg = rj + itz_thickness + itz_clearanceif _dist2(cx, cy, x, y) < (r + r_eff_agg + h_gap) ** 2:ok = Falsebreak# 2) 与已放置气孔之间if ok:for (x, y, rj) in placed_h:if _dist2(cx, cy, x, y) < (r + rj + h_gap) ** 2:ok = Falsebreakif ok:placed_h.append((cx, cy, r))area_h += math.pi * r * rcontinue# shrink-to-fit(孔对孔/孔对骨料 ITZ)r_allow = rfor (x, y, rj) in base_aggs:d = math.sqrt(_dist2(cx, cy, x, y))r_eff_agg = rj + itz_thickness + itz_clearancer_allow = min(r_allow, max(0.0, d - r_eff_agg - h_gap))if r_allow <= 0.0:breakif r_allow > 0.0:for (x, y, rj) in placed_h:d = math.sqrt(_dist2(cx, cy, x, y))r_allow = min(r_allow, max(0.0, d - rj - h_gap))if r_allow <= 0.0:breakif not h_clip and r_allow > 0.0:r_allow = min(r_allow, _min_dist_to_boundary(cx, cy, W, H) - margin)if r_allow >= h_level["r_min"]:r_acc = max(h_level["r_min"], min(r, r_allow))placed_h.append((cx, cy, r_acc))area_h += math.pi * r_acc * r_acc# 构造独立的气孔面列表(供可视化;即使作为“孔洞”加入基体,也保留这份)for (cx, cy, r) in placed_h:hf = _make_circle_face(cx, cy, r)if h_clip:hf = fuzzy_common(hf, domain_face, tol=fuzzy_tol, parallel=parallel_bool)hole_faces.append(hf)holes_circles = placed_h# ---------- 基体面构造 ----------# 情况A:气孔“不截断” -> 先用 Reverse wire 把孔洞直接加入基体;随后再布尔减去骨料# 情况B:其余(气孔截断或未设置气孔)-> 统一走布尔# 先做一个“起始基体”(不含骨料、孔)matrix_mk = BRepBuilderAPI_MakeFace(outer)if holes_circles and holes_as_holes:# 把孔洞作为 inner wires 直接加入(Reverse)for (cx, cy, r) in holes_circles:w = _make_circle_wire(cx, cy, r)w.Reverse()matrix_mk.Add(w)# 得到“带孔但尚未减骨料”的面tmp_matrix = matrix_mk.Face()# 减去骨料(骨料可能截断,必须布尔)if agg_union is not None:matrix_face = fuzzy_cut(tmp_matrix, agg_union, tol=fuzzy_tol, parallel=parallel_bool)else:matrix_face = tmp_matrixelse:# 统一布尔:域 - (骨料 ∪ 孔)cutters = agg_unionif hole_faces:hole_union = _fuse_many(hole_faces)cutters = hole_union if cutters is None else fuzzy_fuse(cutters, hole_union, tol=fuzzy_tol, parallel=parallel_bool)matrix_face = fuzzy_cut(domain_face, cutters, tol=fuzzy_tol, parallel=parallel_bool) if cutters is not None else domain_face# ---------- ITZ ----------itz_faces: List[TopoDS_Face] = []if itz_thickness and itz_thickness > 0.0 and aggregate_faces:itz_list = []agg_union_cached = _fuse_many(aggregate_faces) if agg_union is None else agg_unionfor (cx, cy, r, _) in all_circles:outer_ring = _make_circle_face(cx, cy, r + itz_thickness)outer_ring = fuzzy_common(outer_ring, domain_face, tol=fuzzy_tol, parallel=parallel_bool)ring = fuzzy_cut(outer_ring, agg_union_cached, tol=fuzzy_tol, parallel=parallel_bool) # 去掉骨料本体,剩余在基体中的 ITZitz_list.append(ring)itz_faces = itz_list# ---------- 统计 ----------agg_area_true = sum((_face_area(f) for f in aggregate_faces), 0.0)hole_area_true = sum((_face_area(f) for f in hole_faces), 0.0)matrix_area_true = _face_area(matrix_face)stats = {"requested_frac_sum": sum(float(lv.get("frac", 0.0)) for lv in levels),"approx_agg_area_frac_sum": (sum(math.pi * r * r for (_, _, r, _) in all_circles) / (W * H)),"true_agg_area_frac_sum": agg_area_true / (W * H),"true_hole_area_frac": hole_area_true / (W * H),"true_matrix_area_frac": matrix_area_true / (W * H),"n_aggregates": len(all_circles),"n_holes": len(holes_circles),}return dict(domain_face=domain_face,matrix_face=matrix_face,aggregate_faces=aggregate_faces,itz_faces=itz_faces,hole_faces=hole_faces,circles=all_circles,holes_circles=holes_circles,stats=stats,)__all__ = ["generate_meso_2d"]
主程序调用:
from OCC.Display.SimpleGui import init_display
from meso2d import generate_meso_2d
import OCC.Core.Quantity as Q# ========= 参数区 =========
W, H = 140.0, 100.0 # 域尺寸(单位自定)
MARGIN = 1.0 # 与边界的保护层(用于生成时避免边界问题)
GAP = 0.30 # 骨料本体之间最小净距(不含 ITZ)ITZ_THICKNESS = 0.60 # ITZ 厚度
ITZ_CLEARANCE = 0.30 # ITZ 之间额外安全间距(确保 ITZ 绝不接触)# 最多 5 个层级;这里示例 3 层,总目标 ~ 40%
levels = [{"frac": 0.22, "r_min": 6.0, "r_max": 12.0, "dist": "fuller", "q": 0.6}, # 大{"frac": 0.13, "r_min": 3.0, "r_max": 6.0, "dist": "fuller", "q": 0.6}, # 中{"frac": 0.05, "r_min": 1.5, "r_max": 3.0, "dist": "uniform"}, # 小
]# 气孔:设为“孔洞”方式(clip_boundary=False)
holes_cfg = {"frac": 0.012, # 也可用 "count": 固定个数"r_min": 0.8,"r_max": 2.0,"dist": "uniform","gap": 0.30, # 孔-孔、孔-骨料ITZ 之间净距"clip_boundary": False
}# 生成控制
CLIP_BOUNDARY = True # 骨料可截断边界
MAX_ATTEMPTS_PER_LEVEL = 60000
DENSIFY_MODE = "salvage" # "rsa" 或 "salvage"
SEED = 45
FUZZY_TOL = 1e-4 # 模糊布尔容差
PARALLEL_BOOL = True # 并行布尔(数据量很大时可 True)# ========= 生成 =========
res = generate_meso_2d(width=W, height=H,levels=levels,margin=MARGIN,gap=GAP,itz_thickness=ITZ_THICKNESS,itz_clearance=ITZ_CLEARANCE,max_attempts_per_level=MAX_ATTEMPTS_PER_LEVEL,densify_mode=DENSIFY_MODE,seed=SEED,holes=holes_cfg,fuzzy_tol=FUZZY_TOL,parallel_bool=PARALLEL_BOOL,
)# ========= 显示 =========
display, start_display, *_ = init_display()# 基体(默认色)
display.DisplayShape(res["matrix_face"], color = Q.Quantity_NOC_GRAY31, update=False)# 骨料(蓝)
for f in res["aggregate_faces"]:display.DisplayShape(f, color=Q.Quantity_NOC_BLUE1, update=False)# ITZ(黄)
for f in res["itz_faces"]:display.DisplayShape(f, color=Q.Quantity_NOC_YELLOW, update=False)# 气孔:不指定颜色(默认色显示;它们已作为孔洞加入基体)
# for f in res["hole_faces"]:
# display.DisplayShape(f, update=False)display.FitAll()
display.View_Top()# ========= 统计 =========
print("stats:", res["stats"])# 进入交互
start_display()
"dist"
:在 levels
每一层级的配置里,用来指定 骨料粒径分布的采样方式。
"uniform"
→ 在[r_min, r_max]
区间里 均匀随机采样半径- 举例:
r_min=3, r_max=6
,那么 3~6 之间每个半径被选到的概率是一样的
- 举例:
"fuller"
→ 模拟 Fuller 曲线(级配常用),按幂律采样:r^q
均匀,再开方得到半径,偏向小粒径q
值越小,越倾向小骨料;q≈0.5~0.7 时比较符合实际
作用:控制生成的粒径分布。你要均匀分布用 "uniform"
,要贴近 Fuller 级配就用 "fuller"
。
SEED = 45
: 随机数种子,保证结果可复现。
- 没有设定种子,每次运行都会得到不同的随机布置
- 设定
SEED = 45
,意味着每次运行都会生成一模一样的骨料布置(因为随机数序列固定了) - 换成
SEED = 42
、SEED = 99
等等,都会得到不同但“可复现”的布置
作用:控制生成的 随机性与复现性。调试/论文里需要结果可重复,建议固定 SEED
DENSIFY_MODE = "salvage"
:决定在放置骨料时,如果一个候选位置 本来会和其他骨料重叠,程序怎么处理:
"rsa"
(Random Sequential Adsorption)- 最传统的拒绝采样方式
- 如果随机到的位置重叠,直接舍弃,重新采样
- 简单但容易浪费,填充率比较低
"salvage"
(缩放保留,shrink-to-fit)- 如果位置冲突了,不直接丢掉,而是计算“允许的最大半径”,把骨料缩小一点放进去
- 这样能在孔隙里塞进更多骨料,整体体积分数更高
- 但缺点是,有些骨料的半径会比最初采样的小(只要不小于
r_min
)
作用:控制布置的 致密程度。
- 要严格粒径范围,不想骨料缩小 → 用
"rsa"
- 想尽量填充,提高致密度 → 用
"salvage"