Snapping an object to another object vertex in Maya over Python Script - python

I want to snap / align an object A to vertex of object B. I have tried using below script but its not snapping exactly to the vertex but snapping with an offset. Can any one suggest me a solution. Here are the snapshots.
import maya.cmds as cmds
vertices = [];
srcObj = "pCone1";
def snapToVertex(vertex,object):
cmds.select(vertex);
x,y,z = cmds.pointPosition();
cmds.select(object);
cmds.duplicate();
cmds.move(x,y,z);
def processTask():
cmds.select( cmds.polyListComponentConversion( tv=True ) );
vertices = cmds.ls(sl = True);
print vertices;
for vrtx in vertices:
snapToVertex(vrtx,srcObj);
processTask();
Snapped with my above Script, Which didn't snap exactly to vertex.
But it should be snapped exactly to the Vervex's as below image.

It's better not to use Reset Transformations command for your Cone from Modify main menu, 'cause pivot point goes back to its initial place. The analog for useful Freeze Transformations command in Python is cmds.makeIdentity(). Even if you've moved you pivot point by 1 unit (for example) along +Y axis, do not forget to subtract that 1 from variable y cause Maya somehow remembers pivot's position. Offset of the Cone's pivot point (for snapping pivot to a vertex) depends on the Cone's size itself. By default it's 1.
Add this snippet to your code to move the duplicates in World Space:
# cmds.makeIdentity( 'pCone1', apply=True )
pivSnap = 1
cmds.xform( 'pCone1', piv=[ 0, pivSnap, 0 ] )
cmds.move( x, y-pivSnap, z, a=True, ws=True, wd=True )
You can test this code (here I moved pivot up by 0.5):
import maya.cmds as cmds
cmds.polyCube( sx=1, sy=15, sz=1, w=1, h=15, d=1 )
cmds.polyCone( r=.5, h=1, sx=10 )
cmds.move( 10, x=True )
pivSnap = .5
cmds.xform( 'pCone1', piv=[ 0, pivSnap, 0 ] )
cmds.rotate( 0, 0, '45deg' )
cmds.select( 'pCube1.e[33]','pCube1.e[37]','pCube1.e[41]','pCube1.e[45]','pCube1.e[49]','pCube1.e[53]','pCube1.e[57]','pCube1.e[61]' )
vertices = []
srcObj = "pCone1"
def snapToVertex( vertex, object ):
cmds.select( vertex )
x,y,z = cmds.pointPosition()
print( x,y,z )
cmds.select( object )
cmds.manipMoveContext( m=2 )
cmds.delete( 'pCube1', ch=True )
cmds.duplicate()
cmds.move( x, y-pivSnap, z, a=True, ws=True, wd=True )
def processTask():
cmds.select( cmds.polyListComponentConversion( tv=True ) )
vertices = cmds.ls( sl=True )
print( vertices )
for vrtx in vertices:
snapToVertex( vrtx, srcObj )
processTask()

you could greatly simplify your code and only use a few commands
import maya.cmds as mc
verts = mc.ls(os=True)
src = 'pCone1'
base_name = 'fancy_cone'
for i, vert in enumerate(verts):
dup = mc.duplicate(src, n='%s_%s' %(base_name, i))[0]
mc.xform(dup, ws=True, t=mc.xform(vert, q=True, ws=True, t=True))
keep in mind a few caveats:
1) this is assuming that the pivot of your source cone is at the tip with the tip at the origin... so it requires setting up that source cone the way you want it as if the origin point is a vertex you will be snapping it to. This would also be beneficial if the object you're 'pinning' to is rotated (no need to do any kind of vector math to determine the positional offset that way)
2) to use the orderedSelection (os) flag in the ls command, you need to be sure track selection order is turned on in your maya preferences under the selection sub section - this way you can select the verts you want in the order you want them to be created (top-down, bottum-up, whatever) instead of the vert index order
This should also be significantly faster - by tracking any created objects, you don't actually need to select anything and can manipulate the objects directly. I also put it all into one loop without any function calls which would be faster (although, you probably won't notice it unless you're making a LOOOOOOT of these).
Depending on your needs though, you may actually want to utilize a snapping function multiple places; However, I would suggest keeping the duplication out of it in case there are situations you want to snap without duplicating.
Something like this (subject to same caveats as above):
import maya.cmds as mc
def snapToVertex(vertex, object):
pos = mc.xform(vertex, q=True, ws=True, t=True)
mc.xform(object, ws=True, t=pos) # combine to single line if prefered
def processTask():
verts = mc.ls(os=True)
src = 'pCone1'
base_name = 'fancy_cone'
for i, vert in enumerate(verts):
dup = mc.duplicate(src, n='%s_%s' %(base_name, i))[0]
snapToVertex(vert, dup)
processTask()

