跳转至

细观建模

约 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 = 42SEED = 99 等等,都会得到不同但“可复现”的布置

作用:控制生成的 随机性与复现性。调试/论文里需要结果可重复,建议固定 SEED


DENSIFY_MODE = "salvage":决定在放置骨料时,如果一个候选位置 本来会和其他骨料重叠,程序怎么处理:

  • "rsa"(Random Sequential Adsorption)
    • 最传统的拒绝采样方式
    • 如果随机到的位置重叠,直接舍弃,重新采样
    • 简单但容易浪费,填充率比较低
  • "salvage"(缩放保留,shrink-to-fit)
    • 如果位置冲突了,不直接丢掉,而是计算“允许的最大半径”,把骨料缩小一点放进去
    • 这样能在孔隙里塞进更多骨料,整体体积分数更高
    • 但缺点是,有些骨料的半径会比最初采样的小(只要不小于 r_min

作用:控制布置的 致密程度

  • 要严格粒径范围,不想骨料缩小 → 用 "rsa"
  • 想尽量填充,提高致密度 → 用 "salvage"