Scene object selective export using Blender Python API 2.6 - python

I am writing a custom export script to parse all the objects in a blender file, filter them by name, then check to make sure that they meet some specific criteria.
I am using Blender 2.68a. I've created a blender file with some basic 2d and 3d meshes, as well as some that should fail my test criteria. I am working in the internal Python console inside of Blender. This is the only way to work with the blender python API, as their python environment is customized.
I've sorted how to iterate through the objects using a for loop and the D.objects iterator, then check for name matches using regular expressions, and then get a mesh from the object using:
mesh = obj.to_mesh(C.scene, True, 'RENDER') #where obj is an bpy.data.object[index] in the scene
mesh.update(True, True)
mesh.polygons[index].<long list of possible functions>
lets me access an array of polygons to know if there is a set of vertices with edges that form a polygon, and what their key values are.
What I can't sort out is how to determine from the python console if a poly is a face or just a poly. Is there a built in function, or what tests can i perform to programmatically determine this? For example, I can have a mesh 4 vertices with 4 edges that do not have a face, and I do not want to export this, but if i were to edit the same 4 vertices/edges and put a face on it, then it becomes a desirable export.
Can anyone explain the bpy.data.object data structure or explain where the "faces" are stored? it seems as though it would be a property of the npolys themselves, but the API does not make it obvious. Any assistance in clarifying this would be greatly appreciated. Cheers.

So, i asked this question on the blender.org forums, http://www.blender.org/forum/viewtopic.php?t=28286&postdays=0&postorder=asc&start=0 and a very helpful individual has helped me over the past few days each time I got stuck in my own efforts to plow through this.
The short list of answers is:
1) All polygons are faces. If it isnt stored as a polygon, it isnt a face.
2) using the to_mesh() function on an object returns a copy of the function, and so any selections that are done to the copy are not reflected by the context and therefore the methodology I was using was flawed. The only way to access the live object is through use of:
bpy.data.objects[<index or object name>].data.vertices[<index>].co[<0,1,2> which correspond to x,y,z respectively]
bpy.data.objects[<index or object name>].data.polygons[<index>].edge_keys
The first one gives you access to an ordered index of all the vertices in the object(assuming it is of type 'MESH'), and their coordinates.
The second one gives you access to an 2d array of ordered pairs which represent edges. The numbers it contains within the tuples correspond to the index value in the vertices list from the first command, and so you can get the coordinates which the edge goes between.
One can also create a new BMesh object and copy the object you are interested in into the BMesh. This gives you a lot more functionality that you can't access on the live object. The code in answer 3 shows an example of this.
3)see below for answer to my question regarding checking faces in a mesh.
it turns out that one way to determine if an object has faces and all edges are a part of a face is to use the following code snippet written by a helpful user, CoDEmanX on the above thread.
import bpy, bmesh
for ob in bpy.context.scene.objects:
if ob.type != 'MESH':
continue
bm = bmesh.new()
bm.from_object(ob, bpy.context.scene)
if len(bm.faces) > 0 and 0 not in (len(e.link_faces) for e in bm.edges):
print(ob.name, "is valid")
else:
print(ob.name, "has errors")
I changed this a little bit, as i didnt want it to loop through all the objects, and instead i've got this as a function that returns true if the object passed in is valid and false otherwise. This lets me serialize my calls so that my addon only tries to validate the objects which have a name which matches a regex.
def validate(obj):
import bpy, bmesh
if obj.type == 'MESH':
bm = bmesh.new()
bm.from_object(obj, bpy.context.scene)
if len(bm.faces) > 0 and 0 not in (len(e.link_faces) for e in bm.edges):
return True
return False

Related

Questions about the Gmsh Python API

