Maya Python Create joints Hierachy - python

I'm trying to create o hierarchy of joints for a skeleton in maya python. And I'm doing this
def makeSkelet(args):
helperSkelet('Root_Locator', 'root_Joint')
helperSkelet('Pelvis_Locator', 'pelvis_Joint')
helperSkelet('Spine_Locator', 'spine_Joint')
helperSkelet('Spine01_Locator', 'spine01_Joint')
helperSkelet('Spine02_Locator', 'spine02_Joint')
helperSkelet('Neck_Locator', 'neck_Joint')
helperSkelet('Head_Locator', 'head_Joint')
mc.select(cl=True)
helperSkelet('ArmL_Locator', 'armL_joint')
helperSkelet('ElbowL_Locator', 'elbowL_Joint')
helperSkelet('HandL_Locator', 'handL_Joint')
mc.select(cl=True)
helperSkelet('ArmR_Locator', 'armR_joint')
helperSkelet('ElbowR_Locator', 'elbowR_Joint')
helperSkelet('HandR_Locator', 'handR_Joint')
mc.select(cl=True)
helperSkelet('HipL_Locator', 'hipL_joint')
helperSkelet('KneeL_Locator', 'kneeL_Joint')
helperSkelet('AnkleL_Locator', 'ankleL_Joint')
helperSkelet('FootL_Locator', 'footL_Joint')
mc.select(cl=True)
helperSkelet('HipR_Locator', 'hipR_joint')
helperSkelet('KneeR_Locator', 'kneeR_Joint')
helperSkelet('AnkleR_Locator', 'ankleR_Joint')
helperSkelet('FootR_Locator', 'footR_Joint')
Now this works fine, because the joints must be created in this order. (the helper skelet is a function where i create the joint with the reference to a locator position)
I was wondering if there is a more optimized way to do this considering the order or creation must be kept .
Thank you

