use OpenMaya to give particles specific translate and rotate values - python

I'm struggling with OpenMaya here.
I want to be able to take transform information from a list of locators and plug these values to particles shapes.
The goal is to use this over 25000 locators, so I can't create a particle system for each instance. I really need to store position and rotation values to the particles themselves.
To do that I started to dive into OpenMaya... (╯°□°)╯︵ ┻━┻
Anyway, the problem I'm facing now is that my scene crashes every time I launch this script and I can't figure out what I did wrong. I think I'm pretty close, but crashing Maya is not considered a victory.
import pymel.core as pm
import maya.OpenMaya as om
import maya.OpenMayaFX as omfx
import random
### A short script to create the scene with bunch of locators with random pos rot
numOfLoc = 5 # this number will eventually be set 25000 when the script will work.
# create locators with random position location(for test)
def create_gazillion_locators(numOfLoc):
for i in range(0, numOfLoc):
# to create variation
tx = random.uniform(-10, 10)
ty = random.uniform(0, 5)
tz = random.uniform(-10, 10)
rx = random.uniform(0, 360)
ry = random.uniform(0, 360)
rz = random.uniform(0, 360)
pm.spaceLocator()
pm.move(tx, ty, tz)
pm.rotate(rx, ry, rz, ws=True)
# Select locators
def select_locators():
pm.select(cl=True)
loc_selection = pm.listRelatives(pm.ls(type = 'locator'), p=True)
pm.select(loc_selection, r=True)
return loc_selection
# delete the locators
def clean_the_scene():
#del locators (for testing purpiose)
sel = select_locators()
if sel is not None:
pm.delete(sel)
clean_the_scene()
create_gazillion_locators(numOfLoc)
### Actual script
# Found this on the internet. it seems to be more neat
class Vector(om.MVector):
def __str__(self):
return '{0}, {1}, {2}'.format(self.x, self.y, self.z)
def __repr__(self):
return '{0}, {1}, {2}'.format(self.x, self.y, self.z)
# OpenMaya treatment
sel = select_locators()
mSel = om.MSelectionList()
om.MGlobal.getActiveSelectionList(mSel)
mSel_iter = om.MItSelectionList(mSel)
mSel_DagPath = om.MDagPath()
# bvariables to store the transform in
pos_array = om.MVectorArray()
rot_array = om.MVectorArray()
mLoc = om.MObject()
# Main loop of selection iterator.
while not mSel_iter.isDone():
# Get list of selected
mSel_iter.getDagPath(mSel_DagPath)
mSel_iter.getDependNode(mLoc)
dep_node_name = om.MFnDependencyNode(mLoc).name()
transl = pm.getAttr('{}.translate'.format(dep_node_name))
rotate = pm.getAttr('{}.rotate'.format(dep_node_name))
print(dep_node_name)
print(Vector(transl[0], transl[1], transl[2]))
print(Vector(rotate[0], rotate[1], rotate[2]))
pos_array.append(Vector(transl[0], transl[1], transl[2]))
rot_array.append(Vector(rotate[0], rotate[1], rotate[2]))
mSel_iter.next()
# Up untill there it seems to work ok.
nparticles_transform, nparticles_shape = pm.nParticle(position = pos_array)
pm.setAttr('nucleus1.gravity', 0.0)
nparticles_shape.computeRotation.set(True)
pm.addAttr(nparticles_shape, ln = 'rotationPP', dt = 'vectorArray')
pm.addAttr(nparticles_shape, ln = 'rotationPP0', dt = 'vectorArray')
pm.particleInstancer(nparticles_shape, name = p_instancer, edit = True, rotation = "rotationPP")
particle_fn = omfx.MFnParticleSystem(nparticles_shape.__apimobject__())
particle_fn.setPerParticleAttribute('rotationPP', rot_array)
particle_fn.setPerParticleAttribute('rotationPP0', rot_array)
I read lots of things, went through the stack and google, I based my script on several other stuff I found/learnt (I listened to the OpenMaya course on Youtube by Chayan Vinayak)... But I've had a hard time understanding the OpenMaya documentation though.

I had a look and there is no need to use any openmaya in this case if you need pymel anyway. I used cmds for the creation of the locators because it is a bit faster, so if execution speed is a problem, try to switch everything to cmds.
And I think there is no need to set the computeRotation because it's only used during simulation.
import pymel.core as pm
import maya.cmds as cmds
import random
numOfLoc = 5000
def c_create_gazillion_locators(num_of_loc):
for i in range(num_of_loc):
tx = random.uniform(-10, 10)
ty = random.uniform(0, 5)
tz = random.uniform(-10, 10)
rx = random.uniform(0, 360)
ry = random.uniform(0, 360)
rz = random.uniform(0, 360)
cmds.spaceLocator()
cmds.move(tx, ty, tz)
cmds.rotate(rx, ry, rz, ws=True)
create_gazillion_locators(numOfLoc)
locs = pm.ls(type="locator")
locs = pm.listRelatives(locs, p=True)
pos = []
rot = []
for loc in locs:
pos.append(loc.translate.get())
rot.append(loc.rotate.get())
nparticles_transform, nparticles_shape = pm.nParticle(position=pos)
pm.setAttr("nucleus1.gravity", 0.0)
pm.addAttr(nparticles_shape, ln="rotationPP", dt="vectorArray")
pm.addAttr(nparticles_shape, ln="rotationPP0", dt="vectorArray")
rpp= pm.Attribute(nparticles_shape+".rotationPP")
rpp0= pm.Attribute(nparticles_shape+".rotationPP0")
rpp.set(rot)
rpp0.set(rot)

I've made a couple of changes to make it work. I didn't check the particle setup though. In fact, the main problem is mixing two different APIs. Either stick to OpenMaya (or even OpenMaya v2.0) or PyMEL.
import pymel.core as pm
import maya.OpenMaya as om
import maya.OpenMayaFX as omfx
import random
### A short script to create the scene with bunch of locators with random pos rot
numOfLoc = 5 # this number will eventually be set 25000 when the script will work.
# create locators with random position location(for test)
def create_gazillion_locators(num_of_loc):
for i in range(num_of_loc):
# to create variation
tx = random.uniform(-10, 10)
ty = random.uniform(0, 5)
tz = random.uniform(-10, 10)
rx = random.uniform(0, 360)
ry = random.uniform(0, 360)
rz = random.uniform(0, 360)
pm.spaceLocator()
pm.move(tx, ty, tz)
pm.rotate(rx, ry, rz, ws=True)
# Select locators
def select_locators():
pm.select(cl=True)
loc_selection = pm.listRelatives(pm.ls(type="locator"), p=True)
pm.select(loc_selection, r=True)
return loc_selection
# delete the locators
def clean_the_scene():
# del locators (for testing purpiose)
sel = select_locators()
if sel is not None:
pm.delete(sel)
clean_the_scene()
create_gazillion_locators(numOfLoc)
### Actual script
# Found this on the internet. it seems to be more neat
class Vector(om.MVector):
def __str__(self):
return "{0}, {1}, {2}".format(self.x, self.y, self.z)
def __repr__(self):
return "{0}, {1}, {2}".format(self.x, self.y, self.z)
# OpenMaya treatment
sel = select_locators()
mSel = om.MSelectionList()
om.MGlobal.getActiveSelectionList(mSel)
mSel_iter = om.MItSelectionList(mSel)
mSel_DagPath = om.MDagPath()
# bvariables to store the transform in
pos_array = []
rot_array = om.MVectorArray()
mLoc = om.MObject()
# Main loop of selection iterator.
while not mSel_iter.isDone():
# Get list of selected
mSel_iter.getDagPath(mSel_DagPath)
mSel_iter.getDependNode(mLoc)
dep_node_name = om.MFnDependencyNode(mLoc).name()
transl = pm.getAttr("{}.translate".format(dep_node_name))
rotate = pm.getAttr("{}.rotate".format(dep_node_name))
print(dep_node_name)
print(Vector(transl[0], transl[1], transl[2]))
print(Vector(rotate[0], rotate[1], rotate[2]))
pos_array.append((transl[0], transl[1], transl[2]))
rot_array.append(Vector(rotate[0], rotate[1], rotate[2]))
mSel_iter.next()
# Up untill there it seems to work ok.
nparticles_transform, nparticles_shape = pm.nParticle(position=pos_array)
pm.setAttr("nucleus1.gravity", 0.0)
nparticles_shape.computeRotation.set(True)
pm.addAttr(nparticles_shape, ln="rotationPP", dt="vectorArray")
pm.addAttr(nparticles_shape, ln="rotationPP0", dt="vectorArray")
# Create an instancer before trying to edit
instancer_node = pm.particleInstancer(nparticles_shape, name="p_instancer")
pm.particleInstancer(
nparticles_shape, name=instancer_node, edit=True, rotation="rotationPP"
)
particle_fn = omfx.MFnParticleSystem(nparticles_shape.__apimobject__())
particle_fn.setPerParticleAttribute("rotationPP", rot_array)
particle_fn.setPerParticleAttribute("rotationPP0", rot_array)

Related

Leapfrog algorithm to compute a objects trajectory in a Gravitational field around a central body (Python 3.8.2)

