Repository navigation
Expand file tree
/
Copy pathmodeling_command_bridge.py
More file actions
228 lines (191 loc) · 11.5 KB
/
Copy pathmodeling_command_bridge.py
File metadata and controls
228 lines (191 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#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()