Related

Why Gmsh create more surfaces than expected when I use "gmhs.model.occ.cut()" command?

I am trying to create a volume in Gmsh (using Python API) by cutting some small cylinders from a bigger one.
When I do that, I expect to have one surface for each cutted region, instead, I get the result in the figure. I have highlighted in red the surfaces that give me the problem (some cutted regions behave as expected), as you can see, instead of one surface I get two, that sometimes aren't even equal.
gmsh creates more surfaces than expected:
So, my questions are:
Why gmsh behaves like that?
How can I fix this as I need predictable behavior?
Below is the code I used to generate the geometry.
The code to work requires some parameters such as core_height, core_inner_radius and core_outer_radius, the number of small cylinders and their radius.
gmsh.initialize(sys.argv)
#gmsh.initialize()
gmsh.clear()
gmsh.model.add("circle_extrusion")
inner_cyl_tag = 1
outer_cyl_tag = 2
inner_cyl = gmsh.model.occ.addCylinder(0,0,0, 0, 0, core_height, core_inner_radius, tag = inner_cyl_tag)
outer_cyl = gmsh.model.occ.addCylinder(0,0,0, 0, 0, core_height, core_outer_radius, tag = outer_cyl_tag)
core_tag = 3
cut1 = gmsh.model.occ.cut([(3,outer_cyl)],[(3,inner_cyl)], tag = core_tag)
#create a set of filled cylinders
#set position
angle_vector = np.linspace(0,2*np.pi,number_of_hp+1)
pos_x = hp_radial_position*np.cos(angle_vector)
pos_y = hp_radial_position*np.sin(angle_vector)
pos_z = 0.0
#cut one cylinder at the time and assign the new core tag
for ii in range(0,len(angle_vector)):
old_core_tag = core_tag
heat_pipe = gmsh.model.occ.addCylinder(pos_x[ii], pos_y[ii], pos_z, 0, 0, core_height,hp_outer_radius, tag =-1)
core_tag = heat_pipe+1
core = gmsh.model.occ.cut([(3,old_core_tag)],[(3,heat_pipe)], tag = core_tag)
gmsh.model.occ.synchronize()
#get volume entities and assign physical groups
volumes = gmsh.model.getEntities(dim=3)
solid_marker = 1
gmsh.model.addPhysicalGroup(volumes[0][0], [volumes[0][1]],solid_marker)
gmsh.model.setPhysicalName(volumes[0][0],solid_marker, "solid_volume")
#get surfaces entities and apply physical groups
surfaces = gmsh.model.getEntities(dim=2)
surface_markers= np.arange(1,len(surfaces)+1,1)
for ii in range(0,len(surfaces)):
gmsh.model.addPhysicalGroup(2,[surfaces[ii][1]],tag = surface_markers[ii])
#We finally generate and save the mesh:
gmsh.model.mesh.generate(3)
gmsh.model.mesh.refine()
gmsh.model.mesh.refine()
gmsh.option.setNumber("Mesh.MshFileVersion", 2.2) #save in ASCII 2 format
gmsh.write(mesh_name+".msh")
# Launch the GUI to see the results:
#if '-nopopup' not in sys.argv:
# gmsh.fltk.run()
gmsh.finalize()
I do not think that you have additional surfaces in the sense of gmsh.model.occ surfaces. To me this looks like your volume mesh is sticking out of your surface mesh, i.e. volume and surface mesh do not fit together.
Here is what I did to check your case:
First I added the following lines at the beginning of our code to get a minimum working example:
import gmsh
import sys
import numpy as np
inner_cyl_tag = 1
outer_cyl_tag = 2
core_height = 1
core_inner_radius = 0.1
core_outer_radius = 0.2
number_of_hp = 5
hp_radial_position = 0.1
hp_outer_radius = 0.05
What I get with this code is the following:
To visualize it like this go to "Tools"-->"Options"-->"Mesh" and check "2D element faces", "3D element edges" and "3D element faces".
You can see that there are some purple triangles sticking out of the green/yellowish surfaces triangles of the inner surfaces.
You could try to visualize your case the same way and check <--> uncheck the "3D element faces" a few times.
So here is the solution for this behaviour, I did not know that gmsh behaves like this myself. It seems that when you create your mesh and refine it the refinement will be applied on the 2D surface mesh and the 3D volume mesh seperately, which means that those two meshes are not connected after the refinement anymore. What I did next was to try what happens if you create the 2D mesh only, refine it, and then create the 3D mesh, i.e.:
replace:
gmsh.model.mesh.generate(3)
gmsh.model.mesh.refine()
gmsh.model.mesh.refine()
by:
gmsh.model.mesh.generate(2)
gmsh.model.mesh.refine()
gmsh.model.mesh.refine()
gmsh.model.mesh.generate(3)
The result then looks like this:
I hope that this was actually your problem. But in future it would be good if you could provide us a minimum working example of code that we can copy-paste and get the same visualization you showed us in your image.