I pretty much deleted the last code and started new. I added a new class called Object which is the replacement for the lists called body_1 and body_2. Also all the calculations are now done from within the Object class. Most previous existing issues were resolved through this process but there is still one that presists. I believe its inside the StartVelocity() function which creates the v1/2 needed to start the Leapfrog algorithm. This should give me a geostationary Orbit but as clearly visible the Satelite escapes very quickly after zooming through earth.
Codes are:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from object import Object
import numpy as np
class Simulation:
def __init__(self):
# Index: 0 Name, 1 Position, 2 Velocity, 3 Mass
body_1 = Object("Earth", "g", "r",
np.array([[0.0], [0.0], [0.0]]),
np.array([[0.0], [0.0], [0.0]]),
5.9722 * 10**24)
body_2 = Object("Satelite", "b", "r",
np.array([[42164.0], [0.0], [0.0]]),
np.array([[0.0], [3075.4], [0.0]]),
5000.0)
self.bodies = [body_1, body_2]
def ComputePath(self, time_limit, time_step):
time_range = np.arange(0, time_limit, time_step)
for body in self.bodies:
body.StartVelocity(self.bodies, time_step)
for T in time_range:
for body in self.bodies:
body.Leapfrog(self.bodies, time_step)
def PlotObrit(self):
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for body in self.bodies:
body.ReshapePath()
X, Y, Z = [], [], []
for position in body.path:
X.append(position[0])
Y.append(position[1])
Z.append(position[2])
ax.plot(X, Y, Z, f"{body.linecolor}--")
for body in self.bodies:
last_pos = body.path[-1]
ax.plot(last_pos[0], last_pos[1], last_pos[2], f"{body.bodycolor}o", label=body.name)
ax.set_xlabel("x-Axis")
ax.set_ylabel("y-Axis")
ax.set_zlabel("z-Axis")
ax.legend()
fig.savefig("Leapfrog.png")
if __name__ == "__main__":
sim = Simulation()
sim.ComputePath(0.5, 0.01)
sim.PlotObrit()
import numpy as np
class Object:
def __init__(self, name, bodycolor, linecolor, pos_0, vel_0, mass):
self.name = name
self.bodycolor = bodycolor
self.linecolor = linecolor
self.position = pos_0
self.velocity = vel_0
self.mass = mass
self.path = []
def StartVelocity(self, other_bodies, time_step):
force = self.GetForce(other_bodies)
self.velocity += (force / self.mass) * time_step * 0.5
def Leapfrog(self, other_bodies, time_step):
self.position += self.velocity * time_step
self.velocity += (self.GetForce(other_bodies) / self.mass) * time_step
self.path.append(self.position.copy())
def GetForce(self, other_bodies):
force = 0
for other_body in other_bodies:
if other_body != self:
force += self.Force(other_body)
return force
def Force(self, other_body):
G = 6.673 * 10**-11
dis_vec = other_body.position - self.position
dis_mag = np.linalg.norm(dis_vec)
dir_vec = dis_vec / dis_mag
for_mag = G * (self.mass * other_body.mass) / dis_mag**2
for_vec = for_mag * dir_vec
return for_vec
def ReshapePath(self):
for index, position in enumerate(self.path):
self.path[index] = position.reshape(3).tolist()
Im aware that Body 2's position has to be multiplied by 1000 to get meters but it would just fly in a straight line if i would do that and there would be no signs of gravitational forces what so ever.
The constant G is in kg-m-sec units. The radius of the satellite orbit however only makes sense in km, else the orbit would be inside the Earth core. Then the speed in m/sec gives a near circular orbit with negligible eccentricity. (Code from a math.SE question on Kepler law quantities)
import math as m
G = 6.673e-11*1e-9 # km^3 s^-2 kg^-1
M_E = 5.9722e24 # kg
R_E = 6378.137 # km
R_sat = 42164.0 # km from Earth center
V_sat = 3075.4/1000 # km/s
theta = 0
r0 = R_sat
dotr0 = V_sat*m.sin(theta)
dotphi0 = -V_sat/r0*m.cos(theta)
R = (r0*V_sat*m.cos(theta))**2/(G*M_E)
wx = R/r0-1; wy = -dotr0*(R/(G*M_E))**0.5
E = (wx*wx+wy*wy)**0.5; psi = m.atan2(wy,wx)
T = m.pi/(G*M_E)**0.5*(R/(1-E*E))**1.5
print(f"orbit constants R={R} km, E={E}, psi={psi} rad")
print(f"above ground: min={R/(1+E)-R_E} km, max={R/(1-E)-R_E} km")
print(f"T={2*T} sec, {T/1800} h")
with output
orbit constants R=42192.12133271948 km, E=0.0006669512550867562, psi=-0.0 rad
above ground: min=35785.863 km, max=35842.14320159004 km
T=86258.0162673565 sec, 23.960560074265697 h
for r(phi)=R/(1+E*cos(phi-psi))
Implementing these changes in your code and calling with
sim.ComputePath(86e+3, 600.0)
gives a nice circular orbit

