/* =============================================================================== UNIVERSAL CLOCK DIAL GENERATOR — OpenSCAD Nightly Version 1.0 — 2026-09 Requires a recent OpenSCAD Nightly / development snapshot because text layout deliberately uses textmetrics(). No external libraries are required. Designed for: • 3D printing • laser cutting / engraving • CNC routing / engraving • vinyl / plotter workflows via 2D export Recommended workflow: 1. Select parameters in the Customizer. 2. Use Output_Mode = "3D" for a printable dial. 3. Use 2D_CUT / 2D_ENGRAVE / 2D_MARKS / 2D_TEXT for SVG/DXF export. 4. Use F6 render before final export. textmetrics() notes: We do not assume text() starts at [0,0], and we do not use string length as a width estimate. The helpers below use textmetrics().position and .size to center and fit the actual ink bounds of each rendered string. =============================================================================== */ /* [Output] */ Output_Mode = "3D"; // [3D,2D_CUT,2D_ENGRAVE,2D_MARKS,2D_TEXT,2D_PREVIEW] Preview_Colors = true; Explode_3D = false; Explode_Distance = 8; // [0:1:40] /* [Dial shape] */ Dial_Shape = "Round"; // [Round,Oval,Rectangle,RoundedRectangle,Polygon,Star] Dial_Diameter = 180; // [30:1:500] Dial_Width = 180; // [30:1:500] Dial_Height = 180; // [30:1:500] Corner_Radius = 18; // [0:0.5:100] Polygon_Sides = 8; // [3:1:24] Polygon_Rotation = 22.5; // [-180:1:180] Star_Points = 12; // [3:1:36] Star_Inner_Ratio = 0.88; // [0.1:0.01:0.99] Shape_Margin = 0; // [-20:0.5:40] /* [3D body] */ Dial_Thickness = 3.0; // [0.4:0.1:20] Back_Recess_Enable = false; Back_Recess_Diameter = 58; // [5:1:200] Back_Recess_Depth = 1.2; // [0:0.1:10] Edge_Bevel_Enable = false; Edge_Bevel = 0.8; // [0:0.1:5] /* [Center / movement hole] */ Center_Hole_Enable = true; Center_Hole_Type = "Round"; // [Round,Square,D_Shaft,None] Center_Hole_Diameter = 8.2; // [0.5:0.1:30] Center_Hole_Square = 8.2; // [0.5:0.1:30] D_Shaft_Flat = 1.8; // [0:0.1:10] Center_Hole_X = 0; // [-50:0.1:50] Center_Hole_Y = 0; // [-50:0.1:50] /* [Outer rings and borders] */ Outer_Rim_Enable = true; Outer_Rim_Inset = 4; // [0:0.5:40] Outer_Rim_Width = 1.2; // [0.2:0.1:10] Outer_Rim_Mode = "Raised"; // [Raised,Engraved,None] Outer_Rim_Height = 0.7; // [0.1:0.1:5] Outer_Rim_Depth = 0.5; // [0.1:0.1:5] Inner_Ring_Enable = true; Inner_Ring_Radius = 58; // [5:0.5:240] Inner_Ring_Width = 0.7; // [0.1:0.1:8] Inner_Ring_Mode = "Engraved"; // [Raised,Engraved,None] /* [Minute / second track] */ Track_Enable = true; Track_Count = 60; // [1:1:240] Track_Radius = 78; // [5:0.5:240] Track_Major_Every = 5; // [1:1:60] Track_Major_Style = "Line"; // [Line,Dot,Pill,Triangle,Diamond,None] Track_Minor_Style = "Line"; // [Line,Dot,Pill,Triangle,Diamond,None] Track_Major_Length = 6; // [0.5:0.25:30] Track_Minor_Length = 3; // [0.25:0.25:20] Track_Major_Width = 1.6; // [0.2:0.1:8] Track_Minor_Width = 0.7; // [0.1:0.1:5] Track_Major_Diameter = 3.2; // [0.5:0.1:12] Track_Minor_Diameter = 1.5; // [0.3:0.1:8] Track_Rotation = 0; // [-180:1:180] Track_Mode = "Raised"; // [Raised,Engraved,Through,None] Track_Height = 0.7; // [0.1:0.1:5] Track_Depth = 0.5; // [0.1:0.1:5] /* [Hour markers] */ Hour_Markers_Enable = false; Hour_Marker_Count = 12; // [1:1:36] Hour_Marker_Radius = 66; // [5:0.5:240] Hour_Marker_Style = "Pill"; // [Line,Dot,Pill,Triangle,Diamond,None] Hour_Marker_Length = 8; // [0.5:0.25:30] Hour_Marker_Width = 2.4; // [0.2:0.1:12] Hour_Marker_Diameter = 4; // [0.5:0.1:15] Hour_Marker_Rotation = 0; // [-180:1:180] Hour_Marker_Mode = "Raised"; // [Raised,Engraved,Through,None] Hour_Marker_Height = 0.8; // [0.1:0.1:5] Hour_Marker_Depth = 0.5; // [0.1:0.1:5] /* [Hour numerals] */ Numbers_Enable = true; Number_Set = "Arabic_1_12"; // [Arabic_1_12,Arabic_0_11,Roman,Quarter_Arabic,Quarter_Roman,Words,None] Number_Radius = 61; // [5:0.5:240] Number_Font = "Liberation Sans:style=Bold"; Number_Size = 18; // [2:0.5:80] Number_Max_Width = 22; // [2:0.5:80] Number_Max_Height = 18; // [2:0.5:80] Number_Spacing = 1.0; // [0.5:0.05:2] Number_Orientation = "Upright"; // [Upright,Radial,Tangent] Number_Rotation = 0; // [-180:1:180] Number_Mode = "Raised"; // [Raised,Engraved,Through,None] Number_Height = 0.8; // [0.1:0.1:5] Number_Depth = 0.5; // [0.1:0.1:5] /* [Custom text] */ Top_Text_Enable = true; Top_Text = "MAKERSPACE"; Top_Text_Y = 27; // [-200:0.5:200] Top_Text_Font = "Liberation Sans:style=Bold"; Top_Text_Size = 8; // [1:0.5:50] Top_Text_Max_Width = 70; // [5:1:300] Top_Text_Max_Height = 12; // [2:0.5:100] Top_Text_Mode = "Engraved"; // [Raised,Engraved,Through,None] Bottom_Text_Enable = true; Bottom_Text = "NÆSTVED"; Bottom_Text_Y = -30; // [-200:0.5:200] Bottom_Text_Font = "Liberation Sans"; Bottom_Text_Size = 6; // [1:0.5:50] Bottom_Text_Max_Width = 55; // [5:1:300] Bottom_Text_Max_Height = 10; // [2:0.5:100] Bottom_Text_Mode = "Engraved"; // [Raised,Engraved,Through,None] Center_Text_Enable = false; Center_Text = "QUARTZ"; Center_Text_X = 0; // [-200:0.5:200] Center_Text_Y = -15; // [-200:0.5:200] Center_Text_Font = "Liberation Sans"; Center_Text_Size = 5; // [1:0.5:50] Center_Text_Max_Width = 45; // [5:1:300] Center_Text_Max_Height = 8; // [2:0.5:100] Center_Text_Mode = "Engraved"; // [Raised,Engraved,Through,None] Text_Raised_Height = 0.7; // [0.1:0.1:5] Text_Engrave_Depth = 0.45; // [0.1:0.1:5] /* [Subdial 1] */ Subdial1_Enable = false; Subdial1_X = 0; // [-200:0.5:200] Subdial1_Y = -35; // [-200:0.5:200] Subdial1_Radius = 19; // [4:0.5:80] Subdial1_Count = 60; // [1:1:120] Subdial1_Major_Every = 5; // [1:1:60] Subdial1_Label = "SECONDS"; Subdial1_Label_Size = 3.5; // [1:0.25:20] Subdial1_Center_Hole = 2.2; // [0:0.1:20] /* [Subdial 2] */ Subdial2_Enable = false; Subdial2_X = -38; // [-200:0.5:200] Subdial2_Y = 0; // [-200:0.5:200] Subdial2_Radius = 17; // [4:0.5:80] Subdial2_Count = 24; // [1:1:120] Subdial2_Major_Every = 4; // [1:1:60] Subdial2_Label = "24 H"; Subdial2_Label_Size = 3.5; // [1:0.25:20] Subdial2_Center_Hole = 2.2; // [0:0.1:20] /* [Subdial 3] */ Subdial3_Enable = false; Subdial3_X = 38; // [-200:0.5:200] Subdial3_Y = 0; // [-200:0.5:200] Subdial3_Radius = 17; // [4:0.5:80] Subdial3_Count = 31; // [1:1:120] Subdial3_Major_Every = 5; // [1:1:60] Subdial3_Label = "DATE"; Subdial3_Label_Size = 3.5; // [1:0.25:20] Subdial3_Center_Hole = 2.2; // [0:0.1:20] /* [Subdial appearance] */ Subdial_Ring_Width = 0.65; // [0.1:0.05:5] Subdial_Tick_Width = 0.45; // [0.1:0.05:3] Subdial_Tick_Length = 2.0; // [0.2:0.1:8] Subdial_Major_Length = 3.2; // [0.2:0.1:10] Subdial_Mode = "Engraved"; // [Raised,Engraved,Through,None] Subdial_Depth = 0.35; // [0.1:0.05:3] Subdial_Height = 0.5; // [0.1:0.05:3] Subdial_Font = "Liberation Sans"; /* [Decorative center] */ Center_Ring_Enable = true; Center_Ring_Radius = 8; // [1:0.5:50] Center_Ring_Width = 0.8; // [0.1:0.1:6] Center_Ring_Mode = "Engraved"; // [Raised,Engraved,None] /* [Mounting / auxiliary holes] */ Aux_Holes_Enable = false; Aux_Hole_Diameter = 3.2; // [0.5:0.1:20] Aux_Holes = [[0,75],[0,-75]]; /* [Manufacturing compensation] */ Cut_Kerf_Compensation = 0; // [-1:0.01:1] Engrave_Expansion = 0; // [-1:0.01:1] Min_Feature = 0.2; // [0.05:0.05:3] /* [Quality] */ Curve_Fragments = 128; // [24:8:512] Text_Fragments = 24; // [8:4:128] $fn = Curve_Fragments; // ----------------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------------- function clamp(v, lo, hi) = min(max(v, lo), hi); function safe_div(a,b,fallback=1) = abs(b) < 0.000001 ? fallback : a/b; function polar(r,a) = [r*sin(a), r*cos(a)]; function is_major(i,every) = every > 0 && i % every == 0; ROMAN = ["XII","I","II","III","IV","V","VI","VII","VIII","IX","X","XI"]; WORDS = ["TWELVE","ONE","TWO","THREE","FOUR","FIVE","SIX","SEVEN","EIGHT","NINE","TEN","ELEVEN"]; function hour_label(i) = Number_Set == "Arabic_1_12" ? str(i == 0 ? 12 : i) : Number_Set == "Arabic_0_11" ? str(i) : Number_Set == "Roman" ? ROMAN[i] : Number_Set == "Quarter_Arabic" ? (i==0?"12":i==3?"3":i==6?"6":i==9?"9":"") : Number_Set == "Quarter_Roman" ? (i==0?"XII":i==3?"III":i==6?"VI":i==9?"IX":"") : Number_Set == "Words" ? WORDS[i] : ""; function star_pts(n, ro, ri, rot=0) = [for(i=[0:2*n-1]) let(a = rot + i*180/n, r = (i%2==0 ? ro : ri)) [r*sin(a), r*cos(a)]]; // Nightly textmetrics(): tight ink-bounds fit scale. function text_fit_scale(s, nominal_size, font, max_w, max_h, spacing=1) = let(m = textmetrics(text=s, size=nominal_size, font=font, halign="left", valign="baseline", spacing=spacing), sx = safe_div(max_w, max(m.size.x, 0.0001), 1), sy = safe_div(max_h, max(m.size.y, 0.0001), 1)) min(1, sx, sy); module tight_text_2d(s, nominal_size=10, font="Liberation Sans", max_w=1000, max_h=1000, spacing=1) { if (s != "") { m = textmetrics(text=s, size=nominal_size, font=font, halign="left", valign="baseline", spacing=spacing); fit = min(1, safe_div(max_w, max(m.size.x,0.0001),1), safe_div(max_h, max(m.size.y,0.0001),1)); scale([fit,fit]) translate([-m.position.x - m.size.x/2, -m.position.y - m.size.y/2]) text(text=s, size=nominal_size, font=font, halign="left", valign="baseline", spacing=spacing, $fn=Text_Fragments); } } module ring2d(radius, width) { w = max(width, Min_Feature); difference() { circle(r=radius + w/2); circle(r=max(0.001, radius - w/2)); } } module rounded_rect_2d(w,h,r) { rr = min(max(r,0), min(w,h)/2); if (rr <= 0) square([w,h], center=true); else offset(r=rr) square([max(0.01,w-2*rr), max(0.01,h-2*rr)], center=true); } module raw_dial_shape_2d(delta=0) { if (Dial_Shape == "Round") circle(r=Dial_Diameter/2 + Shape_Margin + delta); else if (Dial_Shape == "Oval") scale([(Dial_Width/2 + Shape_Margin + delta), (Dial_Height/2 + Shape_Margin + delta)]) circle(r=1); else if (Dial_Shape == "Rectangle") square([Dial_Width + 2*(Shape_Margin+delta), Dial_Height + 2*(Shape_Margin+delta)], center=true); else if (Dial_Shape == "RoundedRectangle") rounded_rect_2d(Dial_Width + 2*(Shape_Margin+delta), Dial_Height + 2*(Shape_Margin+delta), max(0,Corner_Radius + delta)); else if (Dial_Shape == "Polygon") rotate(Polygon_Rotation) circle(r=Dial_Diameter/2 + Shape_Margin + delta, $fn=Polygon_Sides); else if (Dial_Shape == "Star") polygon(star_pts(Star_Points, Dial_Diameter/2 + Shape_Margin + delta, (Dial_Diameter/2 + Shape_Margin + delta)*Star_Inner_Ratio, Polygon_Rotation)); } module dial_cut_outline_2d() { delta = Cut_Kerf_Compensation/2; raw_dial_shape_2d(delta); } module center_hole_2d(extra=0) { translate([Center_Hole_X,Center_Hole_Y]) { if (Center_Hole_Enable && Center_Hole_Type == "Round") circle(d=max(0.01,Center_Hole_Diameter + 2*extra)); else if (Center_Hole_Enable && Center_Hole_Type == "Square") square([Center_Hole_Square+2*extra,Center_Hole_Square+2*extra],center=true); else if (Center_Hole_Enable && Center_Hole_Type == "D_Shaft") { d = Center_Hole_Diameter + 2*extra; flat = clamp(D_Shaft_Flat,0,d*0.9); intersection() { circle(d=d); translate([-d/2,-d/2]) square([d-flat,d]); } } } } module aux_holes_2d(extra=0) { if (Aux_Holes_Enable) for(p=Aux_Holes) translate(p) circle(d=max(0.01,Aux_Hole_Diameter + 2*extra)); } module all_through_holes_2d() { center_hole_2d(Cut_Kerf_Compensation/2); aux_holes_2d(Cut_Kerf_Compensation/2); subdial_holes_2d(Cut_Kerf_Compensation/2); } // ----------------------------------------------------------------------------- // Marker primitives // ----------------------------------------------------------------------------- module marker_shape_2d(style="Line", len=5, width=1, diameter=2) { w = max(width, Min_Feature); d = max(diameter, Min_Feature); if (style == "Line") square([w,max(len,Min_Feature)],center=true); else if (style == "Pill") hull() { translate([0,-max(0,len-d)/2]) circle(d=d); translate([0, max(0,len-d)/2]) circle(d=d); } else if (style == "Dot") circle(d=d); else if (style == "Triangle") polygon([[0,len/2],[-w,-len/2],[w,-len/2]]); else if (style == "Diamond") polygon([[0,len/2],[w,0],[0,-len/2],[-w,0]]); } module radial_marker_2d(radius, angle, style, len, width, diameter) { p = polar(radius,angle); translate(p) rotate(-angle) marker_shape_2d(style,len,width,diameter); } module track_geometry_2d() { if (Track_Enable && Track_Mode != "None") for(i=[0:Track_Count-1]) { major = is_major(i,Track_Major_Every); a = Track_Rotation + i*360/Track_Count; style = major ? Track_Major_Style : Track_Minor_Style; len = major ? Track_Major_Length : Track_Minor_Length; wid = major ? Track_Major_Width : Track_Minor_Width; dia = major ? Track_Major_Diameter : Track_Minor_Diameter; radial_marker_2d(Track_Radius,a,style,len,wid,dia); } } module hour_marker_geometry_2d() { if (Hour_Markers_Enable && Hour_Marker_Mode != "None") for(i=[0:Hour_Marker_Count-1]) radial_marker_2d(Hour_Marker_Radius, Hour_Marker_Rotation + i*360/Hour_Marker_Count, Hour_Marker_Style, Hour_Marker_Length, Hour_Marker_Width, Hour_Marker_Diameter); } // ----------------------------------------------------------------------------- // Numerals / text // ----------------------------------------------------------------------------- module numeral_at(i) { s = hour_label(i); if (s != "") { a = Number_Rotation + i*30; p = polar(Number_Radius,a); orient = Number_Orientation == "Radial" ? -a : Number_Orientation == "Tangent" ? -a+90 : 0; translate(p) rotate(orient) tight_text_2d(s, Number_Size, Number_Font, Number_Max_Width, Number_Max_Height, Number_Spacing); } } module number_geometry_2d() { if (Numbers_Enable && Number_Set != "None" && Number_Mode != "None") for(i=[0:11]) numeral_at(i); } module top_text_2d() { if (Top_Text_Enable && Top_Text_Mode != "None") translate([0,Top_Text_Y]) tight_text_2d(Top_Text,Top_Text_Size,Top_Text_Font, Top_Text_Max_Width,Top_Text_Max_Height); } module bottom_text_2d() { if (Bottom_Text_Enable && Bottom_Text_Mode != "None") translate([0,Bottom_Text_Y]) tight_text_2d(Bottom_Text,Bottom_Text_Size,Bottom_Text_Font, Bottom_Text_Max_Width,Bottom_Text_Max_Height); } module center_text_2d() { if (Center_Text_Enable && Center_Text_Mode != "None") translate([Center_Text_X,Center_Text_Y]) tight_text_2d(Center_Text,Center_Text_Size,Center_Text_Font, Center_Text_Max_Width,Center_Text_Max_Height); } module all_custom_text_2d() { top_text_2d(); bottom_text_2d(); center_text_2d(); } // ----------------------------------------------------------------------------- // Rings / subdials // ----------------------------------------------------------------------------- module outer_rim_2d() { // Generalized inset border: offset inward from actual dial outline. if (Outer_Rim_Enable && Outer_Rim_Mode != "None") difference() { offset(delta=-Outer_Rim_Inset + Outer_Rim_Width/2) raw_dial_shape_2d(); offset(delta=-Outer_Rim_Inset - Outer_Rim_Width/2) raw_dial_shape_2d(); } } module inner_ring_2d() { if (Inner_Ring_Enable && Inner_Ring_Mode != "None") ring2d(Inner_Ring_Radius,Inner_Ring_Width); } module center_ring_2d() { if (Center_Ring_Enable && Center_Ring_Mode != "None") translate([Center_Hole_X,Center_Hole_Y]) ring2d(Center_Ring_Radius,Center_Ring_Width); } module subdial_2d(x,y,r,count,major_every,label,label_size,hole_d) { translate([x,y]) { ring2d(r,Subdial_Ring_Width); for(i=[0:count-1]) { major = is_major(i,major_every); a = i*360/count; rr = r - (major ? Subdial_Major_Length : Subdial_Tick_Length)/2; radial_marker_2d(rr,a,"Line", major ? Subdial_Major_Length : Subdial_Tick_Length, Subdial_Tick_Width,Subdial_Tick_Width); } if (label != "") translate([0,-r*0.42]) tight_text_2d(label,label_size,Subdial_Font,r*1.35,label_size*1.35); } } module subdial_holes_2d(extra=0) { if (Subdial1_Enable && Subdial1_Center_Hole > 0) translate([Subdial1_X,Subdial1_Y]) circle(d=Subdial1_Center_Hole+2*extra); if (Subdial2_Enable && Subdial2_Center_Hole > 0) translate([Subdial2_X,Subdial2_Y]) circle(d=Subdial2_Center_Hole+2*extra); if (Subdial3_Enable && Subdial3_Center_Hole > 0) translate([Subdial3_X,Subdial3_Y]) circle(d=Subdial3_Center_Hole+2*extra); } module subdial_geometry_2d() { if (Subdial_Mode != "None") { if (Subdial1_Enable) subdial_2d(Subdial1_X,Subdial1_Y,Subdial1_Radius,Subdial1_Count,Subdial1_Major_Every,Subdial1_Label,Subdial1_Label_Size,Subdial1_Center_Hole); if (Subdial2_Enable) subdial_2d(Subdial2_X,Subdial2_Y,Subdial2_Radius,Subdial2_Count,Subdial2_Major_Every,Subdial2_Label,Subdial2_Label_Size,Subdial2_Center_Hole); if (Subdial3_Enable) subdial_2d(Subdial3_X,Subdial3_Y,Subdial3_Radius,Subdial3_Count,Subdial3_Major_Every,Subdial3_Label,Subdial3_Label_Size,Subdial3_Center_Hole); } } // ----------------------------------------------------------------------------- // Group by manufacturing treatment // ----------------------------------------------------------------------------- module raised_geometry_2d() { if (Track_Mode == "Raised") track_geometry_2d(); if (Hour_Marker_Mode == "Raised") hour_marker_geometry_2d(); if (Number_Mode == "Raised") number_geometry_2d(); if (Outer_Rim_Mode == "Raised") outer_rim_2d(); if (Inner_Ring_Mode == "Raised") inner_ring_2d(); if (Center_Ring_Mode == "Raised") center_ring_2d(); if (Subdial_Mode == "Raised") subdial_geometry_2d(); if (Top_Text_Mode == "Raised") top_text_2d(); if (Bottom_Text_Mode == "Raised") bottom_text_2d(); if (Center_Text_Mode == "Raised") center_text_2d(); } module engraved_geometry_2d() { if (Track_Mode == "Engraved") track_geometry_2d(); if (Hour_Marker_Mode == "Engraved") hour_marker_geometry_2d(); if (Number_Mode == "Engraved") number_geometry_2d(); if (Outer_Rim_Mode == "Engraved") outer_rim_2d(); if (Inner_Ring_Mode == "Engraved") inner_ring_2d(); if (Center_Ring_Mode == "Engraved") center_ring_2d(); if (Subdial_Mode == "Engraved") subdial_geometry_2d(); if (Top_Text_Mode == "Engraved") top_text_2d(); if (Bottom_Text_Mode == "Engraved") bottom_text_2d(); if (Center_Text_Mode == "Engraved") center_text_2d(); } module through_decoration_2d() { if (Track_Mode == "Through") track_geometry_2d(); if (Hour_Marker_Mode == "Through") hour_marker_geometry_2d(); if (Number_Mode == "Through") number_geometry_2d(); if (Subdial_Mode == "Through") subdial_geometry_2d(); if (Top_Text_Mode == "Through") top_text_2d(); if (Bottom_Text_Mode == "Through") bottom_text_2d(); if (Center_Text_Mode == "Through") center_text_2d(); } module all_cutouts_2d() { all_through_holes_2d(); through_decoration_2d(); } module all_marks_2d() { track_geometry_2d(); hour_marker_geometry_2d(); outer_rim_2d(); inner_ring_2d(); center_ring_2d(); subdial_geometry_2d(); } module all_text_2d() { number_geometry_2d(); all_custom_text_2d(); } // ----------------------------------------------------------------------------- // 3D construction // ----------------------------------------------------------------------------- module raw_body_3d() { b = min(max(Edge_Bevel,0), Dial_Thickness/2); if (Edge_Bevel_Enable && b > 0.01) { // True geometric top/bottom bevel using hulls between an inset profile // and the full profile; works for all supported dial outlines. hull() { linear_extrude(height=0.01) offset(delta=-b) raw_dial_shape_2d(); translate([0,0,b]) linear_extrude(height=0.01) raw_dial_shape_2d(); } translate([0,0,b]) linear_extrude(height=max(0.01,Dial_Thickness-2*b)) raw_dial_shape_2d(); hull() { translate([0,0,Dial_Thickness-b-0.01]) linear_extrude(height=0.01) raw_dial_shape_2d(); translate([0,0,Dial_Thickness-0.01]) linear_extrude(height=0.01) offset(delta=-b) raw_dial_shape_2d(); } } else linear_extrude(height=Dial_Thickness) raw_dial_shape_2d(); } module base_body_3d() { difference() { raw_body_3d(); // Through holes and through-decoration get a small Z overcut. translate([0,0,-0.2]) linear_extrude(height=Dial_Thickness+0.4) all_cutouts_2d(); if (Back_Recess_Enable && Back_Recess_Depth > 0) translate([Center_Hole_X,Center_Hole_Y,-0.01]) cylinder(d=Back_Recess_Diameter, h=min(Back_Recess_Depth,Dial_Thickness-0.05)); // Engraving cut from front face. translate([0,0,max(0,Dial_Thickness-max(Track_Depth,Hour_Marker_Depth,Number_Depth,Outer_Rim_Depth,Subdial_Depth,Text_Engrave_Depth)-0.01)]) linear_extrude(height=max(Track_Depth,Hour_Marker_Depth,Number_Depth,Outer_Rim_Depth,Subdial_Depth,Text_Engrave_Depth)+0.02) engraved_geometry_2d(); } } module raised_features_3d() { // Features share one user-visible top height policy: each category may // request its own height, so they are extruded separately. z0 = Dial_Thickness + (Explode_3D ? Explode_Distance : 0); if (Track_Mode == "Raised") translate([0,0,z0]) linear_extrude(height=Track_Height) track_geometry_2d(); if (Hour_Marker_Mode == "Raised") translate([0,0,z0]) linear_extrude(height=Hour_Marker_Height) hour_marker_geometry_2d(); if (Number_Mode == "Raised") translate([0,0,z0]) linear_extrude(height=Number_Height) number_geometry_2d(); if (Outer_Rim_Mode == "Raised") translate([0,0,z0]) linear_extrude(height=Outer_Rim_Height) outer_rim_2d(); if (Inner_Ring_Mode == "Raised") translate([0,0,z0]) linear_extrude(height=Outer_Rim_Height) inner_ring_2d(); if (Center_Ring_Mode == "Raised") translate([0,0,z0]) linear_extrude(height=Outer_Rim_Height) center_ring_2d(); if (Subdial_Mode == "Raised") translate([0,0,z0]) linear_extrude(height=Subdial_Height) subdial_geometry_2d(); if (Top_Text_Mode == "Raised") translate([0,0,z0]) linear_extrude(height=Text_Raised_Height) top_text_2d(); if (Bottom_Text_Mode == "Raised") translate([0,0,z0]) linear_extrude(height=Text_Raised_Height) bottom_text_2d(); if (Center_Text_Mode == "Raised") translate([0,0,z0]) linear_extrude(height=Text_Raised_Height) center_text_2d(); } module clock_dial_3d() { if (Preview_Colors) { color([0.82,0.82,0.82]) base_body_3d(); color([0.12,0.12,0.12]) raised_features_3d(); } else { base_body_3d(); raised_features_3d(); } } // ----------------------------------------------------------------------------- // 2D export views // ----------------------------------------------------------------------------- module export_2d_cut() { difference() { dial_cut_outline_2d(); all_cutouts_2d(); } } module export_2d_engrave() { if (Engrave_Expansion == 0) engraved_geometry_2d(); else offset(delta=Engrave_Expansion) engraved_geometry_2d(); } module export_2d_marks() { all_marks_2d(); } module export_2d_text() { all_text_2d(); } module export_2d_preview() { color([0.85,0.85,0.85]) export_2d_cut(); color([0.15,0.15,0.15]) export_2d_engrave(); color([0.05,0.25,0.75]) raised_geometry_2d(); } // ----------------------------------------------------------------------------- // Diagnostics // ----------------------------------------------------------------------------- echo("Universal Clock Dial Generator — Nightly textmetrics edition"); echo("Output",Output_Mode,"Shape",Dial_Shape); echo("Center hole",Center_Hole_Enable,Center_Hole_Type,Center_Hole_Diameter); if (Numbers_Enable && Number_Set != "None") { _m12 = textmetrics(text=hour_label(0), size=Number_Size, font=Number_Font, halign="left",valign="baseline",spacing=Number_Spacing); echo("12-label textmetrics position",_m12.position,"size",_m12.size,"advance",_m12.advance); } // ----------------------------------------------------------------------------- // Main // ----------------------------------------------------------------------------- if (Output_Mode == "3D") clock_dial_3d(); else if (Output_Mode == "2D_CUT") export_2d_cut(); else if (Output_Mode == "2D_ENGRAVE") export_2d_engrave(); else if (Output_Mode == "2D_MARKS") export_2d_marks(); else if (Output_Mode == "2D_TEXT") export_2d_text(); else if (Output_Mode == "2D_PREVIEW") export_2d_preview();