/* 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); }