I am trying to write a script that will automatically mesh geometries for CFD analysis using the Gmsh Python API. There are a few issues I am running into:
First of all, I would like to be able to write Gmsh script files (.geo) for debugging purposes. I looked through the source code of the Gmsh API and found that the .geo_unrolled extension is supported for the gmsh.write() function, but not just .geo. This extension does the trick mostly, but it seems that any meshing operations (such as marking curves as transfinite) or transformations (such as dilate) are not written to the output file when using gmsh.write('test.geo_unrolled'). I assume this has something to do with the _unrolled part. But is there any way to get the full Gmsh script out of the API?
Secondly, when I try to make a copy of a spline like in this example:
p1 = gmsh.model.geo.addPoint(-1, 0.5, 0, 0.1)
p2 = gmsh.model.geo.addPoint(0, 1, 0, 0.1)
p3 = gmsh.model.geo.addPoint(1, 0.5, 0, 0.1)
s1 = gmsh.model.geo.addSpline([p1, p2, p3])
s2 = gmsh.model.geo.copy([s1])
I get ValueError: ('gmshModelGeoCopy returned non-zero error code: ', 1). The error code, 1, seems to indicate that the tag of the original spline (s1) cannot be found when copy() is called. Am I missing something here? I have tried, for example, to call gmsh.model.geo.synchronize() before attempting to call copy(), but this had no effect.
Finally, when I use the dilate transformation in the Gmsh GUI using Modules - Geometry - Elementary entities - Transform - Scale, checking the Apply scaling on copy option in the dialog, on the example spline from above, I indeed get a scaled version of the curve as expected, including the three points. Assuming I was able to accomplish the same with the API, how do I then refer to the three new points that the scaled spline goes through, for example, if I wanted to draw a line between the start point of the original spline and that of the scaled spline?
In the end, what I want to accomplish is the following: draw a spline through a list of points, create a scaled copy of this spline, draw lines between the start and end points, and create a plane surface bounded by the two splines and lines. Is there a better way to do this than what I am trying to do with the dilation?
It's probably too late, but you never know.
I've never had to create .geo files using the API. But I found this discussion in the Gmsh mailing list archive, which may be helpful.
Regarding your error with copy, you have to specify the dimension of the entity to be copied, and not just the tag (check the documentation, which refers to dimTag). It's the same thing with transformations such as rotate, symmetrize etc. Using the following should work:
s2 = gmsh.model.geo.copy([(1, s1)])
NB: when copying only one entity, I think either the inner parentheses or the brackets are superfluous, and otherwise you have to provide a list of tuples of the form [(dim_1, tag_1), (dim_2, tag_2), ..., (dim_n, tag_n)].
Keep in mind that copy will return a variable of the same kind (list of tuples), i.e. in your case the variable s2 will be [(1, tag_s2)]. Therefore you might not want to use the same kind of variable name, since in order to get the tag you'll have to use s2[0][1] instead of simply s2.
Here you have a partial answer to the following question, as the tags of copied entities will be contained in your return variable.
Hope that helps you or others!

How to create set from list of sets in Python?

In abaqus Python script, several Plies have a large number of copies, each of which has many fibers. In each Fiber, a set of edges has been selected: App1-1, App1-2, ..., App99-1, App99-2, ..., App99-88. How to create a new set that will contain all or some of these set of edges?
Thank you.
Code:
allApps=[]
...
for i in range(Plies):
...
for j in range (Fiber):
appSet = Model.rootAssembly.Set(edges=
Model.rootAssembly.instances['Part'+str(i+1)+'-'+str(1+j)].edges[0:0+1],
name='App'+str(i+1)+'-'+str(1+j))
allApps.append(appSet)
I can guess it should be something like this:
Model.rootAssembly.Set(name='allAppEdges', edges=.?.Array(allApps))
but I'm not sure about this and I have no idea about correct syntax
I tested the following on a simple part and it worked for me. I think you could adapt this to achieve what you're trying to do for your specific model. The key is the part.EdgeArray type. For whatever reason Abaqus requires your edges be supplied within that type, rather than a simple list or tuple. The Abaqus documentation is not clear on this, and when you pass a list of edges it will fail with a vague error: Feature creation failed.
from abaqus import *
import part
mdl = mdb.models['Model-1']
inst = mdl.rootAssembly.instances['Part-1-1']
# Loop through all edges in the instance and add them to a list
my_edges = []
for e in inst.edges:
my_edges.append(e)
# Create a new set with these edges
#mdl.rootAssembly.Set(name='my_edges', edges=my_edges) # This will fail because my_edges needs to be an EdgeArray
mdl.rootAssembly.Set(name='my_edges', edges=part.EdgeArray(my_edges))
For others that may find themselves here - similar types are available for vertices, faces, and cells: part.VertexArray, part.FaceArray, and part.CellArray.

How to create two dimensional set objects under pyomo.environ module

