duplicate objects with trig function translations in Maya + Python - python

I'm trying to create a hexagonal array of spheres around a center sphere in the XY plane using a python loop function (couldn't figure out how to do it using duplicate special). It should end up looking something like this:
0 0
0 0 0
0 0
Here's my code. I' getting a syntax error
# Error: line 1: invalid syntax #
when I call it, though I'm pretty sure there's nothing wrong with line one.
import maya.cmds as cmds
class Sphere(radius, tx=0, ty=0, tz=0, sx=0, sy=0, sz=0):
self.diameter = 2*radius
def createSphere(radius, tx, ty):
newSphere = Sphere(radius=radius, tx=tx, ty=ty)
return newSphere
def duplicateSphere(wholeSphere):
for i in range(6, 1, -1):
createSphere(tx=wholeSphere.diameter*math.cos(2*math.pi/i), ty=wholeSphere.diameter*math.sin(2*math.pi/i))
# create spheres with projections onto x and y axes as translation params
duplicateSphere(createSphere(1.03))
Any ideas as to what's going on?

Ok, first off to answer your question, the SyntaxError is caused by improper class instantiation. class declaration must be separated from the constructor method in python, like so:
class Sphere(object):
def __init__(self, radius, tx=0, ty=0, tz=0, sx=0, sy=0, sz=0):
self.diameter = 2 * radius
Tip: In the maya script editor panel, if you enable History->Show Stack Trace it will give you a better idea of where the actual error is occurring.
However, there are a couple other issues at play. For one, you are never storing the parameters you pass into the sphere class (except radius, which you are storing implicitly by storing the diameter). You probably wanted:
class Sphere(object):
def __init__(self, radius, tx=0, ty=0, tz=0, sx=1, sy=1, sz=1):
self.radius = radius
self.tx = tx
self.ty = ty
self.tz = tz
self.sx = sx
self.sy = sy
self.sz = sz
self.diameter = 2 * radius
I changed the scale defaults to 1 instead of 0 so that the default sphere is not invisibly small.
Also, as theodox pointed out, you have a TypeError in your createSphere method, which takes 3 params (none of them are keyword arguments currently and therefore not optional) and you are only passing in 1.
However, the main issue is that currently you are not actually creating any spheres in maya. If the intention is that your sphere object is an object-oriented wrapper around maya.cmds, you will need it to call cmds.sphere somewhere, and you will probably want to cmds.move() and cmds.scale() it to by your t* and s* values. If you do all this in the constructor, you could actually then avoid setting instance variables for all the sphere properties if you wanted.
This would look something like this:
cmds.sphere(radius=self.radius)
cmds.move(self.tx,self.ty,self.tz)
cmds.scale(self.sx,self.sy,self.sz)
Finally, I think your trig is a bit off (you want each iteration to vary by exactly 60° or π/3 radians). To get the proper angles, I think you want something more along the lines of:
import math
for i in range(6,0,-1):
angle = math.pi * i / 3.0
distance = wholeSphere.diameter
tx = distance * math.cos(angle)
ty = distance * math.sin(angle)
# ...
As a last note, consider looking at an object-oriented solution like pymel to avoid needing to reinvent the wheel in terms of wrapping maya.cmds commands in objects.
Anyways, applying all these corrections produces something like:
import maya.cmds as cmds
import math
class Sphere(object):
def __init__(self, radius, tx=0, ty=0, tz=0, sx=1, sy=1, sz=1):
self.diameter = 2*radius
self.radius = radius
self.tx = tx
self.ty = ty
self.tz = tz
self.sx = sx
self.sy = sy
self.sz = sz
cmds.sphere(radius=self.radius)
cmds.move(self.tx,self.ty,self.tz)
cmds.scale(self.sx,self.sy,self.sz)
def createSphere(radius, tx=0, ty=0):
newSphere = Sphere(radius, tx=tx, ty=ty)
return newSphere
def duplicateSphere(wholeSphere):
for i in range(6, 0, -1):
angle = math.pi * i / 3.0
distance = wholeSphere.diameter
tx = distance * math.cos(angle)
ty = distance * math.sin(angle)
createSphere(wholeSphere.radius, tx=tx, ty=ty)
duplicateSphere(createSphere(1.03))