Blender 3.0 with Modifiers in Python

The following script from https://b3d.interplanety.org/en/how-to-apply-modifier-on-selected-objects/ doesn't work. I'm using Blender 3.0 with the Python API. I tried running it with the cube created and with nothing created.
What am I doing wrong?
import bpy
for obj in bpy.context.selected_objects:
bpy.context.view_layer.objects.active = obj
for modifier in obj.modifiers:
if modifier.type == 'SUBSURF':
bpy.ops.object.modifier_apply(
modifier=modifier.name
)
Thanks any help would be appreciated. Here's the code in the image.
EDITED 24/4/22
You need to select the two vertices of the edge first.
My attempt to select one edge of a default cube and bevel it in python
import bpy
import bmesh
obj = bpy.context.active_object # Get selected object
epsilon = 1e-5 # Threshold to account for floating point precision
if obj:
bpy.ops.object.mode_set(mode='EDIT') # Go into edit mode
bpy.ops.mesh.select_mode(type="EDGE") # Switch to edge select mode
bm = bmesh.from_edit_mesh(obj.data) # Create bmesh object for easy mesh evaluation
obj = bpy.context.active_object
obj.data.polygons[2].select = True
for e in bm.edges: # Check all edges
if e.index == 0:
print ("abc")
first_pos = e.verts[0].co # Get first vert position of this edge
other_pos = e.verts[1].co # Get second vert position of this edge
e.select_set(abs(first_pos.x - other_pos.x) <= epsilon and abs(first_pos.y - other_pos.y) <= epsilon)
bmesh.update_edit_mesh(obj.data) # Update the mesh in edit mode
bpy.ops.object.modifier_set_active(modifier="Bevel")
bpy.ops.object.modifier_add(type='BEVEL')
bpy.context.object.modifiers["Bevel"].segments = 10
bpy.context.object.modifiers["Bevel"].width = 0.37
Perhaps I should ask another question.
Blender bevel selected edge of a default cube with python
Your script is working just fine...
Are you sure you did select all your objects before starting the script ?
Maybe you want it to work for all objects in the scene, thus replacing context.selectd_objects by context.scene.objects

Creating a list of particle positions for each gridcell

