Аффинные преобразования.

Парадигма ZenCad предполагает, что большая часть объектов появляется в точке начала координат, после чего перемещается к месту своего назначения с помощью механизма преобразований.

Обычно, при работе с геометрией, преобразования выполняются с помощью методов класса Shape (представляющего геометрические тела), но для построения сложных преобразований или работы с анимацией аффинные преобразования также могут создаваться в качестве обособленных объектов.

Transform описывает перенос, вращение и равномерный масштаб, включая отражения через знаковый масштаб; для общего аффинного преобразования есть отдельный AffineTransform.

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

Преобразования можно последовательно применять и обращать — см. «Операции над преобразованиями».

Функции для работы с преобразованиями и специальные виды преобразований описаны в разделе "Дополнительные преобразования".


Базовые преобразования.

Существует четыре базовых преобразования: поворот, трансляция, масштабирование и отражение.


Поворот

Поворот тела вокруг оси заданной вектором v и проходящей через начало координат на угол a.

Если угол a не указан, то в качестве угла поворота берётся радианная мера, численно равная модулю вектора v.

Методы трансформируемых геометрических объектов:

# Основной синтаксис:
shp.rotate([x,y,z], angle)
shp.rotate([x,y,z])
shp.rotateX(x)
shp.rotateY(y)
shp.rotateZ(z)

# Сокращенный синтаксис:
shp.rot([x,y,z], angle)
shp.rot([x,y,z])
shp.rotX(x)
shp.rotY(y)
shp.rotZ(z)

Создание объекта трансформации:

rotate([x,y,z], angle)
rotate([x,y,z])
rotateX(x)
rotateY(y)
rotateZ(z)

Трансляция

Паралельный перенос тела на вектор (x,y,z). По историческим причинам (в частности для совместимости с OpenScad), в библиотеке zencad есть два синонимичных семейства функций/методов translate и move, а также их мнемонические обозначения.

Методы трансформируемых геометрических объектов:

# Основной, альтернативный, мнемонический синтаксис:
shp.translate(x,y,z)
shp.translate([x,y,z])
shp.move(x,y,z)
shp.move([x,y,z])
shp.moveX(x)
shp.moveY(y)
shp.moveZ(z)
shp.right(x) # moveX(+x)
shp.left(x)  # moveX(-x)
shp.forw(y)  # moveY(+y)
shp.back(y)  # moveY(-y)
shp.up(z)    # moveZ(+z)
shp.down(z)  # moveZ(-z)

# Сокращенный синтаксис:
shp.movX(x)
shp.movY(y)
shp.movZ(z)

Создание объекта трансформации:

# Основной синтаксис:
translate(x,y,z)
translate([x,y,z])

# Альтернативный синтаксис:
move(x,y,z)
move([x,y,z])
moveX(x)
moveY(y)
moveZ(z)

# Мнемонический синтаксис:
right(x) # moveX(+x)
left(x)  # moveX(-x)
forw(y)  # moveY(+y)
back(y)  # moveY(-y)
up(z)    # moveZ(+z)
down(z)  # moveZ(-z)

Масштабирование

Изменение размера тела на коэффициент a. Может выполняться в направлении заданной оси или изотропно.

Методы трансформируемых геометрических объектов:

shp.scale(a)
shp.scaleX(a)
shp.scaleY(a)
shp.scaleZ(a)

Создание объекта трансформации:

scale(a)
scaleX(a) # general_transformation
scaleY(a) # general_transformation
scaleZ(a) # general_transformation
scaleXYZ(x,y,z) # general_transformation

Отражение

Операция отражения геометрии относительно точки, оси проходящей через начало координат или плоскости, проходящей через начало координат.

При отражение относительно точки задаются координаты центра трансформации. При отражение относительно оси задаётся вектор оси трансформации. При отражение относительно плоскости задаётся вектор нормали отражающей плоскости.

Методы трансформируемых геометрических объектов:

# Отражение относительно центра.
shp.transform(mirrorO(x,y,z))
shp.transform(mirrorO([x,y,z]))

# Отражение относительно оси.
shp.transform(mirror_axis(x,y,z))
shp.transform(mirror_axis([x,y,z]))
shp.mirrorX() # equal to mirror_axis(1,0,0)
shp.mirrorY() # equal to mirror_axis(0,1,0)
shp.mirrorZ() # equal to mirror_axis(0,0,1)

# Отражение относительно плоскости.
shp.transform(mirror_plane(x,y,z))
shp.transform(mirror_plane([x,y,z]))
shp.mirrorXY() # equal to mirror_plane(0,0,1)
shp.mirrorYZ() # equal to mirror_plane(1,0,0)
shp.mirrorXZ() # equal to mirror_plane(0,1,0)

Создание объекта трансформации:

# Отражение относительно центра.
mirrorO(x,y,z)
mirrorO([x,y,z])

# Отражение относительно оси.
mirror_axis(x,y,z)
mirror_axis([x,y,z])
mirrorX() # equal to mirror_axis(1,0,0)
mirrorY() # equal to mirror_axis(0,1,0)
mirrorZ() # equal to mirror_axis(0,0,1)

# Отражение относительно плоскости.
mirror_plane(x,y,z)
mirror_plane([x,y,z])
mirrorXY() # equal to mirror_plane(0,0,1)
mirrorYZ() # equal to mirror_plane(1,0,0)
mirrorXZ() # equal to mirror_plane(0,1,0)

Операции над преобразованиями.

Аффинное преобразование имеет вид p → A·p + t. При ненулевом переносе оно не является линейным оператором над трёхмерными векторами; композицию удобно представлять матрицами 4 × 4 в однородных координатах.