If by "optimize" you mean getting better performace, I agree with what #downshift said.
If what you meant was instead making your code "cleaner" (more general or scalable or simply more pythonic), here's another way you can do the same, which is a bit more compact (and separates the logic from your input):
def helperSkeletGroup(group, symmetric=False):
# quick workaround to capitalize a word, leaving the following letters unchanged
capitalize = lambda s: s[:1].upper() + s[1:]
symmetric_group = []
for elem in group:
if symmetric:
symmetric_group.append('{0}R'.format(elem))
elem = '{0}L'.format(elem)
# format locators and joints
loc, joint = '{0}_Locator'.format(capitalize(elem)), '{0}_Joint'.format(elem)
helperSkelet(loc, joint)
cmds.select(cl=True)
if symmetric_group:
helperSkeletGroup(symmetric_group)
helperSkeletGroup(['root', 'pelvis', 'spine', 'spine01', 'spine02', 'neck', 'head'])
helperSkeletGroup(['arm', 'elbow', 'hand'], True)
helperSkeletGroup(['hip', 'knee', 'ankle', 'foot'], True)
This comes with a few advantages:
it handles symmetry for you
the code doesn't grow too much, as the number of joints increases
if at some point you want to change the naming convention for locators and joints, you can do it by changing a single line
Alternatively, you could go with an OOP approach.
Here's an example:
class Skeleton:
def __init__(self):
self.joint_groups = []
def add_joint_group(self, group, symmetric=False):
# quick workaround to capitalize a word, leaving the following letters unchanged
capitalize = lambda s: s[:1].upper() + s[1:]
processed, processed_symmetric = [], []
for elem in group:
if symmetric:
processed_symmetric.append('{0}R'.format(elem))
elem = '{0}L'.format(elem)
processed.append(('{0}_Locator'.format(capitalize(elem)), '{0}_Joint'.format(elem)))
self.joint_groups.append(processed)
if processed_symmetric:
self.add_joint_group(processed_symmetric)
def helper_skelet(self, loc, joint):
# your helper logic goes here
print loc, joint
def build(self):
for group in self.joint_groups:
for loc, joint in group:
self.helper_skelet(loc, joint)
cmds.select(cl=True)
skeleton = Skeleton()
skeleton.add_joint_group(['root', 'pelvis', 'spine', 'spine01', 'spine02', 'neck', 'head'])
skeleton.add_joint_group(['arm', 'elbow', 'hand'], True)
skeleton.add_joint_group(['hip', 'knee', 'ankle', 'foot'], True)
from pprint import pformat
print pformat(skeleton.joint_groups)
skeleton.build()
Here the code is a bit longer but it is all contained in a single object, where you could store additional data, which you get only at construction time and which you might need later on.
EDIT (to answer #Giakaama's question in the comment):
If you save the class in a separate file skeleton_class.py, you can import the class in your main.py (or whatever you want to call it), as such:
from skeleton_class import Skeleton
where the lower-case skeleton_class refers to your module (read: file) and Skeleton is the class itself.
Once you've done that, you can do the same as above:
skeleton = Skeleton()
skeleton.add_joint_group(['root', 'pelvis', 'spine', 'spine01', 'spine02', 'neck', 'head'])
skeleton.add_joint_group(['arm', 'elbow', 'hand'], True)
skeleton.build()

Related

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

Creating a Hierachy in Maya

I've written some code for a tool that duplicates and moves controllers/objects to joints. Its really just code to copy and move one object to a list of other objects. Its very basic but it does work.
The pasted code is a snippet that I've taken out from the rest but will work if run in Maya.
This code in particular will create a nurbsCurve, put it inside a group, then move that group to the first object on the list. I then used a loop to do it again, but then parent the group underneath the nurbsCurve from the previous group to create a parent-hierarchy all the way down.
import maya.cmds as cmds
def setZero(target):
cmds.setAttr("%s.translateX" % target, 0)
cmds.setAttr("%s.translateY" % target, 0)
cmds.setAttr("%s.translateZ" % target, 0)
cmds.setAttr("%s.rotateX" % target, 0)
cmds.setAttr("%s.rotateY" % target, 0)
cmds.setAttr("%s.rotateZ" % target, 0)
selJoint = cmds.ls(selection = True, shortNames = True)
firstCtrl = cmds.circle(normal =( 1,0,0))
firstGrp = cmds.group(firstCtrl)
cmds.parent(firstGrp,selJoint[0])
setZero(firstGrp)
cmds.parent(firstGrp, world = True)
#Use loop for the rest of the joints
for joint in selJoint:
#Skip the first joint since its already done
if joint == selJoint[0]:
continue
circleCtrl = cmds.circle(normal =( 1,0,0))
offsetGrp = cmds.group(circleCtrl)
cmds.parent(offsetGrp, joint)
setZero(offsetGrp)
cmds.parent(offsetGrp, world = True)
cmds.parent(offsetGrp, firstCtrl) #Parent new offset Group to the old controller
firstCtrl = circleCtrl #The new controller is now the target for the next offset/ctrl to be parented under
It works as intended but I get this warning:
Warning: Cannot parent components or objects in the underworld
I've tried looking this issue up, but I haven't found anything on how to fix the problem. But I do know that the "underworld" is another space in Maya, and that it can cause issues down the line.
The warning is being thrown at the end on this line cmds.parent(offsetGrp, firstCtrl), and if you print out firstCtrl it'll output something like this:
[u'nurbsCircle1', u'makeNurbCircle1']
So what it's doing is it's trying to parent firstCtrl and 'nurbsCircle1' to 'makeNurbCircle1'. Since 'makeNurbCircle1' is a node with no transform, it throws that warning, because obviously you can't parent to something with no transform.
Luckily it's very easy to fix. Now that we know that it's a list, we just parent to the first index of it like so: cmds.parent(offsetGrp, firstCtrl[0])
If you want more info on this same warning then you can also check out this question.

Python: Data-structure and processing of GPS points and properties

I'm trying to read data from a csv and then process it on different way. (For starter just the average)
Data
(OneDrive) https://1drv.ms/u/s!ArLDiUd-U5dtg0teQoKGguBA1qt9?e=6wlpko
The data looks like this:
ID; Property1; Property2; Property3...
1; ....
1; ...
1; ...
2; ...
2; ...
3; ...
...
Every line is a GPS point. All points with same ID together (for example 1) produce one Route. The routes are not of the same length and some IDs are skipped. So it isn't a seamless increase of numbers.
I may need to add, that the points are ALWAYS the same set of meters apart from each other. And I don't need the XY information currently.
Wanted Result
In the end I want something like this:
[ID, AVG_Property1, AVG_Property2...] [1, 1.00595, 2.9595, ...] [2,1.50606, 1.5959, ...]
What I got so far
import os
import numpy
import pandas as pd
data = pd.read_csv(os.path.join('C:\\data' ,'data.csv'), sep=';')
# [id, len, prop1, prop2, ...]
routes = numpy.zeros((data.size, 10)) # 10 properties
sums = numpy.zeros(8)
nr_of_entries = 0;
current_id = 1;
for index, row in data.iterrows():
if(int(row['id']) != current_id): #after the last point of the route
routes[current_id-1][0] = current_id;
routes[current_id-1][1] = nr_of_entries; #how many points are in this route?
routes[current_id-1][2] = sums[0] / nr_of_entries;
routes[current_id-1][3] = sums[1] / nr_of_entries;
routes[current_id-1][4] = sums[2] / nr_of_entries;
routes[current_id-1][5] = sums[3] / nr_of_entries;
routes[current_id-1][6] = sums[4] / nr_of_entries;
routes[current_id-1][7] = sums[5] / nr_of_entries;
routes[current_id-1][8] = sums[6] / nr_of_entries;
routes[current_id-1][9] = sums[7] / nr_of_entries;
current_id = int(row['id']);
sums = numpy.zeros(8)
nr_of_entries = 0;
sums[0] += row[3];
sums[1] += row[4];
sums[2] += row[5];
sums[3] += row[6];
sums[4] += row[7];
sums[5] += row[8];
sums[6] += row[9];
sums[7] += row[10];
nr_of_entries = nr_of_entries + 1;
routes
My problem
1.) The way I did it, I have to copy paste the same code for every other processing approach, since as stated I need to do multiple different way. Average is just an example.
2.) The reading of the data is clumsy and fails when IDs are missing
3.) I'm a C# Developer, so my approach would be to create a Class 'Route' which has all the points and then provide methods for 'calculate average for prop 1'. Or something. This way I could also tweak the data if needed. (extreme values for example). But I have no idea how this would be done in Phyton and if this is a reasonable approach in this language.
4.) Is there a more elegant way to iterate through the original csv and getting like Route ID 1, then Route ID 2 and so on? Maybe something like LINQ Queries in C#?
Thanks for any help.
He is a solution and some ideas you can use. The example features multiple options for the same issue so you have to choose which fits the purpose best. Also it is Python 3.7, you didn't specify a version so i hope this works.
class Route(object):
"""description of class"""
def __init__(self, id, rawdata): # on startup
self.id = id
self.rawdata = rawdata
self.avg_Prop1 = self.calculate_average('Prop1')
self.sum_Prop4 = None
def calculate_average(self, Prop_Name): #selfreference for first argument in class method
return self.rawdata[Prop_Name].mean()
def give_Prop_data(self, Prop_Name): #return the Propdata as list
return self.rawdata[Prop_Name].tolist()
def any_function(self, my_function, Prop_Name): #not sure what dataframes support so turning it into a list first
return my_function(self.rawdata[Prop_Name].tolist())
#end of class definiton
data = pd.read_csv('testdata.csv', sep=';')
# [id, len, prop1, prop2, ...]
route_list = [] #List of all the objects created from the route class
for i in data.id.unique():
print('Current id:', i,' with ',len(data[data['id']==i]),'entries')
route_list.append(Route(i,data[data['id']==i]))
#created the Prop1 average in initialization of route so just accessing attribute
print(route_list[1].avg_Prop1)
for current_route in route_list:
print('Route ',current_route.id , ' Properties :')
for i in current_route.rawdata.columns[1:]: #for all except the first (id)
print(i, ' has average ', current_route.calculate_average(i)) #i is the string of the column not just an id
#or pass any function that you want
route_list[1].sum_Prop4 = (route_list[1].any_function(sum,'Prop4'))
print(route_list[1].sum_Prop4)
#which is equivalent to
print(sum(route_list[1].rawdata['Prop4']))
To adress your individual problems out of order:
For 2. and 4.) Looping only over the existing Ids (data.id.unique()) solves the problem. I have no idea what LINQ Queries are, but i assume they are similar. In general, Python has a great way of looping over objects (like for current_route in route_list), which is worth looking into if you want to use it a little more.
For 1. and 3.) Again looping solves the issue. I created a class in the example, mostly to show the syntax for classes. The benefits and drawbacks for using classes should be the same in Python as in C#.
As it is right now the class probably isn't great, but this depends on how you want to use it. If the class should just be a practical way of storing and accessing data it shouldn't have the methods, because you don't need an individual average method for each route. Then you can just access it's data and use it in a function like in sum(route_list[1].rawdata['Prop4']). If however, depending on the data (amount of rows for example) different calculations are necessary, it might come in handy to use the method calculate_average and differentiate in there.
An other example would be the use of the attributes. If you need the average for Prop1 every time, creating it at the initialization sees a good idea, otherwise i wouldn't bother always calculating it.
I hope this helps!

