Разделение тел: split и slice

Операции split и slice разрезают тело на отдельные объёмные части.

split — разделить тело заданной геометрией

split(body, tools) разрезает тело body с помощью одного или нескольких геометрических инструментов. Инструмент задаёт границу разреза: это может быть плоская грань, например infplane(), или другое тело.

Результат — SplitResult, коллекция отдельных тел Solid. Части можно перебирать, получать по индексу и обрабатывать независимо. Их количество зависит от расположения инструментов.

Две плоскости — три слоя

Разрежем параллелепипед высотой 18 двумя горизонтальными плоскостями на отметках 6 и 12. Получатся три слоя толщиной 6. Для рисунка переместим весь набор вправо и добавим зазоры между слоями.

from zencad import *

body = box(30, 20, 18)
planes = [infplane().up(6), infplane().up(12)]
parts = split(body, planes)

# Расположим три слоя снизу вверх.
layers = sorted(parts, key=lambda part: float(part.center().z))
assert len(layers) == 3

# Слева исходное тело, справа — разнесённые слои.
display(body, color=Color(0.7, 0.7, 0.7))
for index, part in enumerate(layers):
    display(part.translate(45, 0, index * 7),
            color=[Color(0.2, 0.6, 0.8), Color(1, 0.65, 0.2),
                   Color(0.4, 0.75, 0.4)][index])
show()

Слева исходное тело, справа три разнесённых слоя

Разделение цилиндром

Здесь инструментом служит цилиндр, проходящий насквозь через куб. Получаются две части: куб с цилиндрическим отверстием и цилиндрическая сердцевина. При вычитании body - cutter осталась бы только первая часть; split сохраняет обе.

from zencad import *

body = box(24)
cutter = cylinder(r=6, h=32).translate(12, 12, -4)
parts = split(body, cutter)

# При этих размерах цилиндрическая часть имеет меньший объём.
core, remainder = sorted(parts, key=lambda part: float(part.mass()))
assert abs(float(core.mass() + remainder.mass() - body.mass())) < 1e-6

# Исходное тело, тело с отверстием и извлечённая сердцевина.
display(body, color=Color(0.7, 0.7, 0.7))
display(remainder.right(36), color=Color(0.2, 0.6, 0.8))
display(core.right(64), color=Color(1, 0.65, 0.2))
show()

Слева исходный куб, в центре часть с отверстием, справа цилиндрическая сердцевина

Пустой набор инструментов вызывает ValueError; входная форма должна содержать объёмные тела.

slice — разделить тело одной плоскостью

slice разделяет тело одной плоскостью. Для горизонтального разреза достаточно указать высоту: parts = slice(body, z=8). Части упорядочены по проекции их центров на нормаль плоскости.

Результат SliceResult — коллекция тел. Если плоскость не разрезает тело или только касается его, в коллекции остаётся одно тело. Например, len(slice(box(2), z=3)) == 1.

При двух частях можно написать lower, upper = parts. Свойства .lower и .upper — синонимы parts[0] и parts[1]; при одной части обращение к .upper вызывает IndexError при вычислении.

Наклонный разрез

Произвольная плоскость задаётся парой plane=(точка, нормаль). В примере она проходит через (0, 0, 8) и имеет нормаль (0, -0.3, 1): высота разреза увеличивается от 8 до 14 при движении по Y от 0 до 20.

Для наклонной плоскости порядок частей определяется её нормалью: первая лежит с отрицательной стороны, вторая — с положительной.

from zencad import *

body = box(30, 20, 20)
plane = ((0, 0, 8), (0, -0.3, 1))  # Точка на плоскости и её нормаль.
lower, upper = slice(body, plane=plane)

assert abs(float(lower.mass() + upper.mass() - body.mass())) < 1e-6

# Переносы нужны только для наглядного отображения частей.
display(body, color=Color(0.7, 0.7, 0.7))
display(lower.right(45), color=Color(0.2, 0.6, 0.8))
display(upper.translate(45, 0, 12), color=Color(1, 0.65, 0.2))
show()

Слева исходное тело, справа две части с наклонными поверхностями разреза