Contents in this wiki are for entertainment purposes only
This is not fiction ∞ this is psience of mind

Talk:Claude Code on Torus Knot Winding Form (FreeCAD)

From Catcliffe Development
Revision as of 08:21, 23 January 2026 by XenoEngineer (talk | contribs) (→‎In Development)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search


FreeCAD proves unable to create large boolean-cuts of long, continuous Torus Knot Windings... development has changed to OpenSCAD and custom Go.

In Development

(Python runs inside FreeCAD to provide coding against the FreeCAD object model exposed to the Python environment.)
 
Local: C:\Users\XenoEngineer\AppData\Roaming\FreeCAD\Macro

XenoEngineer —research and development 18:42, 16 January 2026 (UTC)

"""
Torus Knot Groove Generator for FreeCAD - v1.6
torusKnotGrooves.FCMacro
C:/Users/XenoEngineer/AppData/Roaming/FreeCAD/Macro

This FreeCAD macro generates a parametric torus knot with parallel grooves based on the golden ratio.
The user can specify the knot parameters (P, Q, phases), scale, and groove dimensions.
The macro provides options to perform boolean cut operations and hide the base torus.

Changelog:
  v1.6 - Handle Compound result from boolean cut by extracting solids
  v1.5 - Fuse-then-cut boolean strategy, added shape validation diagnostics
  v1.4 - Fixed periodic BSpline: removed duplicate point, removed T_OFFSET
  v1.3 - Attempted periodic fix (caused OCC error)
  v1.2 - Extracted default parameters to module-level constants
  v1.1 - Initial working version

Author: Claude (Anthropic AI) <-- on Codexa Omega v.1
Refactored: Claude (Anthropic AI) <-- on Codexa Omega v.2
"""

import FreeCAD as App
import FreeCADGui as Gui
import Part
import math
from FreeCAD import Vector
from PySide import QtCore, QtGui

# =============================================================================
# MATHEMATICAL CONSTANTS
# =============================================================================
PHI = (1 + math.sqrt(5)) / 2          # Golden ratio
PHI_4 = PHI ** 4                       # Fourth power of phi
PHI_4_MINUS_1 = PHI_4 - 1              # Minor radius ratio factor

# =============================================================================
# DEFAULT KNOT PARAMETERS
# =============================================================================
DEFAULT_P = 3                          # Toroidal windings
DEFAULT_Q = 2                          # Poloidal windings
DEFAULT_PHASES = 1                     # Number of parallel grooves

# Parameter ranges
P_MIN, P_MAX = 1, 50
Q_MIN, Q_MAX = 1, 50
PHASES_MIN, PHASES_MAX = 1, 6

# =============================================================================
# DEFAULT SCALE PARAMETERS (mm)
# =============================================================================
DEFAULT_MINOR_RADIUS = 200.0           # r - tube radius
MINOR_RADIUS_MIN = 10.0
MINOR_RADIUS_MAX = 500.0
MINOR_RADIUS_DECIMALS = 3

# =============================================================================
# DEFAULT GROOVE PARAMETERS
# =============================================================================
DEFAULT_GROOVE_DIAMETER = 3.175        # 1/8 inch in mm (standard bit size)
GROOVE_DIAMETER_MIN = 0.5
GROOVE_DIAMETER_MAX = 20.0
GROOVE_DIAMETER_DECIMALS = 3

DEFAULT_PATH_STEPS = 400               # Curve resolution
PATH_STEPS_MIN = 100
PATH_STEPS_MAX = 3000
PATH_STEPS_STEP = 50                   # Spinbox increment

# =============================================================================
# DEFAULT OUTPUT OPTIONS
# =============================================================================
DEFAULT_DO_BOOLEAN = False             # Boolean cut is slow
DEFAULT_HIDE_BASE = True               # Hide torus after cut

# =============================================================================
# DISPLAY CONSTANTS
# =============================================================================
PHASE_COLORS = [
    (1, 0, 0),    # Red
    (0, 1, 0),    # Green
    (0, 0, 1),    # Blue
    (1, 1, 0),    # Yellow
    (1, 0, 1),    # Magenta
    (0, 1, 1),    # Cyan
]
DEFAULT_LINE_WIDTH = 2.0
DEFAULT_GROOVE_TRANSPARENCY = 30
DEFAULT_TORUS_TRANSPARENCY = 70


