Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Применение встроенных паттернов для создания массивов элементов

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

В предыдущем уроке мы научились создавать массивы элементов с помощью циклов for в Python. Мы увидели, как, комбинируя циклы с Locations, можно программно генерировать линейные, сеточные и даже круговые паттерны. Это мощный и гибкий подход, который полностью раскрывает возможности "CAD-as-Code".

Однако для самых частых задач — создания рядов и круговых массивов — существует более простой и декларативный способ. Цель этого урока — изучить встроенные в build123d инструменты LinearPattern и CircularPattern. Они позволяют создавать такие же массивы элементов, но делают код более коротким, читаемым и менее подверженным ошибкам, так как вся математика скрыта "под капотом". Мы сравним этот подход с ручным созданием через циклы и увидим его преимущества.

1. От цикла к LinearPattern

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

# Фрагмент из прошлого урока
start_x = -((hole_count - 1) * hole_spacing) / 2
for i in range(hole_count):
    x_pos = start_x + i * hole_spacing
    with Locations((x_pos, 0)):
        Circle(hole_radius)

LinearPattern позволяет заменить этот код одной строкой. Он берет один или несколько 2D-объектов и создает их копии вдоль заданной линии.

Давайте создадим новый файл lesson12_patterns.py и перепишем пример с рядом отверстий, используя LinearPattern.

# lesson12_patterns.py
from build123d import *

# Параметры
plate_len = 100
plate_width = 50
plate_height = 5
hole_radius = 4
hole_count = 5
hole_spacing = 20

with BuildPart() as part:
    # 1. Создаем базовую пластину
    Box(plate_len, plate_width, plate_height)

    # 2. Создаем эскиз на верхней грани
    with BuildSketch(part.faces().sort_by(Axis.Z)[-1]) as sketch:
        # 3. Создаем ОДНУ окружность, которая будет "шаблоном"
        # 4. Применяем к ней LinearPattern
        LinearPattern(
            x_spacing=hole_spacing,
            count=hole_count,
        )
        Circle(hole_radius) # Эта окружность будет размножена

    # 5. Выдавливаем все созданные окружности
    extrude(amount=-plate_height, mode=Mode.SUBTRACT)

# Отображаем результат
show_object(part.part, name="Linear Pattern of Holes")

Запустите код. Результат будет идентичен тому, что мы получали с помощью цикла for. Однако обратите внимание на разницу в подходе внутри BuildSketch:

  1. Мы больше не используем Locations и не вычисляем координаты вручную.
  2. Мы создаем только одну "исходную" фигуру (Circle).
  3. LinearPattern применяется до создания фигуры. Он действует как модификатор для всех последующих объектов внутри своего контекста (в данном случае, для Circle).
  4. По умолчанию, паттерн центрируется относительно начала координат эскиза. Это очень удобно.

Основные параметры LinearPattern:

  • x_spacing, y_spacing: шаг между элементами по осям X и Y.
  • count: общее количество элементов (включая исходный).
  • x_count, y_count: если нужно создать сетку, можно указать количество элементов по каждой оси.

Этот декларативный стиль ("создай мне линейный массив из 5 окружностей с шагом 20") гораздо лучше отражает намерение, чем императивный стиль цикла ("повтори 5 раз: посчитай координату, сместись, нарисуй окружность"). Для вас, как для опытного разработчика, преимущество в читаемости и поддержке такого кода должно быть очевидно.

2. От тригонометрии к CircularPattern

В конце прошлого урока было задание: создать круговой массив отверстий. Для этого нам пришлось импортировать модуль math и вспоминать тригонометрию для перевода полярных координат в декартовы.

# Фрагмент из решения к прошлому уроку
for i in range(hole_count):
    angle_rad = math.radians((360 / hole_count) * i)
    x = pattern_radius * math.cos(angle_rad)
    y = pattern_radius * math.sin(angle_rad)
    with Locations((x, y)):
        Circle(...)

Как вы уже догадались, CircularPattern полностью избавляет нас от этой рутины.

Давайте воссоздадим тот же пример с его помощью.

# lesson12_patterns.py (продолжение)
from build123d import *

# Параметры
plate_radius = 40
plate_height = 5
hole_count = 8
hole_diameter = 5
pattern_radius = 30 # Радиус, по которому располагаются отверстия

with BuildPart() as part_circular:
    # Создаем круглую пластину
    Cylinder(radius=plate_radius, height=plate_height)

    with BuildSketch(part_circular.faces().sort_by(Axis.Z)[-1]) as sketch:
        # 1. Создаем ОДНУ исходную окружность, смещенную от центра
        #    на расстояние pattern_radius.
        with Locations((pattern_radius, 0)):
            Circle(hole_diameter / 2)
        
        # 2. Применяем CircularPattern ко всему, что было создано в эскизе до этого.
        #    Обратите внимание: здесь CircularPattern вызывается ПОСЛЕ создания объекта.
        #    Он находит последний созданный объект и копирует его.
        CircularPattern(count=hole_count)

    extrude(amount=-plate_height, mode=Mode.SUBTRACT)

