From 6ffc27d86f243108338c1985e06a9f206de7b509 Mon Sep 17 00:00:00 2001 From: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Date: Sun, 4 Oct 2026 21:36:24 -0400 Subject: [PATCH] fix(examples): make the examples follow the rules they teach, and lint it Agents copy these scripts (bmesh-gear is the documented anatomy), but nothing checked them against the repo's own rules, and they broke them: 53 per-face `use_smooth` loops (three in bmesh-gear), 74 per-vertex `v.co.z += ...` loops, more per-element material/co writes, and seven `active_object` reads with no None guard. - 127 single-statement loops became foreach_set / foreach_get by a scripted AST rewrite (inline comments kept); seven more hand-converted (constant material indices, uniform co scale/offset, the moka-pot lean). - Eight loops with genuinely per-element logic (ring/station material bands, n-gon marking, ball-surface tests) carry a `# foreach-exempt: ` marker. - Seven `X = bpy.context.active_object` reads get `if X is None: raise`. - tests/check_example_rules.py (Validate) rejects an unmarked per-element write in a loop over .polygons/.vertices (Mesh-only names, so bmesh code is never hit) and an unguarded active_object read. Old and new versions of six scripts, run on 5.2.1 across ten paths including the touched falsifiers, print identical output with identical exit codes. Closes #400 Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 --- .github/workflows/validate.yml | 3 + docs/new-example-prompt.md | 6 + examples/armature-bend/armature_bend.py | 3 +- .../bake_normal_high_to_low.py | 3 +- examples/bmesh-gear/bmesh_gear.py | 9 +- .../boolean_exact_volume.py | 6 +- .../car_mirror_symmetry.py | 1 + .../collision_hull_proxy.py | 12 +- .../color_attribute_wheel.py | 3 +- examples/compositor-glare/compositor_glare.py | 5 +- .../custom_normals_shade.py | 6 +- examples/depsgraph-export/depsgraph_export.py | 3 +- examples/driver-wave/driver_wave.py | 9 +- .../gltf_export_roundtrip.py | 3 +- examples/gn-sdf-remesh/gn_sdf_remesh.py | 15 ++- examples/gn-socket-rename/gn_socket_rename.py | 3 +- .../gp-lineart-contour/gp_lineart_contour.py | 3 +- .../grease_pencil_rosette.py | 7 +- examples/lattice-deform/lattice_deform.py | 9 +- .../light-link-studio/light_link_studio.py | 6 +- .../lightmap_uv_channel.py | 1 + .../mesh-hygiene-audit/mesh_hygiene_audit.py | 7 +- .../prop_origin_transform.py | 13 ++- examples/ray-cast-space/ray_cast_space.py | 9 +- .../shader-node-group/shader_node_group.py | 6 +- .../sky_texture_sun_elevation.py | 3 +- .../soccer_ball_goldberg.py | 7 +- .../socket_attach_points.py | 3 +- .../solidify_even_thickness.py | 3 +- examples/swatch-grid/swatch_grid.py | 6 +- .../triangulate_tangents.py | 1 + examples/turntable/turntable.py | 5 +- .../usd_export_evaluation_mode.py | 3 +- showcase/anvil/anvil.py | 6 +- showcase/apothecary-shelf/apothecary_shelf.py | 6 +- showcase/archery-target/archery_target.py | 6 +- showcase/bamboo-clump/bamboo_clump.py | 6 +- showcase/basketball-hoop/basketball_hoop.py | 6 +- showcase/book-trolley/book_trolley.py | 6 +- showcase/bookshelf/bookshelf.py | 6 +- showcase/boulder-cluster/boulder_cluster.py | 6 +- showcase/bowling-pins/bowling_pins.py | 7 +- showcase/brazier/brazier.py | 6 +- showcase/broadleaf-oak/broadleaf_oak.py | 6 +- showcase/butter-churn/butter_churn.py | 6 +- showcase/cactus-garden/cactus_garden.py | 6 +- showcase/campfire/campfire.py | 9 +- showcase/cargo-loader/cargo_loader.py | 6 +- showcase/cart/cart.py | 6 +- showcase/crate-stack/crate_stack.py | 6 +- showcase/cricket-wicket/cricket_wicket.py | 6 +- showcase/desk-lamp/desk_lamp.py | 6 +- showcase/espresso-machine/espresso_machine.py | 6 +- showcase/fallen-log/fallen_log.py | 6 +- showcase/farm-tractor/farm_tractor.py | 6 +- showcase/fence-kit/fence_kit.py | 12 +- showcase/fern-mossy-rock/fern_mossy_rock.py | 6 +- showcase/floor-fan/floor_fan.py | 6 +- showcase/garden-gate/garden_gate.py | 6 +- showcase/go-kart/go_kart.py | 6 +- showcase/grain-sacks/grain_sacks.py | 6 +- showcase/grindstone/grindstone.py | 6 +- showcase/hand-pump/hand_pump.py | 9 +- showcase/hay-bale/hay_bale.py | 6 +- showcase/hitching-post/hitching_post.py | 9 +- showcase/hover-bike/hover_bike.py | 6 +- showcase/iron-cauldron/iron_cauldron.py | 6 +- showcase/market-stall/market_stall.py | 6 +- showcase/mine-cart/mine_cart.py | 6 +- showcase/moka-pot/moka_pot.py | 12 +- showcase/motor-scooter/motor_scooter.py | 6 +- showcase/mushroom-stump/mushroom_stump.py | 6 +- showcase/office-chair/office_chair.py | 6 +- showcase/palm-tree/palm_tree.py | 6 +- showcase/park-bench/park_bench.py | 9 +- showcase/pine-tree/pine_tree.py | 6 +- showcase/planet-rover/planet_rover.py | 6 +- showcase/pond-edge/pond_edge.py | 6 +- showcase/quad-drone/quad_drone.py | 6 +- showcase/road-bicycle/road_bicycle.py | 6 +- showcase/rope-bridge/rope_bridge.py | 6 +- showcase/sawhorse-plank/sawhorse_plank.py | 6 +- showcase/sea-stack-arch/sea_stack_arch.py | 6 +- showcase/shipping-crate/shipping_crate.py | 6 +- showcase/ships-wheel/ships_wheel.py | 6 +- showcase/signpost/signpost.py | 9 +- showcase/skate-ramp/skate_ramp.py | 6 +- showcase/soccer-goal/soccer_goal.py | 6 +- showcase/stand-mixer/stand_mixer.py | 6 +- showcase/stone-archway/stone_archway.py | 6 +- showcase/stone-well/stone_well.py | 6 +- showcase/street-lantern/street_lantern.py | 9 +- showcase/sundial/sundial.py | 6 +- showcase/tavern-stool/tavern_stool.py | 6 +- showcase/terrain-scatter/terrain_scatter.py | 6 +- showcase/toboggan/toboggan.py | 6 +- showcase/traffic-cones/traffic_cones.py | 6 +- showcase/treasure-chest/treasure_chest.py | 6 +- showcase/wall-torch/wall_torch.py | 11 +- showcase/watchtower/watchtower.py | 9 +- showcase/water-trough/water_trough.py | 9 +- showcase/weight-rack/weight_rack.py | 6 +- showcase/wheelbarrow/wheelbarrow.py | 9 +- .../wingback-armchair/wingback_armchair.py | 6 +- showcase/wooden-barrel/wooden_barrel.py | 6 +- showcase/wooden-bucket/wooden_bucket.py | 6 +- showcase/wooden-yoke/wooden_yoke.py | 6 +- tests/check_example_rules.py | 106 ++++++++++++++++++ 108 files changed, 503 insertions(+), 274 deletions(-) create mode 100644 tests/check_example_rules.py diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 01817e33..00f082a6 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -79,6 +79,9 @@ jobs: - name: Showcase helper drift (warn-only report) run: python3 tests/check_helper_drift.py + - name: Examples and showcase follow the repo's own rules + run: python3 tests/check_example_rules.py + - name: Check asset_sheet.py can render every showcase piece and reference run: python3 tests/check_asset_sheet_select.py diff --git a/docs/new-example-prompt.md b/docs/new-example-prompt.md index 3b2af9d6..bf993ce3 100644 --- a/docs/new-example-prompt.md +++ b/docs/new-example-prompt.md @@ -136,6 +136,12 @@ intentional, important geometry is not clipped, highlights are not blown out, lights or helper objects are not visible unintentionally, and materials, lighting, background, and camera feel designed rather than left at Blender defaults. +The example must follow the repo's own rules, not just teach them: +`tests/check_example_rules.py` (run by Validate) rejects a per-element write +in a loop over `mesh.polygons` / `mesh.vertices` (use `foreach_set`) and an +`active_object` read with no `None` guard. A loop that really needs per-element +logic carries `# foreach-exempt: ` on or just above its `for` line. + Contact-sheet gate (required before shipping the still): build a composite placing the new hero beside the pinned calibration set—canonical membership is in `CLAUDE.md` § Quality Gates for Example Runs—and compare stage darkness, wedge diff --git a/examples/armature-bend/armature_bend.py b/examples/armature-bend/armature_bend.py index 0b047652..80acf81a 100644 --- a/examples/armature-bend/armature_bend.py +++ b/examples/armature-bend/armature_bend.py @@ -120,8 +120,7 @@ def build_tube(): bm.to_mesh(me) finally: bm.free() - for poly in me.polygons: - poly.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) # collars keep crisp machined steps; the rubber ribs stay smooth me.set_sharp_from_angle(angle=math.radians(50.0)) obj = bpy.data.objects.new("Tube", me) diff --git a/examples/bake-normal-high-to-low/bake_normal_high_to_low.py b/examples/bake-normal-high-to-low/bake_normal_high_to_low.py index e727c5c1..a03aff51 100644 --- a/examples/bake-normal-high-to-low/bake_normal_high_to_low.py +++ b/examples/bake-normal-high-to-low/bake_normal_high_to_low.py @@ -140,8 +140,7 @@ def decimate_apply(obj, target_tris): object=obj, active_object=obj, selected_objects=[obj] ): bpy.ops.object.modifier_apply(modifier=mod.name) - for poly in obj.data.polygons: - poly.use_smooth = True + obj.data.polygons.foreach_set("use_smooth", [True] * len(obj.data.polygons)) obj.data.update() return current, evaluated_triangle_count(obj) diff --git a/examples/bmesh-gear/bmesh_gear.py b/examples/bmesh-gear/bmesh_gear.py index 226eeda4..df586607 100644 --- a/examples/bmesh-gear/bmesh_gear.py +++ b/examples/bmesh-gear/bmesh_gear.py @@ -290,8 +290,7 @@ def companion_pitch_r(teeth): def render_still(obj, path, engine): scene = bpy.context.scene - for poly in obj.data.polygons: - poly.use_smooth = False # crisp machined facets + obj.data.polygons.foreach_set("use_smooth", [False] * len(obj.data.polygons)) # crisp machined facets brass = machined_brass() obj.data.materials.append(brass) blued = principled("BluedSteel", (0.07, 0.11, 0.22), 0.26, metal=1.0, noise=3.0) @@ -329,8 +328,7 @@ def partner(name, teeth, direction, mat, depth, bore=None): cy = HERO_C[1] + dist * math.sin(direction) me = ring_gear_mesh(name, teeth, pr - ADDENDUM, pr + ADDENDUM, depth, bore=bore) me.materials.append(mat) - for p in me.polygons: - p.use_smooth = False + me.polygons.foreach_set("use_smooth", [False] * len(me.polygons)) ob = bpy.data.objects.new(name, me) scene.collection.objects.link(ob) # a gap centred on the line of centres, facing the checked gear's tooth @@ -368,8 +366,7 @@ def web(bm): def add(name, build, mat, cx, cy, smooth=False, bevel=0.01): me = solid_mesh(name, build) me.materials.append(mat) - for p in me.polygons: - p.use_smooth = smooth + me.polygons.foreach_set("use_smooth", [smooth] * len(me.polygons)) ob = bpy.data.objects.new(name, me) scene.collection.objects.link(ob) place(ob, cx, cy) diff --git a/examples/boolean-exact-volume/boolean_exact_volume.py b/examples/boolean-exact-volume/boolean_exact_volume.py index b93bcc56..36943aaf 100644 --- a/examples/boolean-exact-volume/boolean_exact_volume.py +++ b/examples/boolean-exact-volume/boolean_exact_volume.py @@ -288,8 +288,7 @@ def tube_mesh(name, segments, radius, sides=10): bm.to_mesh(me) finally: bm.free() - for p in me.polygons: - p.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) return me @@ -357,8 +356,7 @@ def add(name, me, mat, mw=None): # the boolean output carries the operands' (empty) material slots; # start clean so every face takes the finish below shown.materials.clear() - for poly in shown.polygons: - poly.material_index = 0 + shown.polygons.foreach_set("material_index", [0] * len(shown.polygons)) ob = add(f"{op.title()}Shown", shown, finishes[op], res.matrix_world.copy()) bev = ob.modifiers.new("MachinedChamfer", 'BEVEL') bev.width = 0.012 diff --git a/examples/car-mirror-symmetry/car_mirror_symmetry.py b/examples/car-mirror-symmetry/car_mirror_symmetry.py index 61fb44b8..44720458 100644 --- a/examples/car-mirror-symmetry/car_mirror_symmetry.py +++ b/examples/car-mirror-symmetry/car_mirror_symmetry.py @@ -396,6 +396,7 @@ def _assign_body_materials(body): are glass exactly where the cabin factor ramps (windshield, hatch).""" PAINT, GLASS, TRIM, CHROME = (MATERIALS.index(m) for m in ("Paint", "Glass", "Trim", "Chrome")) + # foreach-exempt: per-face material from the face's ring/station index for poly in body.data.polygons: i, k = divmod(poly.index, RING - 1) if i >= N_ST - 1: # cap ngons (front/rear fascia) diff --git a/examples/collision-hull-proxy/collision_hull_proxy.py b/examples/collision-hull-proxy/collision_hull_proxy.py index 33065207..5cad6503 100644 --- a/examples/collision-hull-proxy/collision_hull_proxy.py +++ b/examples/collision-hull-proxy/collision_hull_proxy.py @@ -223,6 +223,7 @@ def build_hydrant(): body = lathe_object("Body", BODY_PROFILE, SEGMENTS) body.data.materials.append(red) body.data.materials.append(yellow) + # foreach-exempt: conditional per-face material by face height for p in body.data.polygons: # the bonnet dome is the yellow profile tail if p.center.z > 1.09: p.material_index = 1 @@ -384,8 +385,7 @@ def render_still(groups, pieces, path, engine): scene = bpy.context.scene for g in groups: for o in g["render"]: - for p in o.data.polygons: - p.use_smooth = True + o.data.polygons.foreach_set("use_smooth", [True] * len(o.data.polygons)) for g in groups: # cages exist only to build hulls; never rendered for o in g["cage"]: o.hide_render = True @@ -415,8 +415,7 @@ def render_still(groups, pieces, path, engine): for name, hull, _ in pieces: hull.data.materials.append(shell) - for p in hull.data.polygons: - p.use_smooth = False # facets must read as facets + hull.data.polygons.foreach_set("use_smooth", [False] * len(hull.data.polygons)) # facets must read as facets hull.data.materials.append(wire_mat) wire = bpy.data.objects.new(f"{name}Wire", hull.data) mod = wire.modifiers.new("Wire", 'WIREFRAME') @@ -521,8 +520,9 @@ def main(): for g in groups: hull = build_hull(f"{g['name']}Hull", collect_points(g["cage"])) if args.shrink_hull: - for v in hull.data.vertices: - v.co *= 0.5 + co = [0.0] * (len(hull.data.vertices) * 3) + hull.data.vertices.foreach_get("co", co) + hull.data.vertices.foreach_set("co", [c * 0.5 for c in co]) hull.data.update() pieces.append((g["name"], hull, collect_points(g["render"]))) code = check(pieces) diff --git a/examples/color-attribute-wheel/color_attribute_wheel.py b/examples/color-attribute-wheel/color_attribute_wheel.py index fd9f0f73..b079bbce 100644 --- a/examples/color-attribute-wheel/color_attribute_wheel.py +++ b/examples/color-attribute-wheel/color_attribute_wheel.py @@ -259,8 +259,7 @@ def _mat(name, base, rough, metal=0.0, noise=None, coat=0.0): def render_still(obj, path, engine): scene = bpy.context.scene me = obj.data - for poly in me.polygons: - poly.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) mat = build_material() if mat is None: diff --git a/examples/compositor-glare/compositor_glare.py b/examples/compositor-glare/compositor_glare.py index e0f23014..324c0839 100644 --- a/examples/compositor-glare/compositor_glare.py +++ b/examples/compositor-glare/compositor_glare.py @@ -127,10 +127,11 @@ def build_scene(): major_segments=64, minor_segments=16, location=(x, y, RING_Z), rotation=(math.radians(90), 0.0, 0.0)) ring = bpy.context.active_object + if ring is None: + raise RuntimeError("the add operator left no active object") ring.name = name ring.data.materials.append(make_emissive(f"Neon{name[4:]}", color, strength)) - for poly in ring.data.polygons: - poly.use_smooth = True + ring.data.polygons.foreach_set("use_smooth", [True] * len(ring.data.polygons)) world = bpy.data.worlds.new("World") world.use_nodes = True diff --git a/examples/custom-normals-shade/custom_normals_shade.py b/examples/custom-normals-shade/custom_normals_shade.py index e1580bde..b24d24fb 100644 --- a/examples/custom-normals-shade/custom_normals_shade.py +++ b/examples/custom-normals-shade/custom_normals_shade.py @@ -478,8 +478,7 @@ def check_by_angle(objs, mismatch_angle=False): total_sharp = total_manifold = 0 for obj in objs: me = obj.data - for p in me.polygons: - p.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) me.set_sharp_from_angle(angle=mark) dih, nonmanifold = manifold_dihedrals(me) if nonmanifold: @@ -751,8 +750,7 @@ def shade(parts, mode): """The one variable across the three cans.""" for o in parts: me = o.data - for p in me.polygons: - p.use_smooth = mode != "flat" + me.polygons.foreach_set("use_smooth", [mode != 'flat'] * len(me.polygons)) if mode == "byangle": me.set_sharp_from_angle(angle=ANGLE) diff --git a/examples/depsgraph-export/depsgraph_export.py b/examples/depsgraph-export/depsgraph_export.py index 81631de6..553829c1 100644 --- a/examples/depsgraph-export/depsgraph_export.py +++ b/examples/depsgraph-export/depsgraph_export.py @@ -474,8 +474,7 @@ def render_still(shell, path, engine): cage_me.name = "Cage.Shell" cage_me.materials.clear() cage_me.materials.append(ghost("Cage.Ghost", (0.02, 0.12, 0.55, 1.0), 0.30)) - for p in cage_me.polygons: - p.material_index = 0 + cage_me.polygons.foreach_set("material_index", [0] * len(cage_me.polygons)) cage_me.shade_flat() cage_shell = bpy.data.objects.new("Cage.Shell", cage_me) wire = bpy.data.objects.new("Cage.Wire", shell.data.copy()) diff --git a/examples/driver-wave/driver_wave.py b/examples/driver-wave/driver_wave.py index 4ec35acf..9ee6acec 100644 --- a/examples/driver-wave/driver_wave.py +++ b/examples/driver-wave/driver_wave.py @@ -83,8 +83,7 @@ def build_columns(flat_expr=False): bm.to_mesh(me) finally: bm.free() - for poly in me.polygons: - poly.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) me.set_sharp_from_angle(angle=math.radians(50.0)) objs = [] @@ -238,8 +237,7 @@ def box(name, dims, loc, mat, bevel=0.02): bm.to_mesh(fin_me) finally: bm.free() - for poly in fin_me.polygons: - poly.use_smooth = True + fin_me.polygons.foreach_set("use_smooth", [True] * len(fin_me.polygons)) fin_me.materials.append(brass) fin = bpy.data.objects.new(fin_me.name, fin_me) fin.location = (tx, 0.18, 1.06 + 3.40) @@ -258,8 +256,7 @@ def box(name, dims, loc, mat, bevel=0.02): bm.to_mesh(foot_me) finally: bm.free() - for poly in foot_me.polygons: - poly.use_smooth = True + foot_me.polygons.foreach_set("use_smooth", [True] * len(foot_me.polygons)) foot_me.materials.append(brass) dg = bpy.context.evaluated_depsgraph_get() for obj in objs: diff --git a/examples/gltf-export-roundtrip/gltf_export_roundtrip.py b/examples/gltf-export-roundtrip/gltf_export_roundtrip.py index 2298bac0..8c73ac8b 100644 --- a/examples/gltf-export-roundtrip/gltf_export_roundtrip.py +++ b/examples/gltf-export-roundtrip/gltf_export_roundtrip.py @@ -148,8 +148,7 @@ def build_crate(): bm.to_mesh(me) finally: bm.free() # the ownership contract, as always - for poly in me.polygons: - poly.use_smooth = False # crisp low-poly facets; normals are unambiguous + me.polygons.foreach_set("use_smooth", [False] * len(me.polygons)) # crisp low-poly facets; normals are unambiguous uv = me.uv_layers.new(name="UVMap") for poly in me.polygons: for li in poly.loop_indices: diff --git a/examples/gn-sdf-remesh/gn_sdf_remesh.py b/examples/gn-sdf-remesh/gn_sdf_remesh.py index 348eab25..b1abb347 100644 --- a/examples/gn-sdf-remesh/gn_sdf_remesh.py +++ b/examples/gn-sdf-remesh/gn_sdf_remesh.py @@ -51,8 +51,9 @@ def build(): bpy.ops.wm.read_factory_settings(use_empty=True) bpy.ops.mesh.primitive_torus_add(location=(0, 0, 0.55), major_radius=1.2, minor_radius=0.5) obj = bpy.context.active_object - for p in obj.data.polygons: - p.use_smooth = True + if obj is None: + raise RuntimeError("the add operator left no active object") + obj.data.polygons.foreach_set("use_smooth", [True] * len(obj.data.polygons)) mat = bpy.data.materials.new("Ceramic"); mat.use_nodes = True b = mat.node_tree.nodes.get('Principled BSDF') b.inputs['Base Color'].default_value = (0.45, 0.025, 0.05, 1) # crimson ceramic @@ -82,9 +83,10 @@ def build_vase_kit(name): def add(op, mat_i, **kw): op(**kw) ob = bpy.context.active_object + if ob is None: + raise RuntimeError("the add operator left no active object") ob.data.materials.append(mats[mat_i]) - for poly in ob.data.polygons: - poly.use_smooth = True + ob.data.polygons.foreach_set("use_smooth", [True] * len(ob.data.polygons)) parts.append(ob) return ob @@ -145,8 +147,7 @@ def render_still(obj, path, engine): cm.inputs['Roughness'].default_value = 0.6 cage = bpy.data.objects.new("VaseKitCage", kit.data.copy()) cage.data.materials.clear(); cage.data.materials.append(cage_mat) - for poly in cage.data.polygons: - poly.material_index = 0 + cage.data.polygons.foreach_set("material_index", [0] * len(cage.data.polygons)) cage.location = kit.location wire = cage.modifiers.new("cage", 'WIREFRAME') wire.thickness = 0.005; wire.offset = 1.0; wire.use_even_offset = True @@ -172,6 +173,8 @@ def render_still(obj, path, engine): bpy.ops.mesh.primitive_cylinder_add(vertices=64, radius=0.95, depth=0.12, location=(x, 0, 0.06)) bat = bpy.context.active_object + if bat is None: + raise RuntimeError("the add operator left no active object") bat.data.materials.append(wood) bev = bat.modifiers.new("bev", 'BEVEL'); bev.width = 0.025; bev.segments = 3 bats.append(bat) diff --git a/examples/gn-socket-rename/gn_socket_rename.py b/examples/gn-socket-rename/gn_socket_rename.py index d2adf501..f1adde22 100644 --- a/examples/gn-socket-rename/gn_socket_rename.py +++ b/examples/gn-socket-rename/gn_socket_rename.py @@ -467,8 +467,7 @@ def part(name, build, mat, parent=obj): bm.free() me.materials.append(mat) me.set_sharp_from_angle(angle=math.radians(40)) - for p in me.polygons: - p.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) ob = bpy.data.objects.new(name, me) scene.collection.objects.link(ob) ob.parent = parent diff --git a/examples/gp-lineart-contour/gp_lineart_contour.py b/examples/gp-lineart-contour/gp_lineart_contour.py index 2466783b..e0d89f31 100644 --- a/examples/gp-lineart-contour/gp_lineart_contour.py +++ b/examples/gp-lineart-contour/gp_lineart_contour.py @@ -380,8 +380,7 @@ def build_lighthouse(sc): bm.to_mesh(me) finally: bm.free() - for p in me.polygons: - p.use_smooth = False + me.polygons.foreach_set("use_smooth", [False] * len(me.polygons)) for name, col in (("Lighthouse.Red", (0.72, 0.07, 0.05, 1.0)), ("Lighthouse.White", (0.93, 0.90, 0.82, 1.0)), ("Lighthouse.Iron", (0.10, 0.12, 0.15, 1.0)), diff --git a/examples/grease-pencil-rosette/grease_pencil_rosette.py b/examples/grease-pencil-rosette/grease_pencil_rosette.py index a64a72f0..9455a9af 100644 --- a/examples/grease-pencil-rosette/grease_pencil_rosette.py +++ b/examples/grease-pencil-rosette/grease_pencil_rosette.py @@ -217,8 +217,7 @@ def _box(name, dims, loc, mat, bevel=0.0, segments=3): bm.to_mesh(me) finally: bm.free() - for poly in me.polygons: - poly.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) me.materials.append(mat) ob = bpy.data.objects.new(name, me) ob.location = loc @@ -292,6 +291,8 @@ def render_still(obj, path, engine): SIGN_Z + sz * (BH / 2 - 0.25)), rotation=(math.radians(90), 0, 0)) cap = bpy.context.active_object + if cap is None: + raise RuntimeError("the add operator left no active object") cap.data.materials.append(brass) caps.append(cap) # a brass plate under the tubes, a mains cable dropping to the floor @@ -300,6 +301,8 @@ def render_still(obj, path, engine): bpy.ops.mesh.primitive_cylinder_add(vertices=12, radius=0.018, depth=SIGN_Z - BH / 2 - fw, location=(1.35, 0.28, (SIGN_Z - BH / 2 - fw) / 2)) cable = bpy.context.active_object + if cable is None: + raise RuntimeError("the add operator left no active object") cable.data.materials.append(cable_mat) sign = [board, plate, cable] + frame + caps diff --git a/examples/lattice-deform/lattice_deform.py b/examples/lattice-deform/lattice_deform.py index dc5880db..930fa8bf 100644 --- a/examples/lattice-deform/lattice_deform.py +++ b/examples/lattice-deform/lattice_deform.py @@ -236,8 +236,7 @@ def tube_mesh(name, segments, radius, sides=10): bm.to_mesh(me) finally: bm.free() - for p in me.polygons: - p.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) return me @@ -252,8 +251,7 @@ def sphere_mesh(name, centers, radius): bm.to_mesh(me) finally: bm.free() - for p in me.polygons: - p.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) return me @@ -291,8 +289,7 @@ def render_still(obj, lat_obj, path, engine): for i in (0, 1) for j in (0, 1) for k in (0, 1)} obj.data.materials.append(checker_ceramic()) - for p in obj.data.polygons: - p.use_smooth = False + obj.data.polygons.foreach_set("use_smooth", [False] * len(obj.data.polygons)) steel = principled("CageSteel", (0.58, 0.60, 0.64), 0.22, metal=1.0) ghost = principled("RestCage", (0.16, 0.17, 0.19), 0.6, metal=0.3) diff --git a/examples/light-link-studio/light_link_studio.py b/examples/light-link-studio/light_link_studio.py index 9e9ab5ca..3b44a3b7 100644 --- a/examples/light-link-studio/light_link_studio.py +++ b/examples/light-link-studio/light_link_studio.py @@ -58,8 +58,7 @@ def sphere(name, rgb, x, coll): bm.to_mesh(me) finally: bm.free() - for p in me.polygons: - p.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) mat = bpy.data.materials.new(name + "M") mat.use_nodes = True b = mat.node_tree.nodes["Principled BSDF"] @@ -103,8 +102,7 @@ def _cyl(sc, name, r1, r2, depth, loc, mat, bevel=0.0): finally: bm.free() me.update() - for p in me.polygons: # smooth the wall, keep the caps flat - p.use_smooth = abs(p.normal.z) < 0.5 + me.polygons.foreach_set("use_smooth", [abs(p.normal.z) < 0.5 for p in me.polygons]) # smooth the wall, keep the caps flat me.materials.append(mat) ob = bpy.data.objects.new(name, me) ob.location = loc diff --git a/examples/lightmap-uv-channel/lightmap_uv_channel.py b/examples/lightmap-uv-channel/lightmap_uv_channel.py index 7d694ef2..7a16f17d 100644 --- a/examples/lightmap-uv-channel/lightmap_uv_channel.py +++ b/examples/lightmap-uv-channel/lightmap_uv_channel.py @@ -346,6 +346,7 @@ def build_cart_meshes(): top = canopy_z0 + math.sqrt(r_in * r_in - py * py) + post_bite me = _box(f"Cart.Post.{tag}", (0.09, 0.09, top - post_z0), (px, py, 0.5 * (post_z0 + top)), 0.015) + # foreach-exempt: conditional per-vertex arc fit of the post tops for v in me.vertices: if v.co.z > top - 0.05: arc = canopy_z0 + math.sqrt(r_in * r_in - v.co.y * v.co.y) + post_bite diff --git a/examples/mesh-hygiene-audit/mesh_hygiene_audit.py b/examples/mesh-hygiene-audit/mesh_hygiene_audit.py index 97485c51..b924a85b 100644 --- a/examples/mesh-hygiene-audit/mesh_hygiene_audit.py +++ b/examples/mesh-hygiene-audit/mesh_hygiene_audit.py @@ -282,8 +282,7 @@ def _finish_part(name, build, mat, smooth_angle=35.0): finally: bm.free() me.materials.append(mat) - for p in me.polygons: - p.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) if hasattr(me, "set_sharp_from_angle"): me.set_sharp_from_angle(angle=math.radians(smooth_angle)) ob = bpy.data.objects.new(name, me) @@ -559,6 +558,7 @@ def mark_defects_from_audit(sc, ob): emit=(1.0, 0.5, 0.03), estr=1.0) me.materials.append(ngon_mat) slot = len(me.materials) - 1 + # foreach-exempt: marks only the n-gons the audit found for p in me.polygons: if len(p.vertices) > 4: p.material_index = slot @@ -585,8 +585,7 @@ def mark_defects_from_audit(sc, ob): finally: sbm.free() sme.materials.append(red) - for p in sme.polygons: - p.use_smooth = True + sme.polygons.foreach_set("use_smooth", [True] * len(sme.polygons)) s = bpy.data.objects.new(f"Defect.Loose{i}", sme) s.location = co sc.collection.objects.link(s) diff --git a/examples/prop-origin-transform/prop_origin_transform.py b/examples/prop-origin-transform/prop_origin_transform.py index 689b3671..103d06a4 100644 --- a/examples/prop-origin-transform/prop_origin_transform.py +++ b/examples/prop-origin-transform/prop_origin_transform.py @@ -359,10 +359,10 @@ def bbox_delta(a, b): def apply_scale_data_api(obj): """Bake non-uniform scale into mesh verts; leave obj.scale == (1,1,1).""" sx, sy, sz = obj.scale - for v in obj.data.vertices: - v.co.x *= sx - v.co.y *= sy - v.co.z *= sz + co = [0.0] * (len(obj.data.vertices) * 3) + obj.data.vertices.foreach_get("co", co) + scale = (sx, sy, sz) + obj.data.vertices.foreach_set("co", [c * scale[i % 3] for i, c in enumerate(co)]) obj.scale = (1.0, 1.0, 1.0) obj.data.update() @@ -374,8 +374,9 @@ def origin_to_base_center(obj): oy = 0.5 * (mn.y + mx.y) oz = mn.z offset = Vector((ox, oy, oz)) - for v in obj.data.vertices: - v.co -= offset + co = [0.0] * (len(obj.data.vertices) * 3) + obj.data.vertices.foreach_get("co", co) + obj.data.vertices.foreach_set("co", [c - offset[i % 3] for i, c in enumerate(co)]) obj.data.update() # World translation that compensates the mesh shift under current transform bpy.context.view_layer.update() diff --git a/examples/ray-cast-space/ray_cast_space.py b/examples/ray-cast-space/ray_cast_space.py index 94812ea1..62839b03 100644 --- a/examples/ray-cast-space/ray_cast_space.py +++ b/examples/ray-cast-space/ray_cast_space.py @@ -238,8 +238,7 @@ def rod(name, a, b, radius, mat, segs=16): finally: bm.free() me.materials.append(mat) - for poly in me.polygons: - poly.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) ob = bpy.data.objects.new(name, me) bpy.context.scene.collection.objects.link(ob) ob.location = (a + b) / 2 @@ -256,8 +255,7 @@ def sphere(name, at, radius, mat): finally: bm.free() me.materials.append(mat) - for poly in me.polygons: - poly.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) ob = bpy.data.objects.new(name, me) bpy.context.scene.collection.objects.link(ob) ob.location = at @@ -349,8 +347,7 @@ def render_still(obj, path, engine): finally: bm.free() pme.materials.append(plinth_mat) - for poly in pme.polygons: - poly.use_smooth = True + pme.polygons.foreach_set("use_smooth", [True] * len(pme.polygons)) plinth = bpy.data.objects.new("Plinth", pme) scene.collection.objects.link(plinth) plinth.location = (TARGET_LOC[0], TARGET_LOC[1], 0.0) diff --git a/examples/shader-node-group/shader_node_group.py b/examples/shader-node-group/shader_node_group.py index deb6fe13..aa4b9536 100644 --- a/examples/shader-node-group/shader_node_group.py +++ b/examples/shader-node-group/shader_node_group.py @@ -202,8 +202,7 @@ def build_mug_mesh(name): bm.to_mesh(me) finally: bm.free() - for poly in me.polygons: - poly.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) return me @@ -303,8 +302,7 @@ def render_still(objs, path, engine): bm.to_mesh(riser_me) finally: bm.free() - for poly in riser_me.polygons: - poly.use_smooth = True + riser_me.polygons.foreach_set("use_smooth", [True] * len(riser_me.polygons)) rmat = bpy.data.materials.new("Walnut") rmat.use_nodes = True rb = rmat.node_tree.nodes["Principled BSDF"] diff --git a/examples/sky-texture-sun-elevation/sky_texture_sun_elevation.py b/examples/sky-texture-sun-elevation/sky_texture_sun_elevation.py index cbd8b41c..1fbff911 100644 --- a/examples/sky-texture-sun-elevation/sky_texture_sun_elevation.py +++ b/examples/sky-texture-sun-elevation/sky_texture_sun_elevation.py @@ -123,8 +123,7 @@ def _mesh_obj(sc, name, build_bm, loc, mat, smooth=True): finally: bm.free() if smooth: - for p in me.polygons: - p.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) me.materials.append(mat) ob = bpy.data.objects.new(name, me) ob.location = loc diff --git a/examples/soccer-ball-goldberg/soccer_ball_goldberg.py b/examples/soccer-ball-goldberg/soccer_ball_goldberg.py index e27b25e6..4c909bb0 100644 --- a/examples/soccer-ball-goldberg/soccer_ball_goldberg.py +++ b/examples/soccer-ball-goldberg/soccer_ball_goldberg.py @@ -165,6 +165,7 @@ def near(i, j): # bound by face CLASS (vertex count), never by enumeration order: a # builder that assigns "first 12 faces black" passes only by luck of # bmesh face ordering, and the check below must catch it + # foreach-exempt: per-face pentagon/hexagon test on a 32-face ball for poly in me.polygons: pent = len(poly.vertices) == 5 poly.material_index = (0 if pent else 1) if invert_bind else (1 if pent else 0) @@ -440,8 +441,7 @@ def render_still(obj, path, engine): # the faceted Goldberg cage reads as a stitched ball. Panel materials # carry through every stage per face class, so a misbound panel would # still show in the image. - for poly in me.polygons: - poly.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) sub = obj.modifiers.new("Inflate", 'SUBSURF') sub.subdivision_type = 'CATMULL_CLARK' sub.levels = 3 @@ -461,8 +461,7 @@ def render_still(obj, path, engine): # proud of the ball, draw every panel boundary. A render-only copy of # the mesh, so the checked mesh and its binding are untouched. seam_me = me.copy() - for poly in seam_me.polygons: - poly.material_index = 1 + seam_me.polygons.foreach_set("material_index", [1] * len(seam_me.polygons)) seams = bpy.data.objects.new("Seams", seam_me) scene.collection.objects.link(seams) seams.parent = obj diff --git a/examples/socket-attach-points/socket_attach_points.py b/examples/socket-attach-points/socket_attach_points.py index 2d667b32..89dbed09 100644 --- a/examples/socket-attach-points/socket_attach_points.py +++ b/examples/socket-attach-points/socket_attach_points.py @@ -1070,8 +1070,7 @@ def render_still(path, engine, falsify=False): # smooth across its loft bands, while box edges stay crisp. Positions are # untouched, so nothing the socket checks measure changes. hull = next(ob for ob in hero if ob.data.name.endswith("Hull")) - for poly in hull.data.polygons: - poly.use_smooth = True + hull.data.polygons.foreach_set("use_smooth", [True] * len(hull.data.polygons)) hull.data.set_sharp_from_angle(angle=math.radians(38.0)) floor, wall = build_studio(sc) diff --git a/examples/solidify-even-thickness/solidify_even_thickness.py b/examples/solidify-even-thickness/solidify_even_thickness.py index f71e18f7..0dd93487 100644 --- a/examples/solidify-even-thickness/solidify_even_thickness.py +++ b/examples/solidify-even-thickness/solidify_even_thickness.py @@ -377,8 +377,7 @@ def render_still(even_obj, plain_obj, path, engine): bm.to_mesh(rod) finally: bm.free() - for poly in rod.polygons: - poly.use_smooth = True + rod.polygons.foreach_set("use_smooth", [True] * len(rod.polygons)) rod.materials.append(brass) ob = bpy.data.objects.new("Dowel", rod) scene.collection.objects.link(ob) diff --git a/examples/swatch-grid/swatch_grid.py b/examples/swatch-grid/swatch_grid.py index 4586aefe..e76d97b8 100644 --- a/examples/swatch-grid/swatch_grid.py +++ b/examples/swatch-grid/swatch_grid.py @@ -185,8 +185,7 @@ def make_block(name, sx, sy, sz, bevel): bm.to_mesh(me) finally: bm.free() - for p in me.polygons: - p.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) me.set_sharp_from_angle(angle=math.radians(40.0)) return me @@ -275,8 +274,7 @@ def build_scene(mats): bm.to_mesh(me) finally: bm.free() - for poly in me.polygons: - poly.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) ob = bpy.data.objects.new(f"Swatch{i}", me) # seated in the collar: the sphere rests on its rim, 0.4 in from # the equator, so it reads as mounted rather than balanced diff --git a/examples/triangulate-tangents/triangulate_tangents.py b/examples/triangulate-tangents/triangulate_tangents.py index 9dee441e..e034d24c 100644 --- a/examples/triangulate-tangents/triangulate_tangents.py +++ b/examples/triangulate-tangents/triangulate_tangents.py @@ -432,6 +432,7 @@ def render_still(obj, path, engine): boss_end = BOSS_RINGS * SIDES # quads up to the boss step flange_end = (BOSS_RINGS + FLANGE_RINGS - 1) * SIDES dome_end = (BOSS_RINGS + FLANGE_RINGS + DOME_RINGS - 1) * SIDES + # foreach-exempt: per-face material by build-order band for p in me.polygons: i = p.index if i < boss_end: diff --git a/examples/turntable/turntable.py b/examples/turntable/turntable.py index 42a45827..db936180 100644 --- a/examples/turntable/turntable.py +++ b/examples/turntable/turntable.py @@ -44,6 +44,8 @@ def build(no_keys=False): bpy.ops.wm.read_factory_settings(use_empty=True) bpy.ops.mesh.primitive_monkey_add(location=(0, 0, 1.0)) obj = bpy.context.active_object + if obj is None: + raise RuntimeError("the add operator left no active object") obj.data.shade_smooth() mat = bpy.data.materials.new("M"); mat.use_nodes = True b = mat.node_tree.nodes.get('Principled BSDF') @@ -143,8 +145,7 @@ def _mesh_object(name, build, mats, smooth_angle=35.0): bm.to_mesh(me) finally: bm.free() - for p in me.polygons: - p.use_smooth = True + me.polygons.foreach_set("use_smooth", [True] * len(me.polygons)) me.set_sharp_from_angle(angle=math.radians(smooth_angle)) for m in mats: me.materials.append(m) diff --git a/examples/usd-export-evaluation-mode/usd_export_evaluation_mode.py b/examples/usd-export-evaluation-mode/usd_export_evaluation_mode.py index 8a71339f..3df891bc 100644 --- a/examples/usd-export-evaluation-mode/usd_export_evaluation_mode.py +++ b/examples/usd-export-evaluation-mode/usd_export_evaluation_mode.py @@ -390,8 +390,7 @@ def render_still(obj, path, engine): for ob, x in ((left, -1.75), (right, 1.75)): ob.data.materials.clear() ob.data.materials.append(pewter) - for poly in ob.data.polygons: - poly.use_smooth = False + ob.data.polygons.foreach_set("use_smooth", [False] * len(ob.data.polygons)) ob.location = (x, 0.0, 0.0) ob.rotation_euler = (0.0, 0.0, math.radians(11.25)) ob.scale = (1.0, 1.0, 1.0) diff --git a/showcase/anvil/anvil.py b/showcase/anvil/anvil.py index 3cc759c2..88e88945 100644 --- a/showcase/anvil/anvil.py +++ b/showcase/anvil/anvil.py @@ -1264,8 +1264,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() diff --git a/showcase/apothecary-shelf/apothecary_shelf.py b/showcase/apothecary-shelf/apothecary_shelf.py index 7842ed3d..e37819af 100644 --- a/showcase/apothecary-shelf/apothecary_shelf.py +++ b/showcase/apothecary-shelf/apothecary_shelf.py @@ -1320,8 +1320,10 @@ def check(skip_decimate, lift_z=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or not low.data.uv_layers: diff --git a/showcase/archery-target/archery_target.py b/showcase/archery-target/archery_target.py index 4e7e4eb2..73f904cb 100644 --- a/showcase/archery-target/archery_target.py +++ b/showcase/archery-target/archery_target.py @@ -2217,8 +2217,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/bamboo-clump/bamboo_clump.py b/showcase/bamboo-clump/bamboo_clump.py index e33977bc..47e11b13 100644 --- a/showcase/bamboo-clump/bamboo_clump.py +++ b/showcase/bamboo-clump/bamboo_clump.py @@ -1828,8 +1828,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none2 = (None, None) diff --git a/showcase/basketball-hoop/basketball_hoop.py b/showcase/basketball-hoop/basketball_hoop.py index 44d2511e..eafdd818 100644 --- a/showcase/basketball-hoop/basketball_hoop.py +++ b/showcase/basketball-hoop/basketball_hoop.py @@ -1729,8 +1729,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, short_brace=False, if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none6 = (None, None, None) diff --git a/showcase/book-trolley/book_trolley.py b/showcase/book-trolley/book_trolley.py index 13a0fb74..65aa518e 100644 --- a/showcase/book-trolley/book_trolley.py +++ b/showcase/book-trolley/book_trolley.py @@ -1493,8 +1493,10 @@ def check(skip_decimate, lift_z=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or not low.data.uv_layers: diff --git a/showcase/bookshelf/bookshelf.py b/showcase/bookshelf/bookshelf.py index f00cb737..f5acb9ed 100644 --- a/showcase/bookshelf/bookshelf.py +++ b/showcase/bookshelf/bookshelf.py @@ -1391,8 +1391,10 @@ def check(skip_decimate, lift_z=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or not low.data.uv_layers: diff --git a/showcase/boulder-cluster/boulder_cluster.py b/showcase/boulder-cluster/boulder_cluster.py index cbb1515e..b398ec32 100644 --- a/showcase/boulder-cluster/boulder_cluster.py +++ b/showcase/boulder-cluster/boulder_cluster.py @@ -2008,8 +2008,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, perch_boulder=False, fl if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none2 = (None, None) diff --git a/showcase/bowling-pins/bowling_pins.py b/showcase/bowling-pins/bowling_pins.py index 157eec11..5c688a61 100644 --- a/showcase/bowling-pins/bowling_pins.py +++ b/showcase/bowling-pins/bowling_pins.py @@ -857,6 +857,7 @@ def build_ball_mesh(shallow): # version, so classify its faces by geometry instead: a face of the shell # faces straight out from the centre and lies on the sphere; everything # else (countersink, bore, bottom) is the drilled bore. + # foreach-exempt: per-face test of position and normal against the ball for p in out.polygons: c = p.center on = c.length > BALL_R - 0.002 and p.normal.dot(c.normalized()) > 0.9 @@ -1706,8 +1707,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/brazier/brazier.py b/showcase/brazier/brazier.py index d09c2c8c..d7315fb3 100644 --- a/showcase/brazier/brazier.py +++ b/showcase/brazier/brazier.py @@ -1085,8 +1085,10 @@ def check(skip_decimate, lift_z=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or not low.data.uv_layers: diff --git a/showcase/broadleaf-oak/broadleaf_oak.py b/showcase/broadleaf-oak/broadleaf_oak.py index 197d8544..5ad2b938 100644 --- a/showcase/broadleaf-oak/broadleaf_oak.py +++ b/showcase/broadleaf-oak/broadleaf_oak.py @@ -2545,8 +2545,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none2 = (None, None) diff --git a/showcase/butter-churn/butter_churn.py b/showcase/butter-churn/butter_churn.py index c4f098ef..09c3fa25 100644 --- a/showcase/butter-churn/butter_churn.py +++ b/showcase/butter-churn/butter_churn.py @@ -988,8 +988,10 @@ def check(skip_decimate, lift_z=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or not low.data.uv_layers: diff --git a/showcase/cactus-garden/cactus_garden.py b/showcase/cactus-garden/cactus_garden.py index 5c1f912c..d5d40b37 100644 --- a/showcase/cactus-garden/cactus_garden.py +++ b/showcase/cactus-garden/cactus_garden.py @@ -2452,8 +2452,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, short_arm=False, shallo if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none2 = (None, None) diff --git a/showcase/campfire/campfire.py b/showcase/campfire/campfire.py index 033cf418..b9ced132 100644 --- a/showcase/campfire/campfire.py +++ b/showcase/campfire/campfire.py @@ -442,8 +442,7 @@ def uniform(self, a, b): me = bpy.data.meshes.new(name) bm.to_mesh(me) me.update() - for poly in me.polygons: - poly.use_smooth = poly.material_index == WOOD_IDX + me.polygons.foreach_set("use_smooth", [poly.material_index == WOOD_IDX for poly in me.polygons]) finally: bm.free() obj = bpy.data.objects.new(name, me) @@ -1178,8 +1177,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() diff --git a/showcase/cargo-loader/cargo_loader.py b/showcase/cargo-loader/cargo_loader.py index b1cae469..57f10c0d 100644 --- a/showcase/cargo-loader/cargo_loader.py +++ b/showcase/cargo-loader/cargo_loader.py @@ -1915,8 +1915,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, overload=False, **flags if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/cart/cart.py b/showcase/cart/cart.py index 4e28a665..5c310df2 100644 --- a/showcase/cart/cart.py +++ b/showcase/cart/cart.py @@ -1454,8 +1454,10 @@ def check(skip_decimate, lift_z=False, flush_tyre=False, sink_tyre=False, assign_slots(low, wood, metal) assign_slots(high, wood, metal) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if flush_tyre: break_tyre_seat(low.data, 0.0) diff --git a/showcase/crate-stack/crate_stack.py b/showcase/crate-stack/crate_stack.py index 9669d708..e0e0a12d 100644 --- a/showcase/crate-stack/crate_stack.py +++ b/showcase/crate-stack/crate_stack.py @@ -1386,8 +1386,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() diff --git a/showcase/cricket-wicket/cricket_wicket.py b/showcase/cricket-wicket/cricket_wicket.py index 06b5f5ca..795aa105 100644 --- a/showcase/cricket-wicket/cricket_wicket.py +++ b/showcase/cricket-wicket/cricket_wicket.py @@ -1320,8 +1320,10 @@ def check(skip_decimate, lift_z=False, shift_bails=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or not low.data.uv_layers: diff --git a/showcase/desk-lamp/desk_lamp.py b/showcase/desk-lamp/desk_lamp.py index d61fe129..5164b25c 100644 --- a/showcase/desk-lamp/desk_lamp.py +++ b/showcase/desk-lamp/desk_lamp.py @@ -1447,8 +1447,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, offset_pin=False, if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/espresso-machine/espresso_machine.py b/showcase/espresso-machine/espresso_machine.py index b1ba6a11..647c0fc4 100644 --- a/showcase/espresso-machine/espresso_machine.py +++ b/showcase/espresso-machine/espresso_machine.py @@ -1884,8 +1884,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, flags=None): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/fallen-log/fallen_log.py b/showcase/fallen-log/fallen_log.py index 65bc9ce4..ad1624db 100644 --- a/showcase/fallen-log/fallen_log.py +++ b/showcase/fallen-log/fallen_log.py @@ -1789,8 +1789,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, hump_ground=False, if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none2 = (None, None) diff --git a/showcase/farm-tractor/farm_tractor.py b/showcase/farm-tractor/farm_tractor.py index bc1b711f..77bbd66a 100644 --- a/showcase/farm-tractor/farm_tractor.py +++ b/showcase/farm-tractor/farm_tractor.py @@ -3395,8 +3395,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/fence-kit/fence_kit.py b/showcase/fence-kit/fence_kit.py index 4b855166..c2797d20 100644 --- a/showcase/fence-kit/fence_kit.py +++ b/showcase/fence-kit/fence_kit.py @@ -465,8 +465,7 @@ def build_fence_mesh( me = bpy.data.meshes.new(name) bm.to_mesh(me) me.update() - for poly in me.polygons: - poly.use_smooth = False + me.polygons.foreach_set("use_smooth", [False] * len(me.polygons)) finally: bm.free() out = bpy.data.objects.new(name, me) @@ -1038,8 +1037,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if low.data is None or len(low.data.polygons) < 6: @@ -1329,8 +1330,7 @@ def render_still(low, wood, tex, path, engine): bm.to_mesh(path_me) finally: bm.free() - for poly in path_me.polygons: - poly.use_smooth = True + path_me.polygons.foreach_set("use_smooth", [True] * len(path_me.polygons)) pmat = bpy.data.materials.new("PackedEarth") pmat.use_nodes = True pnt = pmat.node_tree diff --git a/showcase/fern-mossy-rock/fern_mossy_rock.py b/showcase/fern-mossy-rock/fern_mossy_rock.py index 754dd4bc..6fefbf80 100644 --- a/showcase/fern-mossy-rock/fern_mossy_rock.py +++ b/showcase/fern-mossy-rock/fern_mossy_rock.py @@ -2962,8 +2962,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none2 = (None, None) diff --git a/showcase/floor-fan/floor_fan.py b/showcase/floor-fan/floor_fan.py index 100a418d..e3c9876a 100644 --- a/showcase/floor-fan/floor_fan.py +++ b/showcase/floor-fan/floor_fan.py @@ -1533,8 +1533,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/garden-gate/garden_gate.py b/showcase/garden-gate/garden_gate.py index 96fe6773..fd3e77fa 100644 --- a/showcase/garden-gate/garden_gate.py +++ b/showcase/garden-gate/garden_gate.py @@ -1293,8 +1293,10 @@ def check(skip_decimate, lift_z=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or not low.data.uv_layers: diff --git a/showcase/go-kart/go_kart.py b/showcase/go-kart/go_kart.py index 635dd54c..783dd315 100644 --- a/showcase/go-kart/go_kart.py +++ b/showcase/go-kart/go_kart.py @@ -3468,8 +3468,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/grain-sacks/grain_sacks.py b/showcase/grain-sacks/grain_sacks.py index bb32c2e7..68521052 100644 --- a/showcase/grain-sacks/grain_sacks.py +++ b/showcase/grain-sacks/grain_sacks.py @@ -1153,8 +1153,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): assign_slots(low, cloth, twine) assign_slots(high, cloth, twine) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or low.data.uv_layers.get("UVMap") is None: diff --git a/showcase/grindstone/grindstone.py b/showcase/grindstone/grindstone.py index 19c0ba04..c04caba2 100644 --- a/showcase/grindstone/grindstone.py +++ b/showcase/grindstone/grindstone.py @@ -1385,8 +1385,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() diff --git a/showcase/hand-pump/hand_pump.py b/showcase/hand-pump/hand_pump.py index 2f19cddf..b511bbe1 100644 --- a/showcase/hand-pump/hand_pump.py +++ b/showcase/hand-pump/hand_pump.py @@ -734,8 +734,7 @@ def build_hand_pump_mesh( me = bpy.data.meshes.new(name) bm.to_mesh(me) me.update() - for poly in me.polygons: - poly.use_smooth = poly.material_index == METAL_IDX + me.polygons.foreach_set("use_smooth", [poly.material_index == METAL_IDX for poly in me.polygons]) finally: bm.free() out = bpy.data.objects.new(name, me) @@ -1267,8 +1266,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if low.data is None or len(low.data.polygons) < 6: diff --git a/showcase/hay-bale/hay_bale.py b/showcase/hay-bale/hay_bale.py index e261cb2a..40be6545 100644 --- a/showcase/hay-bale/hay_bale.py +++ b/showcase/hay-bale/hay_bale.py @@ -1138,8 +1138,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, slack_belt=False, assign_slots(low, hay, twine) assign_slots(high, hay, twine) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if stray_vert: bm = bmesh.new() diff --git a/showcase/hitching-post/hitching_post.py b/showcase/hitching-post/hitching_post.py index f0d85901..e3070f11 100644 --- a/showcase/hitching-post/hitching_post.py +++ b/showcase/hitching-post/hitching_post.py @@ -993,8 +993,7 @@ def build_hitching_post_mesh( me = bpy.data.meshes.new(name) bm.to_mesh(me) me.update() - for poly in me.polygons: - poly.use_smooth = poly.material_index == METAL_IDX + me.polygons.foreach_set("use_smooth", [poly.material_index == METAL_IDX for poly in me.polygons]) finally: bm.free() out = bpy.data.objects.new(name, me) @@ -1304,8 +1303,10 @@ def check( assign_slots(low, wood, metal) assign_slots(high, wood, metal) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if stray_vert: add_stray_vert(low.data) diff --git a/showcase/hover-bike/hover_bike.py b/showcase/hover-bike/hover_bike.py index a9d7db40..a5f7ce31 100644 --- a/showcase/hover-bike/hover_bike.py +++ b/showcase/hover-bike/hover_bike.py @@ -2381,8 +2381,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, float_skid=False, offse if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/iron-cauldron/iron_cauldron.py b/showcase/iron-cauldron/iron_cauldron.py index 3f198fd2..8270a46c 100644 --- a/showcase/iron-cauldron/iron_cauldron.py +++ b/showcase/iron-cauldron/iron_cauldron.py @@ -1389,8 +1389,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() diff --git a/showcase/market-stall/market_stall.py b/showcase/market-stall/market_stall.py index 4da379a6..d2b4cb0b 100644 --- a/showcase/market-stall/market_stall.py +++ b/showcase/market-stall/market_stall.py @@ -1153,8 +1153,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if low.data is None or len(low.data.polygons) < 6: diff --git a/showcase/mine-cart/mine_cart.py b/showcase/mine-cart/mine_cart.py index 74b4e083..1ac78fd4 100644 --- a/showcase/mine-cart/mine_cart.py +++ b/showcase/mine-cart/mine_cart.py @@ -1392,8 +1392,10 @@ def check(skip_decimate, lift_z=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or not low.data.uv_layers: diff --git a/showcase/moka-pot/moka_pot.py b/showcase/moka-pot/moka_pot.py index 6272f90b..06274918 100644 --- a/showcase/moka-pot/moka_pot.py +++ b/showcase/moka-pot/moka_pot.py @@ -1105,11 +1105,15 @@ def check(skip_decimate, lift_z=False, lean_pot=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) if lean_pot: - for v in low.data.vertices: - v.co.x += LEAN * v.co.z / OUTER_SIZE[2] + co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", co) + co[0::3] = [x + LEAN * z / OUTER_SIZE[2] for x, z in zip(co[0::3], co[2::3])] + low.data.vertices.foreach_set("co", co) if lift_z or lean_pot: low.data.update() bpy.context.view_layer.update() diff --git a/showcase/motor-scooter/motor_scooter.py b/showcase/motor-scooter/motor_scooter.py index 8669f28c..4a2640da 100644 --- a/showcase/motor-scooter/motor_scooter.py +++ b/showcase/motor-scooter/motor_scooter.py @@ -1990,8 +1990,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, float_tyre=False, toe_w if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/mushroom-stump/mushroom_stump.py b/showcase/mushroom-stump/mushroom_stump.py index b9934445..a73a9df9 100644 --- a/showcase/mushroom-stump/mushroom_stump.py +++ b/showcase/mushroom-stump/mushroom_stump.py @@ -3038,8 +3038,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none2 = (None, None) diff --git a/showcase/office-chair/office_chair.py b/showcase/office-chair/office_chair.py index 2d0c6601..f1a0b2c5 100644 --- a/showcase/office-chair/office_chair.py +++ b/showcase/office-chair/office_chair.py @@ -1982,8 +1982,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, float_caster=False, ske if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none1 = (None,) diff --git a/showcase/palm-tree/palm_tree.py b/showcase/palm-tree/palm_tree.py index 2e88dd65..6dddce01 100644 --- a/showcase/palm-tree/palm_tree.py +++ b/showcase/palm-tree/palm_tree.py @@ -2146,8 +2146,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none2 = (None, None) diff --git a/showcase/park-bench/park_bench.py b/showcase/park-bench/park_bench.py index 4fd32bea..2a4d124d 100644 --- a/showcase/park-bench/park_bench.py +++ b/showcase/park-bench/park_bench.py @@ -515,8 +515,7 @@ def build_bench_mesh( bm.to_mesh(me) me.update() # Round bar is smooth-shaded; sawn slats keep their chamfer facets. - for poly in me.polygons: - poly.use_smooth = poly.material_index == METAL_IDX and len(poly.vertices) == 4 + me.polygons.foreach_set("use_smooth", [poly.material_index == METAL_IDX and len(poly.vertices) == 4 for poly in me.polygons]) finally: bm.free() out = bpy.data.objects.new(name, me) @@ -1140,8 +1139,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if low.data is None or len(low.data.polygons) < 6: diff --git a/showcase/pine-tree/pine_tree.py b/showcase/pine-tree/pine_tree.py index a7e92a5a..cc9c4bd7 100644 --- a/showcase/pine-tree/pine_tree.py +++ b/showcase/pine-tree/pine_tree.py @@ -1777,8 +1777,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none2 = (None, None) diff --git a/showcase/planet-rover/planet_rover.py b/showcase/planet-rover/planet_rover.py index 6add7d30..96ef8b3f 100644 --- a/showcase/planet-rover/planet_rover.py +++ b/showcase/planet-rover/planet_rover.py @@ -2491,8 +2491,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, overload_turret=False, if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/pond-edge/pond_edge.py b/showcase/pond-edge/pond_edge.py index 02e26a2b..b5399971 100644 --- a/showcase/pond-edge/pond_edge.py +++ b/showcase/pond-edge/pond_edge.py @@ -2155,8 +2155,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, float_reeds=False, sink if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none2 = (None, None) diff --git a/showcase/quad-drone/quad_drone.py b/showcase/quad-drone/quad_drone.py index f88620fb..fe16128f 100644 --- a/showcase/quad-drone/quad_drone.py +++ b/showcase/quad-drone/quad_drone.py @@ -1618,8 +1618,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, float_foot=False, unloc if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/road-bicycle/road_bicycle.py b/showcase/road-bicycle/road_bicycle.py index fbd5f695..f4c8341d 100644 --- a/showcase/road-bicycle/road_bicycle.py +++ b/showcase/road-bicycle/road_bicycle.py @@ -2769,8 +2769,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/rope-bridge/rope_bridge.py b/showcase/rope-bridge/rope_bridge.py index 74ecd795..560782ff 100644 --- a/showcase/rope-bridge/rope_bridge.py +++ b/showcase/rope-bridge/rope_bridge.py @@ -1438,8 +1438,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, low_bake=False, **flags if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() diff --git a/showcase/sawhorse-plank/sawhorse_plank.py b/showcase/sawhorse-plank/sawhorse_plank.py index 43133046..f05fc3a8 100644 --- a/showcase/sawhorse-plank/sawhorse_plank.py +++ b/showcase/sawhorse-plank/sawhorse_plank.py @@ -1190,8 +1190,10 @@ def check(skip_decimate, lift_z=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or not low.data.uv_layers: diff --git a/showcase/sea-stack-arch/sea_stack_arch.py b/showcase/sea-stack-arch/sea_stack_arch.py index acdf357b..d8e530e2 100644 --- a/showcase/sea-stack-arch/sea_stack_arch.py +++ b/showcase/sea-stack-arch/sea_stack_arch.py @@ -3270,8 +3270,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none2 = (None, None) diff --git a/showcase/shipping-crate/shipping_crate.py b/showcase/shipping-crate/shipping_crate.py index 0ea8e04d..5b5dd341 100644 --- a/showcase/shipping-crate/shipping_crate.py +++ b/showcase/shipping-crate/shipping_crate.py @@ -1331,8 +1331,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() diff --git a/showcase/ships-wheel/ships_wheel.py b/showcase/ships-wheel/ships_wheel.py index d7f317e7..7e03b7aa 100644 --- a/showcase/ships-wheel/ships_wheel.py +++ b/showcase/ships-wheel/ships_wheel.py @@ -1144,8 +1144,10 @@ def check(skip_decimate, lift_z=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or not low.data.uv_layers: diff --git a/showcase/signpost/signpost.py b/showcase/signpost/signpost.py index 231db518..64c7c984 100644 --- a/showcase/signpost/signpost.py +++ b/showcase/signpost/signpost.py @@ -436,8 +436,7 @@ def build_signpost_mesh( me = bpy.data.meshes.new(name) bm.to_mesh(me) me.update() - for poly in me.polygons: - poly.use_smooth = False + me.polygons.foreach_set("use_smooth", [False] * len(me.polygons)) finally: bm.free() out = bpy.data.objects.new(name, me) @@ -1011,8 +1010,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += 0.05 + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + 0.05 for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if low.data is None or len(low.data.polygons) < 6: diff --git a/showcase/skate-ramp/skate_ramp.py b/showcase/skate-ramp/skate_ramp.py index 69e9f3e4..00323a03 100644 --- a/showcase/skate-ramp/skate_ramp.py +++ b/showcase/skate-ramp/skate_ramp.py @@ -1916,8 +1916,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/soccer-goal/soccer_goal.py b/showcase/soccer-goal/soccer_goal.py index 4fefdfb3..329a2545 100644 --- a/showcase/soccer-goal/soccer_goal.py +++ b/showcase/soccer-goal/soccer_goal.py @@ -1464,8 +1464,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, float_bar=False, short_ if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/stand-mixer/stand_mixer.py b/showcase/stand-mixer/stand_mixer.py index ef282298..6f5896c0 100644 --- a/showcase/stand-mixer/stand_mixer.py +++ b/showcase/stand-mixer/stand_mixer.py @@ -1852,8 +1852,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, flags=None): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/stone-archway/stone_archway.py b/showcase/stone-archway/stone_archway.py index 2d8daaad..82e759fa 100644 --- a/showcase/stone-archway/stone_archway.py +++ b/showcase/stone-archway/stone_archway.py @@ -1079,8 +1079,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() diff --git a/showcase/stone-well/stone_well.py b/showcase/stone-well/stone_well.py index 40063c71..12eb8cdc 100644 --- a/showcase/stone-well/stone_well.py +++ b/showcase/stone-well/stone_well.py @@ -1244,8 +1244,10 @@ def check(skip_decimate, lift_z=False, stand_posts=False, turn_posts=False, assign_slots(low, stone, wood, metal) assign_slots(high, stone, wood, metal) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if float_stone: float_one_stone(low.data, FLOAT_STONE_Z) diff --git a/showcase/street-lantern/street_lantern.py b/showcase/street-lantern/street_lantern.py index a920d208..fc294642 100644 --- a/showcase/street-lantern/street_lantern.py +++ b/showcase/street-lantern/street_lantern.py @@ -806,8 +806,7 @@ def build_lantern_mesh( me = bpy.data.meshes.new(name) bm.to_mesh(me) me.update() - for poly in me.polygons: - poly.use_smooth = poly.material_index == GLASS_IDX + me.polygons.foreach_set("use_smooth", [poly.material_index == GLASS_IDX for poly in me.polygons]) finally: bm.free() obj = bpy.data.objects.new(name, me) @@ -1319,8 +1318,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if low.data is None or len(low.data.polygons) < 6: diff --git a/showcase/sundial/sundial.py b/showcase/sundial/sundial.py index 5ab3e8eb..80c6b731 100644 --- a/showcase/sundial/sundial.py +++ b/showcase/sundial/sundial.py @@ -1106,8 +1106,10 @@ def check(skip_decimate, lift_z=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or not low.data.uv_layers: diff --git a/showcase/tavern-stool/tavern_stool.py b/showcase/tavern-stool/tavern_stool.py index 96f1c375..3d9d5a16 100644 --- a/showcase/tavern-stool/tavern_stool.py +++ b/showcase/tavern-stool/tavern_stool.py @@ -1164,8 +1164,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() diff --git a/showcase/terrain-scatter/terrain_scatter.py b/showcase/terrain-scatter/terrain_scatter.py index 433c9f6e..939d6241 100644 --- a/showcase/terrain-scatter/terrain_scatter.py +++ b/showcase/terrain-scatter/terrain_scatter.py @@ -1211,8 +1211,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if low.data is None or len(low.data.polygons) < 6: diff --git a/showcase/toboggan/toboggan.py b/showcase/toboggan/toboggan.py index 3a68cc80..8323894b 100644 --- a/showcase/toboggan/toboggan.py +++ b/showcase/toboggan/toboggan.py @@ -1303,8 +1303,10 @@ def check(skip_decimate, lift_z=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or not low.data.uv_layers: diff --git a/showcase/traffic-cones/traffic_cones.py b/showcase/traffic-cones/traffic_cones.py index 638b48ad..0bf3e3ca 100644 --- a/showcase/traffic-cones/traffic_cones.py +++ b/showcase/traffic-cones/traffic_cones.py @@ -1937,8 +1937,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/treasure-chest/treasure_chest.py b/showcase/treasure-chest/treasure_chest.py index 1dc80bab..0eadde93 100644 --- a/showcase/treasure-chest/treasure_chest.py +++ b/showcase/treasure-chest/treasure_chest.py @@ -1153,8 +1153,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() diff --git a/showcase/wall-torch/wall_torch.py b/showcase/wall-torch/wall_torch.py index 86f0bb05..bf28c2a6 100644 --- a/showcase/wall-torch/wall_torch.py +++ b/showcase/wall-torch/wall_torch.py @@ -471,10 +471,7 @@ def build_torch_mesh( me.update() # Turned iron, the haft, the wrap and the flame are smooth; dressed # stone keeps its chamfer facets. Fan caps stay flat. - for poly in me.polygons: - poly.use_smooth = ( - poly.material_index != STONE_IDX and len(poly.vertices) == 4 - ) or poly.material_index == FLAME_IDX + me.polygons.foreach_set("use_smooth", [poly.material_index != STONE_IDX and len(poly.vertices) == 4 or poly.material_index == FLAME_IDX for poly in me.polygons]) finally: bm.free() obj = bpy.data.objects.new(name, me) @@ -1092,8 +1089,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if low.data is None or len(low.data.polygons) < 6: diff --git a/showcase/watchtower/watchtower.py b/showcase/watchtower/watchtower.py index 748e3fc2..90c714c4 100644 --- a/showcase/watchtower/watchtower.py +++ b/showcase/watchtower/watchtower.py @@ -667,8 +667,7 @@ def build_tower_mesh( me = bpy.data.meshes.new(name) bm.to_mesh(me) me.update() - for poly in me.polygons: - poly.use_smooth = False + me.polygons.foreach_set("use_smooth", [False] * len(me.polygons)) finally: bm.free() out = bpy.data.objects.new(name, me) @@ -1277,8 +1276,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if low.data is None or len(low.data.polygons) < 6: diff --git a/showcase/water-trough/water_trough.py b/showcase/water-trough/water_trough.py index fe6d765d..07d642cf 100644 --- a/showcase/water-trough/water_trough.py +++ b/showcase/water-trough/water_trough.py @@ -746,8 +746,7 @@ def build_trough_mesh( me = bpy.data.meshes.new(name) bm.to_mesh(me) me.update() - for poly in me.polygons: - poly.use_smooth = poly.material_index == WATER_IDX + me.polygons.foreach_set("use_smooth", [poly.material_index == WATER_IDX for poly in me.polygons]) finally: bm.free() paint_planks(me) @@ -1464,8 +1463,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() if low.data is None or len(low.data.polygons) < 6: diff --git a/showcase/weight-rack/weight_rack.py b/showcase/weight-rack/weight_rack.py index bc5d53f6..bb512f4f 100644 --- a/showcase/weight-rack/weight_rack.py +++ b/showcase/weight-rack/weight_rack.py @@ -1635,8 +1635,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, float_foot=False, offse if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none3 = (None, None, None) diff --git a/showcase/wheelbarrow/wheelbarrow.py b/showcase/wheelbarrow/wheelbarrow.py index 036ead11..4f85c5ba 100644 --- a/showcase/wheelbarrow/wheelbarrow.py +++ b/showcase/wheelbarrow/wheelbarrow.py @@ -727,8 +727,7 @@ def edge(h, out): me = bpy.data.meshes.new(name) bm.to_mesh(me) me.update() - for poly in me.polygons: - poly.use_smooth = False + me.polygons.foreach_set("use_smooth", [False] * len(me.polygons)) finally: bm.free() out = bpy.data.objects.new(name, me) @@ -1435,8 +1434,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() diff --git a/showcase/wingback-armchair/wingback_armchair.py b/showcase/wingback-armchair/wingback_armchair.py index 30bc1f73..fdec447a 100644 --- a/showcase/wingback-armchair/wingback_armchair.py +++ b/showcase/wingback-armchair/wingback_armchair.py @@ -2105,8 +2105,10 @@ def check(skip_decimate, lift_z=False, stray_vert=False, **flags): if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() none1 = (None,) diff --git a/showcase/wooden-barrel/wooden_barrel.py b/showcase/wooden-barrel/wooden_barrel.py index 59fca4f2..1b128858 100644 --- a/showcase/wooden-barrel/wooden_barrel.py +++ b/showcase/wooden-barrel/wooden_barrel.py @@ -1049,8 +1049,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() diff --git a/showcase/wooden-bucket/wooden_bucket.py b/showcase/wooden-bucket/wooden_bucket.py index 47e0051d..94623eee 100644 --- a/showcase/wooden-bucket/wooden_bucket.py +++ b/showcase/wooden-bucket/wooden_bucket.py @@ -1234,8 +1234,10 @@ def check( if stray_vert: add_stray_vert(low.data) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() diff --git a/showcase/wooden-yoke/wooden_yoke.py b/showcase/wooden-yoke/wooden_yoke.py index 01f95c43..1987e0f2 100644 --- a/showcase/wooden-yoke/wooden_yoke.py +++ b/showcase/wooden-yoke/wooden_yoke.py @@ -977,8 +977,10 @@ def check(skip_decimate, lift_z=False, **flags): assign_slots(low, mats) assign_slots(high, mats) if lift_z: - for v in low.data.vertices: - v.co.z += LIFT_Z + _co = [0.0] * (len(low.data.vertices) * 3) + low.data.vertices.foreach_get("co", _co) + _co[2::3] = [z + LIFT_Z for z in _co[2::3]] + low.data.vertices.foreach_set("co", _co) low.data.update() bpy.context.view_layer.update() if len(low.data.polygons) < 6 or not low.data.uv_layers: diff --git a/tests/check_example_rules.py b/tests/check_example_rules.py new file mode 100644 index 00000000..108a9b3e --- /dev/null +++ b/tests/check_example_rules.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""Examples and showcase scripts must follow the repo's own rules (#400). + +Agents copy these scripts (bmesh-gear is the anatomy every new example +starts from), so a script that breaks a rule teaches the anti-pattern. Two +rules are checked statically over examples/ and showcase/: + +- use-foreach-set-for-bulk-data: a `for x in <...>.polygons` or + `for x in <...>.vertices` loop that assigns to an attribute of `x`. Those two + collection names exist only on Mesh (BMesh uses faces/verts), so a hit is a + per-element RNA write, never bmesh code. Use foreach_set instead. +- type-annotate-props-and-defend-context: `name = bpy.context.active_object` + (or `context.active_object`) not followed directly by `if name is None`. + +A deliberate exception carries a marker comment on the `for` line, or the +line just above it, that says why: + + # foreach-exempt: conditional per-face material ids on a 12-face cap + + python tests/check_example_rules.py (exit 0 ok, 1 on a violation) +""" +from __future__ import annotations + +import ast +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +MESH_ONLY = {"polygons", "vertices"} +MARKER = "# foreach-exempt:" + + +def scripts(root: Path) -> list[Path]: + return sorted([*root.glob("examples/*/*.py"), *root.glob("showcase/*/*.py")]) + + +def writes_loop_var(loop: ast.For) -> bool: + name = loop.target.id + for node in ast.walk(ast.Module(body=loop.body, type_ignores=[])): + targets = (node.targets if isinstance(node, ast.Assign) + else [node.target] if isinstance(node, (ast.AugAssign, ast.AnnAssign)) else []) + for tgt in targets: + base = tgt + while isinstance(base, (ast.Attribute, ast.Subscript)): + base = base.value + if isinstance(base, ast.Name) and base.id == name and tgt is not base: + return True + return False + + +def exempt(lines: list[str], lineno: int) -> bool: + here = lines[lineno - 1] + above = lines[lineno - 2] if lineno >= 2 else "" + return MARKER in here or above.strip().startswith(MARKER) + + +def check_file(path: Path, root: Path) -> list[str]: + rel = path.relative_to(root).as_posix() + src = path.read_text(encoding="utf-8") + lines = src.split("\n") + tree = ast.parse(src) + errors = [] + for node in ast.walk(tree): + if (isinstance(node, ast.For) and isinstance(node.iter, ast.Attribute) + and node.iter.attr in MESH_ONLY and isinstance(node.target, ast.Name) + and writes_loop_var(node) and not exempt(lines, node.lineno)): + errors.append(f"{rel}:{node.lineno}: per-element write in a loop over " + f"{ast.unparse(node.iter)}; use foreach_set " + f"(rule use-foreach-set-for-bulk-data) or mark '{MARKER} '") + for field in ("body", "orelse", "finalbody"): + stmts = getattr(node, field, None) + if not isinstance(stmts, list): + continue + for i, st in enumerate(stmts): + if (isinstance(st, ast.Assign) and len(st.targets) == 1 + and isinstance(st.targets[0], ast.Name) + and ast.unparse(st.value) in ("bpy.context.active_object", + "context.active_object")): + name = st.targets[0].id + nxt = stmts[i + 1] if i + 1 < len(stmts) else None + if not (isinstance(nxt, ast.If) and f"{name} is None" in ast.unparse(nxt.test)): + errors.append(f"{rel}:{st.lineno}: {name} = active_object is used " + f"without 'if {name} is None' " + f"(rule type-annotate-props-and-defend-context)") + return errors + + +def check(root: Path = ROOT) -> list[str]: + errors = [] + for path in scripts(root): + errors += check_file(path, root) + return errors + + +def main() -> int: + errors = check() + for e in errors: + print(f"::error::{e}", file=sys.stderr) + if not errors: + print(f"example rules: {len(scripts(ROOT))} scripts follow use-foreach-set and " + "active_object guards") + return 1 if errors else 0 + + +if __name__ == "__main__": + sys.exit(main())