Maya Python on Renaming Nodes - python

I'm trying to get up to speed on Maya Python so I have been reading This book (http://www.maya-python.com/) There is an online pdf of it... I'm on a section (The For Statement) where I'm not getting the correct result but I am also not getting any errors. If anyone could look at this and give me an idea on whats causing the problem that'd be great.
So it creates 3 file nodes fine and it is supposed to rename all 3 nodes to have the prefix 'dirt_'. But it only renames 'file1' and not the other two nodes
Here is the process:
#The FOR statement
import maya.cmds;
def process_all_textures(**kwargs):
pre = kwargs.setdefault('prefix', 'my_');
textures = kwargs.setdefault('texture_nodes');
new_texture_names = [];
for texture in textures:
new_texture_names.append(
maya.cmds.rename(
texture,
'%s%s'%(pre, texture)
)
);
return new_texture_names;
#create new Maya scene & list 3 file nodes & print their names
maya.cmds.file(new=True, f=True);
textures = [];
for i in range(3):
textures.append(
maya.cmds.shadingNode(
'file',
asTexture=True
)
);
print(textures);
#pass new texture list to process_all_textures() func and print resulting names
new_textures = process_all_textures(
texture_nodes = textures,
prefix = 'dirt_'
);
print(new_textures);
[u'file1', u'file2', u'file3']
[u'dirt_file1']

The line return new_texture_names must be indented by four spaces (not by eight ones).
In your case return statement stops the function and immediately returns the value.
#The FOR statement
import maya.cmds as mc
def process_all_textures(**kwargs):
pre = kwargs.setdefault('prefix', 'my_')
textures = kwargs.setdefault('texture_nodes')
new_texture_names = []
for texture in textures:
new_texture_names.append(mc.rename(texture,'%s%s'%(pre, texture)))
return new_texture_names
#create new Maya scene & list 3 file nodes & print their names
mc.file(new=True,f=True)
textures = []
for i in range(3):
textures.append(mc.shadingNode('file',asTexture=True))
print(textures)
#pass new texture list to process_all_textures() func and print resulting names
new_textures = process_all_textures(texture_nodes = textures,prefix = 'dirt_')
print(new_textures)
[u'file1', u'file2', u'file3']
[u'dirt_file1']

Related

Assign new material slot and random colors to existing objects

I am trying since days to assign new materials and random colors to meshes that already exist in a Blender scene.
I need to do it in Python but I cannot find a solution.
I would like each mesh to have different materials and different colors.
I have found two scripts that are very useful but I don't manage to combine them together, can anyone help please?
Thank you in advance
Script 1) assign a new material to all the meshes in a scene (but color not assigned):
import bpy
bpy.ops.object.select_all(action='SELECT')
# I separated the creation of nodes to a function so it's easier
# to edit later if needed
def create_nodes(mat):
mat.use_nodes = True
nodes = mat.node_tree.nodes
for every_node in nodes: # this removes all existing nodes
nodes.remove(every_node)
# creating Principled node and moving it:
node = nodes.new('ShaderNodeBsdfPrincipled')
node.location = (-190,100)
# creating Output node and moving it:
output_node = nodes.new('ShaderNodeOutputMaterial')
output_node.location = (40,100)
# creating the link between the two nodes:
links = mat.node_tree.links
link = links.new(node.outputs[0], output_node.inputs[0])
# this saves the currently active object so it can be restored later
active = bpy.context.object
# let's loop through all selected objects
for every_object in bpy.context.selected_objects:
# I only want to work with objects capable of having a material
if every_object.type in {'MESH','CURVE', 'SURFACE','META', 'FONT'}:
if every_object.name not in bpy.data.materials:
# if there is no material named after the object yet let's make one
mat = bpy.data.materials.new(every_object.name)
# and let's create the nodes for it
create_nodes(mat)
else:
# if the material already exists let's just use it
mat = bpy.data.materials.get(every_object.name)
if len(every_object.material_slots) == 0: # if there are no material slots
every_object.data.materials.append(mat)
# The only thing left now is to assign the material to
# all material slots. We probably do not want to loose the info
# about how the object is divided into separate materials
for every_slot in every_object.material_slots:
every_slot.material = mat
Script 2) assign a new material and a random color to an existing object in the scene (but works only for 1 object):
import bpy, random
ob = bpy.data.objects.get("Cube")
if ob != None:
# Create materials.
mat_one = bpy.data.materials.get("mat_one")
if mat_one == None:
mat_one = bpy.data.materials.new("mat_one")
mat_one.diffuse_color = (random.random(),random.random(),random.random(),random.random())
# Add materials to slots.
if len(ob.material_slots) != 1:
ob.data.materials.append(mat_one)

