1
$\begingroup$

I'm a 3D newbie but I have programming experience. I'm using Blender 2.91.0 on Linux.

I am trying to write a script to create a path/road from JSON data. The general strategy is to start from a block, and then extrude the "forward-facing" face for each item in the track_data list (this script only goes up and down, no curves). My problem comes from that extrusion: when using "orient_type": "NORMAL" I know that in this context "z" is the normal itself, but I don't seem to have a reliable way to tell which one of x or y points up (global "z"). What is even weirder is that it seems to change according to the value of the extrusion.

I have been able to produce a reduced test case that shows what I'm talking about:

#!/usr/bin/python3

import bpy
import bmesh

# Cut off point is around 1.1945 ????
MULTIPLIER = 1

track_data = [
    {"p1": {"world": {"y": 0}}, "p2": {"world": {"y": 0}}},
    {"p1": {"world": {"y": 0,}}, "p2": {"world": {"y": -6.041601,}}},
    {"p1": {"world": {"y": -6.041601,}}, "p2": {"world": {"y": -24.154236,}}},
    {"p1": {"world": {"y": -24.154236,}}, "p2": {"world": {"y": -54.301429,}}},
    {"p1": {"world": {"y": -54.301429,}}, "p2": {"world": {"y": -96.422468,}}},
    {"p1": {"world": {"y": -96.422468,}}, "p2": {"world": {"y": -150.432527,}}},
    {"p1": {"world": {"y": -150.432527,}}, "p2": {"world": {"y": -216.222836,}}},
    {"p1": {"world": {"y": -216.222836,}}, "p2": {"world": {"y": -293.660902,}}},
    {"p1": {"world": {"y": -293.660902,}}, "p2": {"world": {"y": -382.590776,}}},
    {"p1": {"world": {"y": -382.590776,}}, "p2": {"world": {"y": -482.833365,}}}
]

# Initial segment
bpy.ops.mesh.primitive_cube_add()
cube = bpy.context.object
bpy.ops.transform.resize(value=(6, 4, 5))
bpy.ops.transform.translate(value=(0, 0, -4.95))

bpy.ops.object.mode_set(mode='EDIT')


for segment in track_data:
    altitude_diff = (segment["p2"]["world"]["y"] - segment["p1"]["world"]["y"]) / 400

    bm = bmesh.from_edit_mesh(cube.data)
    bm.faces.ensure_lookup_table()
    for face in bm.faces:
        face.select = False
    # Select the forward-looking face. Hacky, but seems reliable enough.
    forward_face = bm.faces[-5] if len(bm.faces) <= 6 else bm.faces[-4]
    forward_face.select = True

    extrusion = (0, altitude_diff * MULTIPLIER, 2)
    bpy.ops.mesh.extrude_region_move(
        TRANSFORM_OT_translate={"value":extrusion, "orient_type":"NORMAL"}
    )

bpy.ops.object.mode_set(mode='OBJECT')

If you run that script, the result will be the first image (the "y" coordinate is the global "x" so it turns left). If you change the MULTIPLIER constant at the top to 2, the result will be the second image (the "y" coordinate is the global "z", so it goes down... which is what I what).

Result when MULTIPLIER=1. The road turns left Result when MULTIPLIER=2. The road turns down

So, is there any way to make sure that x or y in that extrusion is consistently "pointing up" (in my case it will always literally be global z)? I am not doing any rotations in this script, and the full script will only use rotations on the z axis (for curves).

I have tried playing a bit with other parameters to TRANSFORM_OT_translate, like orient_matrix, but I don't know what to try and I cannot find documentation anywhere.

EDIT: I realised that I will only be rotating for curves (not shown in this script) so the question is a bit simpler, because I am looking for the global z.

$\endgroup$
0
$\begingroup$

Bmesh take on this.

IMO would step straight into API methods using linear algebra, and bmesh for operating on mesh ASAP. ... no mode toggling mind boggling mesh operators, ...

Have used question script to add the initial cube, then from there gone object mode bmesh

  • Transformed (translate and scale) the mesh using matrices.
  • Given all six faces names Y is north, Z is up.
  • Then chosen to "go west". This is travel 2 in direction of west face normal, and up by elevation difference each step.
  • Totally confused as to MULTIPLIER and its cut off point, so left it out.

Test script, goes west.

import bpy
import bmesh
from mathutils import Matrix
# Cut off point is around 1.1945 ????
MULTIPLIER = 1

track_data = [
    {"p1": {"world": {"y": 0}}, "p2": {"world": {"y": 0}}},
    {"p1": {"world": {"y": 0,}}, "p2": {"world": {"y": -6.041601,}}},
    {"p1": {"world": {"y": -6.041601,}}, "p2": {"world": {"y": -24.154236,}}},
    {"p1": {"world": {"y": -24.154236,}}, "p2": {"world": {"y": -54.301429,}}},
    {"p1": {"world": {"y": -54.301429,}}, "p2": {"world": {"y": -96.422468,}}},
    {"p1": {"world": {"y": -96.422468,}}, "p2": {"world": {"y": -150.432527,}}},
    {"p1": {"world": {"y": -150.432527,}}, "p2": {"world": {"y": -216.222836,}}},
    {"p1": {"world": {"y": -216.222836,}}, "p2": {"world": {"y": -293.660902,}}},
    {"p1": {"world": {"y": -293.660902,}}, "p2": {"world": {"y": -382.590776,}}},
    {"p1": {"world": {"y": -382.590776,}}, "p2": {"world": {"y": -482.833365,}}}
]

step = 2

# Initial segment
bpy.ops.mesh.primitive_cube_add()
cube = bpy.context.object
me = cube.data

M = (
    Matrix.Translation((0, 0, -4.95)) @
    Matrix.Diagonal((6, 4, 5)).to_4x4() 
    ) 
me.transform(M)
bm = bmesh.new()
bm.from_mesh(me)

west, north, east, south, down, up = bm.faces

go = west

for segment in track_data:
    altitude_diff = (segment["p2"]["world"]["y"] - segment["p1"]["world"]["y"]) / 400
    go = bmesh.ops.extrude_discrete_faces(
        bm,
        faces=[go],
        )["faces"].pop()
    bmesh.ops.translate(
        bm,
        vec=step * go.normal + altitude_diff * up.normal,
        verts=go.verts,
        )
bm.to_mesh(me)
me.update()
$\endgroup$
2
  • $\begingroup$ Thanks so much! I'm surprised that the order of faces is consistent, similar things have felt pretty random to me, even as in "different every time I run the same, unmodified script". The MULTIPLIER is what I mentioned in my question: changing the amount to extrude by changes what x and y point to! The cutoff is when the change happens: values under the cutoff point seem to always behave the same; same with values over it. $\endgroup$ – user113581 Jan 14 at 16:30
  • $\begingroup$ BTW it seems that the bmesh.ops.translate call should be inside the loop. At least that's how I made it work. I cannot edit the answer to correct it because SE wants me to modify more than just four characters 😄 $\endgroup$ – user113581 Jan 14 at 16:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy