Recommended resources
The freely available resources this lesson will direct you to.
Здравствуйте!
На прошлом уроке мы заложили прочную основу для нашего финального проекта, создав параметрическую функцию create_enclosure_block. Она генерирует монолитный блок корпуса, размеры которого полностью определяются параметрами печатной платы и заданными зазорами.
Сегодня мы переходим от внешней формы к внутреннему содержанию. Наша цель — интегрировать монтажные стойки и вырезы для портов в модель корпуса на основе размеров платы. Мы превратим сплошной блок в функциональное основание корпуса, добавив ключевые элементы для крепления платы и подключения внешних устройств. Этот урок — важный шаг к получению готового к печати изделия.
Мы будем действовать поэтапно:
- Создадим полую заготовку для основания корпуса.
- Добавим модель печатной платы в качестве визуального и геометрического ориентира.
- Спроектируем и разместим монтажные стойки для фиксации платы.
- Прорежем отверстия в стенках корпуса для портов.

1. Подготовка: от монолита к полому основанию
Начнем с модификации кода из предыдущего урока. Нам больше не нужен сплошной блок. Вместо него мы создадим только нижнюю часть корпуса (основание) и сделаем ее полой.
Самый простой способ сделать полый короб — вычесть из внешнего блока внутренний, меньшего размера.
Давайте обновим нашу функцию. Теперь она будет создавать именно полое основание, а не весь блок целиком. Обратите внимание, как мы используем уже знакомые вам булевы операции и позиционирование.
from build123d import *
def create_enclosure_base(
pcb_width: float,
pcb_length: float,
pcb_height: float,
wall_thickness: float = 2.0,
side_clearance: float = 1.0,
bottom_clearance: float = 3.0,
) -> Part:
"""Создает параметрическое полое основание корпуса."""
# Внутренние размеры, необходимые для размещения платы
inner_width = pcb_width + 2 * side_clearance
inner_length = pcb_length + 2 * side_clearance
# Высота основания: зазор снизу + высота стоек + толщина дна
# Предположим, плата будет на стойках высотой bottom_clearance
base_height = bottom_clearance + wall_thickness
# Внешние размеры основания
outer_width = inner_width + 2 * wall_thickness
outer_length = inner_length + 2 * wall_thickness
# Внутренний объем, который нужно вычесть
hollow_width = inner_width
hollow_length = inner_length
hollow_height = base_height # Вычитаем на всю высоту
with BuildPart() as enclosure_base:
# Создаем внешний блок
Box(outer_width, outer_length, base_height)
# Создаем и вычитаем внутренний блок
# Смещаем его вверх на толщину дна
with Locations(Vector(0, 0, wall_thickness)):
Box(hollow_width, hollow_length, hollow_height, mode=Mode.SUBTRACT)
return enclosure_base.part
# ... (остальная часть скрипта будет добавлена ниже)
Мы рассчитали размеры так, чтобы после вычитания у нас остался короб с дном и стенками заданной толщины wall_thickness.
2. Интеграция модели печатной платы (PCB)
Чтобы точно размещать стойки и вырезы, нам нужна сама плата в виде 3D-модели. Мы создадим простую параметрическую модель платы (Box) и разместим её внутри нашего корпуса. Это будет наш главный ориентир.
Добавим функцию для создания платы и вызовем ее в основном скрипте.
def create_pcb_model(width: float, length: float, height: float) -> Part:
"""Создает простую модель печатной платы."""
with BuildPart() as pcb_model:
Box(width, length, height)
return pcb_model.part
# ... (в блоке if __name__ == "__main__")
# 1. Генерируем основание корпуса
base = create_enclosure_base(...)
# 2. Генерируем модель платы
pcb = create_pcb_model(...)
# 3. Позиционируем плату внутри корпуса
# Она должна "парить" на высоте стоек над дном
pcb_z_position = wall_thickness + bottom_clearance
with BuildPart() as final_assembly:
add(base)
# Делаем плату полупрозрачной для наглядности
add(pcb, at=Location(Vector(0, 0, pcb_z_position)), a=0.5, name="pcb")
Теперь при просмотре модели вы будете видеть и корпус, и плату внутри него, что сильно упростит дальнейшее проектирование.
3. Проектирование и размещение монтажных стоек (Standoffs)
Стойки — это цилиндрические выступы на дне корпуса, на которые устанавливается плата. В них делаются отверстия для крепежных винтов.
Для их создания нам понадобятся координаты крепежных отверстий на вашей реальной плате. Их нужно измерить относительно центра платы.
Давайте определим функцию add_standoffs, которая будет добавлять стойки к основанию корпуса. Она будет использовать циклы для итерации по списку координат, что является отличной практикой для создания повторяющихся элементов.
Introductory Examples - Build123d
В официальной документации есть примеры, которые демонстрируют ключевые техники для этой задачи. Например, как создавать объекты в нескольких точках с помощью списка Locations.
Обратите внимание на раздел 'Using Point Lists'. В нем показано, как использовать Locations со списком кортежей координат для создания нескольких окружностей. Мы применим этот же принцип для создания стоек.
Вот как может выглядеть функция и ее вызов:
def add_standoffs(
base_part: Part,
hole_locations: list[tuple[float, float]],
standoff_height: float,
standoff_diameter: float,
pilot_hole_diameter: float,
) -> Part:
"""Добавляет монтажные стойки ко дну корпуса."""
with BuildPart(base_part) as part_with_standoffs:
# Выбираем внутреннюю поверхность дна как рабочую плоскость
bottom_face = part_with_standoffs.faces().sort_by(Axis.Z)[0]
with BuildSketch(bottom_face):
# Размещаем стойки по заданным координатам
with Locations(*hole_locations):
Circle(standoff_diameter / 2)
# Выдавливаем стойки вверх
extrude(amount=standoff_height)
# Добавляем пилотные отверстия для винтов
with BuildSketch(bottom_face):
with Locations(*hole_locations):
Circle(pilot_hole_diameter / 2, mode=Mode.SUBTRACT)
# Вычитаем отверстия из стоек
# Глубина чуть меньше высоты стойки, чтобы не просверлить дно
extrude(amount=standoff_height * 0.8, mode=Mode.SUBTRACT)
return part_with_standoffs.part
# ... (в блоке if __name__ == "__main__")
# Координаты крепежных отверстий относительно центра платы (X, Y)
# ЗАМЕНИТЕ ЭТИ ЗНАЧЕНИЯ НА РЕАЛЬНЫЕ ИЗМЕРЕНИЯ ВАШЕЙ ПЛАТЫ!
pcb_mounting_holes = [
(25, 40), (-25, 40),
(25, -40), (-25, -40)
]
# ...
# Добавляем стойки
base_with_standoffs = add_standoffs(
base_part=base,
hole_locations=pcb_mounting_holes,
standoff_height=bottom_clearance,
standoff_diameter=5.0,
pilot_hole_diameter=2.0
)
Мы использовали BuildSketch на внутренней поверхности дна, чтобы гарантировать, что стойки начинаются именно оттуда. Затем с помощью Locations и цикла мы создали и выдавили цилиндры стоек и отверстия в них.
4. Создание вырезов для портов
Вырезы для портов (USB, Ethernet и т.д.) создаются по тому же принципу, что и стойки, но на боковых стенках корпуса. Мы определим параметры каждого порта (размеры и положение) и в цикле создадим для них вырезы.