Calling a func from a Python file

I have two Python files (using PyCharm). In Python file#2, I want to call a function in Python file#1.
from main import load_data_from_file
delay, wavelength, measured_trace = load_data_from_file("Sweep_0.txt")
print(delay.shape)
which main is the name of python file#1. However, when I run python file#2 (the code posted at the top), I can see that whole python file#1 is also running.
Any suggestion on how I can just run print(delay. shape) without running the entire python file#1??
Here are my codes:
class import_trace:
def __init__(self,delays,wavelengths,spectra):
self.delays = delays
self.wavelengths = wavelengths
self.spectra = spectra
def load_data_from_file(self, filename):
# logging.info("Entered load_data_from_file")
with open(filename, 'r') as data_file:
wavelengths = []
spectra = []
delays = []
for num, line in enumerate(data_file):
if num == 0:
# Get the 1st line, drop the 1st element, and convert it
# to a float array.
delays = np.array([float(stri) for stri in line.split()[1:]])
else:
data = [float(stri) for stri in line.split()]
# The first column contains wavelengths.
wavelengths.append(data[0])
# All other columns contain intensity at that wavelength
# vs time.
spectra.append(np.array(data[1:]))
logging.info("Data loaded from file has sizes (%dx%d)" %
(delays.size, len(wavelengths)))
return delays, np.array(wavelengths), np.vstack(spectra)
and below I use this to get the values, however it does not work:
frog = import_trace(delays,wavelengths,spectra)
delay, wavelength, measured_trace =
frog.load_data_from_file("Sweep_0.txt")
I got this error:
frog = import_trace(delays,wavelengths,spectra)
NameError: name 'delays' is not defined
You can wrap up the functions of file 1 in a class and in file 2 you can create object and can call the specific function. However if you can share the file 1's code then it would be clear. For your reference find below...
File-1
class A:
def func1():
...
File-2
import file1 as f
# function call
f.A.func1()

How do I preserve the colours in a STEP file when modifying the geometry in Open Cascade?

