Создание и редактирование плоских кривых
Сейчас мы будем рассматривать построение плоских кривых. Для этого понадобятся методы (функции) различных классов, которые можно найти по ссылке
Начнём с построения прямоугольника, двух отрезков, размеры которых задаются параметрами и окружности радиуса 5 мм. Прямоугольник будет использоваться для детельного уровня, отрезки — для условного, а окружность — для символьного. Размеры кривых указаны на рисунке ниже.
В файле управления сборкой tasks.json заменим имена файла параметров на 2.json, файла скриптов на 2.lua, а шаблона категории на 2.rst.
Файл параметров скопируем из предыдущего примера (1.json), сохраним его под именем 2.json и отредактируем, поменяв имена и текст параметров и добавив еще один параметр.
{
"metadata": {
"defaultName": "Имя категории Renga",
"description": "Описание",
"version": "1.0.0",
"author": "Автор "
},
"styleParameters": [
{
"name": "Dimensions",
"text": "Размеры" ,
"params": [
{
"name": "Rx",
"text": "Размер вдоль оси x",
"type": "Length",
"default": 200,
"min": 100,
"max": 5000
},
{
"name": "Ry",
"text": "Размер вдоль оси y",
"type": "Length",
"default": 300,
"min": 200,
"max": 5000
},
{
"name": "Ww",
"text": "Длина отрезка",
"type": "Length",
"default": 300,
"min": 200,
"max": 5000
}
]
}
],
"ports": [ ]
}
Исправления выделены красным цветом
Также скопируем текст файла скрипта 1.lua, сохраним его под именем 2.lua и отредактируем.
parameters = Style.GetParameterValues()
dimensions = parameters.Dimensions
u=dimensions.Rx
v=dimensions.Ry
w=dimensions.Ww -- задаём длину отрезков
p=CreateRectangle2D(Point2D(u/2,0),0,u,v) -- создаем прямоугольник
l1=CreateLineSegment2D (Point2D(0,-v/2),Point2D(w,-v/2)) -- создаем отрезок
l2=l1:Clone() -- копируем линию
l2:Shift(0,v) -- смещаем копию линии по оси Y на расстояние v
okr= CreateCircle2D(Point2D(0,0),5) -- создаем окружность радиусом 5 мм
g1=GeometrySet2D () -- создаем набор плоских кривых, в который поместим прямоугольник
g2=GeometrySet2D () -- создаем набор плоских кривых, в который поместим отрезки
g3=GeometrySet2D () -- создаем набор плоских кривых, в который поместим окружность
g1:AddCurve(p) -- помещаем прямоугольник в набор g1
g2:AddCurve(l1) -- помещаем линию l1 в набор g2
g2:AddCurve(l2) -- помещаем линию l2 в набор g2
g3:AddCurve(okr) -- помещаем окружность okr в набор g3
m1=ModelGeometry() -- для детального уровня детализации
m2=ModelGeometry() -- для условного уровня детализации
m2=ModelGeometry() -- для символьного уровня детализации
m1:AddGeometrySet2D(g1) -- помещаем набор gokr2 в m1
m1:AddSolid(s) -- помещаем сферу s в m1
m2:AddGeometrySet2D(g2)
m3:AddGeometrySet2D(g3)
Style.SetDetailedGeometry(m1) -- для детального уровня задёем m1
Style.SetSymbolicGeometry(m2) -- для условного уровня задаём m2
Style.SetSymbolGeometry(m3) -- для символьного уровня задаём m3
Создадим теперь для детального уровня параллелепипид высотой h с размерами основания w и d. В файл параметров добавим еще один параметр, и поменяем в нём наименование параметров. Это нужно сделать, так как мы будем сначала рисовать прясоугольник на плоскости, а потом повернем его и размер вдоль оси x станет размером по вертикали, то есть высотой.
{
"metadata": {
"defaultName": "Имя категории Renga",
"description": "Описание",
"version": "1.0.0",
"author": "Автор "
},
"styleParameters": [
{
"name": "Dimensions",
"text": "Размеры",
"params": [
{
"name": "Rx",
"text": "Высота параллелепипида",
"type": "Length",
"default": 1000,
"min": 100,
"max": 5000
},
{
"name": "Ry",
"text": "Глубина параллелепипида",
"type": "Length",
"default": 300,
"min": 200,
"max": 5000
},
{
"name": "Ww",
"text": "Ширина параллелепипида",
"type": "Length",
"default": 300,
"min": 200,
"max": 5000
}
]
}
],
"ports": [ ]
}
Исправим файл скрипта. Суть исправления состоит в повороте и размещении плоских наборов в пространстве. Для этого нам понадобится класс Placement3D, задающий локальную систему координат в трёхмерном пространстве. Конструктор этого класса Placement3D(p,vz,vx), имеющий три параметра, помещает начало локальной системы координат в точку p, её ось z направляет по вектору vz, а ось x - по вектору vx. При вставке набора плоских геометрических примитивов они разворачиваются так, чтобы расположиться в локальной системе так же, как они были расположены в глобальной системе координат, в которой они создавались.
parameters = Style.GetParameterValues()
dimensions = parameters.Dimensions
u=dimensions.Rx
v=dimensions.Ry
w=dimensions.Ww
p=CreateRectangle2D(Point2D(u/2,0),0,u,v) -- создаем прямоугольник
l1=CreateLineSegment2D (Point2D(0,-v/2),Point2D(w,-v/2)) -- создаем отрезок длиной w
l2=l1:Clone() -- копируем отрезок
l2:Shift(0,v) -- смещаем копию отрезки по оси Y на расстояние y
okr= CreateCircle2D(Point2D(0,0),5) -- создаем окружность радиусом 5 мм
g1=GeometrySet2D () -- создаем набор плоских кривых, в который поместим прямоугольник
g2=GeometrySet2D () -- создаем набор плоских кривых, в который поместим отрезки
g3=GeometrySet2D () -- создаем набор плоских кривых, в который поместим окружность
g1:AddCurve(p) -- помещаем прямоугольник в набор g1
g2:AddCurve(l1) -- помещаем линию l1 в набор g2
g2:AddCurve(l2) -- помещаем линию l2 в набор g2
g3:AddCurve(okr) -- помещаем окружность okr в набор g3
m1=ModelGeometry() -- для детального уровня детализации
m2=ModelGeometry() -- для условного уровня детализации
m3=ModelGeometry() -- для символьного уровня детализации
Placement1=Placement3D(Point3D(0, 0, 0), Vector3D(1, 0, 0), Vector3D(0, 0, -1))
Placement2=Placement3D(Point3D(0, 0, -u), Vector3D(0, 0, 1), Vector3D(1, 0, 0))
m1:AddGeometrySet2D(g1, Placement1)
Placement1:Shift(w,0,0)
m1:AddGeometrySet2D(g1, Placement1)
m1:AddGeometrySet2D(g2)
m1:AddGeometrySet2D(g2,Placement2)
m2:AddGeometrySet2D(g2)
m3:AddGeometrySet2D(g3)
Style.SetDetailedGeometry(m1) -- для детального уровня задёем m1
Style.SetSymbolicGeometry(m2) -- для условного уровня задаём m2
Style.SetSymbolGeometry(m3) -- для символьного уровня задаём m3
Ниже на рисунке изображены кривые и локальные системы координат, используемые для заполнения m1. Буквами обозначены действия команд
а) m1:AddGeometrySet2D(g1, Placement1)
б) Placement1:Shift(w,0,0)
m1:AddGeometrySet2D(g1, Placement1)
в) m1:AddGeometrySet2D(g2) (В этом случае используется глобальная система координат)
г) m1:AddGeometrySet2D(g2,Placement2)
Если потребуется создать ряд из n параллелепипидов, смещая их на расстояние d, то нужно заменить строчки, в которых заполняется m1, на цикл. Перед циклом нужно добавить локальную стстему координат Placement3, которая парвоначально совпадает с глобальной, и вместо m1:AddGeometrySet2D(g2) написать m1:AddGeometrySet2D(g2, Placement3) и при каждом выполнении тела цикла производить смещение локальных систем координат, чтобы наборы плоских кривых располагались со смещением.
d=1000
n=5
Placement3=Placement3D(Point3D(0, 0, 0), Vector3D(0, 0, 1), Vector3D(0, 0, 1))
for i=1,n do
m1:AddGeometrySet2D(g1, Placement1)
Placement1:Shift(w,0,0)
m1:AddGeometrySet2D(g1, Placement1)
m1:AddGeometrySet2D(g2, Placement3)
m1:AddGeometrySet2D(g2,Placement2)
Placement1:Shift(d-w,0,0)
Placement2:Shift(d,0,0)
Placement3:Shift(d,0,0)
end
Теперь рассмотрим построение фигуры, изображенной в левой части на рисунке ниже. Её параметрами будут: k - количество кривых, одна из которых изображена на рисунке в центре; n - число дуг, из которых состоит эта кривая (n>=3); R – радиус окружности, на которой находятся центры окружностей, частями которых являются дуги; r – радиус дуг.
В правой части рисунка жирной линией изображена одна дуга, из которых создаётся кривая. На кривой дуги должны быть соединены концами. Плоскость разбивается на n секторов, исходя из того, что дуг на кривой должно быть n дуг, угол сектора равен 2u, где u=math.pi/n. В каждом секторе создаётся по одной дуге. Сначала создаётся дуга вне сектора, направленного влево из точки (R,0), а затем часть дуги изображенная короткими ширихами удаляется. Крупными штрихами изображена дуга самого маленького радиуса и возможных, если радиус дуги будет меньше, то они не могут соединиться концами.
Тест файла параметров
{
"metadata": {
"defaultName": "Цветок",
"description": "Описание",
"version": "1.0.0",
"author": "Мусиенко"
},
"styleParameters": [
{ "name": "Dimensions",
"text": "Размер",
"params": [
{
"name": "Nn",
"text": "Число дуг",
"type": "Length",
"default": 7,
"min": 3,
"max": 20
},
{
"name": "Rr",
"text": "Радиус дуги",
"type": "Length",
"default": 1000,
"min": 30,
"max": 5000
},
{
"name": "RR",
"text": "Радиус круга центров дуг",
"type": "Length",
"default": 2000,
"min": 100,
"max": 4000
},
{
"name": "Kk",
"text": "Количество кривых",
"type": "Integer",
"default": 3,
"min": 1,
"max": 10
}
]
}
],
"ports": [ ]
}
В файле скриптов для построения кривой напишем функцию Curve, шаги создания которой изображены ниже на рисунке.
Текст скрипта:
parameters = Style.GetParameterValues()
dimensions = parameters.Dimensions
n=dimensions.Nn -- число дуг на кривой
r=dimensions.Rr -- радиус дуг на кривой
R=dimensions.RR -- радиус центров дуг
k=dimensions.Kk -- количество кривых
u=math.pi/n
function Curve (n,R,r,u) — функция построения кривой
local L=R*math.sqrt(2*(1-math.cos(u))) -- самое маленькое значение r
if r
-- Шаг 1. Создадим дугу с помощью параметрической функции
local u1=math.pi-u
local rs=tostring(r) -- преобразуем число r в строку
local function_x =rs.."*cos(t)" -- запишем в виде строки параметрическую функцию
local function_y =rs.."*sin(t)"
local duga=CreateParametricCurve2D(function_x, function_y, CoordinateSystem2D.Cartesian,-u1,u1) -- создаем дугу
-- Шаг 2. Создадим область, внутри которой будет находиться нужная нам часть дуги
local x1=R*math.cos(u) -- координаты вершин области
local y1=R*math.sin(u) -- координаты вершин области
local Rr=2*(R+r)+10 -- этого числа достаточно, чтобы дуга попала внутрь создаваемой области, но можно задать больше
local x2=Rr*math.cos(u) -- координата вершины области
local y2=Rr*math.sin(u) -- координата вершины области
-- по координатам создаём границу области
local l1=CreatePolyline2D ({Point2D(x1,y1),Point2D(x2,y2),Point2D(x2,-y2),Point2D(x1,-y1),Point2D(x1,y1)})
local Fl=FillArea(l1) -- заливаем область
duga:Shift(R,0) -- передвинем дугу вправо на расстояние R
local tab=ClipCurvesByRegions ({duga},{Fl}, false,false)
--[ первый параметр — таблица кривых, которые нужно обрезать, второй параметр- таблица заливок, по которым нужно обрезать, третий параметр — нужно ли инвертировать контуры, четвёртый - нужно ли удалять части кривой, совпадающие с частями границы. tab – таблица с результатом обрезки, кусков кривых может быть много. ]]
duga=tab[1] -- первый элемент таблицы tab, в нашем случае кусок один
-- Шаг 3. Тиражирование дуг по кругу. Результат записываем в таблицу
local u2=2*u
for i=2,n do
duga1=duga:Clone() -- копируем дугу
duga1:Rotate(Point2D(0,0),u2*(i-1)) -- поворачиваем копию
tab[i]=duga1 -- записываем в таблицу
end
return CreateCompositeCurve2D(tab) -- создаем кривую и возвращаем результат из функции
end
geometrySet = GeometrySet2D() -- создаём набор плоских кривых
for i=1,k do
curve=Curve(n,R,r,u) создаем кривую
if (i%2==1) then -- если нечетная кривая
curve:Rotate(Point2D(0,0),u) -- поворачиваем
end
geometrySet:AddCurve(curve) — добавляем кривую в набор
R=R*2/3 r=r*2/3 -- изменяем радиусы
end
detailedGeometry = ModelGeometry()
detailedGeometry:AddGeometrySet2D(geometrySet)
Style.SetDetailedGeometry(detailedGeometry) -- задаём только для детального уровня