Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 74 additions & 62 deletions examples/custom-normals-shade/custom_normals_shade.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,22 +237,26 @@ def build_shell(name):
"""Side band lofted along Y through wear-ring stations, capped by the
two pressed faces. The face/band edge is a true 90-deg break."""
bm = bmesh.new()
wl = bm.verts.layers.float.new(WEAR)
prof = rrect(0.0)
n = len(prof)
rings = []
for y, wear in ((-D / 2, 1.0), (-D / 2 + EDGE_BAND, 0.0), (0.0, 0.0),
(D / 2 - EDGE_BAND, 0.0), (D / 2, 1.0)):
ring = [bm.verts.new((x, y, z)) for x, z in prof]
for v in ring:
v[wl] = wear
rings.append(ring)
for ra, rb in zip(rings, rings[1:]):
for i in range(n):
j = (i + 1) % n
bm.faces.new((ra[i], ra[j], rb[j], rb[i]))
pressed_face(bm, wl, rings[0], -D / 2, -1.0)
pressed_face(bm, wl, rings[-1], D / 2, 1.0)
try:
wl = bm.verts.layers.float.new(WEAR)
prof = rrect(0.0)
n = len(prof)
rings = []
for y, wear in ((-D / 2, 1.0), (-D / 2 + EDGE_BAND, 0.0), (0.0, 0.0),
(D / 2 - EDGE_BAND, 0.0), (D / 2, 1.0)):
ring = [bm.verts.new((x, y, z)) for x, z in prof]
for v in ring:
v[wl] = wear
rings.append(ring)
for ra, rb in zip(rings, rings[1:]):
for i in range(n):
j = (i + 1) % n
bm.faces.new((ra[i], ra[j], rb[j], rb[i]))
pressed_face(bm, wl, rings[0], -D / 2, -1.0)
pressed_face(bm, wl, rings[-1], D / 2, 1.0)
except BaseException:
bm.free() # finish_mesh() frees it on success
raise
return finish_mesh(name, bm)


Expand All @@ -264,27 +268,31 @@ def sweep(name, path, section, axis, closed=False, wear=0.0, matrix=None):
pts = [Vector(p) for p in path]
n = len(pts)
bm = bmesh.new()
wl = bm.verts.layers.float.new(WEAR)
rings = []
for i, p in enumerate(pts):
if closed:
t = (pts[(i + 1) % n] - pts[i - 1])
else:
t = pts[min(i + 1, n - 1)] - pts[max(i - 1, 0)]
nrm = axis.cross(t.normalized()).normalized() # outward for CCW paths
ring = [bm.verts.new(p + nrm * u + axis * v) for u, v in section]
for v in ring:
v[wl] = wear
rings.append(ring)
k = len(section)
for i in range(n if closed else n - 1):
ra, rb = rings[i], rings[(i + 1) % n]
for j in range(k):
jj = (j + 1) % k
bm.faces.new((ra[j], ra[jj], rb[jj], rb[j]))
if not closed:
bm.faces.new(rings[0])
bm.faces.new(rings[-1][::-1])
try:
wl = bm.verts.layers.float.new(WEAR)
rings = []
for i, p in enumerate(pts):
if closed:
t = (pts[(i + 1) % n] - pts[i - 1])
else:
t = pts[min(i + 1, n - 1)] - pts[max(i - 1, 0)]
nrm = axis.cross(t.normalized()).normalized() # outward for CCW paths
ring = [bm.verts.new(p + nrm * u + axis * v) for u, v in section]
for v in ring:
v[wl] = wear
rings.append(ring)
k = len(section)
for i in range(n if closed else n - 1):
ra, rb = rings[i], rings[(i + 1) % n]
for j in range(k):
jj = (j + 1) % k
bm.faces.new((ra[j], ra[jj], rb[jj], rb[j]))
if not closed:
bm.faces.new(rings[0])
bm.faces.new(rings[-1][::-1])
except BaseException:
bm.free() # finish_mesh() frees it on success
raise
return finish_mesh(name, bm, matrix)


