Плоттерное искусство и алгоритмы. Введение.

За последние несколько месяцев я искал способы физического воплощения моего генерирующего кода. Меня волнует идея разработки реальных, осязаемых объектов, которые больше не связаны генеративными системами, которые их сформировали. В конечном итоге я планирую экспериментировать с 3D-печатью, лазерной резкой, фрезерованием с ЧПУ и другими способами реализации моих алгоритмов в реальном мире.

В 2017м мной был приобретён ЧПУ плоттер, способный чертить навесным инструментом.
В отличие от привычного принтера, плоттер производит печать со странным свойством: возникают случайные погрешности, когда перо захватывает край или краска мгновенно высыхает, а фактура чернил имеет тонкую текстуру и тиснение, которые вы обычно видите только на нарисованном человеком рисунке.

Часто эти плоттеры и прочие ЧПУ механические устройства управляются такими форматами, как HP-GL или G-code. Эти форматы определяют, как машина должна поднимать, перемещать и размещать свой рабочий орган с течением времени.

Существует множество программ для конвертации векторных рисунков в формат G-code, в основном это программы подготовки заданий для гравировальных ЧПУ станков.

Ниже представлю результат работы недавней серии алгоритмов, которые я разработал, она состоит из 4 различных алгоритмов. Каждый раз, когда эти алгоритмы работают, Они производят различные выходные данные, что позволяет иметь бесконечное количество уникальных рисунков.

Сгенерированные картинки из линий в G-code

Среда разработки

До сих пор вся моя работа с AxiDraw была связана с JavaScript и экспериментальным инструментом, который я для этого создал, назовём его penplot. Инструмент в первую очередь действует как среда разработки, что упрощает организацию и разработку новых рисунков с минимальной настройкой. Вы можете найти его на GitHub по запросу «canvas-sketch».

Вы можете попробовать инструмент самостоятельно, если у вас установлен node@8.4.x+ и npm@5.3.X+.

  
# install the CLI app globally npm install penplot -g # run it, generating a new file and opening the browser penplot test-print.js --write --open

Флаг —write генерирует новый тестовый отпечаток .js файл и —open запустит ваш браузер с адресом localhost: 9966.

Начнем с простой картинки:

Простейший паттерн из линий

Сгенерированный паттерн файлом test-print.js (можете найти его на гитхаб) готов к работе; вы можете отредактировать код ES2015, чтобы увидеть изменения, отраженные в вашем браузере. Когда результат вас устроит, нажмите Cmd + S (сохранить PNG) или Cmd + P (сохранить SVG), чтобы экспортировать картинку из браузера — файлы будут сохранены в папку Загрузки.

Геометрия и примитивы

Для алгоритмической работы с AxiDraw и его плагином SVG я склонен перегонять все свои визуальные эффекты в серию полилиний, состоящих из вложенных массивов.

 
const lines = [
[
[ 1, 1 ], [ 2, 1 ]
],
[
[ 1, 2 ], [ 2, 2 ]
]
]

Это создает две горизонтальные линии в левом верхнем углу нашей картинки, каждая шириной 1 см. Здесь координаты определяются как [ x, y], а полилиния (т. е. путь) определяется точками [ a, b, C, d,…. ]. Наш список полилиний определяется как [ A, B,… ], что позволяет нам создавать несколько несвязанных сегментов (т. е. где перо поднимается, чтобы создать новую линию).

Итак, приведенный выше код не очень понятный, вы вряд ли когда-нибудь представляли рисунки цифровыми координатами. Вместо этого вы должны попытаться мыслить геометрическими примитивами: точками, квадратами, линиями, кругами, треугольниками и т. д. Например, нарисовать несколько квадратов в центре рисунка:

// Function to create a square
const square = (x, y, size) => {
  // Define rectangle vertices
  const path = [
    [ x - size, y - size ],
    [ x + size, y - size ],
    [ x + size, y + size ],
    [ x - size, y + size ]
  ];
  // Close the path
  path.push(path[0]);
  return path;
};

// Get centre of the print
const cx = width / 2;
const cy = height / 2;

// Create 12 concentric pairs of squares
const lines = [];
for (let i = 0; i < 12; i++) {
  const size = i + 1;
  const margin = 0.25;
  lines.push(square(cx, cy, size));
  lines.push(square(cx, cy, size + margin));
}

Как только строки находятся на месте, их легко отобразить в контекст Canvas2D с помощью beginPath() и stroke () или сохранить в SVG с помощью утилиты penplot polylinesToSVG().

Результат нашего кода выглядит так:

Это становится немного интереснее, но это же слишком просто и такое можно в любом графическом редакторе за минуту нарисовать. Итак, давайте посмотрим, сможем ли мы создать что-то более сложное относительно ручной работы в коде.

Триангуляция Делоне

Простой начальной задачей было бы исследовать триангуляцию Делоне. Для этого мы будем использовать delaunay-triangulate, надежную библиотеку триангуляции Миколы Лысенко, которая работает в 2D и 3D. Мы также будем использовать модуль new-array, простую утилиту создания массива.

Прежде чем мы начнем, вам необходимо установить эти зависимости локально:

# first ensure you have a package.json in your folder
npm init -y

# now you can install the required dependencies
npm install delaunay-triangulate new-array

В нашем JavaScript-коде давайте импортируем некоторые из наших модулей и определим набор 2D-точек, случайно распределенных по печати, вставленных с небольшим запасом.

Для удобства мы используем встроенную библиотеку случайных чисел penplot, которая имеет функцию randomFloat(min, max).

import newArray from 'new-array';
import { randomFloat } from 'penplot/util/random';

// ...

const pointCount = 200;
const positions = newArray(pointCount).map(() => {
  // Margin from print edge in centimeters
  const margin = 2;
  // Return a random 2D point inset by this margin
  return [
    randomFloat(margin, width - margin),
    randomFloat(margin, height - margin)
  ];
});

Если бы мы визуализировали наши точки в виде кругов, это могло бы выглядеть так:

Генерация случайных кружков на плоскости

Следующим шагом является триангуляция этих точек, т. е. превращение их в треугольники. Просто введите массив точек в функцию triangulate, и она вернет список “ячеек».

import triangulate from 'delaunay-triangulate';

// ...

const cells = triangulate(positions);

Возвращаемое значение представляет собой массив треугольников, но вместо того, чтобы давать нам 2D-позиции каждой вершины в треугольнике, он дает нам индекс в массив позиций, который мы передали.

[
  [ 0, 1, 2 ],
  [ 2, 3, 4 ],
  ...
]

Например, чтобы получить 3 вершины первого треугольника:

const triangle = cells[0].map(i => positions[i]);

// log each 2D point in the triangle
console.log(triangle[0], triangle[1], triangle[2]);

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

const lines = cells.map(cell => {
  // Get vertices for this cell
  const triangle = cell.map(i => positions[i]);
  // Close the path
  triangle.push(triangle[0]);
  return triangle;
});

Теперь у нас есть все линии, необходимые для отправки SVG в AxiDraw. В браузере нажмите Cmd + S и Cmd + P, чтобы сохранить файл PNG и SVG соответственно в папку Загрузки.

Код генерирует полигоны для картинки

Если мы увеличим pointCount до более высокого значения, мы начнем получать более четкий край и потенциально более интересный рисунок.

Фон из случайно сгенерированных треугольников

В следующих частях статьи расскажу о методе формирования букв и геометрических фигур с вписанием в них случайных фигур.