← Garden room - 4x3 - Design Profile Demo

Right Wall Back Pier

v5 Gemini approved

Create the right back pier OSB/EPS SIP wall panel, 122mm wide x 1078mm long x 500mm high. Include a horizontal spline joint on its bottom edge to lock into the right wall bottom top spline groove. Provide a horizontal spline groove on top.

Dependencies

right_wall_bottom.top_spline

Interfaces Provided

top_spline (spline)
◉
Agent initialized (status: waiting_for_dependencies, action: check_dependencies)
◉
Provider: google | Profile: balanced
◇
Received interface right_wall_bottom.top_spline: %{name: "top_spline", type: "spline", value: %{}, agent_id: "right_wall_bottom"}
◉
All dependencies resolved, starting generation
◉
Generating v1
○
Calling Gemini API for code generation (gemini-3.1-pro-preview)...
◉
Generation token budget: max_tokens=12288, thinking_budget=0 (retry_count=0)
◉
Provider updated to google / balanced
●
Received response (1885 tokens)
◆
right_wall_back_pier_v1.py · 150 lines
import FreeCAD
import Part
import math
from FreeCAD import Vector

# === PARAMETERS ===
LENGTH = 1078.0
WIDTH = 122.0
HEIGHT = 500.0

OSB_THICKNESS = 11.0
CORE_THICKNESS = 100.0

# Groove matches standard 45x90mm block spline timber
GROOVE_W = 90.0
GROOVE_DEPTH = 45.0

# === DOCUMENT ===
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument("RightBackPier")

# === BUILD GEOMETRY ===
# 1. Base Panel (OSB + EPS Core + OSB)
face1 = Part.makeBox(LENGTH, OSB_THICKNESS, HEIGHT, Vector(0, 0, 0))
core = Part.makeBox(LENGTH, CORE_THICKNESS, HEIGHT, Vector(0, OSB_THICKNESS, 0))
face2 = Part.makeBox(LENGTH, OSB_THICKNESS, HEIGHT, Vector(0, OSB_THICKNESS + CORE_THICKNESS, 0))

panel = face1.fuse(core).fuse(face2)
panel = panel.removeSplitter()

# 2. Horizontal Spline Grooves (Top and Bottom)
# Center the 90mm groove within the 100mm core (5mm of EPS remains on each side)
groove_y_offset = OSB_THICKNESS + (CORE_THICKNESS - GROOVE_W) / 2

top_groove = Part.makeBox(
    LENGTH, GROOVE_W, GROOVE_DEPTH, 
    Vector(0, groove_y_offset, HEIGHT - GROOVE_DEPTH)
)

bottom_groove = Part.makeBox(
    LENGTH, GROOVE_W, GROOVE_DEPTH, 
    Vector(0, groove_y_offset, 0)
)

# Apply groove cuts
panel = panel.cut(top_groove)
panel = panel.cut(bottom_groove)
panel = panel.removeSplitter()

# === PART CODE ===
PART_CODE = "C-7"
PRINT_SCALE = 50  # 1:50 model scale — adjust if printing at a different scale

