细观建模¶
约 517 个字 652 行代码 2 张图片 预计阅读时间 10 分钟
骨料建模¶
可以在挖孔的基础上,做一个混凝土随机骨料模型
import random
import math
from 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.6
u = 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.0
target_area = target_area_frac * W * H
attempts = 0
while placed_area < target_area and attempts < max_attempts:
attempts += 1
r = sample_radius()
cx = random.uniform(margin + r, W - margin - r)
cy = random.uniform(margin + r, H - margin - r)
ok = True
for (x, y, rr) in circles:
dx, dy = cx - x, cy - y
if (dx*dx + dy*dy) < (r + rr + gap)**2:
ok = False
break
if ok:
circles.append((cx, cy, r))
placed_area += math.pi * r * r
return 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, Optional
from 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 None
acc = shapes[0]
for s in shapes[1:]:
acc = fuzzy_fuse(acc, s)
return acc
def _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 均匀 -> r
q = 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 - y2
return dx * dx + dy * dy
def _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_try
for (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.0
if 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.0
attempts = 0
rmin = float(level["r_min"])
while area_sum < target_area_abs and attempts < max_attempts:
attempts += 1
r = _sample_radius(level)
# 采样中心
cx = random.uniform(margin + r, W - margin - r)
cy = random.uniform(margin + r, H - margin - r)
# 快速可行性(考虑 ITZ + 安全间距)
ok = True
for (x, y, rj) in (already + placed):
if _dist2(cx, cy, x, y) < ((r + itz_thickness) + (rj + itz_thickness) + gap + itz_clearance) ** 2:
ok = False
break
if ok:
placed.append((cx, cy, r))
area_sum += math.pi * r * r
continue
# shrink-to-fit
if 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_acc
return 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.0
for idx, lv in enumerate(levels_sorted):
frac = float(lv["frac"])
if frac <= 0.0:
continue
target_area_abs = frac * domain_area
placed, 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_added
all_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 = False
if holes is not None and (holes.get("frac") or holes.get("count")):
h_gap = float(holes.get("gap", gap))
h_clip = False
holes_as_holes = not h_clip # 不截断=>可用 Reverse 方式
target_h_area = None
if "frac" in holes and holes["frac"] is not None:
target_h_area = float(holes["frac"]) * domain_area
target_h_count = int(holes.get("count", 0))
placed_h = []
area_h = 0.0
attempts = 0
max_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 True
if target_h_count and len(placed_h) >= target_h_count:
return True
return False
while not _stop() and attempts < max_attempts_h:
attempts += 1
r = _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_clearance
if _dist2(cx, cy, x, y) < (r + r_eff_agg + h_gap) ** 2:
ok = False
break
# 2) 与已放置气孔之间
if ok:
for (x, y, rj) in placed_h:
if _dist2(cx, cy, x, y) < (r + rj + h_gap) ** 2:
ok = False
break
if ok:
placed_h.append((cx, cy, r))
area_h += math.pi * r * r
continue
# shrink-to-fit(孔对孔/孔对骨料 ITZ)
r_allow = r
for (x, y, rj) in base_aggs:
d = math.sqrt(_dist2(cx, cy, x, y))
r_eff_agg = rj + itz_thickness + itz_clearance
r_allow = min(r_allow, max(0.0, d - r_eff_agg - h_gap))
if r_allow <= 0.0:
break
if 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:
break
if 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_matrix
else:
# 统一布尔:域 - (骨料 ∪ 孔)
cutters = agg_union
if 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_union
for (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) # 去掉骨料本体,剩余在基体中的 ITZ
itz_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"