modeling_command_bridge

Demonstrates how to execute the ‘Bridge’ tool.

modeling_command_bridge.py

#coding: utf-8
"""Demonstrates how to execute the 'Bridge' tool.

Invokes 'Bridge' in two different ways: first with distinct elements, then with selection islands.
Best used with the example scene of the same name in this folder.

Note:
    This example assumes the user is already familiar with modeling commands in general, and explores
    a more complex command in detail: the Bridge tool.

Topics:
    * modeling
"""
__author__ = "Ferdinand Hoppe"
__copyright__ = "Copyright (C) 2026 MAXON Computer GmbH"
__date__ = "23/04/2026"
__license__ = "Apache-2.0 License"
__version__ = "2026.2"

import c4d
import mxutils 

doc: c4d.documents.BaseDocument # The active document.
op: c4d.BaseObject | None # The active object, or None if no object is active.

def CopyObjects(items: list[c4d.BaseObject]) -> tuple[c4d.BaseObject, ...]:
    """Copies the given objects, renames them, moves them aside, and inserts them into the document.

    This is a helper function not directly related to the topic of this example.
    """
    copies: list[c4d.BaseObject] = []
    for item in items:
        copy: c4d.BaseObject = mxutils.CheckType(item.GetClone())
        copy.SetName(item.GetName() + "_Copy")
        radius: c4d.Vector = copy.GetRad()
        copy.SetMg(copy.GetMg() * c4d.utils.MatrixMove(c4d.Vector(0, radius.y * 3, 0)))
        doc.InsertObject(copy)
        doc.AddUndo(c4d.UNDOTYPE_NEW, copy)
        copies.append(copy)
    return tuple(copies)