def _emboss_part_code(shape, code, print_scale=PRINT_SCALE):
    """Cut part code into largest face. Returns shape unmodified on any failure."""
    import os, math
    depth_raw = 0.6 * print_scale
    try:
        # --- font ---
        rd = FreeCAD.getResourceDir()
        font = None
        for fn in ("osifont-lgpl3fe.ttf", "FreeSans.ttf", "FreeCAD.ttf"):
            fp = os.path.join(rd, "Mod", "TechDraw", "Resources", "fonts", fn)
            if os.path.exists(fp):
                font = fp; break
        if font is None:
            return shape

        # --- cap depth at 33% of thinnest component dimension ---
        all_dims = sorted([shape.BoundBox.XLength, shape.BoundBox.YLength, shape.BoundBox.ZLength])
        depth = min(depth_raw, all_dims[0] * 0.33)

        # --- find largest face ---
        best = max(shape.Faces, key=lambda f: f.Area)
        n = best.normalAt(0, 0)
        c = best.CenterOfMass

        # --- text height: 2 mm at print scale, capped at 80% of longest face edge ---
        fb = best.BoundBox
        fb_dims = sorted([d for d in (fb.XLength, fb.YLength, fb.ZLength) if d > 1.0])
        text_h = min(2.0 * print_scale, (fb_dims[-1] * 0.80) if fb_dims else 2.0 * print_scale)

        # --- orthonormal face frame: right (r), up (u), out (n) ---
        wz = Vector(0, 0, 1)
        wy = Vector(0, 1, 0)
        up_ref = wy if abs(n.dot(wz)) > 0.9 else wz
        r = up_ref.cross(n).normalize()
        u = n.cross(r).normalize()

        # --- build text faces from wires (handles letters with holes) ---
        ss = Part.makeShapeString(String=code, FontFile=font, Size=text_h, Tracking=0)
        wires = sorted(ss.Wires, key=lambda w: w.BoundBox.DiagonalLength, reverse=True)
        faces_out = []
        used = set()
        for i, ow in enumerate(wires):
            if i in used:
                continue
            ob = ow.BoundBox
            holes = []
            for j, iw in enumerate(wires):
                if j <= i or j in used:
                    continue
                ib = iw.BoundBox
                if ob.XMin < ib.XMin and ob.XMax > ib.XMax and ob.YMin < ib.YMin and ob.YMax > ib.YMax:
                    holes.append(iw)
                    used.add(j)
            used.add(i)
            try:
                faces_out.append(Part.Face([ow] + holes) if holes else Part.Face(ow))
            except Exception:
                try:
                    faces_out.append(Part.Face(ow))
                except Exception:
                    pass
        if not faces_out:
            return shape
        tf = Part.Compound(faces_out) if len(faces_out) > 1 else faces_out[0]

        # --- combined matrix: center text → rotate onto face → translate to face center ---
        tb = tf.BoundBox
        mc = FreeCAD.Matrix()          # center text at local origin
        mc.A14 = -tb.XLength / 2
        mc.A24 = -tb.YLength / 2
        mr = FreeCAD.Matrix()          # rotate: X→r, Y→u, Z→n
        mr.A11 = r.x;  mr.A12 = u.x;  mr.A13 = n.x
        mr.A21 = r.y;  mr.A22 = u.y;  mr.A23 = n.y
        mr.A31 = r.z;  mr.A32 = u.z;  mr.A33 = n.z
        mt = FreeCAD.Matrix()          # translate to face center, offset 5% outward
        mt.A14 = c.x + n.x * depth * 0.05
        mt.A24 = c.y + n.y * depth * 0.05
        mt.A34 = c.z + n.z * depth * 0.05
        tf2 = tf.transformGeometry(mt.multiply(mr.multiply(mc)))

        # --- extrude inward and cut ---
        extrude_vec = Vector(-n.x * depth * 1.1, -n.y * depth * 1.1, -n.z * depth * 1.1)
        text_solid = tf2.extrude(extrude_vec)
        return shape.cut(text_solid)

    except Exception as e:
        print(f"[part_code] emboss skipped: {e}")
        return shape

panel = _emboss_part_code(panel, PART_CODE)

# === ADD TO DOCUMENT ===
obj = doc.addObject("Part::Feature", "RightBackPier")
obj.Shape = panel

doc.recompute()
if FreeCAD.GuiUp:
    FreeCAD.Gui.ActiveDocument.ActiveView.fitAll()
●
Executing in FreeCAD...
◉
StepAnalyzer failed: FreeCAD could not read STEP file: FreeCAD exception thrown (File to load not existing or not readable) — skipping
●
FreeCAD execution succeeded
●
Exported: model.stl
◉
Generation succeeded and component was auto-approved (v1)
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◇
Received interface right_wall_bottom.top_spline: %{name: "top_spline", type: "spline", value: %{}, agent_id: "right_wall_bottom"}
◇
Received interface right_wall_bottom.top_spline: %{name: "top_spline", type: "spline", value: %{}, agent_id: "right_wall_bottom"}
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◇
Received interface right_wall_bottom.top_spline: %{name: "top_spline", type: "spline", value: %{}, agent_id: "right_wall_bottom"}
◉
Upstream interface changed: right_wall_bottom.top_spline
◉
Regeneration requested by user
◉
Generating v2
○
Calling Gemini API for code generation (gemini-3.1-pro-preview)...
◉
Generation token budget: max_tokens=12288, thinking_budget=0 (retry_count=0)
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
●
Received response (1810 tokens)
◆
right_wall_back_pier_v2.py · 145 lines
import FreeCAD
import Part
import math
from FreeCAD import Vector

# === PARAMETERS ===
LENGTH = 1078.0           # X dimension (panel length)
THICKNESS = 122.0         # Y dimension (panel width/thickness)
HEIGHT = 500.0            # Z dimension (panel height)

OSB_THICKNESS = 11.0
CORE_THICKNESS = 100.0    # 122mm total - 2x 11mm OSB
GROOVE_DEPTH = 45.0       # Depth for top and bottom spline grooves

# === DOCUMENT ===
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument("Model")