I'm writing a script in python using Open Cascade Technology (using the pyOCCT package for Anaconda) to import STEP files, defeature them procedurally and re-export them. I want to preserve the product hierarchy, names and colours as much as possible. Currently the script can import STEP files, simplify all of the geometry while roughly preserving the hierarchy and re-export the step file. The problem is no matter how I approach the problem, I can't manage to make it preserve the colours of the STEP file in a few particular cases.
Here's the model I pass in to the script:
And here's the result of the simplification:
In this case, the simplification has worked correctly but the colours of some of the bodies were not preserved. The common thread is that the bodies that loose their colours are children of products which only have other bodies as their children (ie: they don't contain sub-products).
This seems to be related to the way that Open Cascade imports STEP files which are translated as follows:
Alright, now for some code:
from OCCT.STEPControl import STEPControl_Reader, STEPControl_Writer, STEPControl_AsIs
from OCCT.BRepAlgoAPI import BRepAlgoAPI_Defeaturing
from OCCT.TopAbs import TopAbs_FACE, TopAbs_SHAPE, TopAbs_COMPOUND
from OCCT.TopExp import TopExp_Explorer
from OCCT.ShapeFix import ShapeFix_Shape
from OCCT.GProp import GProp_GProps
from OCCT.BRepGProp import BRepGProp
from OCCT.TopoDS import TopoDS
from OCCT.TopTools import TopTools_ListOfShape
from OCCT.BRep import BRep_Tool
from OCCT.Quantity import Quantity_ColorRGBA
from OCCT.ShapeBuild import ShapeBuild_ReShape
from OCCT.STEPCAFControl import STEPCAFControl_Reader, STEPCAFControl_Writer
from OCCT.XCAFApp import XCAFApp_Application
from OCCT.XCAFDoc import XCAFDoc_DocumentTool, XCAFDoc_ColorGen, XCAFDoc_ColorSurf
from OCCT.XmlXCAFDrivers import XmlXCAFDrivers
from OCCT.TCollection import TCollection_ExtendedString
from OCCT.TDF import TDF_LabelSequence
from OCCT.TDataStd import TDataStd_Name
from OCCT.TDocStd import TDocStd_Document
from OCCT.TNaming import TNaming_NamedShape
from OCCT.Interface import Interface_Static
# DBG
def export_step(shape, path):
writer = STEPControl_Writer()
writer.Transfer( shape, STEPControl_AsIs )
writer.Write(path)
# DBG
def print_shape_type(label, shapeTool):
if shapeTool.IsFree_(label):
print("Free")
if shapeTool.IsShape_(label):
print("Shape")
if shapeTool.IsSimpleShape_(label):
print("SimpleShape")
if shapeTool.IsReference_(label):
print("Reference")
if shapeTool.IsAssembly_(label):
print("Assembly")
if shapeTool.IsComponent_(label):
print("Component")
if shapeTool.IsCompound_(label):
print("Compound")
if shapeTool.IsSubShape_(label):
print("SubShape")
# Returns a ListOfShape containing the faces to be removed in the defeaturing
# NOTE: For concisness I've simplified this algorithm and as such it *MAY* not produce exactly
# the same output as shown in the screenshots but should still do SOME simplification
def select_faces(shape):
exp = TopExp_Explorer(shape, TopAbs_FACE)
selection = TopTools_ListOfShape()
nfaces = 0
while exp.More():
rgb = None
s = exp.Current()
exp.Next()
nfaces += 1
face = TopoDS.Face_(s)
gprops = GProp_GProps()
BRepGProp.SurfaceProperties_(face, gprops)
area = gprops.Mass()
surf = BRep_Tool.Surface_(face)
if area < 150:
selection.Append(face)
#log(f"\t\tRemoving face with area: {area}")
return selection, nfaces
# Performs the defeaturing
def simplify(shape):
defeaturer = BRepAlgoAPI_Defeaturing()
defeaturer.SetShape(shape)
sel = select_faces(shape)
if sel[0].Extent() == 0:
return shape
defeaturer.AddFacesToRemove(sel[0])
defeaturer.SetRunParallel(True)
defeaturer.SetToFillHistory(False)
defeaturer.Build()
if (not defeaturer.IsDone()):
return shape# TODO: Handle errors
return defeaturer.Shape()
# Given the label of an entity it finds it's displayed colour. If the entity has no defined colour the parents are searched for defined colours as well.
def find_color(label, colorTool):
col = Quantity_ColorRGBA()
status = False
while not status and label != None:
try:
status = colorTool.GetColor(label, XCAFDoc_ColorSurf, col)
except:
break
label = label.Father()
return (col.GetRGB().Red(), col.GetRGB().Green(), col.GetRGB().Blue(), col.Alpha(), status, col)
# Finds all child shapes and simplifies them recursively. Returns true if there were any subshapes.
# For now this assumes all shapes passed into this are translated as "SimpleShape".
# "Assembly" entities should be skipped as we don't need to touch them, "Compound" entities should work with this as well, though the behaviour is untested.
# Use the print_shape_type(shapeLabel, shapeTool) method to identify a shape.
def simplify_subshapes(shapeLabel, shapeTool, colorTool, set_colours=None):
labels = TDF_LabelSequence()
shapeTool.GetSubShapes_(shapeLabel, labels)
#print_shape_type(shapeLabel, shapeTool)
#print(f"{shapeTool.GetShape_(shapeLabel).ShapeType()}")
cols = {}
for i in range(1, labels.Length()+1):
label = labels.Value(i)
currShape = shapeTool.GetShape_(label)
print(f"\t{currShape.ShapeType()}")
if currShape.ShapeType() == TopAbs_COMPOUND:
# This code path should never be taken as far as I understand
simplify_subshapes(label, shapeTool, colorTool, set_colours)
else:
''' See the comment at the bottom of the main loop for an explanation of the function of this block
col = find_color(label, colorTool)
#print(f"{name} RGBA: {col[0]:.5f} {col[1]:.5f} {col[2]:.5f} {col[3]:.5f} defined={col[4]}")
cols[label.Tag()] = col
if set_colours != None:
colorTool.SetColor(label, set_colours[label.Tag()][5], XCAFDoc_ColorSurf)'''
# Doing both of these things seems to result in colours being reset but the geometry doesn't get replaced
nshape = simplify(currShape)
shapeTool.SetShape(label, nshape) # This doesn't work
return labels.Length() > 0, cols
# Set up XCaf Document
app = XCAFApp_Application.GetApplication_()
fmt = TCollection_ExtendedString('MDTV-XCAF')
doc = TDocStd_Document(fmt)
app.InitDocument(doc)
shapeTool = XCAFDoc_DocumentTool.ShapeTool_(doc.Main())
colorTool = XCAFDoc_DocumentTool.ColorTool_(doc.Main())
# Import the step file
reader = STEPCAFControl_Reader()
reader.SetNameMode(True)
reader.SetColorMode(True)
Interface_Static.SetIVal_("read.stepcaf.subshapes.name", 1) # Tells the importer to import subshape names
reader.ReadFile("testcolours.step")
reader.Transfer(doc)
labels = TDF_LabelSequence()
shapeTool.GetShapes(labels)
# Simplify each shape that was imported
for i in range(1, labels.Length()+1):
label = labels.Value(i)
shape = shapeTool.GetShape_(label)
# Assemblies are just made of other shapes, so we'll skip this and simplify them individually...
if shapeTool.IsAssembly_(label):
continue
# This function call here is meant to be the fix for the bug described.
# The idea was to check if the TopoDS_Shape we're looking at is a COMPOUND and if so we would simplify and call SetShape()
# on each of the sub-shapes instead in an attempt to preserve the colours stored in the sub-shape's labels.
#status, loadedCols = simplify_subshapes(label, shapeTool, colorTool)
#if status:
#continue
shape = simplify(shape)
shapeTool.SetShape(label, shape)
# The code gets a bit messy here because this was another attempt at fixing the problem by building a dictionary of colours
# before the shapes were simplified and then resetting the colours of each subshape after simplification.
# This didn't work either.
# But the idea was to call this function once to generate the dictionary, then simplify, then call it again passing in the dictionary so it could be re-applied.
#if status:
# simplify_subshapes(label, shapeTool, colorTool, loadedCols)
shapeTool.UpdateAssemblies()
# Re-export
writer = STEPCAFControl_Writer()
Interface_Static.SetIVal_("write.step.assembly", 2)
Interface_Static.SetIVal_("write.stepcaf.subshapes.name", 1)
writer.Transfer(doc, STEPControl_AsIs)
writer.Write("testcolours-simplified.step")
There's a lot of stuff here for a minimum reproducible example but the general flow of the program is that we import the step file:
reader.ReadFile("testcolours.step")
reader.Transfer(doc)
Then we iterate through each label in the file (essentially every node in the tree):
labels = TDF_LabelSequence()
shapeTool.GetShapes(labels)
# Simplify each shape that was imported
for i in range(1, labels.Length()+1):
label = labels.Value(i)
shape = shapeTool.GetShape_(label)
We skip any labels marked as assemblies since they contain children and we only want to simplify individual bodies. We then call simplify(shape) which performs the simplification and returns a new shape, we then call shapeTool.SetShape() to bind the new shape to the old label.
The thing that doesn't work here is that as explained, Component3 and Component4 don't get marked as Assemblies and are treated as SimpleShapes and when they are simplified as one shape, the colours are lost.
One solution I attempted was to call a method simplify_subshapes() which would iterate through each of the subshapes, and do the same thing as the main loop, simplifying them and then calling SetShape(). This ended up being even worse as it resulted in those bodies not being simplified at all but still loosing their colours.
I also attempted to use the simplify_subshapes() method to make a dictionary of all the colours of the subshapes, then simplify the COMPOUND shape and then call the same method again to this time re-apply the colours to the subshapes using the dictionary (the code for this is commented out with an explanation as to what it did).
col = find_color(label, colorTool)
#print(f"{name} RGBA: {col[0]:.5f} {col[1]:.5f} {col[2]:.5f} {col[3]:.5f} defined={col[4]}")
cols[label.Tag()] = col
if set_colours != None:
colorTool.SetColor(label, set_colours[label.Tag()][5], XCAFDoc_ColorSurf)
As far as I see it the issue could be resolved either by getting open cascade to import Component3 and Component4 as Assemblies OR by finding a way to make SetShape() work as intended on subshapes.
Here's a link to the test file:
testcolours.step

How to delete smaller multiple objects in my scene using Blender script?

I am using Blender 2.8. I want to import an object into blender that is made up of a few pieces that aren't connected. So I want to split the object up and only export the largest of the pieces.
So lets say there are 3 pieces in one object, one big and two small. I'm able to turn this object into three objects, each containing one of the pieces. I would like to delete the two smaller objects and only keep the largest one. I'm thinking maybe to somehow find the surface area of the three different objects and only keep the largest while deleting all others? I'm pretty new at Blender.
bpy.ops.import_mesh.stl(filepath='path/of/file.stl')
bpy.ops.mesh.separate(type='LOOSE')
amount_of_pieces = len(context.selected_objects)
if amount_of_pieces > 1:
highest_surface_area = 0
#the rest is pseudocode
for object in scene:
if object.area > highest_surface_area:
highest_surface_area = object.area
else:
bpy.ops.object.delete()
bpy.ops.export_mesh.stl(filepath='path/of/new/file.stl')
The steps would be :-
import file
break into multiple objects
for safety, get a list of mesh objects
list the surface area of each object
get the max from the list of areas
delete the not biggest objects
export the largest
cleanup
We don't need to use bmesh to get the surface area, the normal mesh data includes polygon.area.
Using list comprehension, we can get most steps into one line each.
import bpy
# import and separate
file = (r'path/of/file.stl')
bpy.ops.import_mesh.stl(filepath= file)
bpy.ops.mesh.separate(type='LOOSE')
# list of mesh objects
mesh_objs = [o for o in bpy.context.scene.objects
if o.type == 'MESH']
# dict with surface area of each object
obj_areas = {o:sum([f.area for f in o.data.polygons])
for o in mesh_objs}
# which is biggest
big_obj = max(obj_areas, key=obj_areas.get)
# select and delete not biggest
[o.select_set(o is not big_obj) for o in mesh_objs]
bpy.ops.object.delete(use_global=False, confirm=False)
#export
bpy.ops.export_mesh.stl(filepath= 'path/of/new/file.stl')
# cleanup
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False, confirm=False)
I was able to write a code that works for this, however, it is very long and chaotic. I would appreciate it if anyone could give me some advice on cleaning it up.
import bpy
import os
import bmesh
context = bpy.context
file = (r'path\to\file.stl')
bpy.ops.import_mesh.stl(filepath= file)
fileName = os.path.basename(file)[:-4].capitalize()
bpy.ops.mesh.separate(type='LOOSE')
bpy.ops.object.select_all(action='SELECT')
piece = len(context.selected_objects)
bpy.ops.object.select_all(action='DESELECT')
high = 0
if piece > 1:
bpy.data.objects[fileName].select_set(True)
obj = bpy.context.active_object
bm = bmesh.new()
bm.from_mesh(obj.data)
area = sum(f.calc_area() for f in bm.faces)
high = area
bm.free()
bpy.ops.object.select_all(action='DESELECT')
for x in range (1, piece):
name = fileName + '.00' + str(x)
object = bpy.data.objects[name]
context.view_layer.objects.active = object
bpy.data.objects[name].select_set(True)
obj = bpy.context.active_object
bm = bmesh.new()
bm.from_mesh(obj.data)
newArea = sum(f.calc_area() for f in bm.faces)
bm.free()
if newArea > high:
high = newArea
bpy.ops.object.select_all(action='DESELECT')
else:
bpy.ops.object.delete()
bpy.ops.object.select_all(action='DESELECT')
if area != high:
bpy.data.objects[fileName].select_set(True)
bpy.ops.object.delete()
bpy.ops.export_mesh.stl(filepath= 'path/to/export/file.stl')
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False, confirm=False)

