← Garden room - 4x3 - Design Profile Demo

Roof Span Splice LVL

v7 Gemini approved

Create the structural span splice LVL bearer, 4200mm long x 45mm wide x 122mm high. Connects via adhesive and mechanical span splice joint to the rear edge of the front roof panel, accommodating the extended roof span. Oriented at a 5-degree slope.

Dependencies

roof_panel_front.rear_splice

Interfaces Provided

rear_splice (splice)
◉
Agent initialized (status: waiting_for_dependencies, action: check_dependencies)
◉
Provider: google | Profile: balanced
◉
Provider updated to google / balanced
◇
Received interface roof_panel_front.rear_splice: %{name: "rear_splice", type: "splice", value: %{}, agent_id: "roof_panel_front"}
◉
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)
●
Received response (1651 tokens)
◆
roof_splice_lvl_v1.py · 128 lines
import FreeCAD
import Part
import math
from FreeCAD import Vector

# === PARAMETERS ===
LENGTH = 4200       # mm
WIDTH = 45          # mm
HEIGHT = 122        # mm
SLOPE_ANGLE = 5     # degrees

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

# === BUILD GEOMETRY ===
# Create the structural LVL bearer
bearer = Part.makeBox(LENGTH, WIDTH, HEIGHT)

# Orient at a 5-degree slope (rotate around Y axis)
bearer.rotate(Vector(0, 0, 0), Vector(0, 1, 0), -SLOPE_ANGLE)

# Shift to ensure local origin compliance (Base at Z=0, Left at X=0, Back at Y=0)
bb = bearer.BoundBox
bearer.translate(Vector(-bb.XMin, -bb.YMin, -bb.ZMin))

# === PART CODE ===
PART_CODE = "F-1"
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 embossing
bearer = _emboss_part_code(bearer, PART_CODE)

# === ADD TO DOCUMENT ===
feature = doc.addObject("Part::Feature", "SpanSpliceLVL")
feature.Shape = bearer

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
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◇
Received interface roof_panel_front.rear_splice: %{name: "rear_splice", type: "splice", value: %{}, agent_id: "roof_panel_front"}
◉
Upstream interface changed: roof_panel_front.rear_splice
◉
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
●
Received response (1526 tokens)
◆
roof_splice_lvl_v2.py · 118 lines
import FreeCAD
import Part
from FreeCAD import Vector

# === PARAMETERS ===
LENGTH = 4200     # mm (span)
WIDTH = 45        # mm
HEIGHT = 122      # mm

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

# === BUILD GEOMETRY ===
bearer = Part.makeBox(LENGTH, WIDTH, HEIGHT)

# === PART CODE ===
PART_CODE = "F-1"
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

bearer = _emboss_part_code(bearer, PART_CODE)

# === ADD TO DOCUMENT ===
feature = doc.addObject("Part::Feature", "SpanSpliceLVL")
feature.Shape = bearer

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
◉
Upstream interface changed: roof_panel_front.rear_splice
◉
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 (1514 tokens)
◆
roof_splice_lvl_v3.py · 119 lines
import FreeCAD
import Part
from FreeCAD import Vector

# === PARAMETERS ===
LENGTH = 4200  # mm
WIDTH = 45     # mm
HEIGHT = 122   # mm

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

# === BUILD GEOMETRY ===
# The LVL bearer acts as a spline between the front and rear roof panels
lvl_bearer = Part.makeBox(LENGTH, WIDTH, HEIGHT)

# === PART CODE ===
PART_CODE = "F-1"
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

lvl_bearer = _emboss_part_code(lvl_bearer, PART_CODE)

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

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: roof_panel_front.rear_splice
◉
Upstream interface changed: roof_panel_front.rear_splice
◉
Upstream interface changed: roof_panel_front.rear_splice
◉
Regeneration requested by user
◉
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)
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
●
Received response (1755 tokens)
◆
roof_splice_lvl_v4.py · 135 lines
import FreeCAD
import Part
import math
from FreeCAD import Vector, Placement, Rotation