# === BUILD GEOMETRY ===
# Front OSB Face
face1 = Part.makeBox(LENGTH, OSB_THICKNESS, HEIGHT, Vector(0, 0, 0))

# EPS Core (recessed at top and bottom for spline grooves)
core = Part.makeBox(
    LENGTH, 
    CORE_THICKNESS, 
    HEIGHT - (2 * GROOVE_DEPTH), 
    Vector(0, OSB_THICKNESS, GROOVE_DEPTH)
)

# Back OSB Face
face2 = Part.makeBox(
    LENGTH, 
    OSB_THICKNESS, 
    HEIGHT, 
    Vector(0, OSB_THICKNESS + CORE_THICKNESS, 0)
)

# Fuse the layers into a single SIP panel
panel = face1.fuse([core, face2])
panel = panel.removeSplitter()

# === PART CODE ===
PART_CODE = "C-7"
PRINT_SCALE = 50  # 1:50 model scale — adjust if printing at a different scale

def _emboss_part_code(shape, code, print_scale=PRINT_SCALE):
    """Cut part code into largest face. Returns shape unmodified on any failure."""
    import os, math
    depth_raw = 0.6 * print_scale
    try:
        # --- font ---
        rd = FreeCAD.getResourceDir()
        font = None
        for fn in ("osifont-lgpl3fe.ttf", "FreeSans.ttf", "FreeCAD.ttf"):
            fp = os.path.join(rd, "Mod", "TechDraw", "Resources", "fonts", fn)
            if os.path.exists(fp):
                font = fp; break
        if font is None:
            return shape

        # --- cap depth at 33% of thinnest component dimension ---
        all_dims = sorted([shape.BoundBox.XLength, shape.BoundBox.YLength, shape.BoundBox.ZLength])
        depth = min(depth_raw, all_dims[0] * 0.33)

        # --- find largest face ---
        best = max(shape.Faces, key=lambda f: f.Area)
        n = best.normalAt(0, 0)
        c = best.CenterOfMass

        # --- text height: 2 mm at print scale, capped at 80% of longest face edge ---
        fb = best.BoundBox
        fb_dims = sorted([d for d in (fb.XLength, fb.YLength, fb.ZLength) if d > 1.0])
        text_h = min(2.0 * print_scale, (fb_dims[-1] * 0.80) if fb_dims else 2.0 * print_scale)

        # --- orthonormal face frame: right (r), up (u), out (n) ---
        wz = Vector(0, 0, 1)
        wy = Vector(0, 1, 0)
        up_ref = wy if abs(n.dot(wz)) > 0.9 else wz
        r = up_ref.cross(n).normalize()
        u = n.cross(r).normalize()

        # --- build text faces from wires (handles letters with holes) ---
        ss = Part.makeShapeString(String=code, FontFile=font, Size=text_h, Tracking=0)
        wires = sorted(ss.Wires, key=lambda w: w.BoundBox.DiagonalLength, reverse=True)
        faces_out = []
        used = set()
        for i, ow in enumerate(wires):
            if i in used:
                continue
            ob = ow.BoundBox
            holes = []
            for j, iw in enumerate(wires):
                if j <= i or j in used:
                    continue
                ib = iw.BoundBox
                if ob.XMin < ib.XMin and ob.XMax > ib.XMax and ob.YMin < ib.YMin and ob.YMax > ib.YMax:
                    holes.append(iw)
                    used.add(j)
            used.add(i)
            try:
                faces_out.append(Part.Face([ow] + holes) if holes else Part.Face(ow))
            except Exception:
                try:
                    faces_out.append(Part.Face(ow))
                except Exception:
                    pass
        if not faces_out:
            return shape
        tf = Part.Compound(faces_out) if len(faces_out) > 1 else faces_out[0]

        # --- combined matrix: center text → rotate onto face → translate to face center ---
        tb = tf.BoundBox
        mc = FreeCAD.Matrix()          # center text at local origin
        mc.A14 = -tb.XLength / 2
        mc.A24 = -tb.YLength / 2
        mr = FreeCAD.Matrix()          # rotate: X→r, Y→u, Z→n
        mr.A11 = r.x;  mr.A12 = u.x;  mr.A13 = n.x
        mr.A21 = r.y;  mr.A22 = u.y;  mr.A23 = n.y
        mr.A31 = r.z;  mr.A32 = u.z;  mr.A33 = n.z
        mt = FreeCAD.Matrix()          # translate to face center, offset 5% outward
        mt.A14 = c.x + n.x * depth * 0.05
        mt.A24 = c.y + n.y * depth * 0.05
        mt.A34 = c.z + n.z * depth * 0.05
        tf2 = tf.transformGeometry(mt.multiply(mr.multiply(mc)))

        # --- extrude inward and cut ---
        extrude_vec = Vector(-n.x * depth * 1.1, -n.y * depth * 1.1, -n.z * depth * 1.1)
        text_solid = tf2.extrude(extrude_vec)
        return shape.cut(text_solid)

    except Exception as e:
        print(f"[part_code] emboss skipped: {e}")
        return shape