I am creating a charge smear function. I have a matrix were each row is a particle with a charge and position. I then look at each particles position in a grid, to count how many particles are in each grid-cell, but I need to know which cell each particle is in, so that I may find the average of the positions for every particle in a specific grid-cell. My idea for a fix is to create an list where the number of rows is the amount of grid-cells in my matrix, and let the column be positions in x,y and z direction, but obviously I can't append more then one number to each index, but maybe some variation will work? Sorry for open ended question. Thank you in advance
import matplotlib.pyplot as plt
import random
import numpy as np
###Initalize particle lists
particle_arrayX=[]
particle_arrayY=[]
###The resolution
N = 10
###Number of particles
M = 1000
col=None
row=None
###Size of box
Box_size=100
###gridsize
Grid_size=Box_size/N
###Initalize particles
for i in range(M):
particle_arrayX.append(random.random()*Box_size)
particle_arrayY.append(random.random()*Box_size)
###Intialize matrix
ParticleMatrix_Inital=[[0 for i in range(N)]]*N
###Measure density in each cell
for i in range(M):
col=None
row=None
#The x and y components are diveded by the gridsize
#Then they are converted to integers and then asigned either to a row or column
#If value is float with decimal 0 EX 2.0, then 1 is substracted before converted to int
coln=particle_arrayX[i]/Grid_size
rown=particle_arrayY[i]/Grid_size
if coln.is_integer()==True:
col=int(coln)-1
else:
col=int(coln)
if rown.is_integer()==True:
row=int(rown)-1
else:
row=int(rown)
ParticleMatrix_Inital=np.array(ParticleMatrix_Inital)
ParticleMatrix_Inital[row,col]=ParticleMatrix_Inital[row,col]+1
ParticleMatrix_Inital=ParticleMatrix_Inital.tolist()
#Plot matrix
plt.imshow(ParticleMatrix_Inital)
plt.colorbar()
plt.show()
Welcome to SO!
There are many ways to approach the problem of "bin-ing" empirical data. I'm proposing an object oriented (OO) solution below, because (in my subjective opinion) it provides clean, tidy and highly readable code. On the other hand, OO-solutions might not be the most efficient if you're simulating huge many-particles systems. If the below code doesn't entirely solve your issues, I still hope that parts of it can be of some help to you.
That being said, I propose implementing your grid as a class. To make life easier for our self, we may apply the convention that all particles have positive coordinates. That is x, y and even z (if introduced) stretches from 0 to whatever box_size you define. However, the class Grid does not really care about the actual box_size, only the resolution of the grid!
class Grid:
def __init__(self, _delta_x, _delta_y):
self.delta_x = _delta_x
self.delta_y = _delta_y
def bounding_cell(self, x, y):
# Note that y-coordinates corresponds to matrix rows,
# and that x-coordinates corresponds to matrix columns
return (int(y/self.delta_y), int(x/self.delta_x))
Yes, this could have been a simple function. However, as a class it is easily expandable. Also, a function would have rely on global variables (yuck!) or explicitly be given the grid spanning (delta) in each dimensional direction, for every determining of which matrix cell (or bin) the given coordinate (x,y) belongs to.
Now, how does it work? Imagine the simplest of cases, where your grid resolution is 1. Then, a particle at position (x,y) = (1.2, 4,9) should be placed in the matrix at (row,col) = (4,1). That is row = int(y/delta_y) and likewise for x. The higher resolution (smaller delta) you have, the larger the matrix gets in terms of number of rows and cols.
Now that we have a Grid, let us also object orient the Particle! Rather straight forward:
class Particle:
def __init__(self, _id, _charge, _pos_x, _pos_y):
self.id = _id
self.charge = _charge
self.pos_x = _pos_x
self.pos_y = _pos_y
def __str__(self):
# implementing the __str__ method let's us 'print(a_particle)'
return "{%d, %d, %3.1f, %3.1f}" % (self.id, self.charge, self.pos_x, self.pos_y)
def get_position(self):
return self.pos_x, self.pos_y
def get_charge(self):
return self.charge
This class is more or less just a collection of data, and could easily have been replaced by a dict. However, the class screams its intent clearly, it is clean and tidy, and also easily expanded.
Now, let's create some instances of particles! Here is a function which by list comprehension creates a list of particles with an id, charge and position (x,y):
import random
def create_n_particles(n_particles, max_pos):
return [Particle(id, # unique ID
random.randint(-1,1), # charge [-1, 0, 1]
random.random()*max_pos, # x coord
random.random()*max_pos) # y coord
for id in range(n_particles)]
And finally, we get to the fun part: putting it all together:
import numpy as np
if __name__ == "__main__":
n_particles = 1000
box_size = 100
grid_resolution = 10
grid_size = int(box_size / grid_resolution)
grid = Grid(grid_resolution, grid_resolution)
particles = create_n_particles(n_particles, box_size)
charge_matrix = np.zeros((grid_size, grid_size))
id_matrix = [[ [] for i in range(grid_size)] for j in range(grid_size)]
for particle in particles:
x, y = particle.get_position()
row, col = grid.bounding_cell(x, y)
charge_matrix[row][col] += particle.get_charge()
# The ID-matrix is similar to the charge-matrix,
# but every cell contains a new list of particle IDs
id_matrix[row][col].append(particle.id)
Notice the initialization of the ID-matrix: This is the list of particle positions for each grid cell that you asked for. It is a matrix, representing the particle container, and each cell contains a list to be filled with particle IDs. You could also populate these lists with entire particle instances (not just their IDs): id_matrix[row][col].append(particle).
The last for loop does the real work, and here the Object Oriented strategy shows us how charming it is: The loop is short and it is very easy to read and understand what is going on: A cell in the charge_matrix contains the total charge within this grid cell/bin. Meanwhile, the id_matrix is filled with the IDs of the particles that is contained within this grid cell/bin.
From the way we've constructed the list of particles, particles, we see that a particle's ID is equivalent to that particle's index in the list. Hence, they may be retrieved like this,
for i,row in enumerate(id_matrix):
for j,col in enumerate(row):
print("[%d][%d] : " % (i, j), end="")
for particle_id in id_matrix[i][j]:
p = particles[particle_id]
# do some work with 'p', or just print it:
print(p, end=", ")
print("") # print new line
# Output:
# [0][0] : {32, -1, 0.2, 0.4}, ... <-- all data of that particle
# ....
I leave optimization of this retrieval to you as I don't really know what data you need and what you're going to do with it. Maybe it's better to contain all the particles in a dict instead of a list; I don't know(?). You choose!
At the very end, I'll suggest that you use matshow which is inteded for displaying matrices, as opposed to imshow which is more aiming more for images.
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
cax = ax.matshow(charge_matrix)
fig.colorbar(cax)
ax.invert_yaxis() # flip the matrix such that the y-axis points upwards
fig.savefig("charge_matrix.png")
We can also scatter plot the particles and add grid lines corresponding to our the grid in the matshow above. We color the scatter plots such that negative charges are blue, neutral are gray and positive are red.
def charge_color(charge):
if charge > 0: return 'red'
elif charge < 0: return 'blue'
else: return 'gray'
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_aspect('equal')
ax.set_xticks(np.arange(0, 101, grid_resolution))
ax.set_yticks(np.arange(0, 101, grid_resolution))
ax.grid()
ax.scatter([p.pos_x for p in particles],
[p.pos_y for p in particles],
c=[charge_color(p.get_charge()) for p in particles])
fig.savefig("particle_dist.png")