Why after calling a function from an imported python file within a file the PyQt app stops responding?

I have created a PyQt5 GUI in my main.py file which is in a main app folder. In the file for the interface, a button initiates a new class called Calculation (in the file entry.py) passing in the values of several inputs on the page and in this class the startCalculation() method is called. In this method, the different variables are passed to methods in imported python files, then the result of those calculation is returned and passed to the next calculation in another python file. These returns are in the form of arrays containing values (for the y axis which is then displayed using numpy and plotly).
When I run the app and click on the button in the main interface, the app starts loading (rainbow animation on Mac) and it says it is not responding. It is not a problem with the class itself as a normal print test works in the startCalculation() method, but the function from the imported file causes this to happen. Also, no errors are given in the terminal.
The following is code in the PyQt interface file (main.py)
from app import entry
def startButton(self):
massaTotale = float(self.lineEdit.text())
dragCoefficient = float(self.lineEdit_5.text())
liftCoefficient = float(self.lineEdit_6.text())
powerAvionics = float(self.lineEdit_3.text())
powerPayload = float(self.lineEdit_4.text())
airSpeed = float(self.lineEdit_8.text())
valoreUnico = float(self.valoreUnicoEdit.text())
engineEfficiency = float(self.lineEdit_9.text())
turbolenceCoeff = float(self.lineEdit_10.text())
dayOfYear = float(self.day_LineEdit.text())
latitude = float(self.latitude_LineEdit.text())
sunsetHourAngle = float(self.sunsetHourAngle_LineEdit.text())
declination = float(self.declination_LineEdit.text())
newCaluclation = entry.Calculation(massaTotale, dragCoefficient, liftCoefficient, powerAvionics, powerPayload, airSpeed, valoreUnico, engineEfficiency, turbolenceCoeff, dayOfYear, latitude, sunsetHourAngle, declination)
newCaluclation.startCalculation()
And this is the code in the class calling the function in the external file
from app.mainFunctions import pLevel
#constructor method
def __init__(self, massaTotale, dragCoefficient, liftCoefficient, powerAvionics, powerPayload, airSpeed, valoreUnico, efficiencyEngine, turbolenceCoeff, dayOfYear, latitude, sunsetHourAngle, declination):
# calculate plevel
self.totM = massaTotale
self.vair = airSpeed
self.cl = liftCoefficient
self.cd = dragCoefficient
self.efficiencyEngine = efficiencyEngine
self.valoreUnico = valoreUnico
self.powerAvionics = powerAvionics
self.powerPayload = powerPayload
self.turbolenceCoeff = turbolenceCoeff
self.day_of_year = dayOfYear
self.latitude = latitude
self.sunset_hour_angle = sunsetHourAngle
self.declination = declination
#starting the calculation
def startCalculation(self):
self.x_values, self.pLevel_values = pLevel.calculate_pLevel(self.valoreUnico, self.cd, self.cl, self.totM)
'''
self.pEngine_values = pEngine.calculate_pEngine(self.x_values, self.pLevel_values, self.efficiencyEngine, self.turbolenceCoeff)
self.pOut_values = pOut.calculate_pOut(self.x_values, self.pEngine_values, self.powerAvionics, self.powerPayload)
self.I_loctime = I_loctime.calculate_I_loctime(self.day_of_year, self.latitude, self.sunset_hour_angle, self.declination)
self.asm_values = area_Solar_Module.calculate_asm(self.x_values, self.pOut_values, self.I_loctime)
'''
The pLevel.py file has the following code in it and should return the array of values to pass to the second function in the entry Calculation class file.
import math
import numpy as np
import plotly as py
import plotly.graph_objs as go
import ipywidgets as widgets
import plotly.io as pio
import sys
sys.dont_write_bytecode = True
py.offline.init_notebook_mode(connected=True)
pio.renderers.default = "browser"
# calculating pLevel
x_values = []
y_values = []
layoutPLevel = go.Layout(
title="pLevel",
yaxis=dict(
title='pLevel'
),
xaxis=dict(
title='Surface Area Wing'
)
)
def calculate_pLevel(valoreUnico, cd, cl, totM):
x_values = []
count = 0
while (count < 5):
x_values.append(count)
count = count + 0.01
y_values = []
iteration = 0
while (iteration < len(x_values)):
x_value = x_values[iteration]
if (x_value == 0):
y_value = 0
y_values.append(y_value)
else:
if (valoreUnico == 0.0):
# nessun dato per valoreUnico dato, utilizza i due valori separati
y_value = firstPart(cd, cl) * math.sqrt(secondPart(x_value, totM))
y_values.append(y_value)
else:
y_value = valoreUnico * \
math.sqrt(secondPart(x_value, totM))
y_values.append(y_value)
iteration = iteration + 1
else:
yNpArray = np.array(y_values)
xNpArray = np.array(x_values)
tracePLevel = go.Scatter(
x=xNpArray,
y=yNpArray,
mode='lines',
name='pLevel',
line=dict(
shape='spline'
)
)
figPLevel = go.Figure(data=[tracePLevel], layout=layoutPLevel)
figPLevel.show()
return x_values, y_values
def firstPart(cd, cl):
return (cd / cl**(3/2))
def secondPart(x_value, totM):
return (2*(totM * 9.81)**3) / (1.225 * x_value)
The structure of the files is as follows:
-app
-- __main__.py
-- entry.py
-- __init__.py
-- mainFunctions
--- pLevel.py
--- __init__.py
The loop for the x_values in the pLevel function was not adding one to the iteration so the loop went on forever. I just did not notice my error.
Instead of being
while (iteration < len(x_values)):
x_value = x_values[iteration]
if (x_value == 0):
y_value = 0
y_values.append(y_value)
It should be
while (iteration < len(x_values)):
x_value = x_values[iteration]
if (x_value == 0):
y_value = 0
y_values.append(y_value)
iteration = iteration+1