# Закомментируйте предыдущий show_object
show_object(part_circular.part, name="Circular Pattern of Holes")

Снова сравним подходы. CircularPattern сделал за нас всю тригонометрию. Нам нужно было лишь определить что мы хотим размножить (окружность) и где находится ее первая инстанция (на расстоянии pattern_radius от центра).

Ключевые параметры CircularPattern:

  • count: количество элементов.
  • angle: общая дуга, по которой распределяются элементы (по умолчанию 360 градусов).
  • center: центр вращения (по умолчанию (0, 0)).

Как видите, использование встроенных паттернов не только упрощает код, но и делает его семантически более ясным.

Примеры объектов с паттернами отверстий
Паттерны — это фундаментальный инструмент в CAD. Они используются повсеместно: от вентиляционных решеток и крепежных отверстий до декоративных элементов, как на этих примерах.
Проверьте себя!

Задание:
Создайте макет крепежного фланца. Это должна быть квадратная пластина 80x80 мм, толщиной 5 мм. В центре пластины — сквозное отверстие диаметром 40 мм. Вокруг центрального отверстия, по углам воображаемого квадрата 60x60 мм, расположены 4 крепежных отверстия диаметром 5 мм.

Подсказка:
Самый простой способ создать 4 отверстия по углам — это создать одно отверстие в нужной позиции (например, (30, 30)), а затем использовать PolarLocations или GridLocations. Однако, попробуйте решить задачу, используя только LinearPattern, чтобы закрепить материал. Например, создайте два отверстия в линию, а затем примените к ним еще один LinearPattern в перпендикулярном направлении.

Показать решение
# lesson12_exercise_solution.py
from build123d import *

# Параметры
plate_size = 80
plate_thk = 5
center_hole_d = 40
mount_hole_d = 5
mount_hole_spacing = 60

with BuildPart() as flange:
    # Основа
    Box(plate_size, plate_size, plate_thk)
    
    with BuildSketch() as sketch: # Эскиз на плоскости XY
        # Центральное отверстие
        Circle(center_hole_d / 2)
        
        # Создаем группу из двух отверстий по оси X
        with Locations(
            (-mount_hole_spacing / 2, 0), (mount_hole_spacing / 2, 0)
        ):
            # Применяем LinearPattern по оси Y к этой группе
            LinearPattern(y_spacing=mount_hole_spacing, count=2)
            Circle(mount_hole_d / 2, mode=Mode.SUBTRACT) # Режим вычитания сразу в эскизе

    # Вычитаем центральное отверстие из пластины
    extrude(amount=-plate_thk, mode=Mode.SUBTRACT)

show_object(flange.part, name="Flange with 4 holes")

Примечание: в этом решении используется mode=Mode.SUBTRACT прямо в эскизе. Это альтернативный способ. Сначала создается Rectangle(plate_size, plate_size), а потом из него вычитаются окружности. Затем весь эскиз выдавливается. Это более "2D-центричный" подход. Вариант из начала урока (создать 3D-тело, а потом вырезать эскиз) тоже абсолютно правильный.

3. Паттерны для 3D-объектов

Важно понимать, что паттерны можно применять не только к 2D-фигурам в эскизах, но и к полноценным 3D-объектам (features). Например, вместо того чтобы создавать паттерн из окружностей и затем выдавливать их, можно создать один выступ (boss), а затем размножить его.

Это особенно полезно при создании монтажных стоек (standoffs) внутри корпуса.

# lesson12_patterns.py (третий пример)
from build123d import *

with BuildPart() as part_3d_pattern:
    # 1. Базовая пластина
    Box(100, 40, 3)

    # 2. Создаем одну монтажную стойку (standoff)
    with Locations((-(100/2-10), 0, 3)): # Позиционируем на поверхности
        Cylinder(radius=5, height=10)

    # 3. Выбираем последнюю созданную 3D-операцию (создание цилиндра)
    #    и применяем к ней LinearPattern
    LinearPattern(
        x_spacing=20,
        count=4,
        # Важно указать плоскость и направление для 3D-паттерна
        plane=Plane.XY,
        direction=(1, 0, 0) 
    )

# Закомментируйте предыдущий show_object
show_object(part_3d_pattern.part, name="3D Linear Pattern of Standoffs")

В этом примере LinearPattern вызывается вне BuildSketch и применяется к последнему созданному 3D-элементу. Этот метод дает тот же геометрический результат, но может быть более логичным в некоторых сценариях проектирования.

Ключевые выводы

  • LinearPattern и CircularPattern — это высокоуровневые, декларативные инструменты для создания линейных и круговых массивов.
  • Они делают код более чистым, коротким и читаемым по сравнению с ручной реализацией через циклы for и вычисление координат.
  • Паттерны могут применяться как к 2D-фигурам внутри BuildSketch, так и к 3D-объектам в контексте BuildPart.
  • Использование паттернов — идиоматичный и предпочтительный способ создания регулярных массивов в build123d.

Что дальше?

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

Can't find a good explanation? Sign up and we'll make it for you

Sign up