In Maya, how to find all shells in a UV tile?

I'm looking for a way in Mel or Python to determine if a particular UV shell crosses across multiple texture tiles in Maya. I know Mudbox has a check for this, but I can't find an easy command for this in Maya in the interface or in script.
One approach I thought of is to compare the shells in each tile and see if any shell appears in more than one tile.
I found a page here someone figured out how to select each UV in a given set of texture tiles, but it operates on UVs, not shells. Here's the page:
http://forums.cgsociety.org/showthread.php?t=1123235
Thanks,
Mike
There's not a one-liner way to do it.
Here's an example of a method for getting the uv bounding boxes of the shells in Python. It uses the API to get bounding boxes for all the uv shells in an object (note -- it should be a mesh shape object , I didn't add any checks).
import maya.api.OpenMaya as api
from math import floor
def get_shells(obj, uvset = ''):
selected = api.MGlobal.getSelectionListByName(obj)
node = selected.getDependNode(0)
mesh = api.MFnMesh(node)
howmany, shell_ids = mesh.getUvShellsIds(uvset)
u_list, v_list = mesh.getUVs()
shells = []
for shell_num in range(howmany):
umin = vmin = 9999999999999
umax= vmax = -9999999999999
for idx, value in enumerate(shell_ids):
if value == shell_num:
umin = min(umin, u_list[idx])
umax = max(umax, u_list[idx])
vmin = min(vmin, v_list[idx])
vmax = max(vmax, v_list[idx])
shells.append ( (umin, vmin, umax, vmax) )
return shells
print get_shells('pCylinderShape1')
# [(0.3437499701976776, -7.450580596923828e-08, 0.65625, 0.3125), (0.375, 0.3125, 0.6249997615814209, 0.6884398460388184), (0.3437499701976776, 0.6874999403953552, 0.65625, 1.0)]
The result is a list of 4-item tuples which are the bounding boxes of the uv shells in the object stored as ( u min, v min, u max, v max ) . To catch a tile boundary just check that the minimum and maximum corners of the are the same integer values:
for shell in get_shells('pCylinderShape1'):
mintile = int(shell[0]), int(shell[1])
maxtile = int(shell[2]), int(shell[3])
if mintile != maxtile:
print "shell crosses UV boundary", shell