# Apply the embossing helper to cut the code into the part
panel_final = _emboss_part_code(panel, PART_CODE)

# === ADD TO DOCUMENT ===
obj = doc.addObject("Part::Feature", "Body")
obj.Shape = panel_final

doc.recompute()

if FreeCAD.GuiUp:
    FreeCAD.Gui.ActiveDocument.ActiveView.fitAll()
●
Executing in FreeCAD...
◉
StepAnalyzer failed: FreeCAD could not read STEP file: FreeCAD exception thrown (File to load not existing or not readable) — skipping
●
FreeCAD execution succeeded
●
Exported: model.stl
◉
Generation succeeded and component was auto-approved (v2)
◉
Upstream interface changed: right_wall_bottom.top_spline
◉
Provider updated to google / balanced
◉
Regeneration requested by user
◉
Generating v3
○
Calling Gemini API for code generation (gemini-3.1-pro-preview)...
◉
Generation token budget: max_tokens=12288, thinking_budget=0 (retry_count=0)
●
Received response (2089 tokens)
◆
right_wall_back_pier_v3.py · 164 lines
import FreeCAD
import Part
from FreeCAD import Vector

# === PARAMETERS ===
WIDTH = 122            # Total panel thickness (X)
LENGTH = 1078          # Panel length (Y)
HEIGHT = 500           # Total bounding box height including bottom spline (Z)

FACE_THICKNESS = 11    # OSB face thickness
CORE_THICKNESS = 100   # EPS core thickness
GROOVE_DEPTH = 45      # Depth of spline grooves

# Z-axis staging to keep the bounding box exactly within 0 to HEIGHT:
# Z = 0 to 45: Bottom spline protruding downwards (45mm)
# Z = 45 to 90: Bottom spline inserted into the panel core (45mm)
# Z = 90 to 455: EPS core
# Z = 455 to 500: Top groove (empty space between OSB faces)
# Z = 45 to 500: OSB exterior faces

Z_SPLINE_START = 0
Z_OSB_START = GROOVE_DEPTH
Z_EPS_START = Z_OSB_START + GROOVE_DEPTH
Z_EPS_END = HEIGHT - GROOVE_DEPTH
Z_OSB_END = HEIGHT

# === DOCUMENT ===
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument("SIP_Panel")

# === BUILD GEOMETRY ===
# 1. Protruding bottom horizontal spline (timber block filling the groove and extending down)
spline_height = Z_EPS_START - Z_SPLINE_START
spline = Part.makeBox(
    CORE_THICKNESS, LENGTH, spline_height,
    Vector(FACE_THICKNESS, 0, Z_SPLINE_START)
)

# 2. Left/Inner OSB Face
osb_height = Z_OSB_END - Z_OSB_START
osb_inner = Part.makeBox(
    FACE_THICKNESS, LENGTH, osb_height,
    Vector(0, 0, Z_OSB_START)
)

# 3. Right/Outer OSB Face
osb_outer = Part.makeBox(
    FACE_THICKNESS, LENGTH, osb_height,
    Vector(FACE_THICKNESS + CORE_THICKNESS, 0, Z_OSB_START)
)

# 4. EPS Core
eps_height = Z_EPS_END - Z_EPS_START
eps_core = Part.makeBox(
    CORE_THICKNESS, LENGTH, eps_height,
    Vector(FACE_THICKNESS, 0, Z_EPS_START)
)

# Combine all parts
result = spline.fuse([osb_inner, osb_outer, eps_core])
result = result.removeSplitter()

# === PART CODE ===
PART_CODE = "C-7"
PRINT_SCALE = 50  # 1:50 model scale — adjust if printing at a different scale

