跳转至

布尔运算

约 136 个字 295 行代码 5 张图片 预计阅读时间 4 分钟

Cut(差集)

from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut
from OCC.Display.SimpleGui import init_display

display, start_display, *_ = init_display()

# 几何体 A:立方体
box = BRepPrimAPI_MakeBox(20, 20, 20).Shape()

# 几何体 B:圆柱
cyl = BRepPrimAPI_MakeCylinder(10, 20).Shape()

# 布尔差集:A - B
cut = BRepAlgoAPI_Cut(box, cyl).Shape()

display.DisplayShape(cut, color="BLUE1", transparency=0.5 ,update=True)
display.FitAll()
start_display()

改进版

当遇到多次Cut的时候,会遇到小缝隙或重叠,以及计算成本增加,为此,可以在原来的基础上增加一个更为稳健的版本:

def fuzzy_cut(shape_A, shape_B, tol=5e-5, parallel=False):
    """returns shape_A - shape_B"""
    cut = BRepAlgoAPI_Cut()
    L1 = TopTools_ListOfShape()
    L1.Append(shape_A)
    L2 = TopTools_ListOfShape()
    L2.Append(shape_B)
    cut.SetArguments(L1)
    cut.SetTools(L2)
    cut.SetFuzzyValue(tol)
    cut.SetRunParallel(parallel)
    cut.Build()
    return cut.Shape()

允许A、B为列表的形式,内部可开启并行开关parallel,现在再来试一下,多次布尔的效果:

import random
import time

from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder
from OCC.Core.gp import gp_Pnt, gp_Vec, gp_Ax2, gp_Dir
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut
from OCC.Core.TopTools import TopTools_ListOfShape
import OCC.Core.Quantity as Q
from OCC.Display.SimpleGui import init_display


def random_vec():
    x, y, z = [random.uniform(-1.0, 1.0) for _ in range(3)]
    return gp_Vec(x, y, z)


def fuzzy_cut(shape_a, shape_b, tol=1e-4, parallel=True):
    """
    返回 shape_a - shape_b 的布尔差集。
    关键:SetFuzzyValue(tol) 允许“模糊”匹配,缓解共面/微小间隙导致失败的问题。
    """
    op = BRepAlgoAPI_Cut()
    lst_a = TopTools_ListOfShape()
    lst_a.Append(shape_a)
    lst_b = TopTools_ListOfShape()
    lst_b.Append(shape_b)

    op.SetArguments(lst_a)
    op.SetTools(lst_b)
    op.SetFuzzyValue(tol)          # 模糊容差(根据模型尺度适当调)
    op.SetRunParallel(parallel)    # 可选:多线程
    op.Build()
    return op.Shape()


def main():
    display, start_display, _, _ = init_display()

    scope = 200.0
    n_cyl = 40

    box = BRepPrimAPI_MakeBox(scope, scope, scope).Shape()

    def make_random_cylinder():
        ax2 = gp_Ax2()
        # 随机中心位置
        ax2.SetLocation(gp_Pnt((random_vec() * scope).XYZ()))
        # 随机轴向
        ax2.SetDirection(gp_Dir(random_vec()))
        cyl = BRepPrimAPI_MakeCylinder(ax2, random.uniform(8.0, 36.0), 5000.0)
        return cyl.Shape()

    t0 = time.time()
    shp = box
    for i in range(n_cyl):
        cyl = make_random_cylinder()
        ti = time.time()
        shp = fuzzy_cut(shp, cyl, tol=1e-4, parallel=False)
        print(f"Cut #{i:02d}  took {time.time() - ti:.3f}s")

    print(f"Total time: {time.time() - t0:.3f}s")

    display.DisplayShape(shp, color = Q.Quantity_NOC_SKYBLUE, transparency=0.3,update=True)
    display.FitAll()
    start_display()


if __name__ == "__main__":
    main()

Fuse(并集)

from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Fuse
from OCC.Display.SimpleGui import init_display

display, start_display, *_ = init_display()

# 几何体 A:立方体
box = BRepPrimAPI_MakeBox(20, 20, 20).Shape()

# 几何体 B:圆柱
cyl = BRepPrimAPI_MakeCylinder(10, 20).Shape()

# 布尔并集:A ∪ B
result = BRepAlgoAPI_Fuse(box, cyl).Shape()

