Move object with the same material to next free layer.


(3Dre) #1

hi yall,

Im planning to make a script that will move all objects with the same material to the next free layer. I kinda need this script to clean the models that i import from Allplan. Allplan can export C4D-format perfectly, and i use Deep exploration to convert them to OBJ’s.
In the future i’d really like to see an imoprt-script for C4D, but at the moment importing OBJ’s is fine.

The only issue with these models is that a single building elevation consists out of a lot of objects (mostly around 5000). Most of the objects have similar materials. So if I can move the objects with the same material, i can handle the cleaning a bit better.

But here’s the problem. I’m new to programming (only did the tute in tutorialguide 2 :slight_smile: ).
Are there people who want to give me some tips or advises??

Thanks in advance.


(Crouch) #2

Though this is perfectly possible with the help of python, wouldn’t it be easier to use blender?

Select your object >> SHIFT+L >> Material


(3Dre) #3

That’s the way I do it right now. but this way i have to:
1.select the object
2.select object with the same material
3.move them to a layer.

A complete buiding cosists out of (average) 10 parts
and at least 25 materials.

So i have to repeat this handling for at least 10 * 25 times.
Each elevation is a separate scene (until i cleaned them)

So it would be much easier to run a script that can do these things for me.
In the beginning i find it no problem to select the first object myself and then let the script do the rest.
At the end i want a script that can 1. select objects with same material and moves them to a layer or joins them automaticly.

This could save me a lot of time.

For those who want to give it a try, I have a .blend with 1 elevation of a simple villa with a few materials.


(Crouch) #4

I’d like to have a try at it. This is what I came up with so far:


# material2layer alpha
# contact: [email protected]

import Blender
from Blender import*

objectlist = []
nmeshlist = []
scene = Scene.getCurrent()
materiallist = []

#get a list of all meshes in the current scene
for i in range(len(Object.Get())):
	if Object.Get()[i].getType() == 'Mesh':
		objectlist.append(Object.Get()[i])
	if Object.Get()[i].getType() == 'Mesh':
		nmeshlist.append(NMesh.GetRaw(Object.Get()[i].getName()))

#get a list of all free layers
freeLayers = []
for j in range(1,21):
	freeLayers.append(j)
currentLayers = scene.getLayers()
for k in range(len(currentLayers)):
	freeLayers.remove(currentLayers[k])

#move the objects to the different layers
for l in range(len(objectlist)):
	if len(nmeshlist[l].getMaterials()) != 0:
		material = nmeshlist[l].getMaterials()[0]
		if material.getName() not in materiallist:
			materiallist.append(material.getName())
		move2 = materiallist.index(material.getName())
		objectlist[l].layers = [freeLayers[move2]]

Redraw()

Since I don’t know what the blendfile looks like I made the assumption that the material was linked to the object, and not to the datablock.
At the moment the script counts layers that are set to invisible as free layers. This is of course not entirely correct, but is workable. How exactly do you define ‘free layers’? Layers without any meshes, or layers without anything at all?
The last assumption is that we’re only dealing with meshes; not with curves, lamps, etc.

This script is just a first suggestion. Some feedback as well as the .blend is more than welcome.

Bart.


(3Dre) #5

This script does a good thing!

in a simple scene it does its job. with the scene containing the imported obj’s it gets an error:

Traceback (most recent call last):
File "mat2layer.py, line 29, in ?
AttributeError: ‘NoneType’ object has no attributes ‘getMaterials’

here’s the file the script is already in it.

http://3dre.homelinux.com/Wissel/mat2layer_test.blend

it is a simple house groundfloor. This is actually one of the smaller projects we have.

My defenition of a free/availible layer:
A free layer is a layer with no meshes in it. it’s no problem for a light or a camera to be in it.


(Crouch) #6

:o That file was so heavy for my computer that my computer nearly crashed. Quite strange, since the vertice count is quite low actually.

The positive thing about this is that besides fixing the code I also started searching for the memory sucking parts in my script. Here is the beta1-version:


# material2layer beta1
# contact: [email protected]
# ©Bartius Crouch 2005

import Blender
from Blender import*