How to retrieve the created vertices of cmds.polyExtrude on Maya

I'm writing a script to change the position of the vertices created by an extrude command given a specific vector. But I can't find a way to get the newly generated vertices/faces/edges.
I tried looking in cmds.getAttr('polyExtrudeFace1') or the query mode of cmds.polyExtrudeFacet, but I can't find the right attribute/flag to get what I need.
Im not sure if there is a nice way to get the new extruded component ids but you can easily find it if you have a tool to get the before state.
One other way would be to desactivate every construction nodes , enable the polyExtrudeFace one by one and fill up a dic and then re-enable everything.
Here is an example to select the latest vertices on an extruded object :
'''
This script only work on the last polyExtrudeFace and on vertex
'''
# get the object
sel = cmds.ls(sl=True, o=True)
# get the extrude nodes, useful to create a dic with all polyExtrudeFace new component ids
extrudenodes = [e for e in cmds.listHistory(sel) if cmds.nodeType(e) == 'polyExtrudeFace']
#current vtx count
current_vtx_nb = cmds.polyEvaluate(sel, v=1)
# disable a polyExtude
cmds.setAttr("{}.nodeState".format(extrudenodes[0]), 1)
# get the previous number
previous_vtx_nb = cmds.polyEvaluate(sel, v=1)
# re-enable it
cmds.setAttr("{}.nodeState".format(extrudenodes[0]), 0)
# get the range
nb = current_vtx_nb - previous_vtx_nb
mrang = [current_vtx_nb-nb,current_vtx_nb]
# recreate the vtx s3election
out = ['{}.vtx[{}]'.format(sel[0], i) for i in range(*mrang)]
# select the vertex
cmds.select(out)
EDIT :
here is an example of the building dictionnary loop :
import maya.cmds as cmds
'''
This script build the vertices data loop
'''
class Counter:
idCounter = 0
def __init__(self):
Counter.idCounter += 1
def loopIncSel():
'relaunch the command to loop throught all key of the dic'
if sorted(dataExtrude.keys()):
count = Counter().idCounter % len(dataExtrude.keys())
k = dataExtrude.keys()[count]
cmds.select(dataExtrude[k])
# get the object
sel = cmds.ls(sl=True, o=True)
# get the extrude nodes, useful to create a dic with all polyExtrudeFace new component ids
extrudenodes = [e for e in cmds.listHistory(sel) if cmds.nodeType(e) == 'polyExtrudeFace']
# dic data :
dataExtrude = {}
for n in extrudenodes:
cmds.setAttr("{}.nodeState".format(n), 1)
# reverse the processus to re-enable,
# note that if there is node in between creating vertices and faces, it won't work
for n in extrudenodes[::-1]:
# get the previous number
previous_vtx_nb = cmds.polyEvaluate(sel, v=1)
# re-enable it
cmds.setAttr("{}.nodeState".format(n), 0)
#current vtx count
current_vtx_nb = cmds.polyEvaluate(sel, v=1)
# get the range
nb = current_vtx_nb - previous_vtx_nb
mrang = [current_vtx_nb-nb,current_vtx_nb]
# recreate the vtx s3election
dataExtrude[n] = ['{}.vtx[{}]'.format(sel[0], i) for i in range(*mrang)]
# select the vertex
# cmds.select(dataExtrude['polyExtrudeFace3'])
loopIncSel()
When applying cmds.polyExtrudeFacet onto a mesh, Maya will automatically select the new faces. Knowing this, it's easy to convert the face components to the new vertexes:
cmds.polySphere(name="pSphere1") # Create a sphere to test with.
cmds.polyExtrudeFacet("pSphere1.f[10]") # Extrude a random face.
sel = cmds.polyListComponentConversion(cmds.ls("*.f[*]", sl=True), fromFace=True, toVertex=True) # Convert faces to verts. Filter `ls` to only get face selections.
cmds.select(sel) # Select the newly created vertexes.

Categories

Resources