Related

Getting "maximum recursion depth exceeded" error, should I restructure my code?

I am creating an application which rotates a few points around a center point. The aim is to connect each point using eg. lines/arcs and have the points/(subsequent drawing) rotate around the center point.
I am trying to accomplish this by a method which rotates each point by a given amount each time the method is called, then distributing each point n times around the center point using a for loop.
(For future use I will also need some tkinter widgets running along side the code eg. entries to get user-input.)
My current code simply draws a circle for each point, instead of connecting them. There are a couple of things I dont currently understand:
My code runs fine for a short while, then closes with Error: maximum recursion depth exceeded. - Is it bad to clear canvas by .delete ?
The value of the .after function doesn't seem to have any effect at all hence using time.sleep.
(I have also used a while True: loop to run the code in an earlier version, but I read that it is bad practice to run an infinite loop inside the GUI event loop. And I edited it because of flickering)
Would it be better to structure my code differently?
Sorry for any mis-terminology and the messy and long post/code, I am a new non-english python student.
class Create_gear:
def __init__(self, location, ox, oy, rpm, n):
self.location = location
self.ox = ox
self.oy = oy
self.rpm = rpm
self.n = n
self.rotation_from_normal = 0
#Rotates point px1, py1 by value of "rpm" each time method is called.
def draw_rotating_gear(self, px1, py1, px2, py2, r):
self.rotation_from_normal = self.rotation_from_normal +self.rpm
self.location.delete("all")
#rotates point px1, py1 n times around to form a circle.
for i in range (0, self.n):
angle = (math.radians(self.rotation_from_normal + 360/self.n *i) )
qx = ( self.ox + math.cos(angle) * (px1 - self.ox) - math.sin(angle) * (py1 - self.oy) )
qy = ( self.oy + math.sin(angle) * (px1 - self.ox) + math.cos(angle) * (py1 - self.oy) )
x0 = qx - r
y0 = qy - r
x1 = qx + r
y1 = qy + r
self.location.create_oval(x0, y0, x1, y1, fill = "black")
self.location.update()
time.sleep(0.01)
self.location.after(1000000000, self.draw_rotating_gear(480, 200, 500, 300, 5))
Nothing in the description of your problem indicates the need for recursion at all, and the way you implement it in your code will always fail. You have a call to draw_rotating_gear() at the end of the function draw_rotating_gear() with no conditional for stopping the recursion, so it will go infinitely deep on the first call. Reorganize it to use a simple loop.
You didn't provide enough code for a working example solution but I believe the problem is you're invoking the .after() method with incorrect arguments. The first argument needs to be an integer, the number of milliseconds before the call, not a floating point value. The second argument needs to be a function to call after that delay, not a call to a function which is what you did. Fixing these, and simplifying your example slightly, I'd expect something like:
def draw_rotating_gear(self, px1, py1, r):
self.rotation_from_normal = self.rotation_from_normal + self.rpm
self.location.delete("all")
# rotates point px1, py1 n times around to form a circle.
for i in range(0, self.n):
angle = (math.radians(self.rotation_from_normal + 360/self.n * i))
qx = (self.ox + math.cos(angle) * (px1 - self.ox) - math.sin(angle) * (py1 - self.oy))
qy = (self.oy + math.sin(angle) * (px1 - self.ox) + math.cos(angle) * (py1 - self.oy))
x0 = qx - r
y0 = qy - r
x1 = qx + r
y1 = qy + r
self.location.create_oval(x0, y0, x1, y1, fill="black")
self.location.update()
self.location.after(100, lambda px1=qx, py1=qy, r=r: self.draw_rotating_gear(px1, py1, r))
(I may be passing the wrong variables to the lambda call as I don't have enough code context to work with.) The recursion error you got was due to your incorrect second argument to .after(), i.e. a false recursion due to a programming error.

Python: Intersection of spheres

I am extremely new to programming but I decided to take on an interesting project as I recently learnt how to represent a sphere in parametric form. When intersecting three spheres, there are two points of intersections that are distinct unless they only overlap at a singular point.
Parametric representation of a sphere:
The code I have is modified from the answer from Python/matplotlib : plotting a 3d cube, a sphere and a vector?, adding the ability to dictate the x, y and z origin and the radius of the sphere. Many similar questions were written in C++, Java, and C#, which I cannot understand at all (I barely know what I am doing so go easy on me).
My Code:
import numpy as np
def make_sphere_x(x, radius):
u, v = np.mgrid[0:2 * np.pi:5000j, 0:np.pi:2500j]
x += radius * np.cos(u) * np.sin(v)
return x
def make_sphere_y(y, radius):
u, v = np.mgrid[0:2 * np.pi:5000j, 0:np.pi:2500j]
y += radius * np.sin(u) * np.sin(v)
return y
def make_sphere_z(z, radius):
u, v = np.mgrid[0:2 * np.pi:5000j, 0:np.pi:2500j]
z += radius * np.cos(v)
return z
#x values
sphere_1_x = make_sphere_x(0, 2)
sphere_2_x = make_sphere_x(1, 3)
sphere_3_x = make_sphere_x(-1, 4)
#y values
sphere_1_y = make_sphere_y(0, 2)
sphere_2_y = make_sphere_y(1, 3)
sphere_3_y = make_sphere_y(0, 4)
#z values
sphere_1_z = make_sphere_z(0, 2)
sphere_2_z = make_sphere_z(1, 3)
sphere_3_z = make_sphere_z(-2, 4)
#intercept of x-values
intercept_x = list(filter(lambda x: x in sphere_1_x, sphere_2_x))
intercept_x = list(filter(lambda x: x in intercept_x, sphere_3_x))
print(intercept_x)
Problems:
Clearly there must be a better way of finding the intercepts. Right now, the code generates points at equal intervals, with the number of intervals I specify under the imaginary number in np.mgrid. If this is increased, the chances of an intersection should increase (I think) but when I try to increase it to 10000j or above, it just spits a memory error.
There are obvious gaps in the array and this method would most likely be erroneous even if I have access to a super computer and can crank up the value to an obscene value. Right now the code results in a null set.
The code is extremely inefficient, not that this is a priority but people like things in threes right?
Feel free to flame me for rookie mistakes in coding or asking questions on Stack Overflow. Your help is greatly valued.
Using scipy.optimize.fsolve you can find the root of a given function, given an initial guess that is somewhere in the range of your solution. I used this approach to solve your problem and it seems to work for me. The only downside is that it only provides you one intersection. To find the second one you would have to tinker with the initial conditions until fsolve finds the second root.
First we define our spheres by defining (arbitrary) radii and centers for each sphere:
a1 = np.array([0,0,0])
r1 = .4
a2 = np.array([.3,0,0])
r2 = .5
a3 = np.array([0,.3,0])
r3 = .5
We then define how to transform back into cartesian coordinates, given angles u,v
def position(a,r,u,v):
return a + r*np.array([np.cos(u)*np.sin(v),np.sin(u)*np.sin(v),np.cos(v)])
Now we think about what equation we need to find the root of. For any intersection point, it holds that for perfect u1,v1,u2,v2,u3,v3 the positions position(a1,r1,u1,v1) = position(a2,r2,u2,v2) = position(a3,r3,u3,v3) are equal. We thus find three equations which must be zeros, namely the differences of two position vectors. In fact, as every vector has 3 components, we have 9 equations which is more than enough to determine our 6 variables.
We find the function to minimize as:
def f(args):
u1,v1,u2,v2,u3,v3,_,_,_ = args
pos1 = position(a1,r1,u1,v1)
pos2 = position(a2,r2,u2,v2)
pos3 = position(a3,r3,u3,v3)
return np.array([pos1 - pos2, pos1 - pos3, pos2 - pos3]).flatten()
fsolve needs the same amount of input and output arguments. As we have 9 equations but only 6 variables I simply used 3 dummy variables so the dimensions match. Flattening the array in the last line is necessary as fsolve only accepts 1D-Arrays.
Now the intersection can be found using fsolve and a (pretty random) guess:
guess = np.array([np.pi/4,np.pi/4,np.pi/4,np.pi/4,np.pi/4,np.pi/4,0,0,0])
x0 = fsolve(f,guess)
u1,v1,u2,v2,u3,v3,_,_,_ = x0
You can check that the result is correct by plugging the angles you received into the position function.
The problem would be better tackled using trigonometry.
Reducing the problem into 2D circles, we could do:
import math
import numpy
class Circle():
def __init__(self, cx, cy, r):
"""initialise Circle and set main properties"""
self.centre = numpy.array([cx, cy])
self.radius = r
def find_intercept(self, c2):
"""find the intercepts between the current Circle and a second c2"""
#Find the distance between the circles
s = c2.centre - self.centre
self.dx, self.dy = s
self.d = math.sqrt(numpy.sum(s**2))
#Test if there is an overlap. Note: this won't detect if one circle completly surrounds the other.
if self.d > (self.radius + c2.radius):
print("no interaction")
else:
#trigonometry
self.theta = math.atan2(self.dy,self.dx)
#cosine rule
self.cosA = (c2.radius**2 - self.radius**2 + self.d**2)/(2*c2.radius*self.d)
self.A = math.acos(self.cosA)
self.Ia = c2.centre - [math.cos(self.A+self.theta)*c2.radius, math.sin(self.A+self.theta)*c2.radius]
self.Ib = c2.centre - [math.cos(self.A-self.theta)*c2.radius,-math.sin(self.A-self.theta)*c2.radius]
print("Interaction points are : ", self.Ia, " and: ", self.Ib)
#define two arbitrary circles
c1 = Circle(2,5,5)
c2 = Circle(1,6,4)
#find the intercepts
c1.find_intercept(c2)
#test results by reversing the operation
c2.find_intercept(c1)

changing class variables python

I am writing a script to read DXF files and return length and area of the shapes so that I can automatically calculate the price of laser cut parts.
Arc should take arc name, center point, radius, starting angle, ending angle from the dxf file.
Arc should calculate the starting and ending point of the arc.
The problem is, that the angles are arbitrary so the starting and ending point are arbitrary,
And such it is hard for me to string arcs together to form a full figure,
I need a mechanism to switch the starting and ending point if I notice that it’s backwards.
I tried to write a function in arc class, to switch the starting and ending points
But it isn’t working,
I am not so strong with OOP, please help
attached code
class arc:
def __init__(self, name, center_point, radius, angles):
self.name = name
self.center_point = center_point
self.radius = radius
self.starting_angle = angles[0]
self.ending_angle = angles[1]
starting_angle = angles[0]
ending_angle = angles[1]
self.starting_point = center_point[0]+radius * math.cos((starting_angle)*((math.pi)/(180))),center_point[1]+radius * math.sin((starting_angle)*((math.pi)/(180)))
self.ending_point = center_point[0]+radius * math.cos((ending_angle)*((math.pi)/(180))),center_point[1]+radius * math.sin((ending_angle)*((math.pi)/(180)))
starting_point =center_point[0]+radius * math.cos((starting_angle)*((math.pi)/(180))),center_point[1]+radius * math.sin((starting_angle)*((math.pi)/(180)))
ending_point = center_point[0]+radius * math.cos((ending_angle)*((math.pi)/(180))),center_point[1]+radius * math.sin((ending_angle)*((math.pi)/(180)))
self.length = math.sqrt((starting_point[0]-ending_point[0])**2+(starting_point[1]-ending_point[1])**2)
I desire a function called switch
this is how it should work:
arc1.starting_point = (0,0)
arc1.ending_point = (1,1)
print(arc1.starting_point, arc1.ending_point)
#Desired Output:
((0,0),(1,1))
arc1.switch()
print(arc1.starting_point, arc1.ending_point)
#Desired Output:
((1,1),(0,0))
You can swap the values of two variables with simultaneous assignment:
>>> x = 0
>>> y = 1
>>> x, y = y, x
>>> x
1
>>> y
0
so
def switch(self):
self.starting_point, self.ending_point = self.ending_point, self.starting_point
should do it.

Do method outputs need to be bound to "self" in order to be used by other methods?

I am very new to object-oriented programming in Python and I am working to implement the accepted answer to this question in Python (it's originally in R).
I have a simple question - is it possible to access the output of one method for use in another method without first binding the output to self? I presume the answer is "no" - but I also imagine there is some technique that accomplishes the same task that I am not thinking of.
My start to the code is below. It works fine until you get to the kappa method. I would really like to be able to define kappa as a simple extension to curvature (since it's just the absolute value of the same) but I'm not particularly interested in adding it the list of attributes. I may just been overthinking this too, and either something like a closure is possible in Python or adding to the attribute list is the Pythonic thing to do?
import numpy as np
from scipy.interpolate import InterpolatedUnivariateSpline
class Road(object):
def __init__(self, x, y): #x, y are lists
# Raw data
self.x = x
self.y = y
# Calculate and set cubic spline functions
n = range(1, len(x)+1)
fx = InterpolatedUnivariateSpline(n, x, k=3)
fy = InterpolatedUnivariateSpline(n, y, k=3)
self.fx = fx
self.fy = fy
def curvature(self, t):
# Calculate and return the curvature
xp = self.fx.derivative(); yp = self.fy.derivative()
xpp = xp.derivative(); ypp = yp.derivative()
vel = np.sqrt(xp(t)**2 + yp(t)**2) #Velocity
curv = (xp(t)*ypp(t) - yp(t)*xpp(t)) / (vel**3) #Signed curvature
return curv
def kappa(self, t):
return abs(curv)
Just call the other method:
class Road(object):
...
def kappa(self, t):
return abs(self.curvature(t=t))

Function inconsistent in requirements

I am just learning to code using the book "Think Python" and I'm so confused. The problem I ran into was in creating the flowers in TurtleWorld. The functions I am creating are not consistent in their requirements. First let me post the finished product, that actually works:
from swampy.TurtleWorld import*
world=TurtleWorld()
bob=Turtle()
print bob
bob.delay=.001
def polyline(t,n,length,angle):
t=Turtle
print t
for i in range(n):
fd(bob,length)
lt(bob,angle)
def arc(t, r, angle):
t=Turtle
arc_length=2*math.pi*r*angle/360
n=int(arc_length/3)+1
step_length=arc_length/n
step_angle=float(angle)/n
polyline(t,n,step_length,step_angle)
def petal(t,r,angle):
for i in range(2):
arc(t,r,angle)
lt(t,180-angle)
def flower(t, n, r, angle):
t=Turtle
for i in range(n):
petal(bob,r,angle)
lt(bob,360/n)
flower(bob,5,77,99)
wait_for_user
On the function definition of arc and petal, t suffices for turtle, though when I began, using t in the definitions of flower and polyline returned an error unbound method(fd and lt). turtle instance required, got type instance instead.
The t=Turtle and print turtle added to half the function definitions were added after the fact to try and fix this error. This is the working version, I just want to know why it didn't work before. I'm not even sure why this worked, as I mainly put bob in as t out of frustration, I didn't actually expect it to work.
Although I use Python's supplied turtle library below, instead of swampy.TurtleWorld, I don't think it makes a difference with respect to the issue you're having. You seem to have a basic misunderstanding of formal parameters in function calls and the distinction between function calls and method invocations. Consider this sequence of events:
flower(bob,5,77,99)
def flower(t, n, r, angle):
t=Turtle
...
Here a perfectly good turtle, bob, gets passed in as the turtle argument t only to be immediately replaced with something else. Or consider polyline which has a turtle argument t but instead the global bob gets used when a turtle is required. Here's how I picture your program should come together:
from turtle import Turtle, Screen
from math import pi
def polyline(turtle, n, length, angle):
for _ in range(n):
turtle.fd(length)
turtle.lt(angle)
def arc(turtle, radius, angle):
arc_length = 2 * pi * radius * angle / 360
n = int(arc_length / 3) + 1
step_length = arc_length / n
step_angle = float(angle) / n
polyline(turtle, n, step_length, step_angle)
def petal(turtle, radius, angle):
for _ in range(2):
arc(turtle, radius, angle)
turtle.lt(180 - angle)
def flower(turtle, n, radius, angle):
for _ in range(n):
petal(turtle, radius, angle)
turtle.lt(360 / n)
screen = Screen()
bob = Turtle()
flower(bob, 5, 77, 99)
screen.exitonclick()
OUTPUT

Categories

Resources