def _emboss_part_code(shape, code, print_scale=PRINT_SCALE):
    """Cut part code into largest face. Returns shape unmodified on any failure."""
    import os, math
    depth_raw = 0.6 * print_scale
    try:
        # --- font ---
        rd = FreeCAD.getResourceDir()
        font = None
        for fn in ("osifont-lgpl3fe.ttf", "FreeSans.ttf", "FreeCAD.ttf"):
            fp = os.path.join(rd, "Mod", "TechDraw", "Resources", "fonts", fn)
            if os.path.exists(fp):
                font = fp; break
        if font is None:
            return shape

        # --- cap depth at 33% of thinnest component dimension ---
        all_dims = sorted([shape.BoundBox.XLength, shape.BoundBox.YLength, shape.BoundBox.ZLength])
        depth = min(depth_raw, all_dims[0] * 0.33)

        # --- find largest face ---
        best = max(shape.Faces, key=lambda f: f.Area)
        n = best.normalAt(0, 0)
        c = best.CenterOfMass

        # --- text height: 2 mm at print scale, capped at 80% of longest face edge ---
        fb = best.BoundBox
        fb_dims = sorted([d for d in (fb.XLength, fb.YLength, fb.ZLength) if d > 1.0])
        text_h = min(2.0 * print_scale, (fb_dims[-1] * 0.80) if fb_dims else 2.0 * print_scale)

        # --- orthonormal face frame: right (r), up (u), out (n) ---
        wz = Vector(0, 0, 1)
        wy = Vector(0, 1, 0)
        up_ref = wy if abs(n.dot(wz)) > 0.9 else wz
        r = up_ref.cross(n).normalize()
        u = n.cross(r).normalize()

        # --- build text faces from wires (handles letters with holes) ---
        ss = Part.makeShapeString(String=code, FontFile=font, Size=text_h, Tracking=0)
        wires = sorted(ss.Wires, key=lambda w: w.BoundBox.DiagonalLength, reverse=True)
        faces_out = []
        used = set()
        for i, ow in enumerate(wires):
            if i in used:
                continue
            ob = ow.BoundBox
            holes = []
            for j, iw in enumerate(wires):
                if j <= i or j in used:
                    continue
                ib = iw.BoundBox
                if ob.XMin < ib.XMin and ob.XMax > ib.XMax and ob.YMin < ib.YMin and ob.YMax > ib.YMax:
                    holes.append(iw)
                    used.add(j)
            used.add(i)
            try:
                faces_out.append(Part.Face([ow] + holes) if holes else Part.Face(ow))
            except Exception:
                try:
                    faces_out.append(Part.Face(ow))
                except Exception:
                    pass
        if not faces_out:
            return shape
        tf = Part.Compound(faces_out) if len(faces_out) > 1 else faces_out[0]

        # --- combined matrix: center text → rotate onto face → translate to face center ---
        tb = tf.BoundBox
        mc = FreeCAD.Matrix()          # center text at local origin
        mc.A14 = -tb.XLength / 2
        mc.A24 = -tb.YLength / 2
        mr = FreeCAD.Matrix()          # rotate: X→r, Y→u, Z→n
        mr.A11 = r.x;  mr.A12 = u.x;  mr.A13 = n.x
        mr.A21 = r.y;  mr.A22 = u.y;  mr.A23 = n.y
        mr.A31 = r.z;  mr.A32 = u.z;  mr.A33 = n.z
        mt = FreeCAD.Matrix()          # translate to face center, offset 5% outward
        mt.A14 = c.x + n.x * depth * 0.05
        mt.A24 = c.y + n.y * depth * 0.05
        mt.A34 = c.z + n.z * depth * 0.05
        tf2 = tf.transformGeometry(mt.multiply(mr.multiply(mc)))

        # --- extrude inward and cut ---
        extrude_vec = Vector(-n.x * depth * 1.1, -n.y * depth * 1.1, -n.z * depth * 1.1)
        text_solid = tf2.extrude(extrude_vec)
        return shape.cut(text_solid)

    except Exception as e:
        print(f"[part_code] emboss skipped: {e}")
        return shape

result = _emboss_part_code(result, PART_CODE)

# === ADD TO DOCUMENT ===
obj = doc.addObject("Part::Feature", "Body")
obj.Shape = result

doc.recompute()

if FreeCAD.GuiUp:
    FreeCAD.Gui.ActiveDocument.ActiveView.fitAll()
●
Executing in FreeCAD...
◉
StepAnalyzer failed: FreeCAD could not read STEP file: FreeCAD exception thrown (File to load not existing or not readable) — skipping
●
FreeCAD execution succeeded
●
Exported: model.stl
◉
Generation succeeded and component was auto-approved (v3)
◉
Upstream interface changed: right_wall_bottom.top_spline
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Agent initialized (status: waiting_for_dependencies, action: check_dependencies)
◉
Provider: google | Profile: balanced
◇
Seeded interface right_wall_bottom.top_spline from DB (provider already approved)
◉
All dependencies resolved, starting generation
◉
Generating v4
◉
Regeneration requested by user
○
Calling Gemini API for code generation (gemini-3.1-pro-preview)...
◉
Generating v4
○
Calling Gemini API for code generation (gemini-3.1-pro-preview)...
◉
Generation token budget: max_tokens=12288, thinking_budget=0 (retry_count=0)
◉
Generation token budget: max_tokens=12288, thinking_budget=0 (retry_count=0)
●
Received response (1880 tokens)
◆
right_wall_back_pier_v4.py · 137 lines
import FreeCAD
import Part
import math
from FreeCAD import Vector