Ключевая техника здесь — выбор нужной грани (стенки) и создание на ней эскиза.
Introductory Examples - Build123d
Для создания вырезов на конкретных стенках нам нужно научиться выбирать эти стенки и использовать их как рабочие плоскости. В документации есть наглядный пример.
Изучите раздел 'Creating Workplanes on Faces'. Он показывает, как выбрать грань, создать на ней эскиз (BuildSketch) и затем вырезать (extrude с отрицательным значением) геометрию. Это именно то, что нам нужно для создания вырезов.
Мы можем представить порты в виде структурированного списка, что соответствует вашему опыту в data engineering. Это делает код чистым и легко расширяемым.
def add_port_cutouts(
base_part: Part,
ports: list[dict],
pcb_z_pos: float,
wall_thickness: float,
) -> Part:
"""Вырезает отверстия для портов в стенках корпуса."""
with BuildPart(base_part) as part_with_cutouts:
for port in ports:
# Выбираем нужную стенку
if port["wall"] == "front": # Стена по +Y
wall_face = part_with_cutouts.faces().filter_by(Axis.Y).last
elif port["wall"] == "back": # Стена по -Y
wall_face = part_with_cutouts.faces().filter_by(Axis.Y).first
# Добавьте 'left' и 'right' по аналогии, если нужно
else:
continue
with BuildSketch(wall_face):
# Рассчитываем центр выреза на стенке
# port["center"][0] -> X-координата центра порта на плате
# port["center"][1] -> Z-координата центра порта на плате (относительно низа платы)
cutout_center = (
port["center"][0],
pcb_z_pos + port["center"][1] - part_with_cutouts.part.bounding_box().min.Z
)
Rectangle(*port["size"], at=cutout_center)
# Вырезаем отверстие "насквозь"
extrude(amount=-wall_thickness, mode=Mode.SUBTRACT)
return part_with_cutouts.part
# ... (в блоке if __name__ == "__main__")
# Описание портов на вашей плате
# ЗАМЕНИТЕ НА РЕАЛЬНЫЕ ДАННЫЕ!
# 'wall': на какой стенке корпуса порт ('front' -> +Y, 'back' -> -Y)
# 'center': (X, Z) координаты центра порта относительно центра низа платы
# 'size': (width, height) размер выреза
ports_definitions = [
{
"wall": "back",
"center": (0, 6), # по центру оси Х, на 6 мм выше низа платы
"size": (20, 10) # для USB-C, например
}
]
# ...
# Добавляем вырезы
final_base = add_port_cutouts(
base_part=base_with_standoffs,
ports=ports_definitions,
pcb_z_pos=pcb_z_position,
wall_thickness=WALL_THICKNESS # Используем глобальную константу
)
Итоговый код
Вот как будет выглядеть ваш основной файл проекта после всех сегодняшних добавлений.
# full_script.py
from build123d import *
# ----------------- ПАРАМЕТРЫ ПРОЕКТА -----------------
# (Эти значения вы будете менять под свою плату и нужды)
# Размеры печатной платы
PCB_WIDTH = 65.0
PCB_LENGTH = 100.0
PCB_HEIGHT = 12.0 # Высота с учетом компонентов
# Параметры корпуса
WALL_THICKNESS = 1.8
SIDE_CLEARANCE = 1.5
BOTTOM_CLEARANCE = 5.0 # Это же будет высота стоек
# Параметры стоек
STANDOFF_DIAMETER = 5.0
PILOT_HOLE_DIAMETER = 2.0
# Координаты крепежных отверстий на плате (относительно центра)
PCB_MOUNTING_HOLES = [
(25, 40), (-25, 40),
(25, -40), (-25, -40)
]
# Описание портов
PORTS = [
{
"wall": "back", # Стена в направлении -Y
"center": (0, 6.0), # (X, Z) от центра низа платы
"size": (12.0, 8.0)
}
]
# ----------------- ФУНКЦИИ-КОНСТРУКТОРЫ -----------------
def create_enclosure_base(...) -> Part:
# ... (код функции, как описано выше) ...
def add_standoffs(...) -> Part:
# ... (код функции, как описано выше) ...
def add_port_cutouts(...) -> Part:
# ... (код функции, как описано выше) ...
def create_pcb_model(width: float, length: float, height: float) -> Part:
"""Создает простую модель печатной платы."""
with BuildPart() as pcb_model:
Box(width, length, height)
return pcb_model.part
# ----------------- ОСНОВНОЙ СКРИПТ СБОРКИ -----------------
if __name__ == "__main__":
# 1. Создаем полое основание
base = create_enclosure_base(
pcb_width=PCB_WIDTH,
pcb_length=PCB_LENGTH,
pcb_height=PCB_HEIGHT,
wall_thickness=WALL_THICKNESS,
side_clearance=SIDE_CLEARANCE,
bottom_clearance=BOTTOM_CLEARANCE,
)
# 2. Добавляем монтажные стойки
base_with_standoffs = add_standoffs(
base_part=base,
hole_locations=PCB_MOUNTING_HOLES,
standoff_height=BOTTOM_CLEARANCE,
standoff_diameter=STANDOFF_DIAMETER,
pilot_hole_diameter=PILOT_HOLE_DIAMETER,
)
# 3. Рассчитываем положение платы для следующих шагов
pcb_z_position = WALL_THICKNESS + BOTTOM_CLEARANCE
# 4. Добавляем вырезы для портов
final_base = add_port_cutouts(
base_part=base_with_standoffs,
ports=PORTS,
pcb_z_pos=pcb_z_position,
wall_thickness=WALL_THICKNESS,
)
# 5. Собираем все для визуализации (опционально)
pcb = create_pcb_model(PCB_WIDTH, PCB_LENGTH, PCB_HEIGHT)
# Чтобы видеть результат в OCP CAD Viewer
show_object(final_base, name="base")
show_object(pcb, name="pcb", options={"alpha": 0.6, "color": (0.1, 0.8, 0.2)}, at=Location(Vector(0,0,pcb_z_position)))
# Экспорт финальной модели
final_base.export_stl("enclosure_base.stl")
Проверьте себя!
Вам нужно добавить еще один порт (например, для питания) на переднюю стенку ("front") корпуса. Ваши действия в блоке if __name__ == "__main__"?
Показать ответ
Нужно просто добавить еще один словарь в список PORTS. Например:
PORTS = [
{
"wall": "back",
"center": (0, 6.0),
"size": (12.0, 8.0)
},
{
"wall": "front",
"center": (20.0, 5.0), # Смещен вправо, на 5 мм от низа платы
"size": (7.0, 7.0) # Круглый разъем питания в квадратном вырезе
}
]
Никаких других изменений в коде не потребуется. Функция add_port_cutouts автоматически обработает новый элемент списка и создаст соответствующий вырез на передней стенке. Это и есть мощь параметрического и декларативного подхода.
Ключевые выводы
- Мы успешно интегрировали в нашу модель два важнейших функциональных элемента: монтажные стойки и вырезы для портов.
- Мы использовали данные (списки координат и словари) для управления геометрией, что делает код чистым, читаемым и легко расширяемым.
- Мы активно применяли изученные ранее техники: выбор граней (
.faces().filter_by()), создание эскизов на них (BuildSketch(face)), итерацию для создания множественных объектов и булевы операции. - Наш проект теперь состоит из нескольких логических функций (
create_...,add_...), что является хорошей инженерной практикой.
Что дальше?
У нас есть готовое основание корпуса. Но ему нужна крышка, а также механизм их соединения. В следующем уроке мы займемся именно этим: реализуем механизм соединения (начнем с винтового) для основания и крышки. Мы спроектируем бобышки под винты и соответствующие им элементы на крышке.