Iterate Through Shapes in FreeCAD with Python

This guide explains how to programmatically access and iterate through all geometric shapes within an open FreeCAD document using Python. By accessing the document's object tree and filtering for elements that contain shape representations, you can inspect geometries, extract sub-elements such as edges or faces, and automate CAD analysis directly from FreeCAD's built-in Python console or external macro scripts.

Accessing Document Objects

In FreeCAD, 3D geometric entities are typically stored as features containing a Shape property (most commonly derived from Part::Feature). To access these items, you first reference the active document and loop through its Objects list.

Because a document can contain non-geometric objects—such as groups, spreadsheets, or technical drawings—you should check whether each object possesses a Shape attribute before attempting to interact with its geometry.

import FreeCAD

# Reference the active document
doc = FreeCAD.ActiveDocument

if doc is None:
    print("No active document found.")
else:
    # Iterate through all objects in the document
    for obj in doc.Objects:
        if hasattr(obj, "Shape") and not obj.Shape.isNull():
            print(f"Object: {obj.Label} | Shape Type: {obj.Shape.ShapeType}")

Filtering with isDerivedFrom

Alternatively, you can filter objects using the isDerivedFrom method. This approach ensures you are specifically targeting elements derived from the Part::Feature class, which standardizes geometry handling across the Part and PartDesign workbenches.

import FreeCAD

doc = FreeCAD.ActiveDocument

for obj in doc.Objects:
    if obj.isDerivedFrom("Part::Feature"):
        shape = obj.Shape
        print(f"Found Part Feature: {obj.Name} (Label: {obj.Label})")

Iterating Over Sub-Shapes (Faces, Edges, Vertices)

Once you have isolated an object's Shape (a TopoShape instance), you can access its topological sub-components using built-in list properties such as Faces, Edges, Vertexes, Wires, and Solids.

import FreeCAD

doc = FreeCAD.ActiveDocument

for obj in doc.Objects:
    if hasattr(obj, "Shape") and not obj.Shape.isNull():
        shape = obj.Shape
        
        print(f"\nProcessing {obj.Label}:")
        print(f" - Total Solids: {len(shape.Solids)}")
        print(f" - Total Faces: {len(shape.Faces)}")
        print(f" - Total Edges: {len(shape.Edges)}")
        print(f" - Total Vertices: {len(shape.Vertexes)}")
        
        # Example: Loop over all edges to find total length
        total_length = sum(edge.Length for edge in shape.Edges)
        print(f" - Total Edge Length: {total_length:.2f} mm")

Complete Macro Example

To run a complete script that gathers all shapes and exports basic boundary box metrics to the FreeCAD report view:

import FreeCAD

def analyze_document_shapes():
    doc = FreeCAD.ActiveDocument
    if not doc:
        FreeCAD.Console.PrintError("Error: No document currently open.\n")
        return

    valid_shapes = []

    for obj in doc.Objects:
        if hasattr(obj, "Shape") and not obj.Shape.isNull():
            valid_shapes.append(obj)
            bbox = obj.Shape.BoundBox
            FreeCAD.Console.PrintMessage(
                f"Shape '{obj.Label}' Dimensions: "
                f"X={bbox.XLength:.2f}, Y={bbox.YLength:.2f}, Z={bbox.ZLength:.2f}\n"
            )

    FreeCAD.Console.PrintMessage(f"Total valid shapes found: {len(valid_shapes)}\n")

analyze_document_shapes()