How I Built A Simple (Horrible) 3D Graphics Engine in python matplotlib - Part 5: Afterword

published-date: 18 Feb 2023 16:58 +0700
categories: dumber-day-by-day python-hijinks
tags: python matplotlib

Posts in This Series


There will be long texts below. If you to play around with the code I used to generate the graphics in this blog, then the link is as follows. I’ve modified it a little bit so hopefully it’d be easier to read.

Colab Notebook

Without further ado, there are few things I want to clarify.

Why is it terrible though?

Figure 1

I’ll be honest, this way of doing 3D graphics is not very practical. One of the main reason why I did that in my previous job was the limitation on what I can use at the time, where colabs came in my mind since it was the only IDE environment coupled with interpreter that was not blocked by company’s proxy. During making of the proposal I need some visualization but I could not work with 3D modelling software (i.e Blender) unless I bring my own laptop to the workflow, which was a no go.

There are few limitation in the “library” i made, which is z-buffering. It would be possible to render object on the top of another object by simply carefully putting each ax.plot( ... ) in ordered fashion, but it would be bad in the long run of you have a lot of objects. I believe it is actually possible to implement such thing, but for my purposes and the timeframe the project was given, it was not feasible. Not to mention a whole lot of other functionalities that was not implemented, like poly, faces, shaders, UV mapping, to name a few.

There are one aspect from the source code you can open in the first post which I didn’t cover in the posts, which is “archaic” vertex grouping. It works by splicing the vertex buffer/array with a known length, treating the array as memory. Another array stores the “pointer” and the length of the vertex group (in this case cube has 8 verts). Imagine in C you’re pointing into memory then fetching the values based on the sizeof(vertex) * n. There are better ways but this is simple enough for me to go wild writing the aforementioned code.

Can I import vertices from Blender?

Figure 2

Importing verts from Blender can be done by plugging this simple script into Blender’s script tab then run the script. In this case is just exporting vert coordinates into csv format. Be careful of vert/poly count though.

 1import bpy
 2
 3obj_name = 'Cube'
 4export_path = 'C:/tmp/mesh_verts'
 5delimiter = ';'
 6
 7def get_vert_as_list(obj):
 8  # this one get the coordinates straight from the vert
 9  temp = []
10  for vert in obj.data.vertices:
11    temp.append(vert)
12  return temp
13
14
15def get_vert_as_tris_list(obj):
16  # this method loops over tris of the mesh faces
17  temp = []
18  
19  # calcs tris
20  obj.data.calc_loop_triangles()
21
22  # loop over vertices with tri_n as index
23  for tris in obj.data.loop_triangles:
24    for tri_n in tris.vertices:
25      temp.append(obj.data.vertices[tri_n])
26
27  return temp
28
29
30def main():
31  # you can use your own method on getting the desired object
32  obj_target = list(filter(lambda x: x.name==obj_name, bpy.data.objects))
33
34  if not obj_target:
35    print(f'Object \"{obj_name}\" is not found.')
36    return
37  
38  obj_target = obj_target[0]
39
40  # convert into list using one of our function above  
41  verts = get_vert_as_list(obj_target)
42  
43  # you can export in any format you like. In this case its just a semicolon delimited txt file.
44  with open(export_path + '.txt', 'w') as fp:
45    for vert in verts:
46      fp.writelines(delimiter.join(map(str, [i for i in vert.co])))
47      fp.writelines('\n')
48
49
50if __name__ == '__main__':
51  main()

Now you can unpack this txt file into numpy array on your colab (once you upload the txt file of course). You can get my exported txt file here.

 1def unpack_txt(fpath, delim=';'):
 2  import os
 3  
 4  if not os.path.exists(fpath) and not os.path.isfile(fpath):
 5    return np.zeros((1,4))
 6  
 7  temp = []
 8
 9  with open(fpath, 'r') as fp:
10    for line in fp.readlines():
11      # you can use float instead, whatever floats your boat (HAHAHAHAHAH GOTEM)
12      temp.append(list(map(np.float32, line.split(delim))) + [1])
13
14  return np.array(temp)

Then you can unpack the txt just like so.

fpath = 'mesh_verts.txt'
imported_vert = unpack_txt(fpath)

if imported_vert.any():
  wireframes = np.concatenate((wireframes, imported_vert), axis=0)

So what’s next?

me

idk honestly. I have Power Query stuff I want to share probably I’ll stick with that. Along with Visual Basic stuff I have in my backlog. These posts are fun to tinker with albeit my poor writing skills. Hopefully that will change in the future.

What about ffmpeg stuff you mentioned?

Ah, here’s a quick pics-to-gif snippet that doesn’t jank. I had difficulty with jittering initially, but -filter_complex with a lot of stack overflow browsing comes this monstrosity.

ffmpeg -f image2 -i "frame_%03d.png" -r 30 -filter_complex "crop=iw-150:ih-150,fps=30,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=16[p];[s1][p]paletteuse=dither=bayer" result.gif

Mind the file suffix though, in this case frames filenames are made in such a way there will always prepend with 3 digits of leading zeroes.

Also -r 30 flag is redundant, since its already described in the filter argument.

glhf!