1100 lines
45 KiB
OpenSCAD
1100 lines
45 KiB
OpenSCAD
/*
|
||
SOFT FACET V2 — мастер для керамической кофейной воронки
|
||
и постоянный тонкостенный кожух для заливки гипса.
|
||
|
||
Это самостоятельная форма: не копия Hario V60.
|
||
В одном файле находятся одиннадцать вариантов поверхности. В production
|
||
режиме амплитуда рельефа автоматически ограничивается так, чтобы на каждом
|
||
расчетном луче сохранялся заданный положительный угол съема.
|
||
|
||
РАЗМЕРНЫЙ ПРЕСЕТ (size_preset):
|
||
"custom" — ручные значения из блока custom_* ниже.
|
||
"v60_01" — под бумажный фильтр Hario V60-01 (1-2 чашки), устье ~114 мм.
|
||
"v60_02" — под бумажный фильтр Hario V60-02 (1-4 чашки), устье ~124 мм.
|
||
Это ориентировочные практические размеры, подобранные под посадку
|
||
бумажного фильтра соответствующего номера, а НЕ обмер оригинальной
|
||
формы Hario (внутренние размеры оригинала производитель не публикует).
|
||
Проверяйте посадку конкретной пачки фильтров, которая есть у вас,
|
||
и при необходимости используйте "custom".
|
||
|
||
КАК ЭКСПОРТИРОВАТЬ:
|
||
1. part = "variants" — галерея всех вариантов.
|
||
2. Выбрать size_preset и surface_style.
|
||
3. part = "master"; F6 -> Export STL — мастер уже повернут для печати.
|
||
4. part = "box"; F6 -> Export STL — кожух стоит дном на столе.
|
||
5. part = "assembly" показывает установку и два фиксирующих штифта.
|
||
6. part = "support_plate" — сырая глиняная опорная площадка.
|
||
7. part = "support_cutter" — печатный резак для этой площадки.
|
||
8. part = "clay_blank_2d" — опорный раскрой стенки для DXF/SVG.
|
||
9. part = "support_plate_2d" — раскрой площадки для DXF/SVG.
|
||
10. part = "mount_pin" — один печатный фиксирующий штифт.
|
||
11. part = "mount_pins" — три штифта: два рабочих и один запасной.
|
||
12. part = "dripper_preview" — конус и площадка после соединения.
|
||
|
||
Размеры по умолчанию (size_preset = "custom") рассчитаны примерно на
|
||
бумажный фильтр размера 02, сырой пласт глины 5 мм и усадку 12%.
|
||
|
||
АВТОПРОВЕРКА ПЕЧАТИ:
|
||
Толщина полой стенки мастера в самой глубокой впадине рельефа проверяется
|
||
автоматически (см. функцию style_required_clearance() и переменную
|
||
master_min_wall_check ниже). Если введённого вручную texture_allowance не
|
||
хватает для выбранного стиля, запас увеличивается автоматически
|
||
(effective_texture_allowance) и в консоль выводится echo-предупреждение.
|
||
Смотрите вкладку Console после каждого рендера.
|
||
*/
|
||
|
||
model_version = "2.0";
|
||
part = "master"; // [variants,assembly,master,box,mount_pin,mount_pins,support_plate,support_cutter,clay_blank_2d,support_plate_2d,dripper_preview]
|
||
|
||
// --- Вариант поверхности ---
|
||
surface_style = "smooth_sculpted"; // [smooth_sculpted,vertical_soft,horizontal_rings,cross_wave,diagonal_flow,woven_cells,organic_asymmetric,ribbed_petal,pillow_facets,dimpled_grid,spiral_flow]
|
||
|
||
/*
|
||
smooth_sculpted — широкая мягкая талия по мотивам референса
|
||
vertical_soft — спокойные вертикальные лепестки
|
||
horizontal_rings — широкие горизонтальные волны
|
||
cross_wave — наложение вертикальных и горизонтальных волн
|
||
diagonal_flow — диагональное течение без копирования спиралей V60
|
||
woven_cells — мягкая клеточная интерференция
|
||
organic_asymmetric — свободная слегка асимметричная поверхность
|
||
ribbed_petal — плотные округлые рёбра-лепестки (два наложенных гармоника)
|
||
pillow_facets — мягкие "подушечные" грани с плавными стыками
|
||
dimpled_grid — регулярная сетка мягких вмятин
|
||
spiral_flow — один мягкий спиральный желобок (не повторяет V60)
|
||
*/
|
||
|
||
// --- Качество геометрии и рекомендуемая печать ---
|
||
quality_preset = "ultra"; // [draft,normal,high,ultra]
|
||
|
||
// --- Безопасное извлечение из одночастной гипсовой формы ---
|
||
release_safe_mode = true;
|
||
minimum_release_draft_angle = 5; // минимальный угол расширения к устью, градусы
|
||
release_check_rings = 110; // совпадает с максимальной сеткой по высоте
|
||
release_check_segments = 240; // совпадает с максимальной угловой сеткой
|
||
|
||
surface_segments =
|
||
quality_preset == "draft" ? 60 :
|
||
quality_preset == "normal" ? 100 :
|
||
quality_preset == "high" ? 160 : 240;
|
||
|
||
height_rings =
|
||
quality_preset == "draft" ? 24 :
|
||
quality_preset == "normal" ? 44 :
|
||
quality_preset == "high" ? 72 : 110;
|
||
|
||
recommended_master_layer =
|
||
quality_preset == "draft" ? 0.24 :
|
||
quality_preset == "normal" ? 0.16 :
|
||
quality_preset == "high" ? 0.12 : 0.08;
|
||
|
||
recommended_master_walls =
|
||
quality_preset == "draft" ? 3 :
|
||
quality_preset == "normal" ? 4 : 5;
|
||
|
||
recommended_outer_wall_speed =
|
||
quality_preset == "draft" ? 60 :
|
||
quality_preset == "normal" ? 45 :
|
||
quality_preset == "high" ? 35 : 25;
|
||
|
||
recommended_box_layer =
|
||
quality_preset == "ultra" ? 0.16 : 0.20;
|
||
|
||
/*
|
||
ПРОФИЛЬ СЛАЙСЕРА ДЛЯ МАСТЕРА:
|
||
высота слоя — берется из recommended_master_layer
|
||
внешняя стенка — recommended_outer_wall_speed мм/с
|
||
периметры — recommended_master_walls
|
||
заполнение — 10-15% gyroid
|
||
верх/низ — 5 слоев
|
||
поддержки — выключены
|
||
шов — выровнять с одной стороны; затем зашлифовать
|
||
ironing — выключить
|
||
сопло — рекомендуется 0.4 мм и меньше (мелкий рельеф 1.3-2.8 мм)
|
||
|
||
ПРОФИЛЬ ДЛЯ КОЖУХА:
|
||
слой — recommended_box_layer
|
||
периметры — 3
|
||
заполнение — 0%
|
||
нижние слои — 5
|
||
поддержки — выключены
|
||
*/
|
||
|
||
// --- Размерный пресет ---
|
||
size_preset = "v60_02"; // [custom,v60_01,v60_02]
|
||
|
||
// Ручные размеры — используются только при size_preset = "custom".
|
||
custom_target_inner_top_d = 120; // внутренний диаметр после обжига, мм
|
||
custom_target_inner_outlet_d = 21; // выходное отверстие после обжига, мм
|
||
custom_target_inner_height = 92; // высота внутреннего конуса после обжига, мм
|
||
custom_support_finished_span = 123; // максимальный размер опоры после обжига, мм
|
||
custom_support_finished_hole_d = 21; // отверстие опоры после обжига, мм
|
||
|
||
clay_slab_thickness = 5; // толщина сырого пласта, мм
|
||
clay_shrink_percent = 12; // полная усадка сушки + обжига, %
|
||
|
||
support_raw_thickness = 6; // толщина сырой площадки, мм
|
||
support_corner_ratio = 0.18; // мягкость трех округлых выступов
|
||
|
||
// Итоговые размеры изделия с учетом выбранного пресета.
|
||
// v60_01 / v60_02 — ориентировочные практические размеры под фильтр
|
||
// соответствующего номера (см. пояснение в шапке файла).
|
||
target_inner_top_d =
|
||
size_preset == "v60_01" ? 114 :
|
||
size_preset == "v60_02" ? 124 :
|
||
custom_target_inner_top_d;
|
||
|
||
target_inner_outlet_d =
|
||
size_preset == "v60_01" ? 20 :
|
||
size_preset == "v60_02" ? 21 :
|
||
custom_target_inner_outlet_d;
|
||
|
||
target_inner_height =
|
||
size_preset == "v60_01" ? 87 :
|
||
size_preset == "v60_02" ? 95 :
|
||
custom_target_inner_height;
|
||
|
||
support_finished_span =
|
||
size_preset == "v60_01" ? 117 :
|
||
size_preset == "v60_02" ? 127 :
|
||
custom_support_finished_span;
|
||
|
||
support_finished_hole_d =
|
||
size_preset == "v60_01" ? 20 :
|
||
size_preset == "v60_02" ? 21 :
|
||
custom_support_finished_hole_d;
|
||
|
||
support_cutter_height = 14;
|
||
support_cutter_wall = 1.20;
|
||
support_cutter_spoke_width = 8;
|
||
support_cutter_handle_thickness = 2.4;
|
||
|
||
// --- Характер формы ---
|
||
profile_power = 0.97;
|
||
|
||
vertical_count = 10;
|
||
vertical_depth = 2.20;
|
||
|
||
horizontal_cycles = 3;
|
||
horizontal_depth = 1.80;
|
||
|
||
diagonal_count = 7;
|
||
diagonal_turns = 0.55;
|
||
diagonal_depth = 1.80;
|
||
|
||
cell_columns = 8;
|
||
cell_rows = 3;
|
||
cell_depth = 1.50;
|
||
|
||
organic_depth = 2.20;
|
||
|
||
ribbed_petal_count = 14;
|
||
ribbed_petal_depth = 1.60;
|
||
|
||
pillow_facet_count = 9;
|
||
pillow_facet_depth = 1.90;
|
||
|
||
dimple_count_angle = 16;
|
||
dimple_count_height = 6;
|
||
dimple_depth = 1.15;
|
||
|
||
spiral_leads = 1;
|
||
spiral_turns = 2.3;
|
||
spiral_depth = 1.60;
|
||
spiral_groove_width = 0.42; // доля периода на канавку, 0..0.5 (уже -> глубже видимая канавка)
|
||
|
||
// Ручной минимум запаса под рельеф (может быть увеличен автоматически ниже).
|
||
texture_allowance = 3.5;
|
||
|
||
// --- Перемычка: задает глубину и служит ручкой ---
|
||
bridge_length = 240;
|
||
bridge_width = 28;
|
||
bridge_thickness = 6;
|
||
support_ring_outset = 9;
|
||
support_ring_inset = 5;
|
||
master_shell_thickness = 2.4;
|
||
master_tip_cap = 3.2;
|
||
|
||
// Съемное крепление: два открытых седла на кожухе и два печатных штифта.
|
||
mount_saddle_length = 14;
|
||
mount_saddle_wall = 2.4;
|
||
mount_saddle_floor = 2.8;
|
||
mount_saddle_top_margin = 3.5;
|
||
mount_slot_clearance = 0.35;
|
||
mount_gusset_height = 20;
|
||
mount_pin_d = 4.0;
|
||
mount_pin_hole_clearance = 0.45;
|
||
mount_pin_head_d = 8.0;
|
||
mount_pin_head_h = 2.0;
|
||
mount_pin_end_clearance = 1.2;
|
||
|
||
// --- Постоянный кожух для гипса ---
|
||
plaster_side = 27; // слой гипса вокруг широкой части, мм
|
||
plaster_below_tip = 26; // слой гипса под носиком, мм
|
||
box_wall = 1.8; // стенка печатного кожуха, мм
|
||
box_bottom = 1.8;
|
||
box_sides = quality_preset == "draft" ? 36 : 60;
|
||
rim_height = 5;
|
||
rim_extra = 3.5;
|
||
plaster_fill_margin = 1.2; // гипс остается ниже опорного кольца мастера
|
||
|
||
/*
|
||
--- Автопроверка запаса на текстуру ---
|
||
Разные стили "съедают" разную глубину рельефа в самой глубокой впадине.
|
||
Если введенного вручную texture_allowance не хватает для выбранного
|
||
стиля, полая полость может пробить наружную стенку в этом месте.
|
||
style_required_clearance() консервативно суммирует амплитуды базового
|
||
силуэта и текстуры конкретного стиля (считая худший случай, когда обе
|
||
впадины совпадают по фазе) — это верхняя оценка "с запасом", а не точный
|
||
расчет самой глубокой точки на сетке (та отдельно проверяется ниже
|
||
через master_min_wall_check по реальным точкам поверхности).
|
||
*/
|
||
function style_required_clearance(style) =
|
||
let(sculpted_max = 2.80) // максимальная глубина базового силуэта sculpted_silhouette()
|
||
style == "smooth_sculpted" ? sculpted_max :
|
||
style == "vertical_soft" ? sculpted_max + vertical_depth :
|
||
style == "horizontal_rings" ? sculpted_max + horizontal_depth :
|
||
style == "cross_wave" ? sculpted_max + 0.72 * vertical_depth + 0.72 * horizontal_depth :
|
||
style == "diagonal_flow" ? sculpted_max + diagonal_depth :
|
||
style == "woven_cells" ? sculpted_max + cell_depth :
|
||
style == "organic_asymmetric" ? sculpted_max + organic_depth :
|
||
style == "ribbed_petal" ? sculpted_max + ribbed_petal_depth :
|
||
style == "pillow_facets" ? sculpted_max + pillow_facet_depth :
|
||
style == "dimpled_grid" ? sculpted_max + dimple_depth :
|
||
style == "spiral_flow" ? sculpted_max + spiral_depth :
|
||
sculpted_max;
|
||
|
||
min_safe_wall = 1.0; // минимально допустимая толщина стенки в самой глубокой впадине, мм
|
||
required_total_buffer = style_required_clearance(surface_style) + min_safe_wall;
|
||
|
||
// Итоговый запас: не меньше введенного вручную и не меньше требуемого для стиля.
|
||
function effective_texture_allowance_for(style) =
|
||
max(
|
||
texture_allowance,
|
||
style_required_clearance(style) + min_safe_wall - master_shell_thickness
|
||
);
|
||
|
||
effective_texture_allowance = effective_texture_allowance_for(surface_style);
|
||
|
||
// --- Расчет мастер-формы с учетом усадки и толщины пласта ---
|
||
shrink_scale = 1 - clay_shrink_percent / 100;
|
||
master_top_d = target_inner_top_d / shrink_scale + 2 * clay_slab_thickness;
|
||
master_bottom_d = target_inner_outlet_d / shrink_scale + 2 * clay_slab_thickness;
|
||
master_height = target_inner_height / shrink_scale + clay_slab_thickness * 0.7;
|
||
|
||
support_target_green_span = support_finished_span / shrink_scale;
|
||
support_green_hole_d = support_finished_hole_d / shrink_scale;
|
||
support_envelope_d =
|
||
support_target_green_span
|
||
/ (sqrt(3) * (0.5 - support_corner_ratio) + 2 * support_corner_ratio);
|
||
support_corner_r = support_envelope_d * support_corner_ratio;
|
||
support_lobe_center_r = support_envelope_d / 2 - support_corner_r;
|
||
support_max_green_span = sqrt(3) * support_lobe_center_r + 2 * support_corner_r;
|
||
support_min_green_span = 1.5 * support_lobe_center_r + 2 * support_corner_r;
|
||
support_segments = max(48, floor(surface_segments / 2));
|
||
|
||
master_top_r = master_top_d / 2;
|
||
master_bottom_r = master_bottom_d / 2;
|
||
|
||
valid_parts = [
|
||
"variants", "assembly", "master", "box", "support_plate",
|
||
"support_cutter", "mount_pin", "mount_pins", "clay_blank_2d",
|
||
"support_plate_2d", "dripper_preview"
|
||
];
|
||
|
||
valid_styles = [
|
||
"smooth_sculpted", "vertical_soft", "horizontal_rings", "cross_wave",
|
||
"diagonal_flow", "woven_cells", "organic_asymmetric", "ribbed_petal",
|
||
"pillow_facets", "dimpled_grid", "spiral_flow"
|
||
];
|
||
|
||
function list_contains(value, values, index = 0) =
|
||
index >= len(values) ? false :
|
||
values[index] == value ? true :
|
||
list_contains(value, values, index + 1);
|
||
|
||
assert(list_contains(part, valid_parts), str("Неизвестный part: ", part));
|
||
assert(list_contains(surface_style, valid_styles), str("Неизвестный surface_style: ", surface_style));
|
||
assert(shrink_scale > 0 && shrink_scale <= 1, "clay_shrink_percent должен быть в диапазоне 0..99.");
|
||
assert(clay_slab_thickness > 0, "clay_slab_thickness должен быть больше 0.");
|
||
assert(support_raw_thickness > 0, "support_raw_thickness должен быть больше 0.");
|
||
assert(support_corner_ratio > 0 && support_corner_ratio < 0.5, "support_corner_ratio должен быть между 0 и 0.5.");
|
||
assert(support_cutter_wall > 0, "support_cutter_wall должен быть больше 0.");
|
||
assert(support_green_hole_d > 2 * support_cutter_wall, "Стенки резака перекрывают центральное отверстие.");
|
||
assert(master_tip_cap > 0 && master_tip_cap < master_height, "master_tip_cap должен быть внутри высоты мастера.");
|
||
assert(master_bottom_r > 0 && master_top_r > master_bottom_r, "Диаметры мастера должны задавать расширяющуюся форму.");
|
||
assert(minimum_release_draft_angle > 0 && minimum_release_draft_angle < 30, "minimum_release_draft_angle должен быть между 0 и 30 градусами.");
|
||
assert(release_check_rings >= height_rings && release_check_segments >= surface_segments, "Сетка проверки съема не должна быть грубее геометрии.");
|
||
assert(mount_slot_clearance > 0 && mount_pin_hole_clearance > 0, "Зазоры крепления должны быть положительными.");
|
||
assert(plaster_fill_margin > 0.5, "plaster_fill_margin должен исключать контакт гипса с опорным кольцом.");
|
||
|
||
box_height = plaster_below_tip + master_height;
|
||
box_top_inner_r = master_top_r + effective_texture_allowance + plaster_side;
|
||
box_bottom_inner_r = master_bottom_r + effective_texture_allowance * 0.35 + plaster_side;
|
||
box_top_outer_r = box_top_inner_r + box_wall;
|
||
box_bottom_outer_r = box_bottom_inner_r + box_wall;
|
||
|
||
master_bridge_overlap = 0.25;
|
||
master_bridge_bottom_z = master_height - master_bridge_overlap;
|
||
plaster_fill_level = box_height - plaster_fill_margin;
|
||
mount_saddle_center_x = box_top_outer_r + rim_extra - 0.25;
|
||
mount_saddle_outer_width = bridge_width + 2 * (mount_saddle_wall + mount_slot_clearance);
|
||
mount_saddle_height = mount_saddle_floor + bridge_thickness + mount_saddle_top_margin;
|
||
mount_saddle_base_z = box_height - master_bridge_overlap - mount_saddle_floor;
|
||
mount_pin_center_z = box_height - master_bridge_overlap + bridge_thickness / 2;
|
||
mount_pin_hole_d = mount_pin_d + mount_pin_hole_clearance;
|
||
mount_pin_shaft_length = mount_saddle_outer_width + mount_pin_end_clearance;
|
||
|
||
assert(
|
||
plaster_fill_level < plaster_below_tip + master_bridge_bottom_z,
|
||
"Гипс не должен доходить до опорного кольца мастера."
|
||
);
|
||
assert(
|
||
bridge_length / 2 > mount_saddle_center_x + mount_saddle_length / 2,
|
||
"Перемычка не перекрывает седло целиком."
|
||
);
|
||
assert(
|
||
bridge_width + 2 * mount_slot_clearance < mount_saddle_outer_width,
|
||
"В седле не осталось боковых стенок."
|
||
);
|
||
assert(mount_pin_d < mount_pin_hole_d, "Штифт должен быть меньше отверстия.");
|
||
assert(
|
||
mount_pin_shaft_length > mount_saddle_outer_width,
|
||
"Штифт должен проходить через обе стенки седла."
|
||
);
|
||
|
||
echo("=== Пресет и базовые размеры ===");
|
||
echo("Пресет размера:", size_preset);
|
||
echo("Целевой внутр. диаметр устья, мм:", target_inner_top_d);
|
||
echo("Целевой диаметр слива, мм:", target_inner_outlet_d);
|
||
echo("Целевая высота, мм:", target_inner_height);
|
||
echo("Master top diameter, mm:", master_top_d);
|
||
echo("Master bottom diameter, mm:", master_bottom_d);
|
||
echo("Master height, mm:", master_height);
|
||
echo("Casting shell height, mm:", box_height);
|
||
echo("Casting shell max diameter, mm:", 2 * (box_top_outer_r + rim_extra));
|
||
echo("Surface style:", surface_style);
|
||
echo("Model version:", model_version);
|
||
echo("Geometry quality:", quality_preset);
|
||
echo("Recommended MASTER layer, mm:", recommended_master_layer);
|
||
echo("Recommended MASTER walls:", recommended_master_walls);
|
||
echo("Recommended MASTER outer wall speed, mm/s:", recommended_outer_wall_speed);
|
||
echo("Recommended BOX layer, mm:", recommended_box_layer);
|
||
echo("Supports: OFF; master infill: 10-15%; box infill: 0%");
|
||
echo("GREEN support plate max span, mm:", support_max_green_span);
|
||
echo("GREEN support plate min span, mm:", support_min_green_span);
|
||
echo("GREEN support plate hole, mm:", support_green_hole_d);
|
||
echo("Печатная длина ручки-перемычки (bridge_length), мм:", bridge_length,
|
||
"— проверьте, что стол/область печати вашего принтера достаточно длинная.");
|
||
echo("Уровень заливки гипса от дна кожуха, мм:", plaster_fill_level);
|
||
echo("Воздушный зазор гипс-кольцо мастера, мм:", plaster_below_tip + master_bridge_bottom_z - plaster_fill_level);
|
||
echo("Диаметр отверстия под фиксирующий штифт, мм:", mount_pin_hole_d);
|
||
echo("Центры седел крепления по X, мм:", [-mount_saddle_center_x, mount_saddle_center_x]);
|
||
echo("Полный размах кожуха с седлами, мм:", 2 * (mount_saddle_center_x + mount_saddle_length / 2));
|
||
|
||
function base_radius(z) =
|
||
master_bottom_r
|
||
+ (master_top_r - master_bottom_r)
|
||
* pow(z / master_height, profile_power);
|
||
|
||
function gauss(x, center, width) =
|
||
exp(-pow((x - center) / width, 2));
|
||
|
||
function edge_fade(t) =
|
||
pow(max(0, sin(180 * t)), 0.80);
|
||
|
||
// Одна широкая талия и небольшое раскрытие у края — силуэт с референса.
|
||
function sculpted_silhouette(t) =
|
||
-2.80 * gauss(t, 0.53, 0.22)
|
||
+ 1.60 * gauss(t, 0.88, 0.11);
|
||
|
||
// --- Вспомогательные функции для новых стилей ---
|
||
|
||
// Локальная координата внутри одной грани фасетного узора, диапазон ~[-1, 1].
|
||
function facet_local_u(angle, count) =
|
||
let(seg = 360 / count)
|
||
2 * (angle / seg - floor(angle / seg + 0.5));
|
||
|
||
// Изолированный "бугорок" по периодической координате: 0 везде, кроме пиков.
|
||
function dimple_bump(u) = pow(max(0, cos(u)), 3);
|
||
|
||
// Узкий желобок вдоль фазы (используется для спирали): 0 везде, кроме узкой полосы у phase=0.
|
||
function groove_profile(phase, width_fraction) =
|
||
pow(max(0, cos(phase)), 1 / max(0.05, width_fraction));
|
||
|
||
function style_offset(style, t, angle) =
|
||
style == "smooth_sculpted" ?
|
||
sculpted_silhouette(t) :
|
||
|
||
style == "vertical_soft" ?
|
||
sculpted_silhouette(t)
|
||
+ vertical_depth * (0.25 + 0.75 * t)
|
||
* cos(vertical_count * angle) :
|
||
|
||
style == "horizontal_rings" ?
|
||
sculpted_silhouette(t)
|
||
+ horizontal_depth * edge_fade(t)
|
||
* sin(360 * horizontal_cycles * t) :
|
||
|
||
style == "cross_wave" ?
|
||
sculpted_silhouette(t)
|
||
+ 0.72 * vertical_depth * (0.25 + 0.75 * t)
|
||
* cos(8 * angle)
|
||
+ 0.72 * horizontal_depth * edge_fade(t)
|
||
* sin(360 * horizontal_cycles * t) :
|
||
|
||
style == "diagonal_flow" ?
|
||
sculpted_silhouette(t)
|
||
+ diagonal_depth * (0.20 + 0.80 * t)
|
||
* cos(diagonal_count * angle + 360 * diagonal_turns * t) :
|
||
|
||
style == "woven_cells" ?
|
||
sculpted_silhouette(t)
|
||
+ cell_depth * edge_fade(t)
|
||
* cos(cell_columns * angle)
|
||
* sin(360 * cell_rows * t) :
|
||
|
||
style == "organic_asymmetric" ?
|
||
sculpted_silhouette(t)
|
||
+ organic_depth * edge_fade(t)
|
||
* (
|
||
0.66 * cos(angle - 35 + 115 * t)
|
||
+ 0.34 * cos(3 * angle + 20 - 70 * t)
|
||
) :
|
||
|
||
style == "ribbed_petal" ?
|
||
sculpted_silhouette(t)
|
||
+ ribbed_petal_depth * (0.30 + 0.70 * t)
|
||
* (
|
||
0.65 * cos(ribbed_petal_count * angle)
|
||
+ 0.35 * cos(2 * ribbed_petal_count * angle)
|
||
) :
|
||
|
||
style == "pillow_facets" ?
|
||
sculpted_silhouette(t)
|
||
+ pillow_facet_depth * edge_fade(t)
|
||
* (1 - pow(facet_local_u(angle, pillow_facet_count), 2)) :
|
||
|
||
style == "dimpled_grid" ?
|
||
sculpted_silhouette(t)
|
||
- dimple_depth * edge_fade(t)
|
||
* dimple_bump(dimple_count_angle * angle)
|
||
* dimple_bump(360 * dimple_count_height * t) :
|
||
|
||
style == "spiral_flow" ?
|
||
sculpted_silhouette(t)
|
||
- spiral_depth * edge_fade(t)
|
||
* groove_profile(
|
||
spiral_leads * angle + 360 * spiral_turns * t,
|
||
spiral_groove_width
|
||
) :
|
||
|
||
sculpted_silhouette(t);
|
||
|
||
function release_base_step(ring) =
|
||
base_radius(master_height * (ring + 1) / release_check_rings)
|
||
- base_radius(master_height * ring / release_check_rings);
|
||
|
||
function release_style_step(style, ring, segment) =
|
||
let(
|
||
t0 = ring / release_check_rings,
|
||
t1 = (ring + 1) / release_check_rings,
|
||
angle = 360 * segment / release_check_segments
|
||
)
|
||
style_offset(style, t1, angle) - style_offset(style, t0, angle);
|
||
|
||
release_required_step =
|
||
tan(minimum_release_draft_angle) * master_height / release_check_rings;
|
||
|
||
function release_scale_for(style) =
|
||
release_safe_mode ?
|
||
max(0, min(concat(
|
||
[1],
|
||
[
|
||
for (ring = [0 : release_check_rings - 1])
|
||
for (segment = [0 : release_check_segments - 1])
|
||
let(
|
||
base_step = release_base_step(ring),
|
||
relief_step = release_style_step(style, ring, segment)
|
||
)
|
||
if (relief_step < 0)
|
||
(base_step - release_required_step) / (-relief_step)
|
||
]
|
||
))) :
|
||
1;
|
||
|
||
selected_release_scale = release_scale_for(surface_style);
|
||
|
||
function surface_radius(z, angle, style, release_scale = undef) =
|
||
let(
|
||
t = z / master_height,
|
||
scale = is_undef(release_scale) ? release_scale_for(style) : release_scale
|
||
)
|
||
base_radius(z) + scale * style_offset(style, t, angle);
|
||
|
||
function point_index(ring, segment) =
|
||
ring * surface_segments + segment;
|
||
|
||
// --- Диагностика полой стенки мастера (по реальному текстурированному профилю) ---
|
||
function master_inner_bottom_r_for(style) =
|
||
base_radius(master_tip_cap)
|
||
- master_shell_thickness
|
||
- effective_texture_allowance_for(style);
|
||
|
||
function master_inner_top_r_for(style) =
|
||
master_top_r
|
||
- master_shell_thickness
|
||
- effective_texture_allowance_for(style);
|
||
|
||
master_inner_bottom_r = master_inner_bottom_r_for(surface_style);
|
||
master_inner_top_r = master_inner_top_r_for(surface_style);
|
||
|
||
master_wall_samples = [
|
||
for (ring = [0 : height_rings])
|
||
let(z = master_height * ring / height_rings)
|
||
if (z >= master_tip_cap)
|
||
let(
|
||
inner_r = master_inner_bottom_r
|
||
+ (master_inner_top_r - master_inner_bottom_r)
|
||
* (z - master_tip_cap) / (master_height - master_tip_cap)
|
||
)
|
||
for (segment = [0 : surface_segments - 1])
|
||
let(angle = 360 * segment / surface_segments)
|
||
surface_radius(z, angle, surface_style, selected_release_scale) - inner_r
|
||
];
|
||
|
||
master_min_wall_check = min(master_wall_samples);
|
||
|
||
// Для одночастной формы радиус должен расти к устью на каждом угловом луче.
|
||
draft_steps = [
|
||
for (ring = [0 : height_rings - 1])
|
||
for (segment = [0 : surface_segments - 1])
|
||
let(
|
||
z0 = master_height * ring / height_rings,
|
||
z1 = master_height * (ring + 1) / height_rings,
|
||
angle = 360 * segment / surface_segments
|
||
)
|
||
surface_radius(z1, angle, surface_style, selected_release_scale)
|
||
- surface_radius(z0, angle, surface_style, selected_release_scale)
|
||
];
|
||
|
||
min_draft_step = min(draft_steps);
|
||
actual_ring_height = master_height / height_rings;
|
||
required_actual_draft_step = tan(minimum_release_draft_angle) * actual_ring_height;
|
||
actual_minimum_draft_angle = atan(min_draft_step / actual_ring_height);
|
||
|
||
assert(
|
||
!release_safe_mode || min_draft_step >= required_actual_draft_step - 0.001,
|
||
"Release-safe ограничение не обеспечило заданный угол съема."
|
||
);
|
||
|
||
echo("=== Проверка полой стенки мастера ===");
|
||
echo("Требуемый запас под рельеф (оценка сверху), мм:", style_required_clearance(surface_style));
|
||
echo("texture_allowance (введено вручную), мм:", texture_allowance);
|
||
echo("effective_texture_allowance (фактически используется), мм:", effective_texture_allowance);
|
||
echo(effective_texture_allowance > texture_allowance ?
|
||
str("ВНИМАНИЕ: texture_allowance увеличен автоматически с ", texture_allowance,
|
||
" до ", effective_texture_allowance, " мм для безопасности стенки.") :
|
||
"texture_allowance достаточен для выбранного стиля без автокоррекции.");
|
||
echo("Реальная минимальная толщина стенки мастера по сетке точек, мм:", master_min_wall_check);
|
||
echo(master_min_wall_check < 0.8 ?
|
||
"ОШИБКА: в этой точке стенка может порваться или отсутствовать — увеличьте texture_allowance или master_shell_thickness." :
|
||
master_min_wall_check < 1.2 ?
|
||
"ВНИМАНИЕ: местами стенка тоньше 1.2 мм — печатайте минимум 3 периметра соплом 0.4 мм, без вентилятора на полную." :
|
||
"Толщина стенки мастера в норме для печати.");
|
||
echo("Минимальный прирост радиуса к устью за один пояс, мм:", min_draft_step);
|
||
echo("Автомасштаб амплитуды рельефа для съема:", selected_release_scale);
|
||
echo("Фактический минимальный угол съема по сетке, градусы:", actual_minimum_draft_angle);
|
||
echo(min_draft_step < 0 ?
|
||
"ОШИБКА: найден локальный обратный уклон — этот стиль нельзя использовать для одночастной гипсовой формы." :
|
||
actual_minimum_draft_angle < minimum_release_draft_angle ?
|
||
"ОШИБКА: угол съема меньше production-минимума." :
|
||
"Production-safe угол съема обеспечен на расчетной сетке.");
|
||
|
||
module styled_master_cone(style = surface_style) {
|
||
style_release_scale = release_scale_for(style);
|
||
points = [
|
||
for (ring = [0 : height_rings])
|
||
let(z = master_height * ring / height_rings)
|
||
for (segment = [0 : surface_segments - 1])
|
||
let(
|
||
angle = 360 * segment / surface_segments,
|
||
radius = surface_radius(z, angle, style, style_release_scale)
|
||
)
|
||
[radius * cos(angle), radius * sin(angle), z]
|
||
];
|
||
|
||
// Триангуляция нужна для OpenSCAD 2021.01: текстурированные кольца
|
||
// делают исходные четырехугольники неплоскими и вызывают CGAL warning.
|
||
side_faces = [
|
||
for (ring = [0 : height_rings - 1])
|
||
for (segment = [0 : surface_segments - 1])
|
||
let(next = (segment + 1) % surface_segments)
|
||
each [
|
||
[
|
||
point_index(ring, segment),
|
||
point_index(ring, next),
|
||
point_index(ring + 1, next)
|
||
],
|
||
[
|
||
point_index(ring, segment),
|
||
point_index(ring + 1, next),
|
||
point_index(ring + 1, segment)
|
||
]
|
||
]
|
||
];
|
||
|
||
bottom_face = [
|
||
for (segment = [surface_segments - 1 : -1 : 0])
|
||
point_index(0, segment)
|
||
];
|
||
|
||
top_face = [
|
||
for (segment = [0 : surface_segments - 1])
|
||
point_index(height_rings, segment)
|
||
];
|
||
|
||
polyhedron(
|
||
points = points,
|
||
faces = concat(side_faces, [bottom_face], [top_face]),
|
||
convexity = 10
|
||
);
|
||
}
|
||
|
||
module hollow_master_shell(style = surface_style) {
|
||
difference() {
|
||
styled_master_cone(style);
|
||
|
||
translate([0, 0, master_tip_cap])
|
||
cylinder(
|
||
h = master_height - master_tip_cap + 0.2,
|
||
r1 = master_inner_bottom_r_for(style),
|
||
r2 = master_inner_top_r_for(style),
|
||
$fn = surface_segments
|
||
);
|
||
}
|
||
}
|
||
|
||
module rounded_bridge() {
|
||
end_radius = bridge_width / 2;
|
||
|
||
difference() {
|
||
hull() {
|
||
translate([-(bridge_length / 2 - end_radius), 0, 0])
|
||
cylinder(h = bridge_thickness, r = end_radius, $fn = 48);
|
||
translate([(bridge_length / 2 - end_radius), 0, 0])
|
||
cylinder(h = bridge_thickness, r = end_radius, $fn = 48);
|
||
}
|
||
|
||
for (side = [-1, 1])
|
||
translate([
|
||
side * mount_saddle_center_x,
|
||
0,
|
||
bridge_thickness / 2
|
||
])
|
||
rotate([90, 0, 0])
|
||
cylinder(
|
||
h = bridge_width + 2,
|
||
d = mount_pin_hole_d,
|
||
center = true,
|
||
$fn = 32
|
||
);
|
||
}
|
||
}
|
||
|
||
module support_ring() {
|
||
difference() {
|
||
cylinder(
|
||
h = bridge_thickness,
|
||
r = master_top_r + effective_texture_allowance + support_ring_outset,
|
||
$fn = surface_segments
|
||
);
|
||
|
||
translate([0, 0, -0.1])
|
||
cylinder(
|
||
h = bridge_thickness + 0.2,
|
||
r = master_top_r - support_ring_inset,
|
||
$fn = surface_segments
|
||
);
|
||
}
|
||
}
|
||
|
||
// Рабочее положение: носик вниз, перемычка лежит на верхнем краю кожуха.
|
||
module master_use_orientation(style = surface_style) {
|
||
union() {
|
||
hollow_master_shell(style);
|
||
translate([0, 0, master_bridge_bottom_z])
|
||
union() {
|
||
support_ring();
|
||
rounded_bridge();
|
||
}
|
||
}
|
||
}
|
||
|
||
// Положение для печати: перемычка на столе, конус сужается вверх.
|
||
module master_print_orientation(style = surface_style) {
|
||
translate([0, 0, master_bridge_bottom_z + bridge_thickness])
|
||
rotate([180, 0, 0])
|
||
master_use_orientation(style);
|
||
}
|
||
|
||
module centered_box(size) {
|
||
translate([-size[0] / 2, -size[1] / 2, 0])
|
||
cube(size);
|
||
}
|
||
|
||
function box_outer_radius_at(z) =
|
||
box_bottom_outer_r
|
||
+ (box_top_outer_r - box_bottom_outer_r) * z / box_height;
|
||
|
||
module mount_saddle_gusset(side = 1) {
|
||
lower_z = box_height - mount_gusset_height;
|
||
lower_r = box_outer_radius_at(lower_z) - 1.0;
|
||
|
||
hull() {
|
||
translate([side * lower_r, 0, lower_z])
|
||
centered_box([4, mount_saddle_outer_width, 1.2]);
|
||
|
||
translate([side * mount_saddle_center_x, 0, mount_saddle_base_z])
|
||
centered_box([
|
||
mount_saddle_length,
|
||
mount_saddle_outer_width,
|
||
mount_saddle_floor
|
||
]);
|
||
}
|
||
}
|
||
|
||
module mount_saddle(side = 1) {
|
||
union() {
|
||
mount_saddle_gusset(side);
|
||
|
||
difference() {
|
||
translate([side * mount_saddle_center_x, 0, mount_saddle_base_z])
|
||
centered_box([
|
||
mount_saddle_length,
|
||
mount_saddle_outer_width,
|
||
mount_saddle_height
|
||
]);
|
||
|
||
// Открытый сверху паз: мастер опускается вертикально без защелкивания.
|
||
translate([
|
||
side * mount_saddle_center_x,
|
||
0,
|
||
box_height - master_bridge_overlap
|
||
])
|
||
centered_box([
|
||
mount_saddle_length + 0.4,
|
||
bridge_width + 2 * mount_slot_clearance,
|
||
mount_saddle_height + 1
|
||
]);
|
||
|
||
translate([
|
||
side * mount_saddle_center_x,
|
||
0,
|
||
mount_pin_center_z
|
||
])
|
||
rotate([90, 0, 0])
|
||
cylinder(
|
||
h = mount_saddle_outer_width + 2,
|
||
d = mount_pin_hole_d,
|
||
center = true,
|
||
$fn = 32
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
module mount_pin() {
|
||
union() {
|
||
cylinder(h = mount_pin_head_h, d = mount_pin_head_d, $fn = 32);
|
||
translate([0, 0, mount_pin_head_h - 0.1])
|
||
cylinder(h = mount_pin_shaft_length, d = mount_pin_d, $fn = 32);
|
||
}
|
||
}
|
||
|
||
module mount_pin_in_use(side = 1) {
|
||
translate([
|
||
side * mount_saddle_center_x,
|
||
mount_saddle_outer_width / 2 + mount_pin_head_h,
|
||
mount_pin_center_z
|
||
])
|
||
rotate([90, 0, 0])
|
||
mount_pin();
|
||
}
|
||
|
||
module mount_pins_print_set() {
|
||
for (index = [-1, 0, 1])
|
||
translate([index * (mount_pin_head_d + 4), 0, 0])
|
||
mount_pin();
|
||
}
|
||
|
||
module top_stiffening_rim() {
|
||
translate([0, 0, box_height - rim_height])
|
||
difference() {
|
||
cylinder(
|
||
h = rim_height,
|
||
r = box_top_outer_r + rim_extra,
|
||
$fn = box_sides
|
||
);
|
||
translate([0, 0, -0.1])
|
||
cylinder(
|
||
h = rim_height + 0.2,
|
||
r = box_top_inner_r,
|
||
$fn = box_sides
|
||
);
|
||
}
|
||
}
|
||
|
||
module plaster_fill_marker() {
|
||
marker_r =
|
||
box_bottom_inner_r
|
||
+ (box_top_inner_r - box_bottom_inner_r) * plaster_fill_level / box_height;
|
||
|
||
translate([0, 0, plaster_fill_level - 0.35])
|
||
difference() {
|
||
cylinder(h = 0.7, r = marker_r, $fn = box_sides);
|
||
translate([0, 0, -0.1])
|
||
cylinder(h = 0.9, r = marker_r - 0.6, $fn = box_sides);
|
||
}
|
||
}
|
||
|
||
module casting_box() {
|
||
union() {
|
||
difference() {
|
||
cylinder(
|
||
h = box_height,
|
||
r1 = box_bottom_outer_r,
|
||
r2 = box_top_outer_r,
|
||
$fn = box_sides
|
||
);
|
||
|
||
translate([0, 0, box_bottom])
|
||
cylinder(
|
||
h = box_height - box_bottom + 0.2,
|
||
r1 = box_bottom_inner_r,
|
||
r2 = box_top_inner_r + 0.1,
|
||
$fn = box_sides
|
||
);
|
||
}
|
||
|
||
top_stiffening_rim();
|
||
plaster_fill_marker();
|
||
mount_saddle(-1);
|
||
mount_saddle(1);
|
||
}
|
||
}
|
||
|
||
// --- Опорная площадка и печатный резак для сырого пласта ---
|
||
module support_outline_2d() {
|
||
hull() {
|
||
for (angle = [90, 210, 330])
|
||
translate([
|
||
support_lobe_center_r * cos(angle),
|
||
support_lobe_center_r * sin(angle)
|
||
])
|
||
circle(r = support_corner_r, $fn = support_segments);
|
||
}
|
||
}
|
||
|
||
module support_plate_2d() {
|
||
difference() {
|
||
support_outline_2d();
|
||
circle(d = support_green_hole_d, $fn = support_segments);
|
||
}
|
||
}
|
||
|
||
// Геометрия сырой глиняной детали с уже учтенной усадкой.
|
||
module support_plate_model() {
|
||
linear_extrude(height = support_raw_thickness)
|
||
support_plate_2d();
|
||
}
|
||
|
||
// Опорная развертка усеченного конуса. Она точно совпадает с верхней и
|
||
// нижней окружностями базового профиля, но рельеф и sculpted-талия требуют
|
||
// пластической доработки в форме. Производственный A3 PDF создается скриптом
|
||
// tools/generate_a3_templates.py и содержит более точный 4-секторный вариант.
|
||
clay_blank_slant = sqrt(
|
||
pow(master_height, 2)
|
||
+ pow(master_top_r - master_bottom_r, 2)
|
||
);
|
||
clay_blank_outer_r =
|
||
clay_blank_slant * master_top_r / (master_top_r - master_bottom_r);
|
||
clay_blank_inner_r = clay_blank_outer_r - clay_blank_slant;
|
||
clay_blank_angle = 360 * master_top_r / clay_blank_outer_r;
|
||
clay_blank_segments = max(96, surface_segments);
|
||
|
||
module clay_blank_reference_2d() {
|
||
polygon(points = concat(
|
||
[
|
||
for (i = [0 : clay_blank_segments])
|
||
let(angle = -clay_blank_angle / 2 + clay_blank_angle * i / clay_blank_segments)
|
||
[clay_blank_outer_r * cos(angle), clay_blank_outer_r * sin(angle)]
|
||
],
|
||
[
|
||
for (i = [clay_blank_segments : -1 : 0])
|
||
let(angle = -clay_blank_angle / 2 + clay_blank_angle * i / clay_blank_segments)
|
||
[clay_blank_inner_r * cos(angle), clay_blank_inner_r * sin(angle)]
|
||
]
|
||
));
|
||
}
|
||
|
||
module support_outer_blade_2d() {
|
||
difference() {
|
||
offset(delta = support_cutter_wall)
|
||
support_outline_2d();
|
||
support_outline_2d();
|
||
}
|
||
}
|
||
|
||
module support_hole_blade_2d() {
|
||
difference() {
|
||
circle(d = support_green_hole_d, $fn = support_segments);
|
||
circle(
|
||
d = support_green_hole_d - 2 * support_cutter_wall,
|
||
$fn = support_segments
|
||
);
|
||
}
|
||
}
|
||
|
||
module support_cutter_spokes_2d() {
|
||
for (angle = [90, 210, 330])
|
||
rotate([0, 0, angle])
|
||
translate([
|
||
support_green_hole_d / 2 - support_cutter_wall,
|
||
-support_cutter_spoke_width / 2
|
||
])
|
||
square([
|
||
support_envelope_d / 2
|
||
- support_green_hole_d / 2
|
||
+ support_cutter_wall,
|
||
support_cutter_spoke_width
|
||
]);
|
||
}
|
||
|
||
module support_cutter_use_orientation() {
|
||
union() {
|
||
linear_extrude(height = support_cutter_height)
|
||
support_outer_blade_2d();
|
||
|
||
linear_extrude(height = support_cutter_height)
|
||
support_hole_blade_2d();
|
||
|
||
translate([
|
||
0,
|
||
0,
|
||
support_cutter_height - support_cutter_handle_thickness
|
||
])
|
||
linear_extrude(height = support_cutter_handle_thickness)
|
||
support_cutter_spokes_2d();
|
||
}
|
||
}
|
||
|
||
// Положение для печати: соединительные спицы лежат на столе.
|
||
module support_cutter_print_orientation() {
|
||
translate([0, 0, support_cutter_height])
|
||
rotate([180, 0, 0])
|
||
support_cutter_use_orientation();
|
||
}
|
||
|
||
module clay_cone_preview(style = surface_style) {
|
||
preview_inner_bottom_r =
|
||
master_bottom_r - clay_slab_thickness - effective_texture_allowance / 2;
|
||
preview_inner_top_r =
|
||
master_top_r - clay_slab_thickness - effective_texture_allowance / 2;
|
||
|
||
difference() {
|
||
styled_master_cone(style);
|
||
|
||
translate([0, 0, -0.1])
|
||
cylinder(
|
||
h = master_height + 0.2,
|
||
r1 = preview_inner_bottom_r,
|
||
r2 = preview_inner_top_r,
|
||
$fn = surface_segments
|
||
);
|
||
}
|
||
}
|
||
|
||
module dripper_preview(style = surface_style) {
|
||
color([0.68, 0.35, 0.25])
|
||
support_plate_model();
|
||
|
||
color([0.78, 0.43, 0.31])
|
||
translate([0, 0, support_raw_thickness - 0.5])
|
||
clay_cone_preview(style);
|
||
}
|
||
|
||
module assembly(style = surface_style) {
|
||
color([0.82, 0.86, 0.90, 0.72])
|
||
casting_box();
|
||
|
||
color([0.18, 0.55, 0.68])
|
||
translate([0, 0, plaster_below_tip])
|
||
master_use_orientation(style);
|
||
|
||
color([0.92, 0.58, 0.18]) {
|
||
mount_pin_in_use(-1);
|
||
mount_pin_in_use(1);
|
||
}
|
||
}
|
||
|
||
module variants_gallery() {
|
||
styles = [
|
||
"smooth_sculpted",
|
||
"vertical_soft",
|
||
"horizontal_rings",
|
||
"cross_wave",
|
||
"diagonal_flow",
|
||
"woven_cells",
|
||
"organic_asymmetric",
|
||
"ribbed_petal",
|
||
"pillow_facets",
|
||
"dimpled_grid",
|
||
"spiral_flow"
|
||
];
|
||
|
||
columns = 4;
|
||
spacing_x = 190;
|
||
spacing_y = 185;
|
||
|
||
for (i = [0 : len(styles) - 1]) {
|
||
column = i % columns;
|
||
row = floor(i / columns);
|
||
|
||
color([
|
||
0.20 + 0.06 * column,
|
||
0.50 + 0.05 * row,
|
||
0.68 - 0.04 * column
|
||
])
|
||
translate([
|
||
(column - (columns - 1) / 2) * spacing_x,
|
||
-row * spacing_y,
|
||
0
|
||
])
|
||
styled_master_cone(styles[i]);
|
||
}
|
||
}
|
||
|
||
if (part == "master") {
|
||
master_print_orientation(surface_style);
|
||
} else if (part == "box") {
|
||
casting_box();
|
||
} else if (part == "variants") {
|
||
variants_gallery();
|
||
} else if (part == "support_plate") {
|
||
support_plate_model();
|
||
} else if (part == "support_cutter") {
|
||
support_cutter_print_orientation();
|
||
} else if (part == "mount_pin") {
|
||
mount_pin();
|
||
} else if (part == "mount_pins") {
|
||
mount_pins_print_set();
|
||
} else if (part == "clay_blank_2d") {
|
||
clay_blank_reference_2d();
|
||
} else if (part == "support_plate_2d") {
|
||
support_plate_2d();
|
||
} else if (part == "dripper_preview") {
|
||
dripper_preview(surface_style);
|
||
} else {
|
||
assembly(surface_style);
|
||
}
|