# === PARAMETERS ===
PANEL_LENGTH = 1078.0
PANEL_WIDTH = 122.0
PANEL_HEIGHT = 500.0

OSB_THICKNESS = 11.0
CORE_THICKNESS = 100.0  # 122 - 2 * 11
SPLINE_DEPTH = 45.0

# === DOCUMENT ===
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument("Model")

# === BUILD GEOMETRY ===
# 1. Front OSB face (base at Z=0)
osb_front = Part.makeBox(PANEL_LENGTH, OSB_THICKNESS, PANEL_HEIGHT, Vector(0, 0, 0))

# 2. Back OSB face (base at Z=0)
osb_back = Part.makeBox(PANEL_LENGTH, OSB_THICKNESS, PANEL_HEIGHT, Vector(0, OSB_THICKNESS + CORE_THICKNESS, 0))

# 3. EPS Core (stops short of the top to leave a groove)
core_height = PANEL_HEIGHT - SPLINE_DEPTH
core = Part.makeBox(PANEL_LENGTH, CORE_THICKNESS, core_height, Vector(0, OSB_THICKNESS, 0))

# 4. Protruding Bottom Spline (extends downwards from Z=0 into -Z)
bottom_spline = Part.makeBox(PANEL_LENGTH, CORE_THICKNESS, SPLINE_DEPTH, Vector(0, OSB_THICKNESS, -SPLINE_DEPTH))

# Fuse all components into a single solid
final_shape = osb_front.fuse([osb_back, core, bottom_spline])
final_shape = final_shape.removeSplitter()

# === PART CODE ===
PART_CODE = "C-7"
PRINT_SCALE = 50  # 1:50 model scale — adjust if printing at a different scale

def _emboss_part_code(shape, code, print_scale=PRINT_SCALE):
    """Cut part code into largest face. Returns shape unmodified on any failure."""
    import os, math
    depth_raw = 0.6 * print_scale
    try:
        # --- font ---
        rd = FreeCAD.getResourceDir()
        font = None
        for fn in ("osifont-lgpl3fe.ttf", "FreeSans.ttf", "FreeCAD.ttf"):
            fp = os.path.join(rd, "Mod", "TechDraw", "Resources", "fonts", fn)
            if os.path.exists(fp):
                font = fp; break
        if font is None:
            return shape

        # --- cap depth at 33% of thinnest component dimension ---
        all_dims = sorted([shape.BoundBox.XLength, shape.BoundBox.YLength, shape.BoundBox.ZLength])
        depth = min(depth_raw, all_dims[0] * 0.33)

        # --- find largest face ---
        best = max(shape.Faces, key=lambda f: f.Area)
        n = best.normalAt(0, 0)
        c = best.CenterOfMass

        # --- text height: 2 mm at print scale, capped at 80% of longest face edge ---
        fb = best.BoundBox
        fb_dims = sorted([d for d in (fb.XLength, fb.YLength, fb.ZLength) if d > 1.0])
        text_h = min(2.0 * print_scale, (fb_dims[-1] * 0.80) if fb_dims else 2.0 * print_scale)

        # --- orthonormal face frame: right (r), up (u), out (n) ---
        wz = Vector(0, 0, 1)
        wy = Vector(0, 1, 0)
        up_ref = wy if abs(n.dot(wz)) > 0.9 else wz
        r = up_ref.cross(n).normalize()
        u = n.cross(r).normalize()

        # --- build text faces from wires (handles letters with holes) ---
        ss = Part.makeShapeString(String=code, FontFile=font, Size=text_h, Tracking=0)
        wires = sorted(ss.Wires, key=lambda w: w.BoundBox.DiagonalLength, reverse=True)
        faces_out = []
        used = set()
        for i, ow in enumerate(wires):
            if i in used:
                continue
            ob = ow.BoundBox
            holes = []
            for j, iw in enumerate(wires):
                if j <= i or j in used:
                    continue
                ib = iw.BoundBox
                if ob.XMin < ib.XMin and ob.XMax > ib.XMax and ob.YMin < ib.YMin and ob.YMax > ib.YMax:
                    holes.append(iw)
                    used.add(j)
            used.add(i)
            try:
                faces_out.append(Part.Face([ow] + holes) if holes else Part.Face(ow))
            except Exception:
                try:
                    faces_out.append(Part.Face(ow))
                except Exception:
                    pass
        if not faces_out:
            return shape
        tf = Part.Compound(faces_out) if len(faces_out) > 1 else faces_out[0]

        # --- combined matrix: center text → rotate onto face → translate to face center ---
        tb = tf.BoundBox
        mc = FreeCAD.Matrix()          # center text at local origin
        mc.A14 = -tb.XLength / 2
        mc.A24 = -tb.YLength / 2
        mr = FreeCAD.Matrix()          # rotate: X→r, Y→u, Z→n
        mr.A11 = r.x;  mr.A12 = u.x;  mr.A13 = n.x
        mr.A21 = r.y;  mr.A22 = u.y;  mr.A23 = n.y
        mr.A31 = r.z;  mr.A32 = u.z;  mr.A33 = n.z
        mt = FreeCAD.Matrix()          # translate to face center, offset 5% outward
        mt.A14 = c.x + n.x * depth * 0.05
        mt.A24 = c.y + n.y * depth * 0.05
        mt.A34 = c.z + n.z * depth * 0.05
        tf2 = tf.transformGeometry(mt.multiply(mr.multiply(mc)))

        # --- extrude inward and cut ---
        extrude_vec = Vector(-n.x * depth * 1.1, -n.y * depth * 1.1, -n.z * depth * 1.1)
        text_solid = tf2.extrude(extrude_vec)
        return shape.cut(text_solid)

    except Exception as e:
        print(f"[part_code] emboss skipped: {e}")
        return shape

