
Тут немного о том, как я сделал библиотеку для проектировки простой мебели из ДСП не визуальным методом, а в виде PHP-скрипта.
В далёкой-далёкой галактике...
Точно уже не помню, когда именно это произошло, но если и не 10 лет назад, то близко к этому. В какой-то момент я решил, что больше не хочу покупать себе готовую мебель из ДСП. Выбирать её слишком сложно, для этого должно сойтись очень много звёзд: она должна помещаться в квартире, но при этом быть не слишком маленькой, иметь необходимый функционал, внешний вид и цвет, а ещё вписываться в бюджет. И вот чтобы совпало всё — бывает крайне редко. Часто совпадает большая часть условий, в остальном приходится идти на компромисс. И я решил — доколе? Может и не всё, но что-то я могу сделать сам! Первые мои попытки были просты и примитивны — я просто покупал мебельные щиты и полки разных размеров в строительных магазинах и делал всё из них. Как правило, я даже ничего не проектировал, весь план был только у меня в голове. Я не заморачивался с кромками, поэтому старался приводить свои идеи к существующим размерам полок, чтобы если и требовались отрезы, то эти стороны не были бы видны снаружи. Какое-то время я делал этих квадратных несуразных Буратин, но постепенно я понял, что все мои идеи становятся заложниками этих существующих размеров полок, за которые нельзя было выходить, а вся мебель была топорно-квадратная, а иногда скругления смотрелись бы куда эстетичнее, и уже пора бы двигаться дальше. Двигаться в сторону проектирования мебели и заказа распилов тех размеров, которые мне были нужны. Отверстия для конфирмата я был в состоянии просверлить сам, а вот скругления и кромкование мне бы хотелось, чтобы было сделано за меня. Я стал искать программу для проектирования мебели и остановился на старенькой маленькой программке, которая называлась «Астра Конструктор Мебели». Но долго я на ней не задержался.
Путь к OpenSCAD
К этому времени у меня уже появился 3D-принтер, и я параллельно искал программу для проектирования моделей для него. Я перебирал множество программ для 3D-моделирования, но ни одна из них, в конечном счёте, так и не нашла во мне отклика. И вот я наткнулся на OpenSCAD. Идея рисовать 3D-модели с помощью кода показалась мне странной, но позже я осознал, что на самом деле мне это подходит больше. Я понял, что мне не нужно визуально что-то рисовать, достаточно того, чтобы я мог видеть результат. И поменять что-то в коде мне было гораздо проще, чем менять это визуально. Для себя я отметил следующие преимущества:
- Программа маленькая и не требует установки.
- Файлы текстовые и просто описывают геометрию, потому занимают мало места.
- Сложно испортить такой файл — это же просто текст. И если я что-то такое наворотил, что модель начала безбожно тормозить и с ней уже ничего не сделать, я могу просто удалить любым редактором лишний текст — и всё снова станет нормально.
Я почти никогда не печатал художку — все эти игрушки, фигурки, фанатская дребедень, кажется, я уже слишком стар для этого дерьма. А печатал я разные детали взамен сломанных, крепления, дополнения к существующим вещам, улучшения, и, работая с этим, я понял, что главное — это размеры, всё начинается с них. Они есть у всего, у каждой части есть длина, ширина, высота и координаты — вокруг этого строится всё. И очень часто с размерами промахиваешься, и хочется быстро их поменять, чтобы при этом не поехала вся остальная деталь. Поэтому-то мне и полюбился OpenSCAD, где всё строится на цифрах. Я старался для всего заводить переменные и проектировать так, чтобы потом их легко было менять, и вся модель пересчитывалась на новые параметры и строилась правильно. И вот в какой-то момент, когда нужно было в очередной раз проектировать что-то из мебели, мне пришла в голову безумная идея:

Действительно, почему бы и нет? И я написал себе библиотеку для OpenSCAD для «упрощения» проектировки мебели, в которую добавлялись детали, чтобы потом экспортировать список для распила, но пошёл я не самым простым путём. Я заложил в её работу те же принципы, что и в обычное проектирование 3D-моделей. Нужно было создать ДСП-панель заданных размеров, поместить её на нужные координаты, с нужным поворотом. И хоть я и старался во всех вычислениях использовать переменные и не вставлять «magic numbers», но всё равно, порой, код получался слишком сложным. Для меня это стало общей проблемой OpenSCAD. Пока ты варишься в этом — всё просто. Но стоит оставить проект на пару месяцев, и если там нет подробнейших комментариев, ты понимаешь, что там не просто чёрт ногу сломит, а этот самый чёрт там переломался весь и погребён в этой каше кода так, что и не найдёшь. И вот тут я понял, что мне становится тесновато в OpenSCAD. Да, я видел, что ему есть альтернативы, такие как ZenSCAD, но Python, бррр.