display.DisplayShape(result, color="BLUE1", transparency=0.5 ,update=True)
display.FitAll()
start_display()

相同的道理可以做一个更为稳健的并集操作:

def fuzzy_fuse(shape_a, shape_b, tol=1e-4, parallel=False):
    """
    返回 shape_a ∪ shape_b 的模糊并集结果
    """
    op = BRepAlgoAPI_Fuse()
    lst_a = TopTools_ListOfShape()
    lst_a.Append(shape_a)
    lst_b = TopTools_ListOfShape()
    lst_b.Append(shape_b)

    op.SetArguments(lst_a)
    op.SetTools(lst_b)
    op.SetFuzzyValue(tol)         
    op.SetRunParallel(parallel)   
    op.Build()

    return op.Shape()

Common(交集)

from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Common
from OCC.Display.SimpleGui import init_display

display, start_display, *_ = init_display()

# 几何体 A:立方体
box = BRepPrimAPI_MakeBox(20, 20, 20).Shape()

# 几何体 B:圆柱
cyl = BRepPrimAPI_MakeCylinder(10, 20).Shape()

# 布尔交集:A ∩ B
result = BRepAlgoAPI_Common(box, cyl).Shape()

display.DisplayShape(result, color="BLUE1", transparency=0.5 ,update=True)
display.FitAll()
start_display()

相同的道理,提供一个更稳健的版本:

def fuzzy_common(shape_a, shape_b, tol=1e-4, parallel=False):
    """
    返回交集 shape_a ∩ shape_b
    """
    op = BRepAlgoAPI_Common()
    lst_a = TopTools_ListOfShape(); lst_a.Append(shape_a)
    lst_b = TopTools_ListOfShape(); lst_b.Append(shape_b)

    op.SetArguments(lst_a)
    op.SetTools(lst_b)
    op.SetFuzzyValue(tol)
    op.SetRunParallel(parallel)
    op.Build()

    return op.Shape()

综合应用

from OCC.Core.gp import gp_Pnt
from OCC.Core.BRepBuilderAPI import (
    BRepBuilderAPI_MakeFace,
    BRepBuilderAPI_MakePolygon,
    BRepBuilderAPI_Sewing,
    BRepBuilderAPI_MakeSolid,
)
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Fuse, BRepAlgoAPI_Common, BRepAlgoAPI_Cut
from OCC.Core.TopoDS import topods
from OCC.Display.SimpleGui import init_display


def MakeSolidFromShell(shell):
    ms = BRepBuilderAPI_MakeSolid()
    ms.Add(topods.Shell(shell))
    solid = ms.Solid()
    return solid


def make_face_from_4_points(pnt1, pnt2, pnt3, pnt4):
    # 用 4 点闭合多段线 Wire
    poly = BRepBuilderAPI_MakePolygon(pnt1, pnt2, pnt3, pnt4, True).Wire()
    # 做Face
    return BRepBuilderAPI_MakeFace(poly).Face()