Expand All @@ -298,31 +306,35 @@ def lathe(name, profile, segments=16, closed=False, wear=None, matrix=None):
profile makes a ring. `wear` is an optional per-profile-point list."""
wear = list(wear) if wear else [0.0] * len(profile)
bm = bmesh.new()
wl = bm.verts.layers.float.new(WEAR)
bot = top = None
if not closed and profile[0][0] == 0.0:
bot = bm.verts.new((0.0, 0.0, profile[0][1]))
profile, wear = profile[1:], wear[1:]
if not closed and profile[-1][0] == 0.0:
top = bm.verts.new((0.0, 0.0, profile[-1][1]))
profile, wear = profile[:-1], wear[:-1]
rings = []
for i in range(segments):
a = 2.0 * math.pi * i / segments
ring = [bm.verts.new((r * math.cos(a), r * math.sin(a), z)) for r, z in profile]
for v, w in zip(ring, wear):
v[wl] = w
rings.append(ring)
m = len(profile)
for i in range(segments):
j = (i + 1) % segments
for k in range(m if closed else m - 1):
kk = (k + 1) % m
bm.faces.new((rings[i][k], rings[j][k], rings[j][kk], rings[i][kk]))
if bot is not None:
bm.faces.new((rings[j][0], rings[i][0], bot))
if top is not None:
bm.faces.new((rings[i][-1], rings[j][-1], top))
try:
wl = bm.verts.layers.float.new(WEAR)
bot = top = None
if not closed and profile[0][0] == 0.0:
bot = bm.verts.new((0.0, 0.0, profile[0][1]))
profile, wear = profile[1:], wear[1:]
if not closed and profile[-1][0] == 0.0:
top = bm.verts.new((0.0, 0.0, profile[-1][1]))
profile, wear = profile[:-1], wear[:-1]
rings = []
for i in range(segments):
a = 2.0 * math.pi * i / segments
ring = [bm.verts.new((r * math.cos(a), r * math.sin(a), z)) for r, z in profile]
for v, w in zip(ring, wear):
v[wl] = w
rings.append(ring)
m = len(profile)
for i in range(segments):
j = (i + 1) % segments
for k in range(m if closed else m - 1):
kk = (k + 1) % m
bm.faces.new((rings[i][k], rings[j][k], rings[j][kk], rings[i][kk]))
if bot is not None:
bm.faces.new((rings[j][0], rings[i][0], bot))
if top is not None:
bm.faces.new((rings[i][-1], rings[j][-1], top))
except BaseException:
bm.free() # finish_mesh() frees it on success
raise
return finish_mesh(name, bm, matrix)


Expand Down
29 changes: 29 additions & 0 deletions rules/always-free-bmesh.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,35 @@ class MyOperator(bpy.types.Operator):
self.bm = bmesh.new() # leaks on operator unregister
```

## Ownership transfer is allowed, if exactly one owner frees

A function whose job is to build a BMesh may return it, and a builder may
hand it to a finisher, as long as every path has exactly one owner that frees
it:

```python
# OK: a factory returns a new BMesh; the CALLER owns it and frees in finally
cb = cone_bm(tone)
try:
merge_bm(scene_bm, cb, matrix)
finally:
cb.free()

# OK: build, then hand off to a finisher that frees in its own finally;
# free here only if the build itself fails
bm = bmesh.new()
try:
build_geometry(bm)
except BaseException:
bm.free()
raise
return finish_mesh(name, bm) # finish_mesh(): try: bm.to_mesh(me) finally: bm.free()
```

What stays wrong is a path with **no** owner: a caller that frees a returned
BMesh outside `finally`, a build step that can raise before the hand-off,
or an object attribute (`self.bm`) that nothing frees.

## The required pattern

```python
Expand Down
40 changes: 24 additions & 16 deletions showcase/traffic-cones/traffic_cones.py
Original file line number Diff line number Diff line change
Expand Up @@ -822,25 +822,31 @@ def build_cones(bm, flags):
for i, (x, y, yaw) in enumerate(STANDING):
cb = cone_bm(CONE_TONES[k], lean_deg=LEAN_DEG if (flags["lean_cone"] and i == 1) else 0.0,
float_band=FLOAT_BAND if (flags["float_band"] and i == 0) else 0.0)
lift = FLOAT_CONE if (flags["float_cone"] and i == 2) else 0.0
M = Matrix.Translation((x, y, SLAB_T - SEAT + lift)) @ Matrix.Rotation(
math.radians(yaw), 4, "Z")
merge_bm(bm, cb, M)
cb.free()
try:
lift = FLOAT_CONE if (flags["float_cone"] and i == 2) else 0.0
M = Matrix.Translation((x, y, SLAB_T - SEAT + lift)) @ Matrix.Rotation(
math.radians(yaw), 4, "Z")
merge_bm(bm, cb, M)
finally:
cb.free()
k += 1
for j, yaw in enumerate(STACK_YAW):
cb = cone_bm(CONE_TONES[k])
lift = LOOSE_STACK if (flags["loose_stack"] and j == len(STACK_YAW) - 1) else 0.0
M = Matrix.Translation((STACK_XY[0], STACK_XY[1],
SLAB_T - SEAT + j * STACK_PITCH + lift)) @ Matrix.Rotation(
math.radians(yaw), 4, "Z")
merge_bm(bm, cb, M)
cb.free()
try:
lift = LOOSE_STACK if (flags["loose_stack"] and j == len(STACK_YAW) - 1) else 0.0
M = Matrix.Translation((STACK_XY[0], STACK_XY[1],
SLAB_T - SEAT + j * STACK_PITCH + lift)) @ Matrix.Rotation(
math.radians(yaw), 4, "Z")
merge_bm(bm, cb, M)
finally:
cb.free()
k += 1
cb = cone_bm(CONE_TONES[k])
M, _phi = tipped_matrix(cb)
merge_bm(bm, cb, M)
cb.free()
try:
M, _phi = tipped_matrix(cb)
merge_bm(bm, cb, M)
finally:
cb.free()


# --------------------------------------------------------------------------
Expand Down Expand Up @@ -999,8 +1005,10 @@ def keep(verts, tone):
M = Matrix((X, d, n_out)).transposed().to_4x4()
M.translation = C
bb = board_bm(n_corner, 0.2 + 0.2 * bi + (0.4 if back else 0.0))
merge_bm(bm, bb, M)
bb.free()
try:
merge_bm(bm, bb, M)
finally:
bb.free()
ux = LEG_X + (LEG_SPLIT if back else -LEG_SPLIT)
for su in (-1.0, 1.0):
for sv_ in (-1.0, 1.0):
Expand Down
Loading