Композиция.

Композиции аффинных преобразований выполняются с помощью оператора умножения. Следует учесть, что композиции аффинных преобразований некоммутативны.

Композиции преобразований следует читать справа налево. В записи moveX(20) * rotateZ(deg(60)) сначала выполняется поворот на 60 градусов, затем перенос по оси X на 20 единиц.

Пример:

trans = moveX(20) * rotateZ(deg(60))
from zencad.internal_models import knight
m = knight()
disp(trans(m))

# alternate: box(5, center=True).rotZ(deg(60)).movX(20)
До После
complextrans0 complextrans1

Инверсия.

Вычисление обратного преобразования.

Сигнатура:

trsf.inverse()

Пример:

trans = rotateZ(deg(45))
from zencad.internal_models import knight
m = knight()
disp(trans(m), color.green)
disp(trans.inverse()(m), color.red)
Преобразование Инверсия
invtrans0 invtrans1

Пример:

trans = moveX(20) * rotateZ(deg(45))
from zencad.internal_models import knight
m = knight()
disp(trans(m), color.green)
disp(trans.inverse()(m), color.red)
Преобразование Инверсия
invtrans0 invtrans1

Примечание. Инверсия композиции преобразований может быть вычислена как:

(A * B)-1 = B-1 * A-1


Дополнительные преобразования.


Преобразование само в себя.

Преобразование, никак не изменяющее объект. Его создают transform() или nulltrans().

nulltrans()
До После
nulltrans0 nulltrans0

Минимальный поворот.

Данное преобразование соответствует минимальному поворота от вектора f к вектору t.

Сигнатура:

short_rotate(f, t)

Пример:

from zencad.internal_models import knight
short_rotate((0,0,1), (1,1,1))(knight())
До После
multitrans0 multitrans0

Множественное преобразование.

Проводит операцию множественного преобразования прототипа на массив объектов преобразований transes. При этом, если опция array неактивна, производится булево объединение результата. Если активен array, будет возвращен массив результатов.

Сборка создаётся явно: unit(parts=copies) после импорта unit из zencad.assemble.

Сигнатура:

copies = multitrans(transes, array=True)(model)
fused = multitrans(transes)(model)
# multitransform is a synonym for multitrans

Пример:

def extrans():
    return multitransform([
        translate(-20,20,0) * rotateZ(deg(60)),
    translate(-20,-20,0) * rotateZ(deg(120)),
    translate(20,20,0) * rotateZ(deg(180)),
    nulltrans()
])
from zencad.internal_models import knight
disp(extrans()(knight()))
До После
multitrans0 multitrans0

Круговой массив.

Множественное преобразование, порождающее круговой массив из n объектов на угловом диапазоне yaw. Параметр endpoint отвечает за включение последней точки линейного пространства углов. (параметр array — см. Множественное преобразование.)

Сигнатура и код преобразования:

rotate_array(n, yaw=deg(360), endpoint=False, array=False)

Примеры:

from zencad.internal_models import knight
m = knight().move(20,20)
disp(rotate_array(6, yaw=deg(270), endpoint=True)(m))
До После
ra0 ra1

Круговой массив c дополнительным креном.

Множественное преобразование, порождающее круговой массив из n объектов на угловом диапазоне yaw. Параметр endpoint отвечает за включение последней точки линейного пространства углов. (параметр array — см. Множественное преобразование.)

Опция roll задаёт интервал крена тела вокруг траектории вращения.

В отличии от rotatearray_ имеет несколько отличную семантику работы с исходным объектом. В rotatearray2_ исходный объект изначально находится в начале координат, после чего разворачивается на 90 градусов вокруг оси X и смещается по оси X на растояние равное радиусу r.

Сигнатура:

rotate_array2(
    n, r=None,
    yaw=(0,deg(360)), roll=(0,0),
    endpoint=False, array=False)

Пример:

rotate_array2(
    n=60,
    r=20,
    yaw=(0,deg(270)),
    roll=(0,deg(360)),
    array=True)(
        square(10, center=True, wire=True)
    )
До После
raa0 ra1

Квадратное отражение.

Достраивает 3 отражения исходного объекта.

Сигнатура и код преобразования:

sqrmirror(array=False)
sqrtrans(array=False) # synonym

Пример:

from zencad.internal_models import knight
sqrmirror()(knight().move(20,30))
До После
ra0 ra1

Преобразование точки и матрица

Объект преобразования можно применить к точке. Метод Transform.matrix() возвращает числовую матрицу 4×4, вычисляя необходимые зависимости.

import zencad as z

move = z.translate(10, 0, 0)
turn = z.rotateZ(z.deg(90))
combined = move * turn
p = combined(z.point3(1, 0, 0))
assert abs(float(p.x) - 10) < 1e-7
assert abs(float(p.y) - 1) < 1e-7
matrix = combined.matrix()

Кватернионы

quaternion(x, y, z, w) задаёт кватернион; скалярная компонента w идёт последней. quaternion(0, 0, 0, 1) соответствует отсутствию поворота. Для задания оси и угла удобнее quaternion_axis_angle:

from zencad import *

q = quaternion_axis_angle(vector3(0, 0, 1), deg(90))
v = q.rotate(vector3(1, 0, 0))
assert abs(float(v.x)) < 1e-7
assert abs(float(v.y) - 1) < 1e-7
placement = q.to_transform()
display(placement(box(10, 5, 3)))
show()

q1 * q2 составляет повороты справа налево, q.inverse() возвращает обратный поворот. q.normalized() нормирует кватернион, q.to_transform() преобразует его в Transform.