I tried to create a LP model by using pyomo.environ. However, I'm having a hard time on creating sets. For my problem, I have to create two sets. One set is from a bunch of nodes, and the other one is from several arcs between nodes. I create a network by using Networkx to store my nodes and arcs.
The node data is saved like (Longitude, Latitude) in tuple form. The arcs are saved as (nodeA, nodeB), where nodeA and nodeB are both coordinates in tuple.
So, a node is something like:
(-97.97516252657978, 30.342243012086083)
And, an arc is something like:
((-97.97516252657978, 30.342243012086083),
(-97.976196300350608, 30.34247219922803))
The way I tried to create a set is as following:
# import pyomo.envrion as pe
# create a model m
m = pe.ConcreteModel()
# network is an object I created by Networkx module
m.node_set = pe.Set(initialize= self.network.nodes())
m.arc_set = pe.Set(initialize= self.network.edges())
However, I kept getting an error message on arc_set.
ValueError: The value=(-97.97516252657978, 30.342243012086083,
-97.976196300350608, 30.34247219922803) does not have dimension=2,
which is needed for set=arc_set
I found it's weird that somehow my arc_set turned into one tuple instead of two. Then I tried to convert my nodes and arcs into string but still got the error.
Could somebody show me some hint? Or how do delete this bug?
Thanks!
Underneath the hood, Pyomo "flattens" all indexing sets. That is, it removes nested tuples so that each set member is a single tuple of scalar values. This is generally consistent with other algebraic modeling languages, and helps to make sure that we can consistently (and correctly) retrieve component members regardless of how the user attempted to query them.
In your case, Pyomo will want each member of the the arc set as a single 4-member tuple. There is a utility in PyUtilib that you can use to flatten your tuples when constructing the set:
from pyutilib.misc import flatten
m.arc_set = pe.Set(initialize=(tuple(flatten(x)) for x in self.network.edges())
You can also perform some error checking, in this case to make sure that all edges start and end at known nodes:
from pyutilib.misc import flatten
m.node_set = pe.Set( initialize=self.network.nodes() )
m.arc_set = pe.Set(
within=m.node_set*m.node_set,
initialize=(tuple(flatten(x)) for x in self.network.edges() )
This is particularly important for models like this where you are using floating point numbers as indices, and subtle round-off errors can produce indices that are nearly the same but not mathematically equal.
There has been some discussion among the developers to support both structured and flattened indices, but we have not quite reached consensus on how to best support it in a backwards compatible manner.

Abaqus Python getByBoundingBox command

I have a 2D part in Abaqus with many partitions and I therefore want to select many edges with the getByBoundingBox command to create a surface set. This is the bit of code I have:
p = mdb.models['Model-1'].parts['Plate']
s = p.edges
edges = s.getByBoundingBox((0,0.02,0,0.003,0.04,0))
p.Surface(side1Edges=edges, name='r1')
But it gives me the following error: "edges = s.getByBoundingBox((0,0.02,0,0.003,0.04,0)) TypeError: arg1; found tuple, expecting float".
Any advice much appreciated.
The corners of the bounding box should be provided as 6 separate numbers and not as a single tuple. The solution is very simple, just change the leading "((" and trailing "))" to single "(" and ")". So the call looks like this s.getByBoundingBox(0,0.02,0,0.003,0.04,0).
this comes quite late but in case anyone enters and has the same doubt:
When telling to Abaqus which edge/face/element you are actually selecting, sometimes you need to specify the "ID" of that object, that's why it's asking for a float instead of a tuple in the error message. This can be solved as:
You select the edge/face/node/element you want:
edge = s.getByBoundingBox((0,0.02,0,0.003,0.04,0))
Create a intermediate variable to know the "ID" of the element:
edge_id = edge.id
You now can refenciate it in the dialog for creating surfaces:
p.Surface(side1Edges=p.edges[edge_id], name='r1')
In this case, you're telling Abaqus to select the edge with the id "edge_id" from all the edges that your part "p" has.
This happens many times and you've to be aware what Abaqus is expecting from the code. Sometimes can be the object itselft, a tuple of elements or simply a float expressed as a tupple e.g: edge = (number, )
Regards
I tried the modified code on a 2D plate with the following code
p = mdb.models['Model-1'].parts['Plate']
s = p.edges
edges=s.getByBoundingBox(0,0,0,25,25,1)
And it does not crash. But its not really clear how you can create a surface using this. You need to use a different strategy to achieve what you want. You can find create a surface using the 'pointOn' method.

Networkx node traversal

Using Python's Networkx library, I created an undirected graph to represent a relationship network between various people. A snippet of my code is below:
import networkx as nx
def creategraph(filepath):
G=nx.Graph()
#All the various nodes and edges are added in this stretch of code.
return G
From what I understand, each node is basically a dictionary. The problem that this presents to me is that I want to perform a different kind of Random Walk algorithm. Now before you jump on me and tell me to use one of the standard functions of the Networkx library, I want to point out that it is a custom algorithm. Suppose I run the creategraph function, and the G object is returned and stored in another object (let's call it X). I want to start off at a node called 'Bob.' Bob is connected to Alice and Joe. Now, I want to reassign Y to point to either Alice or Bob at random (with the data I'm dealing with, a given node could have hundreds of edges leaving it). How do I go about doing this? Also, how do I deal with unicode entries in a given node's dict (like how Alice and Joe are listed below?)
X = creategraph("filename")
Y=X['Bob']
print Y
>> {u'Alice': {}, u'Joe': {}}
The choice function in the random module could help with the selection process. You don't really need to worry about the distinction between unicode and string unless you're trying to write them out somewhere as sometimes unicode characters aren't translatable into the ASCII charset that Python defaults to.
The way you'd use random.choice would be something along the lines of:
Y = Y[random.choice(Y.keys())]

Categories