# === PARAMETERS ===
# Dimensions in mm
LENGTH = 4200.0
WIDTH = 45.0
HEIGHT = 122.0
SLOPE_ANGLE = 5.0  # degrees

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

# === BUILD GEOMETRY ===
# Create the base block for the LVL bearer
bearer = Part.makeBox(LENGTH, WIDTH, HEIGHT)

# Orient at a 5-degree slope (assuming the 4200mm length runs up/down the roof slope)
# We rotate around the Y axis so the X-axis length tilts.
bearer.rotate(Vector(0, 0, 0), Vector(0, 1, 0), -SLOPE_ANGLE)

# Ensure the piece strictly abides by the origin rules (X>=0, Y>=0, Z>=0)
# Rotation causes some corners to dip into negative coordinates. 
# We translate the bounding box minimums precisely to (0,0,0).
bb = bearer.BoundBox
bearer.translate(Vector(-bb.XMin, -bb.YMin, -bb.ZMin))

final_shape = bearer

# === PART CODE ===
PART_CODE = "F-1"
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)

# === FINAL EXPORT SETUP ===
# Adding to document strictly as "Body" for export compatibility
feature = doc.addObject("Part::Feature", "Body")
feature.Shape = final_shape

doc.recompute()

if FreeCAD.GuiUp:
    FreeCAD.Gui.ActiveDocument.ActiveView.fitAll()
●
Executing in FreeCAD...
◉
Upstream interface changed: roof_panel_front.rear_splice
◉
Upstream interface changed: roof_panel_front.rear_splice
◉
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)
◉
Provider updated to google / balanced
◉
Upstream interface changed: roof_panel_front.rear_splice
◉
Provider updated to google / balanced
◉
Provider updated to google / balanced
◉
Agent initialized (status: waiting_for_dependencies, action: check_dependencies)
◉
Provider: google | Profile: balanced
◉
Regeneration requested by user
◉
Generating v5
○
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 (1537 tokens)
◆
roof_splice_lvl_v5.py · 119 lines
import FreeCAD
import Part
import math
from FreeCAD import Vector, Placement, Rotation

# === PARAMETERS ===
LENGTH = 4200      # mm
WIDTH = 45         # mm
HEIGHT = 122       # mm

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

# === BUILD GEOMETRY ===
# Modelled orthogonally; assembly positioning will apply the 5-degree slope.
bearer = Part.makeBox(LENGTH, WIDTH, HEIGHT)

# === PART CODE ===
PART_CODE = "F-1"
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(bearer, PART_CODE)

# === FINAL ===
obj = doc.addObject("Part::Feature", "SpanSpliceLVL")
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)
◉
Upstream interface changed: roof_panel_front.rear_splice
◉
Regeneration requested by user
◉
Generating v6
○
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: roof_panel_front.rear_splice
◉
Regeneration requested by user
◉
Generating v6
○
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 (1594 tokens)
◆
roof_splice_lvl_v6.py · 125 lines
import FreeCAD
import Part
import math
from FreeCAD import Vector

# === PARAMETERS ===
LENGTH = 4200      # mm
WIDTH = 45         # mm
HEIGHT = 122       # mm
SLOPE_ANGLE = 5    # degrees

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

# === BUILD GEOMETRY ===
# Create the base rectangular LVL bearer
bearer = Part.makeBox(LENGTH, WIDTH, HEIGHT, Vector(0, 0, 0))

# Rotate the bearer around the X-axis to match the 5-degree roof slope.
# This tilts the cross-section so it sits flush within the roof panel core.
bearer.rotate(Vector(0, 0, 0), Vector(1, 0, 0), SLOPE_ANGLE)

# === PART CODE ===
PART_CODE = "F-1"
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

bearer = _emboss_part_code(bearer, PART_CODE)

# === FINAL OUTPUT ===
obj = doc.addObject("Part::Feature", "Span_Splice_Bearer")
obj.Shape = bearer

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 (v6)
●
Received response (1627 tokens)
v0.0.985