final_shape = _emboss_part_code(final_shape, PART_CODE)

# === ADD TO DOCUMENT ===
feature = doc.addObject("Part::Feature", "RightBackPierPanel")
feature.Shape = final_shape
doc.recompute()

if FreeCAD.GuiUp:
    FreeCAD.Gui.ActiveDocument.ActiveView.fitAll()
●
Executing in FreeCAD...
◉
StepAnalyzer failed: FreeCAD could not read STEP file: FreeCAD exception thrown (File to load not existing or not readable) — skipping
●
FreeCAD execution succeeded
●
Exported: model.stl
◉
Generation succeeded and component was auto-approved (v4)
●
Received response (1985 tokens)
◆
right_wall_back_pier_v4.py · 160 lines
import FreeCAD
import Part
import math
from FreeCAD import Vector

# === PARAMETERS ===
LENGTH = 1078.0
WIDTH = 122.0
HEIGHT = 500.0

FACE_T = 11.0
CORE_T = 100.0

# Joinery dimensions
SPLINE_W = 45.0
SPLINE_D = 45.0

# Clearance applied for receiving grooves (Assembly fit: +0.1mm per face width, +1.0mm depth)
GROOVE_W = SPLINE_W + 0.2
GROOVE_D = SPLINE_D + 1.0

# === DOCUMENT ===
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument("RightBackPier")

# === BUILD GEOMETRY ===
# 1. Bottom Spline (protruding out of the core at the base)
# The local bounding box origin begins at Z=0 for the bottom-most geometry.
bottom_spline = Part.makeBox(
    LENGTH, 
    SPLINE_W, 
    SPLINE_D, 
    Vector(0, (WIDTH - SPLINE_W) / 2, 0)
)

# 2. Main Panel Body
# The main OSB & EPS faces start above the bottom spline so the entire bounding box HEIGHT equals 500.
panel_z = SPLINE_D
panel_h = HEIGHT - SPLINE_D

face1 = Part.makeBox(LENGTH, FACE_T, panel_h, Vector(0, 0, panel_z))
core  = Part.makeBox(LENGTH, CORE_T, panel_h, Vector(0, FACE_T, panel_z))
face2 = Part.makeBox(LENGTH, FACE_T, panel_h, Vector(0, FACE_T + CORE_T, panel_z))

# Fuse panel components and the bottom spline together
panel = face1.fuse(core).fuse(face2).fuse(bottom_spline)

# 3. Top Groove
# A recess at the top of the panel EPS core allowing a corresponding top plate or spline block.
top_groove = Part.makeBox(
    LENGTH, 
    GROOVE_W, 
    GROOVE_D, 
    Vector(0, (WIDTH - GROOVE_W) / 2, HEIGHT - GROOVE_D)
)

panel = panel.cut(top_groove)
panel = panel.removeSplitter()

# === PART CODE ===
PART_CODE = "C-7"
PRINT_SCALE = 50  # 1:50 model scale — adjust if printing at a different scale