class TorusKnotDialog(QtGui.QDialog):
    def __init__(self, parent=None):
        super(TorusKnotDialog, self).__init__(parent)
        self.setWindowTitle("3-Phase Golden Quartic Torus Knot")
        self.setMinimumWidth(380)
        self.init_ui()

    def init_ui(self):
        layout = QtGui.QVBoxLayout(self)

        info = QtGui.QLabel(
            "Golden Quartic: R = phi^4, r = phi^4 - 1\n"
            "Hole:Major ratio = 1 : 6.8541"
        )
        info.setStyleSheet("color: #555; font-style: italic;")
        layout.addWidget(info)

        knot_group = QtGui.QGroupBox("Knot Parameters")
        knot_layout = QtGui.QFormLayout(knot_group)

        self.spin_p = QtGui.QSpinBox()
        self.spin_p.setRange(P_MIN, P_MAX)
        self.spin_p.setValue(DEFAULT_P)
        knot_layout.addRow("P (toroidal):", self.spin_p)

        self.spin_q = QtGui.QSpinBox()
        self.spin_q.setRange(Q_MIN, Q_MAX)
        self.spin_q.setValue(DEFAULT_Q)
        knot_layout.addRow("Q (poloidal):", self.spin_q)

        self.spin_phases = QtGui.QSpinBox()
        self.spin_phases.setRange(PHASES_MIN, PHASES_MAX)
        self.spin_phases.setValue(DEFAULT_PHASES)
        knot_layout.addRow("Phases:", self.spin_phases)

        layout.addWidget(knot_group)

        scale_group = QtGui.QGroupBox("Scale (Golden Quartic Locked)")
        scale_layout = QtGui.QFormLayout(scale_group)

        self.spin_minor = QtGui.QDoubleSpinBox()
        self.spin_minor.setRange(MINOR_RADIUS_MIN, MINOR_RADIUS_MAX)
        self.spin_minor.setValue(DEFAULT_MINOR_RADIUS)
        self.spin_minor.setDecimals(MINOR_RADIUS_DECIMALS)
        self.spin_minor.setSuffix(" mm")
        self.spin_minor.valueChanged.connect(self.update_calculated_values)
        scale_layout.addRow("Minor Radius (r):", self.spin_minor)

        self.label_major = QtGui.QLabel()
        scale_layout.addRow("Major Radius (R):", self.label_major)

        self.label_hole = QtGui.QLabel()
        scale_layout.addRow("Hole Radius:", self.label_hole)

        self.label_outer = QtGui.QLabel()
        scale_layout.addRow("Outer Diameter:", self.label_outer)

        layout.addWidget(scale_group)

        groove_group = QtGui.QGroupBox("Groove Parameters")
        groove_layout = QtGui.QFormLayout(groove_group)

        self.spin_groove = QtGui.QDoubleSpinBox()
        self.spin_groove.setRange(GROOVE_DIAMETER_MIN, GROOVE_DIAMETER_MAX)
        self.spin_groove.setValue(DEFAULT_GROOVE_DIAMETER)
        self.spin_groove.setDecimals(GROOVE_DIAMETER_DECIMALS)
        self.spin_groove.setSuffix(" mm")
        groove_layout.addRow("Groove Diameter:", self.spin_groove)

        self.spin_steps = QtGui.QSpinBox()
        self.spin_steps.setRange(PATH_STEPS_MIN, PATH_STEPS_MAX)
        self.spin_steps.setValue(DEFAULT_PATH_STEPS)
        self.spin_steps.setSingleStep(PATH_STEPS_STEP)
        groove_layout.addRow("Path Steps:", self.spin_steps)

        layout.addWidget(groove_group)

        output_group = QtGui.QGroupBox("Output Options")
        output_layout = QtGui.QVBoxLayout(output_group)

        self.check_boolean = QtGui.QCheckBox("Perform Boolean Cut (slow)")
        self.check_boolean.setChecked(DEFAULT_DO_BOOLEAN)
        output_layout.addWidget(self.check_boolean)

        self.check_hide_base = QtGui.QCheckBox("Hide base torus after cut")
        self.check_hide_base.setChecked(DEFAULT_HIDE_BASE)
        output_layout.addWidget(self.check_hide_base)

        layout.addWidget(output_group)

        button_box = QtGui.QDialogButtonBox(
            QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel
        )
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        layout.addWidget(button_box)

        self.update_calculated_values()

    def update_calculated_values(self):
        r = self.spin_minor.value()
        unit = r / PHI_4_MINUS_1
        R = PHI_4 * unit
        hole_radius = unit
        outer_diameter = 2 * (R + r)
        self.label_major.setText(f"{R:.3f} mm")
        self.label_hole.setText(f"{hole_radius:.3f} mm")
        self.label_outer.setText(f"{outer_diameter:.3f} mm")

    def get_parameters(self):
        r = self.spin_minor.value()
        unit = r / PHI_4_MINUS_1
        R = PHI_4 * unit
        return {
            'P': self.spin_p.value(),
            'Q': self.spin_q.value(),
            'phases': self.spin_phases.value(),
            'major_radius': R,
            'minor_radius': r,
            'groove_diameter': self.spin_groove.value(),
            'steps': self.spin_steps.value(),
            'do_boolean': self.check_boolean.isChecked(),
            'hide_base': self.check_hide_base.isChecked()
        }