Мне хотелось сделать что-то своё, удобное именно мне.
include <../lib/export-details.scad>
EdgeThick = 0.4;
Thick = 16;
ClosetInsideHeight = 800;
ClosetInsideMirrorWidth = 400;
ClosetInsideShelfWidth = 200;
ClosetDeep = 140;
ShelfCount = 2;
BoxesCount = 3;
BoxRailingThick = 13;
BoxSpaceWidth = 460;
BoxFasadeWidth = 486;
BoxFasadeHeight = 230;
BoxDeep = 400;
BoxBottomHeight = 4;
BoxWidth = BoxSpaceWidth - BoxRailingThick * 2;
BoxHeight = BoxFasadeHeight - 16 - BoxBottomHeight;
BoxesSpace = 2;
CablesHolderWidth = 86 * 3;
CablesHolderHeight = 150;
CablesHolderDepth = 80;
CablesHolderUpHeight = 70;
translate([0, 0, 0]) rotate([90, 0, 90]) detail([ClosetDeep, ClosetInsideHeight + Thick * 2, Thick], "Шкафчик: правая стенка", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate([Thick * 2 + ClosetInsideMirrorWidth + ClosetInsideShelfWidth, 0, 0]) rotate([90, 0, 90]) detail([ClosetDeep, ClosetInsideHeight + Thick * 2, Thick], "Шкафчик: левая стенка", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate([ClosetInsideMirrorWidth + Thick, 0, Thick]) rotate([90, 0, 90]) detail([ClosetDeep, ClosetInsideHeight, Thick], "Шкафчик: средняя стенка", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate([Thick, ClosetDeep, 0]) rotate([0, 0, -90]) detail([ClosetDeep, ClosetInsideMirrorWidth + ClosetInsideShelfWidth + Thick, Thick], "Шкафчик: дно", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate([Thick, ClosetDeep, ClosetInsideHeight + Thick]) rotate([0, 0, -90]) detail([ClosetDeep, ClosetInsideMirrorWidth + ClosetInsideShelfWidth + Thick, Thick], "Шкафчик: верх", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate([ClosetInsideMirrorWidth + Thick * 2, 0, Thick])
for (i = [1 : ShelfCount]) {
translate([0, 0, i * (ClosetInsideHeight / (ShelfCount + 1))]) rotate([0, 0, 0]) detail([ClosetInsideShelfWidth, ClosetDeep, Thick], "Шкафчик: полка", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
}
translate([ClosetInsideMirrorWidth + Thick, ClosetDeep, Thick]) rotate([90, -90, 00]) detail([ClosetInsideHeight, ClosetInsideMirrorWidth, Thick], "Шкафчик: зад", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate ([ClosetInsideHeight + 400, 0, 0])
for (i = [0 : BoxesCount - 1]) {
translate ([0, 0, i * (BoxFasadeHeight + BoxesSpace)]) {
translate([BoxRailingThick, 0, 0]) {
translate([0, 0, 0]) rotate([90, 0, 90]) detail([BoxDeep, BoxHeight, Thick], "Ящик: правая стенка", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate([BoxWidth - Thick, 0, 0]) rotate([90, 0, 90]) detail([BoxDeep, BoxHeight, Thick], "Ящик: левая стенка", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate([Thick, Thick, 0]) rotate([90, 0, 0]) detail([BoxWidth - Thick * 2, BoxHeight, Thick], "Ящик: левая стенка", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate([Thick, BoxDeep, 0]) rotate([90, 0, 0]) detail([BoxWidth - Thick * 2, BoxHeight, Thick], "Ящик: левая стенка", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate([0, 0, -BoxBottomHeight]) color([1,0.9,0.7]) cube([BoxWidth, BoxDeep,BoxBottomHeight]);
//translate([0, 20, BoxHeight]) cube([BoxWidth, 10,10]);
}
translate([-(BoxFasadeWidth - BoxSpaceWidth) / 2, 0, -BoxBottomHeight]) rotate([90, 0, 0]) detail([BoxFasadeWidth, BoxFasadeHeight, Thick], "Ящик: фасад", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
}
}
translate ([ClosetInsideHeight + 1000, 0, 0]) {
translate([0, 0, 0]) rotate([90, 0, 90]) detail([CablesHolderDepth, CablesHolderHeight, Thick], "Для проводов: левая стенка", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate([CablesHolderWidth + Thick, 0, 0]) rotate([90, 0, 90]) detail([CablesHolderDepth, CablesHolderHeight, Thick], "Для проводов: правая стенка", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate([0, 0, ]) rotate([90, 0, 0]) detail([CablesHolderWidth + Thick * 2, CablesHolderHeight, Thick], "Для проводов: фасад", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate([Thick, CablesHolderDepth, 0]) rotate([0, 0, -90]) detail([CablesHolderDepth, CablesHolderWidth, Thick], "Для проводов: дно", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
translate([0, CablesHolderDepth, CablesHolderHeight * 1.5]) rotate([90, 0, 0]) detail([CablesHolderWidth + Thick * 2, CablesHolderUpHeight, Thick], "Для проводов: фасад", [EdgeThick, EdgeThick, EdgeThick, EdgeThick]);
}
PHP.SCAD
Прошло чуток времени, и я, наконец, понял, чего мне не хватало в OpenSCAD при создании 3D-моделей. Слоёв. Можно из одной фигуры вырезать другую, но иногда бывает, что мне нужно вырезать отверстие через большую часть модели, куда входит много различных элементов, и все они раскиданы по коду в файле. Всё это как-то нужно сгруппировать, чтобы сделать такой вырез, но специфика OpenSCAD такая, что это больше декларативный язык, нельзя создать модели, присвоить их переменным и потом провести действия над группой переменных. Там это немного сложнее, и каша кода от этого становится ещё наваристей. Было бы здорово, если бы я мог сказать, что вырезаю на слое 10, и это бы значило, что вырез будет через всё на слоях с 1 по 9, а на слои, начиная с 11 и выше вырез не распространяется. Но средствами OpenSCAD это было не сделать, вносить правки в сам OpenSCAD мне совершенно не хотелось, как и придумывать свой альтернативный язык программирования, чтобы сделать свой SCAD, этих языков и так развелось больше, чем хотелось бы. Поэтому я решил написать библиотеку для другого, существующего языка, которая реализует все нужные мне функции из OpenSCAD, добавляет то, чего мне не хватало, а на выходе будет генерировать файл формата OpenSCAD. И выбор мой пал на PHP. Вообще, PHP чаще используется для web-разработок, говорят даже, что он как рок, мёртв, но я не из тех, кто так быстро закапывает стюардесс, и я продолжаю его использовать для написания любых скриптов в системе. Почему всё-таки не Python? Он, казалось бы, сейчас более популярен. Не люблю я его, субъективно. Я привык к тому, что отступы — это просто форматирование текста, и мне не нравится идея того, что то, как будут стоять отступы в коде, будет как-то влиять на работу самой программы. И хоть иногда и приходится на нём что-то писать, делал я это всегда без любви и удовольствия. Поэтому я выбрал PHP. Чтобы работа была удобнее, я настроил VSCode так, чтобы по F5 он выполнял мой скрипт, который в свою очередь при выполнении обновляет SCAD-файл, а OpenSCAD довольно умный, и когда он видит, что файл изменился даже извне, он моментально его перезагружает и обновляет картинку. Поэтому работать удобно, F5 — и ты сразу видишь, что наворотил.

{
"configurations": [
{
"name": "F5 Anything",
"type": "f5anything",
"request": "launch",
"command": "c:/Projects/Bin/php7/php.exe ${file}",
}
]
}
Не скажу, что это решило проблему каши в коде, но сильно всё упростило и принесло мне нормальную работу переменных и функций (кто не знает, как это работает в OpenSCAD, то скажу так, специфически). Но я эту поделку никуда не выкладывал, и описываю её только как промежуточный шаг на пути к тому, что было дальше.
PHP.MEBEL.SCAD :)
Что? Mebel? Серьёзно? Да. Я знаю, что по-английски это будет furniture, но в русском языке есть слово фурнитура и у него немного другое значение, не хотелось путаницы, поэтому долго я не думал:

Но прежде чем сделать эту библиотеку, я решил переосмыслить саму концепцию проектирования мебели кодом. Мне не хотелось повторять то, что было сделано в виде библиотеки для OpenSCAD, где надо было размещать каждую доску, задавая ей смещения по 3м осям и углы поворота. Это всё громоздко и не слишком удобно и трудно читаемо в будущем. Я хотел чего-то нового. И в этом случае у нас есть два пути:
- Изучить то, что уже сделано в этом направлении, и адаптировать под свои нужды.
- Ничего не изучать и сразу начать изобретать свой велосипед.
А что тут думать? Конечно же, второй пункт! Ещё чего-то изучать я буду, делать мне больше нечего! Но если серьёзно, то в этом есть свои плюсы и минусы. Минусы в том, что я могу «изобрести» то, что уже давным-давно изобретено до меня, причём изобрести не в лучшем виде. Или изобрести что-то мертворождённое, что не приведёт меня ни к чему, кроме понимания того, что эта идея была плохая. Но плюсы в том, что так я думаю сам, и это иногда приводит к хорошим оригинальным решениям. Если я посмотрю, как сделали другие, мне будет гораздо сложнее потом придумать что-то своё, кардинально от этого отличающееся. Мозг зацепляется за протоптанную тропинку и не хочет топтать новую. Поэтому я ничего не изучал, да и вообще, не думаю, что в мире есть еще хоть один псих, который решит проектировать мебель PHP-скриптом. А если он есть, я бы предпочёл держаться от него подальше, мало ли что ему ещё в голову взбредёт? И вот я просто ходил и думал. И что-то придумал. Квадрат! Точнее, нет — куб. Всё — куб. А еще точнее, всё — параллелепипед. И немного из HTML/CSS разметки. Так, наверное это надо как-то объяснить) Сейчас попробую, итак, в отличие от произвольных 3D-моделей, мебель из ДСП в 99% случаев строится по одному принципу: панель ложится либо горизонтально, либо вертикально, без хитрых углов и причудливых форм, она сама параллелепипед и стыкуется с другими параллелепипедами. И в результате образуется другой большой параллелепипед (один или много). Сейчас покажу наглядно, и станет понятно о чём я и что у меня вышло.
Итак, сначала был размер.
Всё всегда начинается с размера, место в которое нужно эту мебель поставить, и оно ограничено. (Сразу буду писать на PHP.MEBEL.SCAD и визуализировать)
И создал я пространство!
require_once "scad.mebel.php";
$box = box(500, 800, 600);
draw_box($box);
render_scad();

Вот он, этот параллелепипед гражданской наружности шириной 500мм, высотой 800мм, глубиной 600мм. Я сразу решил оперировать не x,y,z, а более человеческими — ширина (h, он же horizontal), высота (v, он же vertical) и глубина (d, он же depth). Дальше я совместил трёхмерное пространство и принцип html/css разметки, когда у нас есть div фиксированного размера, у которого есть border или padding, который его уменьшает и дочерние элементы остаются уже в меньшем прямоугольнике.
Далее создал я полку сверху и понял, что не плохо это:
require_once "scad.mebel.php";
$box = box(500, 800, 600);
$box = wall_top($box, "Мебель: %W");
render_scad();

Панель примагнитилась к верхней грани параллелепипеда, а сам параллелепипед уменьшился на толщину этой панели. Нам не нужно указывать размеры панели, мы только указываем сторону параллелепипеда, размеры она возьмёт по размеру прямоугольника этой стороны (ну ладно, мы можем задать padding или размер для панели, но это не обязательно).
И создал я вторую полку снизу, и понравилось это мне:
require_once "scad.mebel.php";
$box = box(500, 800, 600);
$box = wall_top($box, "Мебель: %W");
$box = wall_bottom($box, "Мебель: %W");
render_scad();

Теперь то же самое произошло снизу, параллелепипед уменьшается, создавая каркас будущего изделия.
И создал я стенки боковые, как опоры меж полок горизонтальных:
require_once "scad.mebel.php";
$box = box(500, 800, 600);
$box = wall_top($box, "Мебель: %W");
$box = wall_bottom($box, "Мебель: %W");
$box = wall_left($box, "Мебель: %W");
$box = wall_right($box, "Мебель: %W");
render_scad();

И вот наша коробка. Обращаю внимание, что верхняя и нижняя панели зашли над боковыми, потому что мы нарисовали их первыми, и боковые уже рисовались в уменьшенном параллелепипеде.

Но если поменять местами порядок создания стенок, то будет так:
require_once "scad.mebel.php";
$box = box(500, 800, 600);
$box = wall_left($box, "Мебель: %W");
$box = wall_right($box, "Мебель: %W");
$box = wall_top($box, "Мебель: %W");
$box = wall_bottom($box, "Мебель: %W");
render_scad();

И разбил я пространство оставшееся на несколько частей равных:
require_once "scad.mebel.php";
$box = box(500, 800, 600);
$box = wall_top($box, "Мебель: %W");
$box = wall_bottom($box, "Мебель: %W");
$box = padding($box, "front=16");
$box = wall_left($box, "Мебель: %W");
$box = wall_right($box, "Мебель: %W");
$box = wall_back($box, "Мебель: %W");
$boxes = split_vertical($box, "*,*,*,*,*", 16);
foreach ($boxes as $b) {
if (is_part($b)) {
draw_box($b);
}
}
render_scad();

Здесь они разбиты на равные интервалы с пропуском в 16 см, это не случайно, а чтобы при сборке ящиков класть между ними обрезок ДСП, и так их будет не нужно выравнивать. Разбиение задано "*,*,*,*,*", но можно вместо * задавать либо % от всего пространства, либо размеры в мм. Сначала вычисляются все фиксированные значения, а оставшееся пространство делится между *.
И закрыл я каждую из полученных частей каркасом деревянным:
require_once "scad.mebel.php";
$box = box(500, 800, 600);
$box = wall_top($box, "Мебель: %W");
$box = wall_bottom($box, "Мебель: %W");
$box = wall_left($box, "Мебель: %W");
$box = wall_right($box, "Мебель: %W");
$box = wall_back($box, "Мебель: %W");
$boxes = split_vertical($box, "*,*,*,*,*", 16);
foreach ($boxes as $b) {
if (is_part($b)) {
$b = padding($b, "left=10,right=10,back=5");
$b = wall_front($b, "Ящик: %W");
$b = wall_bottom($b, "Ящик: %W");
$b = wall_back($b, "Ящик: %W");
$b = wall_left($b, "Ящик: %W");
$b = wall_right($b, "Ящик: %W");
}
}
render_scad();

И нарёк я тумбою изделие своё, но сперва облагородил её незначительно:
require_once "scad.mebel.php";
$box = box(500, 800, 600);
$box["closed"]["b"] = true;
$box = wall_top($box, "Мебель: %W");
$box = wall_bottom($box, "Мебель: %W");
$box = padding($box, "front=16");
$box = wall_left($box, "Мебель: %W");
$box = wall_right($box, "Мебель: %W");
$box = wall_back($box, "Мебель: %W");
$box = padding($box, "top=10,bottom=10");
$boxes = split_vertical($box, "*,*,*,*,*", 16);
foreach ($boxes as $b) {
if (is_part($b)) {
wall_front($b, "door=1,edge=f", "Ящик: %W", "front=-16,left=-16,right=-16,top=-6,bottom=-6");
$b = padding($b, "left=10,right=10,back=5");
$b = wall_front($b, "Ящик: %W");
$b = wall_bottom($b, "Ящик: %W");
$b = wall_back($b, "Ящик: %W");
$b = wall_left($b, "Ящик: %W");
$b = wall_right($b, "Ящик: %W");
}
}
render_scad();


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

Если мы хотим посмотреть картинку в разрезе, добавим команду:
view_cut_front(400);
И рассёк я пространство, чтобы все могли лицезреть срез творения моего

И вот она в разрезе (OpenSCAD не всегда правильно отображает такие разрезы в реальном времени, но после рендеринга (F6) всё становится как надо, а рендеринг тут почти мгновенный).
Добавив строчку
render_parts_list();
мы получим рядом с нашим файлом с кодом csv-файл с частями для распила:

Но это всё баловство, давайте посмотрим на реальные проекты, которые я не только нарисовал, но и собрал.
Реальные проекты
▍ Стол-стеллаж
Идея была в том, что над столом часто пропадает пространство, а можно сделать там стеллаж, и оно будет использоваться с пользой.
require_once "scad.mebel.php";
$box = box(1400, 780 + 1516, 600);
list($tableBox, $upperBox) = split_vertical($box, "780,*");
$thick = 24;
$tableBox = set_thick($tableBox, $thick);
$tableBox = set_edge_thick($tableBox, 2);
$tableBox = wall_top($tableBox, "edge=f,round-front-left=100,round-front-right=100", "Стол: %W");
$tableBox = padding($tableBox, "left=16,right=16,front=100");
$tableBox = wall_left($tableBox, "", "Стол: %W");
$tableBox = wall_right($tableBox, "", "Стол: %W");
$tableBox = padding($tableBox, "bottom=200");
$tableBox = wall_back($tableBox, "", "Стол: %W");
$shelfBox = split_vertical($tableBox, "*,200")[1];
$shelfBox = split_horizontal($shelfBox, "25%,*")[1];
$shelfBox = split_depth($shelfBox, "200,*")[0];
$shelfBox = wall_bottom($shelfBox, "", "Полка: %W");
$shelfBox = wall_left($shelfBox, "", "Полка: %W");
$thick = 16;
$upperBox = set_thick($upperBox, $thick);
$upperBox = set_edge_thick($upperBox, 0.4);
list($upperBottomBox, $upperTopBox) = split_vertical($upperBox, "600,*");
$upperBottomBox = split_depth($upperBottomBox, "300,*")[0];
$upperBottomBox = wall_left($upperBottomBox, "", "Опоры стеллажа: %W");
$upperBottomBox = wall_right($upperBottomBox, "", "Опоры стеллажа: %W");
$upperTopBox = split_depth($upperTopBox, "400,*")[0];
$upperTopBox = wall_top($upperTopBox, "", "Стеллаж: %W");
$upperTopBox = wall_left($upperTopBox, "round-front-bottom=100", "Стеллаж: %W");
$upperTopBox = wall_right($upperTopBox, "round-front-bottom=100", "Стеллаж: %W");
$upperTopBox = padding($upperTopBox, "bottom=100,front=$thick");
$upperTopShelfBoxes = split_vertical($upperTopBox, "*,*,*");
$n = 0;
foreach ($upperTopShelfBoxes as $shelfBox) {
$shelfBox = wall_bottom($shelfBox, "edge-back=0", "Полка стеллажа");
$splitBox = split_horizontal($shelfBox, ($n % 2 == 0 ? "58%,*" : "*,58%"))[1];
$splitBox = wall_left($splitBox, "edge-bottom=0,edge-top=0,edge-back=0", "Перегородка стеллажа");
$n++;
}
render_scad();
render_parts_list();
Рендер:

В жизни (требует обязательного крепления верхней части к стене):

▍ Франкенштейн из стола-стеллажа
Продолжение стола-стеллажа, сделано место под компьютер и сзади отступ на 10 см, чтобы там пустить все провода и закрыть это крышкой внизу.
require_once "scad.mebel.php";
$railsThick = 10;
$box = box(1500, 780 + 1516, 600, 1600);
$box["closed"]["b"] = true;
list($tableBox, $upperBox) = split_vertical($box, "780,*");
$thick = 24;
$tableBox = set_thick($tableBox, $thick);
$tableBox = set_edge_thick($tableBox, 2);
$tableBox = wall_top($tableBox, "edge=f,edge-back=0,round-front-left=100,round-front-right=100", "Стол: %W");
$tableBox = padding($tableBox, "left=230,front=100");
list($tableBox, $computerBox) = split_horizontal($tableBox, "*,230");
$tableBox = padding($tableBox, "back=100");
$tableBox = wall_left($tableBox, "", "Стол: %W");
$tableBox = wall_right($tableBox, "", "Стол: %W");
$computerBox = wall_bottom($computerBox, "edge-left=0", "Стол: Под компьютер" );
list($tableCloseBox,$tablePartBox) = split_vertical($tableBox, "360,*");
$tableCloseBox = wall_back($tableCloseBox, "edge=f", "Стол: закрывашка");
$tablePartBox = wall_back($tablePartBox, "", "Стол: %W");
$thick = 16;
$upperBox = set_thick($upperBox, $thick);
$upperBox = set_edge_thick($upperBox, 0.4);
list($upperBottomBox, $upperTopBox) = split_vertical($upperBox, "600,*");
$upperBottomBox = split_depth($upperBottomBox, "300,*")[0];
$upperBottomBox = wall_left($upperBottomBox, "", "Опоры стеллажа: %W");
$upperBottomBox = wall_right($upperBottomBox, "", "Опоры стеллажа: %W");
$upperTopBox = split_depth($upperTopBox, "400,*")[0];
$upperTopBox = wall_top($upperTopBox, "", "Стеллаж: %W");
$upperTopBox = wall_left($upperTopBox, "round-front-bottom=100", "Стеллаж: %W");
$upperTopBox = wall_right($upperTopBox, "round-front-bottom=100", "Стеллаж: %W");
$upperTopBox = padding($upperTopBox, "bottom=100,front=$thick");
$upperTopShelfBoxes = split_vertical($upperTopBox, "*,*,*");
$n = 0;
foreach ($upperTopShelfBoxes as $shelfBox) {
$shelfBox = wall_bottom($shelfBox, "edge-back=0", "Полка стеллажа");
$splitBox = split_horizontal($shelfBox, ($n % 2 == 1 ? "60%,*" : "*,60%"))[1];
$splitBox = wall_left($splitBox, "edge-bottom=0,edge-top=0,edge-back=0", "Перегородка стеллажа");
$n++;
}
$box = box(1830, 780 - 24, 500);
$box["closed"]["b"] = true;
$box = wall_top($box, "edge-right=0", "Центральная часть: %W");
$box = padding($box, "front=$thick");
list($boxesBox, $centerBox) = split_horizontal($box, "400,*");
$boxesBox = wall_left($boxesBox, "", "Под ящики: %W");
$boxesBox = wall_right($boxesBox, "", "Под ящики: %W");
$boxesBox = wall_bottom($boxesBox, "edge-left=0", "Под ящики: %W");
$thick2 = $thick / 2;
$space = $thick - 4;
$space2 = $space / 2;
$boxesBoxes = split_vertical(padding($boxesBox, "top=4"), "*,*,*,*,*", $thick);
foreach ($boxesBoxes as $boxBox) {
if (is_part($boxBox)) {
wall_front($boxBox, "door=1,edge=f", "Ящик: %W", "front=-$thick,left=-$thick,right=-$thick,bottom=-$space");
$boxBox = padding($boxBox, "left=$railsThick,right=$railsThick,back=5");
$boxBox = wall_front($boxBox, "", "Ящик: %W");
$boxBox = wall_bottom($boxBox, "", "Ящик: %W");
$boxBox = wall_back($boxBox, "", "Ящик: %W");
$boxBox = wall_left($boxBox, "", "Ящик: %W");
$boxBox = wall_right($boxBox, "", "Ящик: %W");
}
}
$centerBox = wall_bottom($centerBox, "edge-left=0,edge-right=0", "Центральная часть: %W");
list($bottomCenterBox, $upperCenterBox) = split_vertical($centerBox, "320,*");
$upperCenterBox = wall_bottom($upperCenterBox, "edge-left=0,edge-right=0", "Центральная часть: Вертикальный разделитель");
wall_left(split_horizontal($upperCenterBox, "*,*", $thick)[1], "", "Центральная часть: Верхний горизонтальный разделитель");
list($bottomCenterLeftBox, $bottomCenterSplitBox, $bottomCenterRightBox) = split_horizontal($bottomCenterBox, "*,*", $thick);
wall_left($bottomCenterSplitBox, "", "Центральная часть: Нижний горизонтальный разделитель");
$bottomCenterLeftBox = wall_front($bottomCenterLeftBox, "edge=f", "Центральная часть: Левая крышка", "front=-$thick,top=-$thick,bottom=-$space,left=2,right=-$space2");
$bottomCenterRightBox = wall_front($bottomCenterRightBox, "edge=f", "Центральная часть: Правая крышка", "front=-$thick,top=-$thick,bottom=-$space,right=2,left=-$space2");
render_scad();
render_parts_list();
Рендер:


В жизни (требует обязательного крепления верхней части к стене):

▍ Стол на кухню
Основная мысль была в том, чтобы за ним можно было нормально сидеть с любой из трёх сторон, и ноги не упирались ни во что, а табуретки убирались под него и получалось компактнее кухонных уголков.
require_once "scad.mebel.php";
$thick = 24;
$edgeThick = 2;
setEdgeThick($edgeThick);
setThick($thick);
$tableBox = box(1200, 742, 800);
$tableBox = wall_top($tableBox, "round-right-front=100,round-right-back=100", "Столешница");
$tableBox = padding($tableBox, "left=30,right=400,front=50,back=50");
$tableBox = wall_left($tableBox, "", "Стол: %W");
$tableBox = padding($tableBox, "front=110,back=110");
$tableBox = wall_right($tableBox, "", "Стол: %W");
$tableBox = split_vertical($tableBox, "*,250")[1];
$tableBox = wall_back(split_depth($tableBox, "*,*", $thick)[1], "", "Стол: Перегородка");
for ($i = 0; $i < 2; $i++) {
for ($j = 0; $j < 2; $j++) {
$chairBox = box(350, 452, 350, 60 + $i * 355, 0, $j * 450);
$chairBox = wall_top($chairBox, "round=100", "Табуретка: Сиденье");
$chairBox = padding($chairBox, "left=60,right=60,back=60,front=60");
$chairBox = wall_left($chairBox, "", "Табуретка: %W");
$chairBox = wall_right($chairBox, "", "Табуретка: %W");
$chairBox = split_vertical($chairBox, "*,200")[1];
$chairBox = wall_back(split_depth($chairBox, "*,*", $thick)[1], "", "Табуретка: Перегородка");
}
}
render_scad();
render_parts_list();
Рендер:

В жизни:



И резюмирую
Если вдруг вам захотелось делать мебель также безумно, как это делаю я, вот что нужно:
- OpenSCAD — рекомендую не стабильный билд, а найтли, он намного быстрее.
- PHP — я использую 7.2.
- Любой редактор, я использую VSCode с плагином для PHP, а также плагин F5 Anything, чтобы по F5 выполнять скрипт. Он создаёт файл с таким же названием и расширением .scad. Его надо открыть в OpenSCAD. Каждый раз, когда будете нажимать F5 в VSCode, он будет видеть, что scad файл изменился и сразу же отображать изменения.
- Сама библиотека и документация всех функций: https://github.com/CodeName33/php.mebel.scad
Ну и как обычно всё MIT, делайте с этим чего хотите.
© 2025 ООО «МТ ФИНАНС»
Telegram-канал со скидками, розыгрышами призов и новостями IT ?

Комментарии (10)
DDroll
17.05.2025 15:04Ну, главное, результат есть, руки более чем прямые) А так, как то сложно выглядит. Не проще ли было запустить SketchUp?)) В свое время ремонт квартиры проектировал в связке SketchUp -> blender -> UE4 -> HTC Vive. Стены, мебель схематично по размерам - в скетче, материалы-текстуры, подгонки моделей - блендер; освещение, запуск ВР режиме - анриал. Был забавный момент - в анриале можно выставить свет в соответствии с географией и временем, я выставил по панорамной фотке, которую юзал для вида за окном, и почему-то в двух комнатах получился разный по яркости свет, в одной было прямо сильно темнее. Думал артефакт, а в реальности так и оказалось - сыграл козырек балкона при определенном положении солнца.
CBET_TbMbI
17.05.2025 15:04Такую мебель на бумаге будет "спроектировать" быстрее. Или в любой рисовальной программе.
А тут даже ничего не сказано про крепления, полозья и прочую фурнитуру.
CodeName33 Автор
17.05.2025 15:04Я никогда не заказываю отверстия для конфирмата при распиле, это бессмысленно удорожает заказ, их я делаю сам и сразу по месту, поэтому не вижу смысла вносить это в проект. С фурнитурой также.
pnmv
17.05.2025 15:04Так, ну, в наши то непростые времена, понятное дело, но десять то лет назад, ну икея же вовсю трудилась, предоставляя полный фарш, из этого вашего дсп...
Ладно бы, там, из цельного дуба...
devlev
17.05.2025 15:04Как же это близко и лампово)) Но писать кодом это конечно мощно! Тут выше написали про sketchup.
Лет 10 назад его освоил и с тех пор все модели в нем рисую. Т.е. рисую сразу по тем размерам которые нужны мне. В своем городе нашел компанию, которая потом все мои идеи перерисовывает уже в своей специальной распиловочной программе, а там сама расставляет присадки. Плюс кромка бывает тоже разной. Использую в основном 2мм а например при распиле ящиков это нужно учитывать чтобы присадки совпали. Программа распиловщика это все учитывает. Так что это их головная боль, не моя.
Так же можно кучу времени потратить на подгонку к направляющим. Ладно если это просто шариковые 8мм. А если хочется скрытого монтажа, то тут вручную все это предусмотреть может быть не просто. А так фабрика сразу размечает места под будущие крепления, только саморез закрути.
CodeName33 Автор
17.05.2025 15:04Мне sketchup тоже не зашел в свое время, я его тоже пробовал. Наверное у меня такое мышление, что код мне понятнее)
У меня кромка всегда уменьшает деталь. Т.е. работает как padding в css, сохраняя внешний размер делали. А с направляющими на ящиках - я просто делаю между ними зазор в 16мм, чтобы на предыдущий ящик положить лист дсп и следующий на своем месте. А фасады уже потом, закрывают лишние зазоры.
Roland21
Eсть же программа pro100 - которая и для частника (сам делал в ней мебель на всю квартиру) и для небольших фирм ок...
CodeName33 Автор
Наверное, проблема во мне) я эту программу пробовал много лет назад, тогда не зашло