How to move points of a line to specific locations?

I'm trying visualise the sine waves addition in python using tkinter, and I'm trying to build lines between each circles center, but what I've tried so far didnt work as I thought it would. is there a way to fix what I've tried (see code), or a way to move only one coordinates point of a line independantly from the other?
As you'll see in the code if you run it, I've tried a method where each iteration the previous line is erased and a new one is created. When I run the code there is actually a line between each center of the circles just like I want, but facts are those lines persist and won't erase themselves; for some reason it seems like the canvas.delete(line) doesnt work as I expected it to.
here's the full code. The interesting part is in the 'updateline' fonction, into 'act()' func.
import math
import tkinter as tk
##important to know! -- the way I'm creating the circles is by setting an object, the bounds of the circle, depending on amplitude asked by user.
##then the programs calculates the path of these bounds, depending on circles, amplitude, phase and frequency of the sine waves asked by the user from the tkinter GUI.
##finally, the program creates and moves along this path a circle, representing visually the sine wave.
top = tk.Tk()
top.title('Superposition')
choice = tk.Tk()
choice.title('Parametres')
f = tk.Frame(choice,bd=3)
f.pack(side='top')
g = tk.Frame(choice,bd=3)
g.pack(side='bottom')
tk.Label(f,text="nbre ondes:",width = 10).grid(row=0,column=0)
sines = tk.Spinbox(f,from_=1,to=50,width=10,textvariable=tk.DoubleVar(value=2))
sines.grid(row=0,column=1)
sines.delete(0,5)
sines.insert(0,2)
delai = tk.Scale(g, orient='vertical', from_=100, to=1,resolution=1, length=100,label='delai')
delai.grid(row=0,column=0)
hauteur = tk.Scale(g, orient='vertical', from_=1100, to=100,resolution=100, length=100,label='fenetre')
hauteur.grid(row=0,column=1)
taillec1 = tk.Scale(g, orient='vertical', from_=3.5, to=0.1,resolution=0.1, length=100,label='taille')
taillec1.grid(row=0,column=2)
delai.set(20)
hauteur.set(600)
taillec1.set(1.5)
def grilledechoix():
numberofsines = int(sines.get())
for i in f.grid_slaves():
if int(i.grid_info()["row"]) > numberofsines+2:
i.grid_forget()
for i in range(1,numberofsines+1):
tk.Label(f,text="phase n."+str(i),width = 10).grid(row=i+2,column=4)
phase = tk.Spinbox(f,from_=-180,to=180,width=10)
phase.grid(row=i+2,column=5)
phase.delete(0,5)
phase.insert(0, 0)
for i in range(1,numberofsines+1):
tk.Label(f,text="amp. n."+str(i),width = 10).grid(row=i+2,column=0)
ampli = tk.Spinbox(f,from_=1,to=10000000,width=10)
ampli.grid(row=i+2,column=1)
ampli.delete(0,5)
ampli.insert(0,10)
for i in range(1,numberofsines+1):
tk.Label(f,text="freq n."+str(i),width = 10).grid(row=i+2,column=2)
freq = tk.Spinbox(f,from_=-1000,to=1000,width=10)
freq.grid(row=i+2,column=3)
freq.delete(0,5)
freq.insert(0,5)
def act():
h = g.grid_slaves()[1].get()
delai = g.grid_slaves()[2].get()
taillec1 = g.grid_slaves()[0].get()
w = h
ampdict = {'box1':100 * ((h/700)*taillec1)}
frqdict = {}
aaadict = {}
fffdict = {}
phadict = {}
numberofsines = int(sines.get())
sin = lambda degs: math.sin(math.radians(degs))
cos = lambda degs: math.cos(math.radians(degs))
for i in range(1,numberofsines+1):
fffdict['box'+str(numberofsines-i+1)] = f.grid_slaves()[(2*i)-2].get()
aaadict['box'+str(numberofsines-i+1)] = f.grid_slaves()[(2*i)-2+2*numberofsines].get()
phadict['box'+str(numberofsines-i+1)] = f.grid_slaves()[(2*i)-2+4*numberofsines].get()
for i in range(1,numberofsines+1):
ampdict['box'+str(i)] = (float(ampdict['box1'])/float(aaadict['box1'])) * float(aaadict['box'+str(i)])
frqdict['box'+str(i)] = float(fffdict['box'+str(i)])/float(fffdict['box1'])
class obj(object):
cos0, cos180 = cos(0), cos(180)
sin90, sin270 = sin(90), sin(270)
def __init__(i, x, y, rayon):
i.x, i.y = x, y
i.rayon = rayon
def bounds(i):
return (i.x + i.rayon*i.cos0, i.y + i.rayon*i.sin270,
i.x + i.rayon*i.cos180, i.y + i.rayon*i.sin90)
def updateposition(canvas, id, cent, obj, path):
obj.x, obj.y = next(path)
x0, y0, x1, y1 = canvas.coords(id)
oldx, oldy = (x0+x1) // 2, (y0+y1) // 2
dx, dy = obj.x - oldx, obj.y - oldy
canvas.move(id, dx, dy)
canvas.move(cent, dx, dy)
canvas.after(delai, updateposition, canvas, id, cent, obj, path)
def updateline(canvas, line, robj0, cent0, robj1, cent1):
x00, y00, x01, y01 = canvas.coords(cent0) ##defining coords of the two ovals asked, representing centers of circles
x10, y10, x11, y11 = canvas.coords(cent1)
oldx0, oldy0 = (x00+x01) // 2, (y00+y01) // 2 ##defining center coords of the two ovals
oldx1, oldy1 = (x10+x11) // 2, (y10+y11) // 2
dx0, dy0 = robj0.x - oldx0, robj0.y - oldy0 ##defining the deltax and deltay, difference of movements between frames, of the two ovals
dx1, dy1 = robj1.x - oldx1, robj1.y - oldy1
canvas.after(delai, canvas.delete, line) ##deleting previous line, does not work and I don't know why. I've also tried 'canvas.delete(line)', giving same results
canvas.create_line(oldx0+dx0, oldy0+dy0, oldx1+dx1, oldy1+dy1) ##creating new line
canvas.after(delai, updateline, canvas, line, robj0, cent0, robj1, cent1) ##function invoking itself after delay 'delai'
def posobj(pt,ang,deltang):
while True:
yield pt.x + pt.rayon*cos(ang), pt.y + pt.rayon*sin(ang)
ang = (ang+deltang)%360
try:
top.pack_slaves()[0].destroy()
except:
pass
canvas = tk.Canvas(top, bg='white', height=h, width=w)
canvas.pack(side='right')
robj = {}
r = {}
posobjet = {}
line = {}
cent = {}
## the following 'for' loop creates a number of circles corresponding to sine waves, as much as the user asked.
for i in range(1,int(sines.get())+2):
if i != int(sines.get())+1:
if i == 1:
robj[str(i)] = obj(h/2,h/2,float(ampdict['box'+str(i)]))
r[str(i)] = canvas.create_oval(robj[str(i)].bounds(),fill='',outline='black')
cent[str(i)] = canvas.create_oval(h/2+h/200,h/2+h/200.,h/2-h/200,h/2-h/200, fill='white', outline='red')
posobjet[str(i)] = posobj(robj[str(i)],float(phadict['box'+str(i)]),float(frqdict['box'+str(i)]))
else:
robj[str(i)] = obj(robj[str(i-1)].x,robj[str(i-1)].y,float(ampdict['box'+str(i)]))
r[str(i)] = canvas.create_oval(robj[str(i)].bounds(),fill='',outline='black')
cent[str(i)] = canvas.create_oval(robj[str(i)].x+h/200,robj[str(i)].y+h/200,robj[str(i)].x-h/200,robj[str(i)].y-h/200, fill='white', outline='blue')
line[str(i)] = canvas.create_line(0,0,0,0)
posobjet[str(i)] = posobj(robj[str(i)],float(phadict['box'+str(i)]),float(frqdict['box'+str(i)]))
top.after(delai, updateposition, canvas, r[str(i)], cent[str(i)], robj[str(i)], posobjet[str(i-1)])
##here I'm invoking the updateline function using the constant 'delai', the line i, and objects defining the bounds of the center objects, the little blue/red dots appearing as the center of each circles(run the code, it'll be easier to understand)
top.after(delai, updateline, canvas, line[str(i)], robj[str(i-1)], cent[str(i-1)], robj[str(i)], cent[str(i)])
else:
robj[str(i)] = obj(robj[str(i-1)].x,robj[str(i-1)].y,h/200)
r[str(i)] = canvas.create_oval(robj[str(i)].bounds(),fill='white',outline='red')
cent[str(i)] = canvas.create_oval(robj[str(i)].x+h/200,robj[str(i)].y+h/200,robj[str(i)].x-h/200,robj[str(i)].y-h/200, fill='white', outline='red')
line[str(i)] = canvas.create_line(0,0,0,0)
top.after(delai, updateposition, canvas, r[str(i)], cent[str(i)], robj[str(i)], posobjet[str(i-1)])
##2nd and last time invoking the updateline function, for the line between the last circle's point and the final red point.
top.after(delai, updateline, canvas, line[str(i)], robj[str(i-1)], cent[str(i-1)], robj[str(i)], cent[str(i)])
top.mainloop()
ok = tk.Button(f,text='NBRE',command=grilledechoix)
ok.grid(row=0,column=2)
ac = tk.Button(f,text='APPLY',command=act)
ac.grid(row=0,column=3)
grilledechoix()
act()
I expected the lines to disappear once the updateline function called itself again, because of that 'canvas.delete(line)' line into updateline, and I can't really understand why it does that.
anyway if you have a solution to make the lines move, without creating and deleting them each time the function is called, feel free to tell me.
Thanks!
If I understand the problem correctly, I believe the issue is with this code:
canvas.after(delai, canvas.delete, line)
canvas.create_line(oldx0+dx0, oldy0+dy0, oldx1+dx1, oldy1+dy1)
canvas.after(delai, updateline, canvas, line, robj0, cent0, robj1, cent1)
It fails to reassign the new line to the line variable for the next call. Instead try:
canvas.after(delai, canvas.delete, line)
line = canvas.create_line(oldx0+dx0, oldy0+dy0, oldx1+dx1, oldy1+dy1)
canvas.after(delai, updateline, canvas, line, robj0, cent0, robj1, cent1)
Which gets rid of the extra lines when I run it. Let me know if I've missed the point.