def setup_document():
    """Create a new document or clear the existing one."""
    if App.ActiveDocument is None:
        doc = App.newDocument("GoldenTorusKnot")
    else:
        doc = App.ActiveDocument
        for obj in doc.Objects:
            doc.removeObject(obj.Name)
    doc.recompute()
    Gui.updateGui()
    return doc


def generate_knot_path(P, Q, phase_offset, major_radius, minor_radius, steps):
    """
    Generate a CLOSED knot path for a given phase.
    
    Uses PeriodicFlag=True to create a seamless closed curve.
    No duplicate endpoint is added — the periodic flag handles wrap-around.
    """
    knot_points = []

    for i in range(steps):
        # No T_OFFSET needed for periodic curves
        t = (2 * math.pi * i) / steps
        poloidal_angle = Q * t + phase_offset

        radius = major_radius + minor_radius * math.cos(poloidal_angle)
        x = radius * math.cos(P * t)
        y = radius * math.sin(P * t)
        z = minor_radius * math.sin(poloidal_angle)

        knot_points.append(Vector(x, y, z))

    # PeriodicFlag=True makes the curve seamlessly closed
    # Do NOT append duplicate point — that causes OCC error
    knot_curve = Part.BSplineCurve()
    knot_curve.interpolate(knot_points, PeriodicFlag=True)

    knot_edge = knot_curve.toShape()
    knot_wire = Part.Wire([knot_edge])
    return knot_wire


def sweep_groove(knot_wire, groove_diameter):
    """Sweep a circular profile along a knot path to create a groove solid."""
    start_point = knot_wire.Vertexes[0].Point
    first_edge = knot_wire.Edges[0]
    tangent = first_edge.tangentAt(first_edge.FirstParameter)

    profile_circle = Part.makeCircle(groove_diameter / 2, start_point, tangent)
    profile_wire = Part.Wire([profile_circle])

    solid = sweep_with_fallbacks(profile_wire, knot_wire)
    return solid