def get_faceted_L_shape(x, y, z):
    # 手工拼 8 个面(其中 2 个是 6 边形面)
    pnt_A = gp_Pnt(x + 0, y + 0, z + 0)
    pnt_B = gp_Pnt(x + 20, y + 0, z + 0)
    pnt_C = gp_Pnt(x + 20, y + 10, z + 0)
    pnt_D = gp_Pnt(x + 0, y + 10, z + 0)
    pnt_E = gp_Pnt(x + 0, y + 0, z + 20)
    pnt_F = gp_Pnt(x + 10, y + 0, z + 20)
    pnt_G = gp_Pnt(x + 10, y + 10, z + 20)
    pnt_H = gp_Pnt(x + 0, y + 10, z + 20)
    pnt_I = gp_Pnt(x + 10, y + 0, z + 10)
    pnt_J = gp_Pnt(x + 10, y + 10, z + 10)
    pnt_K = gp_Pnt(x + 20, y + 0, z + 10)
    pnt_L = gp_Pnt(x + 20, y + 10, z + 10)

    face_1 = make_face_from_4_points(pnt_A, pnt_B, pnt_C, pnt_D)
    face_2 = make_face_from_4_points(pnt_B, pnt_C, pnt_L, pnt_K)
    face_3 = make_face_from_4_points(pnt_E, pnt_F, pnt_G, pnt_H)
    face_4 = make_face_from_4_points(pnt_A, pnt_E, pnt_H, pnt_D)
    face_5 = make_face_from_4_points(pnt_G, pnt_F, pnt_I, pnt_J)
    face_6 = make_face_from_4_points(pnt_I, pnt_K, pnt_L, pnt_J)

    polygon_1 = BRepBuilderAPI_MakePolygon()
    polygon_1.Add(pnt_A)
    polygon_1.Add(pnt_B)
    polygon_1.Add(pnt_K)
    polygon_1.Add(pnt_I)
    polygon_1.Add(pnt_F)
    polygon_1.Add(pnt_E)
    polygon_1.Close()

    face_7 = BRepBuilderAPI_MakeFace(polygon_1.Wire()).Face()
    polygon_2 = BRepBuilderAPI_MakePolygon()
    polygon_2.Add(pnt_D)
    polygon_2.Add(pnt_H)
    polygon_2.Add(pnt_G)
    polygon_2.Add(pnt_J)
    polygon_2.Add(pnt_L)
    polygon_2.Add(pnt_C)
    polygon_2.Close()
    face_8 = BRepBuilderAPI_MakeFace(polygon_2.Wire()).Face()

    # 缝合,合并共边,
    sew = BRepBuilderAPI_Sewing()
    for face in [face_1, face_2, face_3, face_4, face_5, face_6, face_7, face_8]:
        sew.Add(face)
    sew.Perform()

    # 得到封闭壳
    return sew.SewedShape()


spacing = 30
# cut (box - box)
yshift = 0
myBox11 = BRepPrimAPI_MakeBox(gp_Pnt(0, yshift, 0), 10, 10, 10).Shape()
myBox12 = BRepPrimAPI_MakeBox(gp_Pnt(5, yshift + 5, 5), 10, 10, 10).Shape()
myCut1 = BRepAlgoAPI_Cut(myBox11, myBox12).Shape()

# cut (L-Shape - box)
yshift += spacing
myBox21 = get_faceted_L_shape(0, yshift, 0)
myBox22 = BRepPrimAPI_MakeBox(gp_Pnt(15, yshift + 5, 5), 10, 10, 10).Shape()
myCut2 = BRepAlgoAPI_Cut(myBox21, myBox22).Shape()

# intersection  (L-Shape - box)
yshift += spacing
myBox23 = get_faceted_L_shape(0, yshift, 0)
myBox24 = BRepPrimAPI_MakeBox(gp_Pnt(15, yshift + 5, 5), 10, 10, 10).Shape()
myCut3 = BRepAlgoAPI_Common(myBox23, myBox24).Shape()

# Simple box CUT from LShape using MakeSolidFromShell
yshift += spacing
myBox31 = MakeSolidFromShell(get_faceted_L_shape(0, yshift, 0))
myBox32 = BRepPrimAPI_MakeBox(gp_Pnt(15, yshift + 5, 5), 10, 10, 10).Shape()
myCut4 = BRepAlgoAPI_Cut(myBox31, myBox32).Shape()

# Simple box FUSE from LShape using MakeSolidFromShell
yshift += spacing
myBox33 = MakeSolidFromShell(get_faceted_L_shape(0, yshift, 0))
myBox34 = BRepPrimAPI_MakeBox(gp_Pnt(15, yshift + 5, 5), 10, 10, 10).Shape()
myCut5 = BRepAlgoAPI_Fuse(myBox33, myBox34).Shape()


# Simple box COMMON with LShape using MakeSolidFromShell
yshift += spacing
myBox35 = MakeSolidFromShell(get_faceted_L_shape(0, yshift, 0))
myBox36 = BRepPrimAPI_MakeBox(gp_Pnt(15, yshift + 5, 5), 10, 10, 10).Shape()
myCut6 = BRepAlgoAPI_Common(myBox35, myBox36).Shape()

# display stage
display, start_display, add_menu, add_function_to_menu = init_display()
display.DisplayShape(myCut1, color="RED", transparency=0.7)
display.DisplayShape(myCut2, color="BLUE", transparency=0.7)
display.DisplayShape(myCut3, color="WHITE", transparency=0.7)
display.DisplayShape(myCut4, color="GREEN", transparency=0.7)
display.DisplayShape(myCut5, color="YELLOW", transparency=0.7)
display.DisplayShape(myCut6, color="CYAN", transparency=0.7)
display.FitAll()
start_display()