My functions definition give different result when I saved as tools.py file

I have written two functions in Jupiter lab. When I integrated those to transfer it to a tools.py file and imported in Jupyter lab, I get different results. Can anyone tell me what the difference is?
Actually, the functions are for converting a kind of nested data frame to a flat data frame. I feel the problem is in a way that I used to apply function with two arguments
Here is my code in jupyter lab:
def findTS(s):
out=""
for i,r in om.iterrows():
sci=r["sec_ID"]
if sci in s:
out=r["type_sec"]
exit
return(out)
def Js2Geodf(df):
# df=OmegaJ["chapters"][0]
om=json_normalize(df,"section")
om=om.dropna(subset=["sec_part"])
l=[]
for i,x in om.iterrows():
k={"type_sec":x["type_sec"]}
l.append([x["sec_part"]])
listItems=list(flatten_list(l))
dfout=pd.DataFrame(listItems)
dfout["type_sec"]=dfout.apply(lambda x: findTS(x["ID"]),axis=1)
return(dfout)
Then I integrated those in a way that I can use them independently as a method in module tools.py
def findTS(s,df):
out=""
om=json_normalize(df,"section")
for i,r in om.iterrows():
sci=r["sec_ID"]
if sci in s:
out=r["type_sec"]
exit
return(out)
def Js2Geodf(df):
# df=OmegaJ["chapters"][0]
om=json_normalize(df,"section")
om=om.dropna(subset=["sec_part"])
l=[]
for i,x in om.iterrows():
k={"type_sec":x["type_sec"]}
l.append([x["sec_part"]])
listItems=list(flatten_list(l))
dfout=pd.DataFrame(listItems)
dfout["type_sec"]=dfout.apply(lambda x: findTS(x["ID"],df),axis=1)
return(dfout)
As you see in each function, there are some small changes.
In the first function:
-def findTS(s)--->def findTS(s,df)
-adding ....om=json_normalize(df,"section")
In the second function in the last line:
-dfout["type_sec"]=dfout.apply(lambda x: findTS(x["ID"]),axis=1)-->
-dfout["type_sec"]=dfout.apply(lambda x: findTS(x["ID"],df),axis=1)
The problem is that it gives me different results, I am kind of suspicious regarding the usage of apply and lambda with two arguments.
It's hard to figure things out without actual data, but here's one thing that might be responsible:
Original variant:
Js2Geodf:
# ...
om = json_normalize(df, "section") # #TODO - cfati: MARKED LINE 1
om = om.dropna(subset=["sec_part"]) # #TODO - cfati: MARKED LINE 2
# ...
findTS:
# ...
for i, r in om.iterrows():
# ...
In findTS, you operate on the om variable (from Js2Geodf) resulted after executing MARKED LINE 2
Modified variant:
Js2Geodf:
Things are exactly the same as in #1.
findTS:
# ...
om = json_normalize(df, "section") # #TODO - cfati: Identical to MARKED LINE 1
for i, r in om.iterrows():
# ...
In findTS, you operate on the om variable which is defined here, and its definition is identical to MARKED LINE 1, as a consequence:
You're missing MARKED LINE 2
It's also highly inefficient to execute this line every time findTS is called
As I stated in a comment (at that point I only saw a design flaw, now it's clear that it's also a semantic flaw in the code), pass om as an argument instead of df. Here's the code (I did some small improvements as well, there's a lot of room to improve further, but I didn't follow the logic, so I didn't change it):
def findTS(s, om):
for i, r in om.iterrows():
sci = r["sec_ID"]
if sci in s:
return r["type_sec"]
return ""
def Js2Geodf(df):
#df = OmegaJ["chapters"][0]
om = json_normalize(df, "section")
om = om.dropna(subset=["sec_part"])
l = []
for i, x in om.iterrows():
l.append([x["sec_part"]])
listItems = list(flatten_list(l))
dfout = pd.DataFrame(listItems)
dfout["type_sec"] = dfout.apply(lambda x: findTS(x["ID"], om), axis=1)
return dfout
#EDIT0:
Summarizing information from comments as well:
Js2Geodf's last 4 lines indentation differs in original and modified variants (I assume it's a typo in the former)
Make sure input data is constant across runs (and different code versions). Apparently, this one generated a lot of headaches, and once solved, the code was working as well