def main() -> None:
    """Runs the example.
    """
    # Get the selected objects. We need exactly two polygon objects for this example.
    selObjects: list[c4d.BaseObject] = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE)
    if len(selObjects) != 2 or not all(isinstance(obj, c4d.PolygonObject) for obj in selObjects):
        return c4d.gui.MessageDialog("Please select exactly two polygon objects.")
    
    objA: c4d.PolygonObject = selObjects[0]
    objB: c4d.PolygonObject = selObjects[1]

    # Get the polygon and point selections of the two objects and convert them into indices. 
    # Selections are stored as a list of Booleans in Cinema 4D. E.g., an object with 4 points and 
    # the first and last point being selected would have a selection of [True, False, False, True]. 
    # Often we have to convert such data into the indices of selected elements, e.g., [0, 3] in the 
    # previous example. We do this with a list comprehension that enumerates the selection and 
    # collects the indices of selected elements.
    pointSelectionA: c4d.BaseSelect = objA.GetPointS()
    pointSelectionB: c4d.BaseSelect = objB.GetPointS()
    polySelectionA: c4d.BaseSelect = objA.GetPolygonS()
    polySelectionB: c4d.BaseSelect = objB.GetPolygonS()

    # Convert the BaseSelect into indices of selected elements.
    selPointIndicesA: list[int] = [
        i for i, s in enumerate(pointSelectionA.GetAll(objA.GetPointCount())) if s]
    selPointIndicesB: list[int] = [
        i for i, s in enumerate(pointSelectionB.GetAll(objB.GetPointCount())) if s]
    selPolyIndicesA: list[int] = [
        i for i, s in enumerate(polySelectionA.GetAll(objA.GetPolygonCount())) if s]
    selPolyIndicesB: list[int] = [
        i for i, s in enumerate(polySelectionB.GetAll(objB.GetPolygonCount())) if s]
    
    if not len(selPointIndicesA) == len(selPointIndicesB) == 1:
        return c4d.gui.MessageDialog("Please select exactly one point in each object.")
    if len(selPolyIndicesA) < 1 or len(selPolyIndicesB) < 1:
        return c4d.gui.MessageDialog("Please select at least one polygon in each object.")
    
    selPointIndexA: int = selPointIndicesA[0]
    selPointIndexB: int = selPointIndicesB[0]

    # When we bridge an object, we have many settings as defined in toolbridge.h, and we have the 
    # usual decision of operating in point, edge, or polygon mode for a tool. But the Bridge tool 
    # also has another distinction in how it operates. We can bridge distinct 
    # elements in one or many objects, or we can bridge selection islands. Let's split this into two examples.

    # --- SIMPLE CASE: BRIDGING DISTINCT ELEMENTS --------------------------------------------------

    # This case is relatively simple. We have distinct elements in one or many input objects:
    #
    #   - Point Mode: 4 points in 1-4 objects
    #   - Edge Mode: 2 edges in 1-2 objects
    #   - Polygon Mode: 2 polygons in 1-2 objects

    # We draw two copies of the objects we want to bridge, so that we can run two examples. Since
    # we already add objects (modify the document), we start our undo step here.
    doc.StartUndo()
    objACopy, objBCopy = CopyObjects([objA, objB])

    # Now we set up our data. First, we must describe the objects we want to bridge. If we had 
    # more objects (in point mode), we would also have to set MDATA_BRIDGE_OBJINDEX3 and 
    # MDATA_BRIDGE_OBJINDEX4. When we have fewer objects than the mode supports, we just set the same 
    # objects multiple times.
    settings: c4d.BaseContainer = c4d.BaseContainer()
    settings[c4d.MDATA_BRIDGE_OBJINDEX1] = objACopy
    settings[c4d.MDATA_BRIDGE_OBJINDEX2] = objBCopy

    # Now we set the indices of the elements we want to bridge. The meaning is derived from
    # the MODELINGCOMMANDMODE we specify when executing the command. We will use polygon mode, so
    # the indices we specify will be interpreted as polygon indices in relation to the objects we 
    # specified above. We just pick the first selected polygon of each object; there is no guarantee
    # that this will result in a meaningful bridge. Figuring this out programmatically (which 
    # polygons 'line up' with each other) is not trivial and realistically requires some user input
    # for meaningful results.
    settings[c4d.MDATA_BRIDGE_ELEMENT1] = selPolyIndicesA[0]
    settings[c4d.MDATA_BRIDGE_ELEMENT2] = selPolyIndicesB[0]

    # Now we carry out the command. Since we will modify the objects in the document, we also add
    # undo steps for them. We could also use the flag MODELINGCOMMANDFLAGS_CREATEUNDO, but it can
    # be a bit unreliable, so I prefer to add the undo steps myself.
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, objACopy)
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, objBCopy)
    if not c4d.utils.SendModelingCommand(command=c4d.ID_MODELING_BRIDGE_TOOL,
                                         list=[objACopy, objBCopy],
                                         mode=c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
                                         bc=settings,
                                         doc=doc):
        doc.EndUndo()
        doc.DoUndo()
        return c4d.gui.MessageDialog("Bridge command failed.")

    # --- ADVANCED CASE: BRIDGING ISLANDS ----------------------------------------------------------

    # In this case, we do not bridge distinct elements, but instead bridge whole selection islands. This
    # only works in edge and polygon mode. This largely works the same but has some differences:
    #
    #   - Since this is only possible in edge and polygon mode, we can have at most two input objects.
    #   - This always requires at least one selection island in each input object (and for a single 
    #     object two selections) that matches the operation mode we are in (in our case a polygon 
    #     selection).
    #   - There is a special syntax as how to specify the element indices.

    # We again define the object(s) we want to bridge.
    settings: c4d.BaseContainer = c4d.BaseContainer()
    settings[c4d.MDATA_BRIDGE_OBJINDEX1] = objA
    settings[c4d.MDATA_BRIDGE_OBJINDEX2] = objB

    # Now we have to specify one polygon in the active selection of each object that belongs to 
    # the island we want to bridge. The tool will then 'grow' out from these polygons to find the 
    # whole island, and then bridge the two islands. We again just pick the first element of our
    # selections, but we will overwrite this here in second case below to make a better choice.
    settings[c4d.MDATA_BRIDGE_ELEMENT1] = selPolyIndicesA[0]
    settings[c4d.MDATA_BRIDGE_ELEMENT2] = selPolyIndicesB[0]

    # But even though we only have two input objects, we here also have to specify elements
    # 3 and 4. We have to fill them with the starting and end point of the bridge input (what the 
    # user would pick in the editor). These points must lie within the edges or polygons we specified 
    # for MDATA_BRIDGE_ELEMENT1 and MDATA_BRIDGE_ELEMENT2. 
    # 
    # Here I show two ways to do this: randomly pick a point derived from the polygon indices (not 
    # recommended), or use a user-provided point selection to guide us (already prepared above).

    # Option 1: Just pick a random point from the more or less randomly selected polygons from the 
    # islands (we just picked the first polygon). This is very likely to yield undesirable results,
    # as the points and polygons we pick 'line up' with each other.
    polyA: c4d.CPolygon = objA.GetPolygon(selPolyIndicesA[0])
    polyB: c4d.CPolygon = objB.GetPolygon(selPolyIndicesB[0])
    p: int = polyA.a
    q: int = polyB.a

    # Option 2: Instead, we use a user provided point selection and derive from that the polygons that
    # must be the starting and end polygons.

    # Get all polygons of both objects.
    polygonsA: list[c4d.CPolygon] = objA.GetAllPolygons()
    polygonsB: list[c4d.CPolygon] = objB.GetAllPolygons()
    
    # Find the polygon of #A in its active polygon selection that contains the selected point in #A.
    startPolygonIndexA: int | None = next(
        # For each index #i and polygon #poly in #objA (next() will return the first match)
        (i for i, poly in enumerate(objA.GetAllPolygons())
         # Include #i if it is in the selected polygon indices and if the selected point is part of 
         # that polygon #poly.
         if i in selPolyIndicesA and selPointIndexA in (poly.a, poly.b, poly.c, poly.d)),
        # Otherwise, returns None.
        None)
    
    # Repeat for object #B and its selected point and polygon selection.
    endPolygonIndexB: int | None = next(
        (i for i, poly in enumerate(objB.GetAllPolygons()) 
         if i in selPolyIndicesB and selPointIndexB in (poly.a, poly.b, poly.c, poly.d)), 
         None)
    
    if startPolygonIndexA is None or endPolygonIndexB is None:
        doc.EndUndo()
        doc.DoUndo()
        return c4d.gui.MessageDialog("Please select points that lie within the selected polygons.")

    # Now we assign the polygon and point indices to the settings.
    settings[c4d.MDATA_BRIDGE_ELEMENT1] = startPolygonIndexA
    settings[c4d.MDATA_BRIDGE_ELEMENT2] = endPolygonIndexB
    settings[c4d.MDATA_BRIDGE_ELEMENT3] = selPointIndexA
    settings[c4d.MDATA_BRIDGE_ELEMENT4] = selPointIndexB

    # We must also set these two fields to enable island growing behavior.
    settings.SetBool(c4d.MDATA_BRIDGE_FIRST_SELECT_ONLY, True)
    settings.SetBool(c4d.MDATA_BRIDGE_SECOND_SELECT_ONLY, True)

    # Now we carry out the command.
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, objA)
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, objB)
    if not c4d.utils.SendModelingCommand(command=c4d.ID_MODELING_BRIDGE_TOOL,
                                         list=[objA, objB],
                                         mode=c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
                                         bc=settings,
                                         doc=doc):
        doc.EndUndo()
        doc.DoUndo()
        return c4d.gui.MessageDialog("Bridge command failed.")
    
    # Finally, we end our undo step and inform Cinema 4D that the document has been modified.
    doc.EndUndo()
    c4d.EventAdd()


# Run it
if __name__ == '__main__':
    main()