objectlist = []
nmeshlist = []
scene = Scene.getCurrent()
materiallist = []

#get a list of all meshes in the current scene
list = Object.Get()
for i in range(len(list)):
   if list[i].getType() == 'Mesh':
      nmeshlist.append(NMesh.GetRawFromObject(list[i].getName()))
      objectlist.append(Object.Get(list[i].getName()))

#get a list of all free layers
freeLayers = []
for j in range(1,21):
   freeLayers.append(j)
currentLayers = scene.getLayers()
for k in range(len(currentLayers)):
   freeLayers.remove(currentLayers[k])

#move the objects to the different layers
for l in range(len(objectlist)):
   if nmeshlist[l]:
      if len(nmeshlist[l].getMaterials()) != 0:
         material = nmeshlist[l].getMaterials()[0]
         if material.getName() not in materiallist:
            materiallist.append(material.getName())
         move2 = materiallist.index(material.getName())
         objectlist[l].layers = [freeLayers[move2]]

Redraw()

The free layers are still determined by looking at what layers are set to invisible. This can be changed (I think it won’t be very difficult), but it will further slow down the script. So just tell me what you’d like (it’s only beta1, so there’s a lot of room for improvement).

About the cleaning you manually do: what actions do you exactly do? I’m quite interested in coding that as well (no promises though), if it is possible. By the way: is this for a commercial application, or what?


(3Dre) #7

First of all, Sorry for my late response

The plugin does a good job.
One problem occured with one of the imports:
There were more then 25 materials in the scene, the plugin didn’t want to do it’s job anymore.

Would it be an idea to joint the objects, instead of moving them to a layer?
That way the plugin can handle even more than 20 materials in a scene.

To answer your question about the use:
At the moment we use lightwave at our office. I really would like to see a major transfer to blender.
The only thing that’s holding us off is the workflow.
We’ve developed a very good workflow to get from the technical program to Lightwave.
If i can show my co-workers that it isn’t that hard to do the same things with blender only then we are going fullforce for Blender.


(Crouch) #8

At the moment it’s not very easy to join the objects in python, so I submitted a request to the python mailing list for that feature in the new ThinMesh module they’re working on.
To prevent the script from crashing when using more than 20 materials, use this script:


# material2layer beta2
# contact: [email protected]
# ©Bartius Crouch 2005

import Blender
from Blender import*

objectlist = []
nmeshlist = []
scene = Scene.getCurrent()
materiallist = []

#get a list of all meshes in the current scene
list = Object.Get()
for i in range(len(list)):
   if list[i].getType() == 'Mesh':
      nmeshlist.append(NMesh.GetRawFromObject(list[i].getName()))
      objectlist.append(Object.Get(list[i].getName()))

#get a list of all free layers
freeLayers = []
for j in range(1,21):
   freeLayers.append(j)
currentLayers = scene.getLayers()
for k in range(len(currentLayers)):
   freeLayers.remove(currentLayers[k])

#function for several materials on 1 layer
def matlayer():
   global move2, freeLayers
   if move2+1 > len(freeLayers):
      move2 -= len(freeLayers)
      matlayer()
   return()

#move the objects to the different layers
if len(freeLayers) > 0:
   for l in range(len(objectlist)):
      if nmeshlist[l]:
         if len(nmeshlist[l].getMaterials()) != 0:
            material = nmeshlist[l].getMaterials()[0]
            if material.getName() not in materiallist:
               materiallist.append(material.getName())
            move2 = materiallist.index(material.getName())
            matlayer()
            objectlist[l].layers = [freeLayers[move2]]
else:
   Draw.PupMenu("Error%t|No free layers")

Redraw()

It will put several materials on the same layer if necessary. I’ll keep you updated on the joining of the meshes.
By the way: wouldn’t it be easier if the script would only join meshes if they belong to the same object? So that each chair will be a different object for example.


(antont) #9

a few optimizations / programming style hints, if you allow:

instead of this:

 nmeshlist = [] 
 
 #get a list of all meshes in the current scene 
 list = Object.Get() 
 for i in range(len(list)): 
    if list[i].getType() == 'Mesh': 
       nmeshlist.append(NMesh.GetRawFromObject(list[i].getName())) 
       objectlist.append(Object.Get(list[i].getName()))