Getting variables and calling functions with UI. Maya python

I am currently working on a little script that creates a crane-like rig automatically in Autodesk Maya, the user gets to choose the amount of joints by a UI.
My question is how do I take the integer input of the user and use it as the variable value for my "jointAmount"?
I am also wondering how I would be able to call my function(AutoCraneRig) to actually run the script from the UI. I have a "apply"-button but I am unsure how to connect it to my function.
I have seen similar posts like mine but I feel that the solutions shown are somewhat hard for me to understand and/or I can't really relate what is shown to my own problem.
If anything is unclear or more information is needed from me please don't hesitate to call me out.
Here is what my current UI look like
import maya.cmds as cmds
import pymel.core as pm
def jntctrl():
number = pm.intField(jnt, q=1, v=1)
print(number)
if pm.window("stuff", exists = True):
pm.deleteUI("stuff")
pm.window("stuff", t = "Crane Rig Generator", w=400, h=200)
pm.columnLayout(adj = True)
pm.text(label="Joint Amount:")
jnt = pm.intField(changeCommand = 'jntctrl()')
pm.button(label="Create Crane")
pm.showWindow()
#Defining how many joints the user want to have for their crane rig
jointAmmount = 5
#Defining how many controllers the user want to have to orient the crane.
#May not exceed the joint amount
controllerAmount = 5
def autoCraneRig():
#Creating the joints
for i in range(jointAmmount):
pm.joint()
pm.move(0, i, 0)
#Creating the controllers
for i in range(controllerAmount):
pm.circle()
pm.rotate (0,90,0)
pm.makeIdentity (apply= True)
#Creating the groups
for i in range(controllerAmount):
pm.group()
#Somehow one of the nurbs get parented to a group when running the script, here i select both the groups and then unparent them.
pm.select("group*", "nurbsCircle*")
pm.parent(world = True)
#Creating lists/dictionaries for the groups
#Since I wanted to parent my objects by their number I had to put all objects in lists/dictionries to get access.
groups = pm.ls('group*')
nbs = [int(n.split('group')[-1]) for n in groups]
groupDic = dict(zip(nbs, groups))
#Create a list/dictionary for the joints
joint = pm.ls('joint*', type='joint')
nbs = [int(n.split('joint')[-1]) for n in joint]
jointDic = dict(zip(nbs, joint))
common = list(set(groupDic.keys())&set(jointDic.keys()))
#Parenting the groups to the joints
for i in common:
pm.parent(groupDic[i], jointDic[i])
#Reseting the transformations of the groups and then unparenting them to still have the transformation data of the joints
pm.select("group*")
pm.makeIdentity()
pm.parent(world = True)
#Creating a list/dictionary for the nurbs aswell that will be parented to the groups in numeric order
nurbs_sh = pm.ls('nurbsCircle*', type='nurbsCurve')
#I had to get the transformation information from the nurbs before parenting them with anything would work(took a long time to get it right).
nurbs_tr = pm.listRelatives(nurbs_sh, p=1)
nbs = [int(n.split('nurbsCircle')[-1]) for n in nurbs_tr]
curveDic = dict(zip(nbs, nurbs_tr))
common = list(set(groupDic.keys())&set(curveDic.keys()))
#Parent the nurbs to the groups
for i in common:
pm.parent(curveDic[i], groupDic[i])
#Select the nurbs and reset transformations and then freeze transform
pm.select("nurbsCircle*")
pm.makeIdentity()
#Orient constrain the controllers/nurbs to the joints
for i in common:
pm.orientConstraint(curveDic[i], jointDic[i])
#Parent the 2nd group with the first controller. Do this for the whole hierarchy.
for i in common:
pm.parent(groupDic[i+1], curveDic[i])
#I'm getting keyError after I put the "+1" in my groupDic and I don't know why, although it still works, I guess.
autoCraneRig()
Here's an example for how to call a specific function/command when a button is clicked, and how to get the value of an int field. The key is in naming the fields, so you can reference the UI control later.
import pymel.core as pm
def ui():
if (pm.window("myWindow", exists=True)):
pm.deleteUI("myWindow")
window = pm.window("myWindow", t="My Window", w=400, h=200)
pm.columnLayout(adj=True)
pm.intField("myIntField")
pm.button("Button", aop=True, command="action()")
pm.showWindow(window)
def action():
print("Button clicked!")
value = pm.intField("myIntField", q=True, v=True)
print(value)
ui()
If you want to get more into making UI's, I would recommend you watch these two videos:
PySide UI Creation in Maya: Video One
PySide UI Creation in Maya: Video Two

Categories

Resources