← Garden room - 4x3 - Design Profile Demo

Rear Wall Panel

v2 Gemini approved

Create the continuous rear OSB/EPS SIP wall panel, 3756x122x2069mm. Include a spline joint on both left and right vertical edges to mate with the corner post spline grooves. Base sits flat on the back sole plate.

Dependencies

back_sole_plate.top_face back_left_corner_post.right_spline back_right_corner_post.left_spline

Interfaces Provided

top_face (face)
Agent initialized (status: waiting_for_dependencies, action: check_dependencies)
Provider: google | Profile: balanced
Received interface back_sole_plate.top_face: %{name: "top_face", type: "face", value: %{}, agent_id: "back_sole_plate"}
Received interface back_right_corner_post.left_spline: %{name: "left_spline", type: "spline", value: %{}, agent_id: "back_right_corner_post"}
Received interface back_right_corner_post.front_spline: %{name: "front_spline", type: "spline", value: %{}, agent_id: "back_right_corner_post"}
Received interface back_left_corner_post.right_spline: %{name: "right_spline", type: "spline", value: %{}, agent_id: "back_left_corner_post"}
All dependencies resolved, starting generation
Generating v1
Received interface back_left_corner_post.front_spline: %{name: "front_spline", type: "spline", value: %{}, agent_id: "back_left_corner_post"}
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 (2180 tokens)
back_wall_sip_v1.py · 166 lines
import FreeCAD
import Part
import math
import os
from FreeCAD import Vector, Placement, Rotation

# === PARAMETERS ===
WALL_LENGTH = 3756
PANEL_HEIGHT = 2069
CORE_THICKNESS = 100
FACE_THICKNESS = 11
TOTAL_THICKNESS = 122
PREFERRED_PANEL_W = 1200
SPLINE_W_EXT = 45      # Depth of the core recess on external edges (for corner post splines)
SPLINE_W_INT = 45      # Total width of internal block splines

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

# Calculate panel breakdown
full_panels = int(WALL_LENGTH // PREFERRED_PANEL_W)
remainder = WALL_LENGTH % PREFERRED_PANEL_W
panel_widths = [PREFERRED_PANEL_W] * full_panels + ([remainder] if remainder > 0 else [])

parts = []
current_x = 0.0

# === BUILD PANELS AND INTERNAL SPLINES ===
for i, pw in enumerate(panel_widths):
    # Determine the EPS core recess on the left and right of this specific panel
    left_recess = SPLINE_W_EXT if i == 0 else (SPLINE_W_INT / 2.0)
    right_recess = SPLINE_W_EXT if i == len(panel_widths) - 1 else (SPLINE_W_INT / 2.0)
    
    # Exterior OSB face (Y = 0)
    face1 = Part.makeBox(pw, FACE_THICKNESS, PANEL_HEIGHT, Vector(current_x, 0, 0))
    
    # Interior OSB face (Y = FACE_THICKNESS + CORE_THICKNESS)
    face2 = Part.makeBox(pw, FACE_THICKNESS, PANEL_HEIGHT, Vector(current_x, FACE_THICKNESS + CORE_THICKNESS, 0))
    
    # EPS Core (recessed at edges for splines)
    core_w = pw - left_recess - right_recess
    if core_w > 0:
        core_x = current_x + left_recess
        core = Part.makeBox(core_w, CORE_THICKNESS, PANEL_HEIGHT, Vector(core_x, FACE_THICKNESS, 0))
        parts.extend([face1, core, face2])
    else:
        # Fallback if a remainder panel is unusually small
        parts.extend([face1, face2])

    # Internal Block Spline (joining this panel to the next)
    if i < len(panel_widths) - 1:
        spline_x = current_x + pw - (SPLINE_W_INT / 2.0)
        spline = Part.makeBox(SPLINE_W_INT, CORE_THICKNESS, PANEL_HEIGHT, Vector(spline_x, FACE_THICKNESS, 0))
        parts.append(spline)

    current_x += pw

# Combine all parts into a single solid
result = parts[0]
for p in parts[1:]:
    result = result.fuse(p)
result = result.removeSplitter()

# === PART CODE ===
PART_CODE = "C-3"
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 part code
result = _emboss_part_code(result, PART_CODE)

# Export body correctly
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 (v1)
Provider updated to google / balanced
Provider updated to google / balanced
Upstream interface changed: back_sole_plate.top_face
Received interface back_left_corner_post.right_spline: %{name: "right_spline", type: "spline", value: %{}, agent_id: "back_left_corner_post"}
Received interface back_left_corner_post.front_spline: %{name: "front_spline", type: "spline", value: %{}, agent_id: "back_left_corner_post"}
Received interface back_right_corner_post.left_spline: %{name: "left_spline", type: "spline", value: %{}, agent_id: "back_right_corner_post"}
Received interface back_right_corner_post.front_spline: %{name: "front_spline", type: "spline", value: %{}, agent_id: "back_right_corner_post"}
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)
Upstream interface changed: back_right_corner_post.left_spline
Upstream interface changed: back_left_corner_post.right_spline
Received response (2248 tokens)
back_wall_sip_v2.py · 181 lines
import FreeCAD
import Part
import math
from FreeCAD import Vector