def perform_boolean_cuts(base_torus_shape, groove_solids, base_torus, hide_base):
    """
    Perform boolean cut: fuse all grooves first, then cut once.
    
    Handles Compound results by extracting valid solids.
    
    Args:
        base_torus_shape: The Part.Shape of the torus
        groove_solids: List of groove solid shapes to subtract
        base_torus: The document object (for visibility control)
        hide_base: Whether to hide the base torus after cutting
    """
    try:
        App.Console.PrintMessage("Boolean operation starting...\n")
        
        # Validate inputs
        App.Console.PrintMessage(f"  Base torus valid: {base_torus_shape.isValid()}\n")
        App.Console.PrintMessage(f"  Base torus type: {base_torus_shape.ShapeType}\n")
        App.Console.PrintMessage(f"  Base torus volume: {base_torus_shape.Volume:.2f}\n")
        
        for i, groove in enumerate(groove_solids):
            App.Console.PrintMessage(f"  Groove {i} valid: {groove.isValid()}, type: {groove.ShapeType}\n")
        
        # Fuse all groove solids into one shape
        App.Console.PrintMessage("Fusing groove solids...\n")
        if len(groove_solids) == 1:
            combined_grooves = groove_solids[0]
        else:
            combined_grooves = groove_solids[0]
            for i, groove in enumerate(groove_solids[1:], start=1):
                App.Console.PrintMessage(f"  Fusing groove {i}...\n")
                combined_grooves = combined_grooves.fuse(groove)
        
        App.Console.PrintMessage(f"Combined grooves type: {combined_grooves.ShapeType}\n")
        App.Console.PrintMessage(f"Combined grooves valid: {combined_grooves.isValid()}\n")
        
        # Single cut operation
        App.Console.PrintMessage("Performing cut...\n")
        result_shape = base_torus_shape.cut(combined_grooves)
        
        App.Console.PrintMessage(f"Cut result type: {result_shape.ShapeType}\n")
        App.Console.PrintMessage(f"Cut result valid: {result_shape.isValid()}\n")
        
        # Handle Compound result - extract solids
        if result_shape.ShapeType == "Compound":
            App.Console.PrintMessage("Result is Compound - extracting solids...\n")
            solids = result_shape.Solids
            App.Console.PrintMessage(f"  Found {len(solids)} solid(s) in compound\n")
            
            if len(solids) == 0:
                # No solids - check for shells
                shells = result_shape.Shells
                App.Console.PrintMessage(f"  Found {len(shells)} shell(s) in compound\n")
                if len(shells) > 0:
                    # Try to make solid from shells
                    App.Console.PrintMessage("  Attempting to create solid from shells...\n")
                    try:
                        result_shape = Part.makeSolid(shells[0])
                        App.Console.PrintMessage(f"  Created solid from shell: {result_shape.ShapeType}\n")
                    except Exception as e:
                        App.Console.PrintError(f"  Could not create solid from shell: {e}\n")
                        return None
                else:
                    App.Console.PrintError("No solids or shells in compound!\n")
                    return None
            elif len(solids) == 1:
                result_shape = solids[0]
                App.Console.PrintMessage(f"  Using single solid, volume: {result_shape.Volume:.2f}\n")
            else:
                # Multiple solids - report all and select largest
                App.Console.PrintMessage("  Multiple solids found:\n")
                for i, s in enumerate(solids):
                    vol = s.Volume if hasattr(s, 'Volume') else 0
                    App.Console.PrintMessage(f"    Solid {i}: volume = {vol:.2f}\n")
                
                # Filter out degenerate solids (near-zero volume)
                valid_solids = [s for s in solids if s.Volume > 1.0]
                App.Console.PrintMessage(f"  Valid solids (volume > 1): {len(valid_solids)}\n")
                
                if len(valid_solids) == 0:
                    App.Console.PrintError("All solids have negligible volume!\n")
                    return None
                elif len(valid_solids) == 1:
                    result_shape = valid_solids[0]
                else:
                    # Fuse all valid solids back together
                    App.Console.PrintMessage("  Fusing valid solids together...\n")
                    result_shape = valid_solids[0]
                    for s in valid_solids[1:]:
                        result_shape = result_shape.fuse(s)
                    
                    # Try to make it a proper solid
                    if result_shape.ShapeType != "Solid":
                        try:
                            result_shape = Part.makeSolid(result_shape)
                        except:
                            pass  # Keep as-is if makeSolid fails
                
                App.Console.PrintMessage(f"  Final result type: {result_shape.ShapeType}\n")
        
        # Final validation
        if hasattr(result_shape, 'Volume'):
            App.Console.PrintMessage(f"Final result volume: {result_shape.Volume:.2f}\n")
        
        if result_shape.isNull():
            App.Console.PrintError("Final result is null!\n")
            return None
            
        if hide_base:
            base_torus.ViewObject.Visibility = False

        return result_shape
        
    except Exception as e:
        App.Console.PrintError(f"Boolean cut failed: {e}\n")
        import traceback
        App.Console.PrintError(traceback.format_exc())
        return None


def setup_phase_objects(doc, phase_index, knot_wire, groove_solid, do_boolean):
    """Set up display properties for phase-specific objects."""
    phase_name = chr(ord('A') + phase_index)

    knot_obj = doc.addObject("Part::Feature", f"KnotPath_{phase_name}")
    knot_obj.Shape = knot_wire
    knot_obj.ViewObject.LineColor = PHASE_COLORS[phase_index % len(PHASE_COLORS)]
    knot_obj.ViewObject.LineWidth = DEFAULT_LINE_WIDTH

    groove_obj = doc.addObject("Part::Feature", f"GrooveSolid_{phase_name}")
    groove_obj.Shape = groove_solid

    if not do_boolean:
        groove_obj.ViewObject.Visibility = True
        groove_obj.ViewObject.ShapeColor = PHASE_COLORS[phase_index % len(PHASE_COLORS)]
        groove_obj.ViewObject.Transparency = DEFAULT_GROOVE_TRANSPARENCY
    else:
        groove_obj.ViewObject.Visibility = False