How to properly create function that moves a shape using Zelle's 'graphics.py' program?

Question 17 of Ch. 6 in Zelle's 'Programming Python' books asks for a function that takes two arguments: 'shape', and 'newCenter', which will then move, or re-draw, an existing object based on the new points provided by a mouse-click.
I've been able to figure out how to do it if I add two more parameters to the function, "myX", and "myY", so I can then subtract the difference for the move method. What is eluding me is how to perform the same calculations using just the two parameters specified above. Here is my code:
def moveTo(shape, newCenter, myX, myY):
myShape = shape
myNewX = newCenter.getX()
myNewY = newCenter.getY()
myXUpd = myNewX - myX
myYUpd = myNewY - myY
myShape.move(myXUpd, myYUpd)
return myNewX, myNewY
def main():
win = GraphWin("My Graph Win", 500, 500)
win.setBackground("white")
win.setCoords(0, 0, 10, 10)
Text(Point(5, 8.5), "Please click 10 times.").draw(win)
myPoint = win.getMouse()
myX = myPoint.getX()
myY = myPoint.getY()
myCircle = Circle(myPoint, 2)
myCircle.draw(win)
for x in range(1, 10):
myNewPoint = win.getMouse()
myX, myY = moveTo(myCircle, myNewPoint, myX, myY)
win.close()
Also, any general tips for streamlining, style, or structure are also appreciated as I'm a rather new Python developer.
Thanks!
I figured this out on my own, so anyone going through the Zelle "Python Programming" book can learn from my experience. I just pulled the X and Y out of the shape object I was passing in, thus saving myself having to pass them in again separately. Here is the updated code with some of the fat trimmed down.
def moveTo(shape, newCenter):
oldCenter = shape.getCenter()
myOldX, myOldY = oldCenter.getX(), oldCenter.getY()
myNewX, myNewY = newCenter.getX(), newCenter.getY()
moveX = myNewX - myOldX
moveY = myNewY - myOldY
shape.move(moveX, moveY)
return shape
def main():
win = GraphWin("My Graph Win", 500, 500)
win.setBackground("white")
win.setCoords(0, 0, 10, 10)
Text(Point(5, 8.5), "Please click 10 times.").draw(win)
myPoint = win.getMouse()
myX, myY = myPoint.getX(), myPoint.getY()
myShape = Circle(myPoint, 2)
myShape.draw(win)
for x in range(1, 10):
newCenter = win.getMouse()
myShape = moveTo(myShape, newCenter)
win.close()