you can do this:

meshoblist = [o for o in Object.Get() if o.type == 'Mesh']

that is:

you rarely need range(), len() or ‘i’ iterators in python - if you need to go through all the objects in a list, you just do: “for item in list”, and the items get assigned to ‘item’ one at a time.

then, if you only need to do a simple operation on the objects you go through, you can use so-called list comprehensions … like i did above. they are also really-really fast, 'cause instead of evaluation Python stuff in every step it actually does the loop in C.

finally, you dont need two lists, 'cause the mesh a meshobject has is there as object.data anyway.

so similarily this becomes:

:#get a list of all free layers 
 freeLayers = [] 
 for j in range(1,21): 
    freeLayers.append(j) 
 currentLayers = scene.getLayers() 
 for k in range(len(currentLayers)): 
    freeLayers.remove(currentLayers[k]) 

something like:

currentLayers = scene.getLayers()
freeLayers = [layer for layer in range(1,21) if layer not in currentLayers]

ok?

i didnt test these but tried to show the principle, tried to make running code tho … hopefully was helpful :o

~Toni


(Crouch) #10

Thanks a lot for the hints. I’m still quite new to python, so it’s greatly appreciated.
I came across object.data() yesterday and it’s indeed handy, because it speeds up the script significantly.
Using list comprehensions was completely new to me, but they are really useful as well and make fur a further speed increase.
So here is the new code for the moment:


# material2layer beta3
# contact: [email protected]
# ©Bartius Crouch 2005

import Blender
from Blender import*

scene = Scene.getCurrent()
materiallist = []

#get a list of all meshes in the current scene
objectlist = [o for o in Object.Get() if o.getType() == 'Mesh']

#get a list of all free layers
freeLayers = [o for o in range(1,21) if o not in scene.getLayers()]

#function for several materials on 1 layer
def matlayer():
   global move2, freeLayers
   if move2+1 > len(freeLayers):
      move2 -= len(freeLayers)
      matlayer()
   return()

#move the objects to the different layers
if len(freeLayers) > 0:
   for l in objectlist:
      if l.getData().getMaterials():
         material = l.getData().getMaterials()[0]
         if material.getName() not in materiallist:
            materiallist.append(material.getName())
         move2 = materiallist.index(material.getName())
         matlayer()
         l.layers = [freeLayers[move2]]
else:
   Draw.PupMenu("Error%t|No free layers")

Redraw()

I’m still looking for a method to clean up the last chunk of code (moving the objects to different layers), but I’m afraid that it can’t be improved much anymore. The reason is that l.layers = [freeLayers[move2]] takes more than 90% of the entire time the script needs, and that command is absolutely necessary since it does the actual moving of the objects.


(antont) #11

i’m really happy to see that it was useful, and you could incorporate the suggestions so well :slight_smile:


def matlayer():
   global move2, freeLayers
   if move2+1 > len(freeLayers):
      move2 -= len(freeLayers)
      matlayer()
   return()

this i dont wholly understand, but i guess it is dealing with the layer numbers, keeping track of what is where … as a style thing, it is usually better not to use globals in functions, but have them receive and return objects, but it does not really matter in a small script like this.


#move the objects to the different layers
if len(freeLayers) > 0:
   for l in objectlist:
      if l.getData().getMaterials():
         material = l.getData().getMaterials()[0]
         if material.getName() not in materiallist:
            materiallist.append(material.getName())
         move2 = materiallist.index(material.getName())
         matlayer()
         l.layers = [freeLayers[move2]]

so does materiallist have names of materials, with the list index matching the layer where objects with that material are put? or what? that part is a bit difficult to understand.

as mappings go, be sure to know how to use Python dictionaries - they are taught in every tutorial, and everyone should go through one of them.

for example, if you keep a list of material names and wanna know to which layer to put them, i’d have a dictionary that maps names to numbers, like this:

mat2layer = {}
mat2layer[material] = layernum

..

layernum = mat2layer[material]

