Files
v60/dripper_mold.scad

1100 lines
45 KiB
OpenSCAD
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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);
}