what space is this Matrix in?

In Blender 2.6 API, PoseBone is used for animating a bone. PoseBone.matrix is one way to do that. API says PoseBone.matrix is in "object space".
http://www.blender.org/documentation/blender_python_api_2_63_5/bpy.types.PoseBone.html#bpy.types.PoseBone.matrix
PoseBone.matrix is nothing I've seen at all.
I still can't get my animation importer working.
What's the deal with PoseBone.matrix? In Blender 2.4 API there were two matrices: one in local space, one in armature space.
But the new PoseBone.matrix is neither! It's not a local matrix:
Position isn't local, it's global.
But rotation
<Euler (x=1.5708, y=-0.7854, z=-0.0000), order='XYZ'>
is.
So what is this "object space" that the API says PoseBone.matrix is in?
I'm trying to assign my armature-space matrices to a hierarchial armature and I can't get them right.
I tried decomposing the matrices I have, undoing parent rotations then recomposing the matrix again before setting it as "PoseBone.matrix".
It just doesn't work.
oldmatrix = myMatrix
loc, rot, scale = oldmatrix.decompose()
#rot = rot * pose.bones[bonename].parent.rotation_quaternion.conjugated()
for i in pose.bones[bonename].parent_recursive:
rot = rot * i.conjugated()
newmatrix = rot.to_matrix().to_4x4()
newmatrix[0][3] = loc.x
newmatrix[1][3] = loc.y
newmatrix[2][3] = loc.z
pose.bones[bonename].matrix = newmatrix
EDIT:
I found the solution that transforms these Object Space transforms to Bone Local Space.
To cut the long story short - here's a code snippet that accomplishes that task:
poseBone = C.object.pose.bones[2] # <----- set your bone here
# poseBone.matrix is in object space - we need to convert it to local space
if poseBone.parent is not None:
parentRefPoseMtx = poseBone.parent.bone.matrix_local
boneRefPoseMtx = poseBone.bone.matrix_local
parentPoseMtx = poseBone.parent.matrix
bonePoseMtx = poseBone.matrix
boneLocMtx = ( parentRefPoseMtx.inverted() * boneRefPoseMtx ).inverted() * ( parentPoseMtx.inverted() * bonePoseMtx )
else:
boneRefPoseMtx = poseBone.bone.matrix_local
bonePoseMtx = poseBone.matrix
boneLocMtx = boneRefPoseMtx.inverted() * bonePoseMtx
loc, rot, scale = boneLocMtx.decompose()
MY OLD QUESTION:
Have you found the solution yet?
I'm dealing with exactly the same problem.
Object space matrix should be the bone local space matrix expressed with respect to the parent's object space matrix, so:
boneOS = parentOS * boneLS
But as you noticed - it doesn't give the correct solution. The bone is still offset.
One thing that came to my mind was that it's offset by the rest pose transform, so I tried rolling it back, but that didn't work either, but perhaps the equations I used were wrong.
Here's how I tried calculating the local space transform of a pose bone ( let's assume bone 1 is the bone of my interest, and bone 0 is its parent, and that 0 doesn't have a parent ):
boneOS = C.object.pose.bones[2].matrix
parentBoneOS = C.object.pose.bones[2].parent.matrix
boneRP = C.object.pose.bones[2].bone.matrix_local # rest pose matrix in bone local space
parentBoneRP = C.object.pose.bones[1].bone.matrix_local # parent bone's rest pose matrix in bone local space
boneLS = ( parentBoneRP * boneRP ).inverted() * parentOS.inverted() * boneOS
The matrix is in object-space and it should behave like you expect from your code. What's happening is that the bones' matrices aren't being updated immediately after changing the matrix. So far I've found a hack-solution of updating the entire scene after every bone with
bpy.context.scene.update()

Categories

Resources