Repository: FreeCAD/FreeCAD
File: src/Mod/BIM/Arch.py (Python)

Code before:

    # If all checks pass, the wall is debasable.
    return True


def debaseWall(wall):
    """
    Converts a line-based Arch Wall to be parametrically driven by its own
    properties (Length, Width, Height) and Placement, removing its dependency
    on a Base object.

    This operation preserves the wall's exact size and global position.
    It is only supported for walls based on a single, straight line.

    Returns True on success, False otherwise.
    """
    import FreeCAD

    if not is_debasable(wall):
        FreeCAD.Console.PrintWarning(f"Wall '{wall.Label}' is not eligible for debasing.\n")
        return False

    doc = wall.Document

    is_in_transaction = doc.HasPendingTransaction
    if hasattr(wall, "Proxy") and getattr(wall.Proxy, "InTransaction", False):
        is_in_transaction = True

    start_transaction = not is_in_transaction

    if start_transaction:
        doc.openTransaction(f"Debase Wall: {wall.Label}")

    try:
        # Calculate the final state before making any changes
        # A wall's final position and orientation are derived from its Base object and its own
        # properties like Width and Align. To make the wall independent, this final state must be
        # captured. A simple transfer of the Base object's Placement property is unreliable for two
        # main reasons:
        # - Ambiguity: A Draft.Line's direction is defined by its vertex coordinates, so a line at
        #   45° can have a 0° rotation in its Placement.
        # - Coordinate Systems: A universal method is needed to handle both Draft objects (defined
        #   in global coordinates) and Sketch objects (defined in a local coordinate system).
        #
        # The solution is to use the wall.Proxy.basewires internal attribute. It is non-persistent
        # and populated on the fly by calling getExtrusionData(). It contains the baseline edge
        # already transformed into the document's global coordinate system. From the vertex
        # positions of this globally-aware edge, the new wall's final Placement is calculated.
        extrusion_data = wall.Proxy.getExtrusionData(wall)
        if not extrusion_data or not hasattr(wall.Proxy, "basewires") or not wall.Proxy.basewires:
            raise Exception("Could not retrieve extrusion data to calculate global placement.")

Review comment (suggestion):
Can you explain why the `InTransaction` attribute is required? You mention this in the PR description, but without going into much detail. If you edit a wall and change its additions (for example) the action is undoable without this temporary attribute.

Code after:

    # If all checks pass, the wall is debasable.
    return True


def debaseWall(wall):
    """
    Converts a line-based Arch Wall to be parametrically driven by its own
    properties (Length, Width, Height) and Placement, removing its dependency
    on a Base object.

    This operation preserves the wall's exact size and global position.
    It is only supported for walls based on a single, straight line.

    Parameters
    ----------
    wall : FreeCAD.DocumentObject
        The Arch Wall object to debase.

    Returns
    -------
    bool
        True on success, False otherwise.
    """
    import FreeCAD

    if not is_debasable(wall):
        FreeCAD.Console.PrintWarning(f"Wall '{wall.Label}' is not eligible for debasing.\n")
        return False

    doc = wall.Document

    try:
        # --- Calculation of the final placement ---
        base_obj = wall.Base
        base_edge = base_obj.Shape.Edges[0]

        # Step 1: Get global coordinates of the baseline's endpoints.
        # For Draft objects, Vertex coordinates are already in the global system. For Sketches,
        # they are local, but ArchWall's internal logic transforms them. The most reliable
        # way to get the final global baseline is to use the vertices of the base object's
        # final shape, which are always in global coordinates for these object types.
        p1_global = base_edge.Vertexes[0].Point
        p2_global = base_edge.Vertexes[1].Point

        # Step 2: Determine the extrusion normal vector.
        normal = wall.Normal
        if normal.Length == 0:
            normal = base_obj.Placement.Rotation.multVec(FreeCAD.Vector(0, 0, 1))
            if normal.Length == 0:
                normal = FreeCAD.Vector(0, 0, 1)