Лекция 4. Создание и редактирование плоских кривых

Создание и редактирование плоских кривых

Сейчас мы будем рассматривать построение плоских кривых. Для этого понадобятся методы (функции) различных классов, которые можно найти по ссылке

Начнём с построения прямоугольника, двух отрезков, размеры которых задаются параметрами и окружности радиуса 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) -- задаём только для детального уровня