Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 34

"Размещение компонентов и вырезов в корпусе по размерам платы"

Recommended resources

Здравствуйте!

На прошлом уроке мы заложили прочную основу для нашего финального проекта, создав параметрическую функцию create_enclosure_block. Она генерирует монолитный блок корпуса, размеры которого полностью определяются параметрами печатной платы и заданными зазорами.

Сегодня мы переходим от внешней формы к внутреннему содержанию. Наша цель — интегрировать монтажные стойки и вырезы для портов в модель корпуса на основе размеров платы. Мы превратим сплошной блок в функциональное основание корпуса, добавив ключевые элементы для крепления платы и подключения внешних устройств. Этот урок — важный шаг к получению готового к печати изделия.

Мы будем действовать поэтапно:

  1. Создадим полую заготовку для основания корпуса.
  2. Добавим модель печатной платы в качестве визуального и геометрического ориентира.
  3. Спроектируем и разместим монтажные стойки для фиксации платы.
  4. Прорежем отверстия в стенках корпуса для портов.
Конструкция корпуса для печатной платы
Наша сегодняшняя цель — создать основание корпуса ("Enclosure Body") с элементами для крепления печатной платы ("PCB") и вырезами для портов, как показано в этой концепции.

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_...), что является хорошей инженерной практикой.

Что дальше?

У нас есть готовое основание корпуса. Но ему нужна крышка, а также механизм их соединения. В следующем уроке мы займемся именно этим: реализуем механизм соединения (начнем с винтового) для основания и крышки. Мы спроектируем бобышки под винты и соответствующие им элементы на крышке.