locking camera in mayavi

I'm trying to make an animation with a sequence of datafiles in mayavi. Unfortunately i have noticed that camera doesn't lock (it is zooming and zooming out). I think it is happening because the Z componrnt of my mesh is changing and mayavi is trying to recalculate scales.
How can I fix it?
import numpy
from mayavi import mlab
mlab.figure(size = (1024,768),bgcolor = (1,1,1))
mlab.view(azimuth=45, elevation=60, distance=0.01, focalpoint=(0,0,0))
#mlab.move(forward=23, right=32, up=12)
for i in range(8240,8243):
n=numpy.arange(10,400,20)
k=numpy.arange(10,400,20)
[x,y] = numpy.meshgrid(k,n)
z=numpy.zeros((20,20))
z[:] = 5
M = numpy.loadtxt('B:\\Dropbox\\Master.Diploma\\presentation\\movie\\1disk_j9.5xyz\\'+'{0:05}'.format(i)+'.txt')
Mx = M[:,0]; My = M[:,1]; Mz = M[:,2]
Mx = Mx.reshape(20,20); My = My.reshape(20,20); Mz = Mz.reshape(20,20);
s = mlab.quiver3d(x,y,z,Mx, My, -Mz, mode="cone",resolution=40,scale_factor=0.016,color = (0.8,0.8,0.01))
Mz = numpy.loadtxt('B:\\Dropbox\\Master.Diploma\\presentation\\movie\\Mzi\\' + '{0:05}'.format(i) + '.txt')
n=numpy.arange(2.5,400,2)
k=numpy.arange(2.5,400,2)
[x,y] = numpy.meshgrid(k,n)
f = mlab.mesh(x, y, -Mz/1.5,representation = 'wireframe',opacity=0.3,line_width=1)
mlab.savefig('B:\\Dropbox\\Master.Diploma\\presentation\\movie\\figs\\'+'{0:05}'.format(i)+'.png')
mlab.clf()
#mlab.savefig('B:\\Dropbox\\Master.Diploma\\figures\\vortex.png')
print(i)
mlab.show()
for anyone still interested in this, you could try wrapping whatever work you're doing in this context, which will disable rendering and return the disable_render value and camera views to their original states after the context exits.
with constant_camera_view():
do_stuff()
Here's the class:
class constant_camera_view(object):
def __init__(self):
pass
def __enter__(self):
self.orig_no_render = mlab.gcf().scene.disable_render
if not self.orig_no_render:
mlab.gcf().scene.disable_render = True
cc = mlab.gcf().scene.camera
self.orig_pos = cc.position
self.orig_fp = cc.focal_point
self.orig_view_angle = cc.view_angle
self.orig_view_up = cc.view_up
self.orig_clipping_range = cc.clipping_range
def __exit__(self, t, val, trace):
cc = mlab.gcf().scene.camera
cc.position = self.orig_pos
cc.focal_point = self.orig_fp
cc.view_angle = self.orig_view_angle
cc.view_up = self.orig_view_up
cc.clipping_range = self.orig_clipping_range
if not self.orig_no_render:
mlab.gcf().scene.disable_render = False
if t != None:
print t, val, trace
ipdb.post_mortem(trace)
I do not really see the problem in your plot but to reset the view after each plotting instance insert your view point:
mlab.view(azimuth=45, elevation=60, distance=0.01, focalpoint=(0,0,0))
directly above your mlab.savefig callwithin your for loop .
You could just use the vmin and vmax function in your mesh command, if u do so the scale will not change with your data and your camera should stay where it is.
Like this:
f = mlab.mesh(x, y, -Mz/1.5,representation = 'wireframe',vmin='''some value''',vmax='''some value''',opacity=0.3,line_width=1)

Categories

Resources