# === PARAMETRIC CONSTANTS ===
PANEL_LENGTH = 3756.0      # mm
PANEL_THICKNESS = 122.0    # mm
PANEL_HEIGHT = 2069.0      # mm
FACE_THICKNESS = 11.0      # mm (OSB faces)
CORE_THICKNESS = 100.0     # mm (EPS core)
SPLINE_W = 45.0            # mm (block spline width across joint)
SPLINE_D = 90.0            # mm (block spline depth inside core)
END_GROOVE_DEPTH = 45.0    # mm (empty core routing for corner post splines)
STANDARD_PANEL_W = 1200.0  # mm

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

# === PANEL CALCULATION ===
full_panels = int(PANEL_LENGTH // STANDARD_PANEL_W)
remainder = PANEL_LENGTH % STANDARD_PANEL_W
panel_widths = [STANDARD_PANEL_W] * full_panels + ([remainder] if remainder > 0 else [])

parts = []
x_pos = 0.0

# === BUILD GEOMETRY ===
for i, pw in enumerate(panel_widths):
    # OSB Faces for this segment
    f1 = Part.makeBox(pw, FACE_THICKNESS, PANEL_HEIGHT, Vector(x_pos, 0, 0))
    f2 = Part.makeBox(pw, FACE_THICKNESS, PANEL_HEIGHT, Vector(x_pos, FACE_THICKNESS + CORE_THICKNESS, 0))
    
    # Calculate EPS Core dimensions (routed back for splines)
    core_x = x_pos
    core_w = pw
    
    # Left edge of THIS segment
    if i == 0:
        left_groove = END_GROOVE_DEPTH
    else:
        left_groove = SPLINE_W / 2.0
        
    core_x += left_groove
    core_w -= left_groove
    
    # Right edge of THIS segment
    if i == len(panel_widths) - 1:
        right_groove = END_GROOVE_DEPTH
    else:
        right_groove = SPLINE_W / 2.0
        
    core_w -= right_groove
    
    # Add Core to parts if valid width exists
    if core_w > 0:
        core = Part.makeBox(core_w, CORE_THICKNESS, PANEL_HEIGHT, Vector(core_x, FACE_THICKNESS, 0))
        parts.extend([f1, core, f2])
    else:
        parts.extend([f1, f2])
        
    # Internal Spline bridging this segment and the next
    if i < len(panel_widths) - 1:
        sp = Part.makeBox(SPLINE_W, SPLINE_D, PANEL_HEIGHT,
                          Vector(x_pos + pw - SPLINE_W / 2.0,
                                 (PANEL_THICKNESS - SPLINE_D) / 2.0,
                                 0))
        parts.append(sp)
        
    x_pos += pw

# Fuse all components into one solid SIP panel feature
result = parts[0]
for p in parts[1:]:
    result = result.fuse(p)

result = result.removeSplitter()


# === PART CODE ===
PART_CODE = "C-3"
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", "RearWallPanel")
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 (v2)
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.542