sure, let me break down your questions into relevant chunks:
import bpy, bmesh
Someone mentioned that the import commands aren’t needed any longer?
Not sure who said that or why, but it’s python and python needs imports to be able to work correctly.
from math import radians
Yeah, I calculated the radian number ahead of time rather than using the math command. Since the angle threshold isn’t actually All that important to be accurate I thought it might actually run faster if I just gave it a number. But you’re right, this is more understandable. When I go back to it, it won’t just be some random looking number later on.
It will technically run faster if you just give it a number, but we’re talking like nanoseconds here. Readability should always be factored into the code you write because confusing code leads to inefficient code (or errors), this is the basis of the “Readability Counts” commandment in the pep guidelines. Being able to come back and understand exactly what your code is doing even years later is also very important.
ctx = bpy.context.copy()
ctx variable creation, ok, you’re avoiding writing bpy.context over and over. I like that, but I don’t understand the copy() part.
Ok so this one is Blender specific. when you run copy() on a context type object you create a dictionary copy of it, which you can use to modify and pass into operators as an override. Say for example, you have a script that runs an operator that will throw an invalid context exception if you don’t launch it from a 3D viewport. With context overrides, you can override your context.area to point to another area, even if it isn’t active in the current context.
Here’s another area related example- you’re modeling in the 3D viewport and you want to pack your UVs without going over to the UV area for some reason. With a context override this is possible. I’m using it your script to override the selection so the operator thinks certain objects are selected even though they are not. This avoids us having to go in and call object.select_set(True/False) on all of the various objects we need to work on (and then restore whatever the user’s selection was at the end).
for o in bpy.context.scene.objects:
if not isinstance(o.data, bpy.types.Mesh):
It Looks to me like you made a function named isinstance, but I don’t understand how that works. isinstance doesn’t exist already right, so you’ve named it that? I found it being used once in the Python API documentation, but only in an example code without any explaination. It seems like it’s looking at the current object’s data, and also checking if it is a mesh. I’m not understanding this one, and can’t find results with google that help me understand it. ifinstance( but how do I know what goes here?)
Actually, isinstance() is a built in python function. You give it an object as your first parameter, and a type as your second parameter and it will return either True or False. You can also use a tuple of types as the second parameter if you need to check multiple types. For example, if I need to see if an element is a vert, edge, or face, I would do something like this:
isinstance(my_element, (bmesh.types.BMVert, bmesh.types.BMEdge, bmesh.types.BMFace))
ctx['object'] = o
ctx['active_object'] = o
ctx['selected_objects'] = [o]
ctx['selected_editable_objects'] = [o]
There’s not much about this block I understand. This creates a context override to handle the different types of objects, like if it’s selected, editable, active, etc. But I don’t understand how the brackets work, and why the = o, then on the other two = [o]. I don’t fully understand what brackets are doing in python yet though either. Best I can find is that it brackets hold lists. But I’m confused if you are saying, an object is o, then an active object is o, then selected objects is [o]. The first two are single items so don’t need to be defined as a list? But I’m confused because I thought the “for o in bpy.context.scene.objects” would automatically be handling this. Wouldn’t that just do that for every single object?
This is where the operator override that I mentioned above comes into play. We are overriding the relevant parts of the context that
object.shade_smooth() is expecting. In this scenario, we are looping over every object in the scene and handling each object one by one, so on each loop we are changing the context override to point at the current object. selected_objects and selected_editable_objects are both expected to be lists, so that is why you see the object in brackets.
I was running actions on the object by changing to edit mode, merging, then returning to object mode, but you are:
- creating a new bmesh, which is just a python memory only mesh object?
- change the bmesh object to be the same as the .data from the current object
- remove doubles, not sure I understand why you need more information than the distance here, and the documentation wasn’t much help in me understanding those other values.
- push the bmesh data back to the object data
- delete the bmesh data to free up that memory.
bm = bmesh.new()
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=MergeThreshold)
Yes, under the hood the built-in operator to remove doubles actually just does what I’ve written here (it actually uses bmesh.ops.remove_doubles). Notice that I’m calling the bmesh operator for remove doubles, not the mesh operator with the same name, this is why the parameters are different. the bmesh operator needs to know what bmesh it’s operating on (there could be many you’ve created), and which verts should be considered.
The main difference between what I’ve done and what you were doing is that simulating user actions (actually changing edit/object mode, updating selections, etc), has a huge amount of overhead. You might not notice it on a small scene with a simple script- but something like this where you’re importing hundreds of objects with nearly a million triangles, you will definitely notice. This is one of the reasons bmesh was created- it allows us to modify meshes without the overhead of having to make our scripts pretend to be users.