anymore. The reason is that l.layers = [freeLayers[move2]] takes more than 90% of the entire time the script needs, and that command is absolutely necessary since it does the actual moving of the objects.

ok. that part is implemented in C in the API code -if the speed is a problem, you or someone can look how it is made there for possible improvements. but i guess the speed is not bad as it is?

~Toni


(Crouch) #12

Materiallist does indeed have the names of the materials*. Then I get the index of the material belonging to the object we’re currently handling. This index retrieves the layer in the freelayers list with the same index.
This gives a problem if there are more materials than free layers, so I added the matlayer function. If the index we wish to retrieve from the freelayers list doesn’t exist it lowers the index with the length of freelayers list. Then it calls the matlayer function again so we repeat the cycle. Obviously we need to be able to escape this, so it was put in a function.

About the libraries: I know how to use them, but they’re not really necessary. I just wrote a version using libraries instead of the way I first did it, and kicking the global declarations in favour of return calls, but it didn’t really make any difference in the speed.


# material2layer using a dictionary
# contact: [email protected]
# ©Bartius Crouch 2005

import Blender
from Blender import*

scene = Scene.getCurrent()
materiallist = []

#get a list of all meshes in the current scene
objectlist = [o for o in Object.Get() if o.getType() == 'Mesh']

#get a list of all free layers
freeLayers = [o for o in range(1,21) if o not in scene.getLayers()]

#function for several materials on 1 layer
def matlayer(m):
   if m+1 > len(freeLayers):
      m -= len(freeLayers)
      m = matlayer(m)
   return(m)

#move the objects to the different layers
if len(freeLayers) > 0:
   for l in objectlist:
      if l.getData().getMaterials():
         material = l.getData().getMaterials()[0]
         if material.getName() not in materiallist:
            materiallist.append(material.getName())
   move = {}
   for m in materiallist:
      move[m] = freeLayers[matlayer(materiallist.index(m))]
   for l in objectlist:
      l.layers = [move[l.getData().getMaterials()[0].getName()]]
else:
   Draw.PupMenu("Error%t|No free layers")

Redraw()

And shortening the last bit of code to:


if len(freeLayers) > 0:
   move = {}
   for l in objectlist:
      if l.getData().getMaterials():
         material = l.getData().getMaterials()[0]
         if material.getName() not in materiallist:
            materiallist.append(material.getName())
         for m in materiallist:
            move[m] = freeLayers[matlayer(materiallist.index(m))]
         l.layers = [move[l.getData().getMaterials()[0].getName()]]
else:
   Draw.PupMenu("Error%t|No free layers") 

didn’t make any difference either.

The speed isn’t too bad, but 3Dre can better comment on that since he uses it for really large files. Besides I don’t know a single bit about C (if I ever find the time I might try to learn it and be useful to blender).

  • = if I put the materials instead of the names in the list, the script somehow treats the material of each object as unique, and when I use a library it returns a KeyError (has to do with mutable objects). Since using the names works without any problem I didn’t get any further in the origin of the error.

Bart.


(3Dre) #13

Wow, you guys rock.

I will test the scripts as soon as i can.

I will reply with the results as soon as i can.


(3Dre) #14

Im trying the script on a .obj-file with 3550 objects, aprox 89000 faces. It contains 27 materials.

beta2 works correct. No problems handling large files with multiple materials. takes about 5 seconds.
beta3 also works correct, takes about 3 seconds
beta4(with dictionary) works, takes about 5 seconds.

About joining objects: the guys at our technical departement give materials to each object. They do that for their own comfert and for me. every object that shares a material, belong to the same group of objects. eg inner and outer walls, doors, etc.

Right now, I use this script to move objects belonging to the same group to a layer. When this is done, i join these objects to a single object. Then the only thing that need to be done are: remove 1-point- and 2-point-polys from it. They go to a separate scene in the same file (they may be usefull again).

hopefully the joining is going to be a possibility


(antont) #15

sure, there are python-written functions for joining i believe, and it is in the todo-list in things to be added to the new Mesh module (as there already is the required function in c, it is not much work to add a hook to it in the py api).

great to hear that these simple solutions can be used for such heavy-lifting sounding job you describe :slight_smile:

~Toni