def generate_torus_knot(params):
    """
    Generate a torus with parallel knot grooves.

    Args:
        params (dict): Dictionary of input parameters.
    """
    doc = setup_document()
    base_torus_shape, base_torus = create_base_torus(doc, params)
    groove_solids = []

    for phase_index in range(params['phases']):
        knot_wire, groove_solid = generate_phase_geometry(doc, phase_index, params)
        if knot_wire is None or groove_solid is None:
            continue
        setup_phase_objects(doc, phase_index, knot_wire, groove_solid, params['do_boolean'])
        groove_solids.append(groove_solid)

    if params['do_boolean'] and groove_solids:
        # Pass base_torus object so we can hide it after cut
        result_shape = perform_boolean_cuts(
            base_torus_shape, groove_solids, base_torus, params['hide_base']
        )
        if result_shape and not result_shape.isNull():
            final = doc.addObject("Part::Feature", "TorusWithGrooves")
            final.Shape = result_shape
            App.Console.PrintMessage("TorusWithGrooves object created.\n")
        else:
            App.Console.PrintWarning("Boolean cut did not produce valid geometry.\n")
    else:
        App.Console.PrintMessage("Preview mode - boolean skipped\n")

    doc.recompute()
    Gui.updateGui()

    App.Console.PrintMessage("=" * 50 + "\n")
    App.Console.PrintMessage("Done!\n")
    App.Console.PrintMessage("=" * 50 + "\n")


def create_base_torus(doc, params):
    """Create the base torus shape."""
    R = params['major_radius']
    r = params['minor_radius']
    base_torus_shape = Part.makeTorus(R, r)

    base_torus = doc.addObject("Part::Feature", "BaseTorus")
    base_torus.Shape = base_torus_shape

    if not params['do_boolean']:
        base_torus.ViewObject.Transparency = DEFAULT_TORUS_TRANSPARENCY

    return base_torus_shape, base_torus


def generate_phase_geometry(doc, phase_index, params):
    """Generate the knot wire and groove solid for a single phase."""
    phase_offset = phase_index * 2 * math.pi / params['phases']
    knot_wire = generate_knot_path(
        params['P'], params['Q'], phase_offset,
        params['major_radius'], params['minor_radius'], params['steps']
    )
    groove_solid = sweep_groove(knot_wire, params['groove_diameter'])
    if groove_solid is None:
        App.Console.PrintError(f"Failed to create groove solid for phase {phase_index}\n")
        return None, None
    return knot_wire, groove_solid


def sweep_with_fallbacks(profile_wire, path_wire):
    """Attempt multiple sweep methods with graceful fallback."""
    methods = [
        ("makePipeShell Frenet+Solid",
         lambda: path_wire.makePipeShell([profile_wire], True, True)),
        ("makePipeShell Frenet+Shell->Solid",
         lambda: Part.makeSolid(path_wire.makePipeShell([profile_wire], False, True))),
        ("makePipe",
         lambda: path_wire.makePipe(profile_wire)),
        ("makePipeShell NonFrenet",
         lambda: path_wire.makePipeShell([profile_wire], True, False)),
    ]

    for name, method in methods:
        try:
            App.Console.PrintMessage(f"    Trying {name}...\n")
            shape = method()
            if shape is None:
                continue
            if shape.ShapeType == "Solid":
                App.Console.PrintMessage(f"    Success: {name}\n")
                return shape
            if shape.ShapeType in ("Shell", "Compound"):
                solid = Part.makeSolid(shape)
                if solid.ShapeType == "Solid":
                    App.Console.PrintMessage(f"    Success: {name} + makeSolid\n")
                    return solid
        except Exception as e:
            App.Console.PrintMessage(f"    {name} failed: {e}\n")

    return None


def run():
    """Entry point: show dialog and generate geometry."""
    dialog = TorusKnotDialog(Gui.getMainWindow())
    if dialog.exec_() == QtGui.QDialog.Accepted:
        params = dialog.get_parameters()
        generate_torus_knot(params)
    else:
        App.Console.PrintMessage("Cancelled.\n")


if __name__ == "__main__":
    run()