def _emboss_part_code(shape, code, print_scale=PRINT_SCALE):
    """Cut part code into largest face. Returns shape unmodified on any failure."""
    import os, math
    depth_raw = 0.6 * print_scale
    try:
        # --- font ---
        rd = FreeCAD.getResourceDir()
        font = None
        for fn in ("osifont-lgpl3fe.ttf", "FreeSans.ttf", "FreeCAD.ttf"):
            fp = os.path.join(rd, "Mod", "TechDraw", "Resources", "fonts", fn)
            if os.path.exists(fp):
                font = fp; break
        if font is None:
            return shape

        # --- cap depth at 33% of thinnest component dimension ---
        all_dims = sorted([shape.BoundBox.XLength, shape.BoundBox.YLength, shape.BoundBox.ZLength])
        depth = min(depth_raw, all_dims[0] * 0.33)

        # --- find largest face ---
        best = max(shape.Faces, key=lambda f: f.Area)
        n = best.normalAt(0, 0)
        c = best.CenterOfMass

        # --- text height: 2 mm at print scale, capped at 80% of longest face edge ---
        fb = best.BoundBox
        fb_dims = sorted([d for d in (fb.XLength, fb.YLength, fb.ZLength) if d > 1.0])
        text_h = min(2.0 * print_scale, (fb_dims[-1] * 0.80) if fb_dims else 2.0 * print_scale)

        # --- orthonormal face frame: right (r), up (u), out (n) ---
        wz = Vector(0, 0, 1)
        wy = Vector(0, 1, 0)
        up_ref = wy if abs(n.dot(wz)) > 0.9 else wz
        r = up_ref.cross(n).normalize()
        u = n.cross(r).normalize()

        # --- build text faces from wires (handles letters with holes) ---
        ss = Part.makeShapeString(String=code, FontFile=font, Size=text_h, Tracking=0)
        wires = sorted(ss.Wires, key=lambda w: w.BoundBox.DiagonalLength, reverse=True)
        faces_out = []
        used = set()
        for i, ow in enumerate(wires):
            if i in used:
                continue
            ob = ow.BoundBox
            holes = []
            for j, iw in enumerate(wires):
                if j <= i or j in used:
                    continue
                ib = iw.BoundBox
                if ob.XMin < ib.XMin and ob.XMax > ib.XMax and ob.YMin < ib.YMin and ob.YMax > ib.YMax:
                    holes.append(iw)
                    used.add(j)
            used.add(i)
            try:
                faces_out.append(Part.Face([ow] + holes) if holes else Part.Face(ow))
            except Exception:
                try:
                    faces_out.append(Part.Face(ow))
                except Exception:
                    pass
        if not faces_out:
            return shape
        tf = Part.Compound(faces_out) if len(faces_out) > 1 else faces_out[0]

        # --- combined matrix: center text → rotate onto face → translate to face center ---
        tb = tf.BoundBox
        mc = FreeCAD.Matrix()          # center text at local origin
        mc.A14 = -tb.XLength / 2
        mc.A24 = -tb.YLength / 2
        mr = FreeCAD.Matrix()          # rotate: X→r, Y→u, Z→n
        mr.A11 = r.x;  mr.A12 = u.x;  mr.A13 = n.x
        mr.A21 = r.y;  mr.A22 = u.y;  mr.A23 = n.y
        mr.A31 = r.z;  mr.A32 = u.z;  mr.A33 = n.z
        mt = FreeCAD.Matrix()          # translate to face center, offset 5% outward
        mt.A14 = c.x + n.x * depth * 0.05
        mt.A24 = c.y + n.y * depth * 0.05
        mt.A34 = c.z + n.z * depth * 0.05
        tf2 = tf.transformGeometry(mt.multiply(mr.multiply(mc)))

        # --- extrude inward and cut ---
        extrude_vec = Vector(-n.x * depth * 1.1, -n.y * depth * 1.1, -n.z * depth * 1.1)
        text_solid = tf2.extrude(extrude_vec)
        return shape.cut(text_solid)

    except Exception as e:
        print(f"[part_code] emboss skipped: {e}")
        return shape

final_shape = _emboss_part_code(panel, PART_CODE)

# === ADD TO DOCUMENT ===
obj = doc.addObject("Part::Feature", "Body")
obj.Shape = final_shape

doc.recompute()
if FreeCAD.GuiUp:
    FreeCAD.Gui.ActiveDocument.ActiveView.fitAll()
●
Executing in FreeCAD...
◉
StepAnalyzer failed: FreeCAD could not read STEP file: FreeCAD exception thrown (File to load not existing or not readable) — skipping
●
FreeCAD execution succeeded
●
Exported: model.stl
◉
Generation succeeded and component was auto-approved (v5)
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
v0.0.985