I'm new to Blender and new to Python, on my Layer 1 I have a ball named "BallB".
Now I want to make a simple bubbling-animation using Python in Blender but I'm unable to make a keyframe. This should happen on Layer 2.
I tried many and got many errors... all the Snippets I found didn't work and my script allways crashed with Python-Errors like
RuntimeError: Operator bpy.ops.anim.change ... expected an timeline/animation area to be activated
and many more.
Has anybody some hints for me?
I'd like to learn scripted animations in Blender so I'm very thankful for every hint which advances me ;-)
My Code:
import bpy, math, random
d = 4
anz = 100
frameAnz = 10
scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = 100
for anz in range (0,anz):
ball = bpy.data.objects["ballB"]
tball = ball.copy()
xpos = -1 * (d/2) + random.randint(0,(d-1))
xpos += random.random()
ypos = -1 * (d/2) + random.randint(0,(d-1))
ypos += random.random()
zpos = random.randint(0,(d-1))
zpos += random.random()
bn = str(anz).zfill(5)
bn = "zz_Ball-" + bn
tball.name = bn
tball.location = (xpos, ypos, zpos)
sz = random.uniform(0.015,0.09)
tball.scale = (sz,sz,sz)
#tball.nodes["Emission"].inputs[1].default_value = 200
tball.select = False
scene.objects.link(tball)
#print ("done!")
obj = bpy.context
for actFrame in range(1,frameAnz):
# scene = bpy.context.scene
# scene.keyframe_insert(data_path="gravity", frame = actFrame)
for ob in scene.objects:
ploc = ob.location
#print (ploc)
xpos = ploc[0]
ypos = ploc[1]
zpos = ploc[2]
zpos = zpos + random.random()
ob.location = (xpos, ypos, zpos)
#ypos = ball.location[1]
#zpos = ball.location]2]
#zpos = zpos - random.random()
#ball.location = (xpoy, ypos, zpos)
#obj.keyframe_insert_menu('Location')
#bpy.context.scene.frame_set(0)
#scene = bpy.context.scene
#scene.keyframe_insert(data_path="Location", frame=actFrame)
Actually it looks so:
You are close, you want to use obj.keyframe_insert(), using the index parameter you can keyframe just the one location value.
One issue you will have is that copying the initial object means the new object will use the same animation data, keeping them moving in unison. You can create a new object and use the same mesh data.
An objects layers property is an array of 20 booleans to specify where it is visible, when you add an object to a scene it will be set to be visible on the active layer, so set this after you link it to the scene.
import bpy, random
d = 4
anz = 100
frameAnz = 20
scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = 100
ball = bpy.data.objects["ballB"]
for anz in range (0,anz):
xpos = -1 * (d/2) + random.randint(0,(d-1))
xpos += random.random()
ypos = -1 * (d/2) + random.randint(0,(d-1))
ypos += random.random()
zpos = random.randint(0,(d-1))
zpos += random.random()
bn = str(anz).zfill(5)
bn = "zz_Ball-" + bn
tball = bpy.data.objects.new(bn, ball.data)
tball.location = (xpos, ypos, zpos)
sz = random.uniform(0.015,0.09)
tball.scale = (sz,sz,sz)
tball.select = False
scene.objects.link(tball)
tball.layers = [False,True] + [False]*18
for actFrame in range(1,frameAnz):
for ob in scene.objects:
ob.location.z += random.random()
ob.keyframe_insert(data_path='location', index=2, frame=actFrame)
Related
I feel like this is a little bit complicated or at least I'm confused on it, so I'll try to explain it by rendering the issue. Let me know if the issue isn't clear.
I get the output from my viewing_box through the __init__ method and it shows:
(0, 0, 378, 265)
Which is equivalent to a width of 378 and a height of 265.
When failing, I track the output:
1 false
1 false
here ([0.0, -60.0], [100.0, 40.0]) (0, 60, 378, 325)
The tracking is done in _scan_view with the code:
if not viewable:
current = self.itemcget(item,'tags')
if isinstance(current, tuple):
new = current-('viewable',)
else:
print('here',points, (x1,y1,x2,y2))
new = ''
self.inview_items.discard(item)
So the rectangle stays with width and height of 100, the coords however failing to be the expected ones. While view width and height stays the same and moves correctly in my current understanding. Expected:
if x1 <= point[0] <= x2 and y1 <= point[1] <= y2: and it feels like I've created two coordinate systems but I don't get it. Is someone looking on it and see it immediately?
Full Code:
import tkinter as tk
class InfiniteCanvas(tk.Canvas):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
self.inview_items = set() #in view
self.niview_items = set() #not in view
self._xshifted = 0 #view moved in x direction
self._yshifted = 0 #view moved in y direction
self._multi = 0
self.configure(confine=False,highlightthickness=0,bd=0)
self.bind('<MouseWheel>', self._vscroll)
self.bind('<Shift-MouseWheel>', self._hscroll)
root.bind('<Control-KeyPress>',lambda e:setattr(self,'_multi', 10))
root.bind('<Control-KeyRelease>',lambda e:setattr(self,'_multi', 0))
print(self.viewing_box())
return None
def viewing_box(self):
'returns x1,y1,x2,y2 of the currently visible area'
x1 = 0 - self._xshifted
y1 = 0 - self._yshifted
x2 = self.winfo_reqwidth()-self._xshifted
y2 = self.winfo_reqheight()-self._yshifted
return x1,y1,x2,y2
def _scan_view(self):
x1,y1,x2,y2 = self.viewing_box()
for item in self.find_withtag('viewable'):
#check if one felt over the edge
coords = self.coords(item)
#https://www.geeksforgeeks.org/python-split-tuple-into-groups-of-n/
points = tuple(
coords[x:x + 2] for x in range(0, len(coords), 2))
viewable = False
for point in points:
if x1 <= point[0] <= x2 and y1 <= point[1] <= y2:
#if any point is in viewing box
viewable = True
print(item, 'true')
else:
print(item, 'false' )
if not viewable:
current = self.itemcget(item,'tags')
if isinstance(current, tuple):
new = current-('viewable',)
else:
print('here',points, (x1,y1,x2,y2))
new = ''
self.inview_items.discard(item)
self.itemconfigure(item,tags=new)
for item in self.find_overlapping(x1,y1,x2,y2):
#check if item inside of viewing_box not in inview_items
if item not in self.inview_items:
self.inview_items.add(item)
current = self.itemcget(item,'tags')
if isinstance(current, tuple):
new = current+('viewable',)
elif isinstance(current, str):
if str:
new = (current, 'viewable')
else:
new = 'viewable'
self.itemconfigure(item,tags=new)
print(self.inview_items)
def _create(self, *args):
if (current:=args[-1].get('tags', False)):
args[-1]['tags'] = current+('viewable',)
else:
args[-1]['tags'] = ('viewable',)
ident = super()._create(*args)
self._scan_view()
return ident
def _hscroll(self,event):
offset = int(event.delta/120)
if self._multi:
offset = int(offset*self._multi)
canvas.move('all', offset,0)
self._xshifted += offset
self._scan_view()
def _vscroll(self,event):
offset = int(event.delta/120)
if self._multi:
offset = int(offset*self._multi)
canvas.move('all', 0,offset)
self._yshifted += offset
self._scan_view()
root = tk.Tk()
canvas = InfiniteCanvas(root)
canvas.pack(fill=tk.BOTH, expand=True)
size, offset, start = 100, 10, 0
canvas.create_rectangle(start,start, size,size, fill='green')
canvas.create_rectangle(
start+offset,start+offset, size+offset,size+offset, fill='darkgreen')
root.mainloop()
PS: Before thinking this is over-complicated and using just find_overlapping isn't working, since it seems the item needs to be at least 51% in the view to get tracked with tkinters algorithm.
You can find an improved version now on CodeReview!
I still don't know what I have done wrong but it works with scan_dragto.
import tkinter as tk
class InfiniteCanvas(tk.Canvas):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
self.inview_items = set() #in view
self.niview_items = set() #not in view
self._xshifted = 0 #view moved in x direction
self._yshifted = 0 #view moved in y direction
self._multi = 0
self.configure(confine=False,highlightthickness=0,bd=0)
self.bind('<MouseWheel>', self._vscroll)
self.bind('<Shift-MouseWheel>', self._hscroll)
root.bind('<Control-KeyPress>',lambda e:setattr(self,'_multi', 10))
root.bind('<Control-KeyRelease>',lambda e:setattr(self,'_multi', 0))
return None
def viewing_box(self):
'returns x1,y1,x2,y2 of the currently visible area'
x1 = 0 - self._xshifted
y1 = 0 - self._yshifted
x2 = self.winfo_reqwidth()-self._xshifted
y2 = self.winfo_reqheight()-self._yshifted
return x1,y1,x2,y2
def _scan_view(self):
x1,y1,x2,y2 = self.viewing_box()
for item in self.find_withtag('viewable'):
#check if one felt over the edge
coords = self.coords(item)
#https://www.geeksforgeeks.org/python-split-tuple-into-groups-of-n/
points = tuple(
coords[x:x + 2] for x in range(0, len(coords), 2))
viewable = False
for point in points:
if x1 <= point[0] <= x2 and y1 <= point[1] <= y2:
#if any point is in viewing box
viewable = True
if not viewable:
current = self.itemcget(item,'tags')
if isinstance(current, tuple):
new = current-('viewable',)
else:
print('here',points, (x1,y1,x2,y2))
new = ''
self.inview_items.discard(item)
self.itemconfigure(item,tags=new)
for item in self.find_overlapping(x1,y1,x2,y2):
#check if item inside of viewing_box not in inview_items
if item not in self.inview_items:
self.inview_items.add(item)
current = self.itemcget(item,'tags')
if isinstance(current, tuple):
new = current+('viewable',)
elif isinstance(current, str):
if str:
new = (current, 'viewable')
else:
new = 'viewable'
self.itemconfigure(item,tags=new)
print(self.inview_items)
def _create(self, *args):
if (current:=args[-1].get('tags', False)):
args[-1]['tags'] = current+('viewable',)
else:
args[-1]['tags'] = ('viewable',)
ident = super()._create(*args)
self._scan_view()
return ident
def _hscroll(self,event):
offset = int(event.delta/120)
if self._multi:
offset = int(offset*self._multi)
cx,cy = self.winfo_rootx(), self.winfo_rooty()
self.scan_mark(cx, cy)
self.scan_dragto(cx+offset, cy, gain=1)
self._xshifted += offset
self._scan_view()
def _vscroll(self,event):
offset = int(event.delta/120)
if self._multi:
offset = int(offset*self._multi)
cx,cy = self.winfo_rootx(), self.winfo_rooty()
self.scan_mark(cx, cy)
self.scan_dragto(cx, cy+offset, gain=1)
self._yshifted += offset
self._scan_view()
root = tk.Tk()
canvas = InfiniteCanvas(root)
canvas.pack(fill=tk.BOTH, expand=True)
size, offset, start = 100, 10, 0
canvas.create_rectangle(start,start, size,size, fill='green')
canvas.create_rectangle(
start+offset,start+offset, size+offset,size+offset, fill='darkgreen')
root.mainloop()
When running deepcopy from the Python copy module on instances of a class, the copy process time doubles with each iteration, even though the instances are all of the same class and (at least in my mind) should therefor take an identical amount of time to copy.
The object I'm copying uses kwargs to set a bunch of attributes. It also originally called a set_dynamic_attributes method in __init__ to configure some of the attributes according to some logic, but in order to test if that was causing the slowdown, I removed the call from the __init__ and called it specifically.
Here's the offending section, and the results of it:
for a in base_wpn.base_attacks:
t0 = time.time()
a.set_dynamic_attributes()
t1 = time.time()
print('Dyn attributes set time: ' + str(t1-t0))
atk = deepcopy(a)
t2 = time.time()
print('Deepcopy time: ' + str(t2-t1))
Result:
Dyn attributes set time: 0.0
Deepcopy time: 0.01399993896484375
Dyn attributes set time: 0.0
Deepcopy time: 0.03599882125854492
Dyn attributes set time: 0.0
Deepcopy time: 0.04999995231628418
Dyn attributes set time: 0.0
Deepcopy time: 0.09999823570251465
Dyn attributes set time: 0.0
Deepcopy time: 0.011002540588378906
Dyn attributes set time: 0.0
Deepcopy time: 0.021996021270751953
Dyn attributes set time: 0.0
Deepcopy time: 0.0429990291595459
Dyn attributes set time: 0.0
Deepcopy time: 0.08499836921691895
Dyn attributes set time: 0.0
Deepcopy time: 0.17699956893920898
Dyn attributes set time: 0.0
Deepcopy time: 0.32700061798095703
Dyn attributes set time: 0.0
Deepcopy time: 0.6589939594268799
Dyn attributes set time: 0.0
Deepcopy time: 1.4200007915496826
Dyn attributes set time: 0.0
Deepcopy time: 2.466003656387329
Dyn attributes set time: 0.0
Deepcopy time: 5.228000164031982
Dyn attributes set time: 0.0
Deepcopy time: 10.528998374938965
In case it helps (I know it's not reproable without a ton more code, lots of object references), here's the object definition that the instances are built from:
class Attack():
def __init__(self, name, weapon, **kwargs):
self.name = name
self.weapon = weapon
self.skill = [self.weapon.skill]
self.attack_mod = self.weapon.attack_mod
self.parry_mod = -self.weapon.parry_mod #Modifier to OPPONENT'S parry chance
self.stamina = self.weapon.stamina
self.main_shape = None
self.striker = None
self.hands = 1
self.damage_type = 'b'
self.base_ap = 0
self.hand = True
self.added_mass = self.weapon.added_mass
self.length = 0 #Used to add or subtract from base weapon length got added/reduced reach
self.side_restrict = True #Determines if the attack can only hit one side of the enemy (i.e. hook from R hand only hitting left side)
self.restricted_locs = [] #Locations that can never be targeted with this attack (i.e. foot with uppercut)
self.allowed_angles_r = [] #Angles that are allowed as an index of angles (0 = N-> S, 7 = NW -> SE, 8 = thrust) (i.e. N->S with an uppercut)
self.allowed_angles_l = [] #Angles that are allowed as an index of angles (0 = N-> S, 7 = NW -> SE, 8 = thrust) (i.e. N->S with an uppercut)
self.main_area = 0
self.mech_adv = 0
self.force_scalar = 1 #Used to adjust force/damage for the attack
for k in kwargs:
for key in self.__dict__:
if k == key:
self.__dict__.update(kwargs)
#self.set_dynamic_attributes()
def set_dynamic_attributes(self):
for t in self.damage_type:
if t == 'b':
if self.striker == 'main':
shape = self.weapon.main_shape
else:
shape = 'round'
if shape == 'wedge':
self.main_area = self.weapon.main_length * self.weapon.avg_main_width
self.mech_adv = self.weapon.main_depth / self.weapon.main_width
elif shape == 'round':
#Using Hertz's result, but using fixed f value built off of added_mass + fist mass (.86) and v of 40 f/s2 and fixed p_ratio for target
#Equation: https://www.quora.com/Is-the-area-of-contact-between-a-cylinder-and-a-flat-surface-infinitely-small-Is-it-a-point
if self.striker == 'main':
material = self.weapon.main_material
width = self.weapon.main_width
length = self.weapon.main_length
elif self.striker == 'shaft':
material == self.weapon.shaft_material
width = 1
length = self.weapon.shaft_length
else:
material = self.weapon.accent_material
width = length = 1
e_calc = ((1-(material.p_ratio * material.p_ratio))/(material.elasticity*10))+((1-(.4*.4))/5)
self.main_area = sqrt((4*((.86 + self.added_mass)*40)*width)/(3.514*(e_calc)*min(length, 8)))
elif self.main_shape == 'flat':
self.main_area = min(self.weapon.main_length,8) * min(self.weapon.main_width,8)
elif t == 's':
if self.main_shape == 'blade':
self.main_area = min(self.weapon.main_length, 8) * self.weapon.avg_main_width
self.mech_adv = self.weapon.main_depth / self.weapon.main_width
elif self.main_shape == 'de blade':
self.main_area = min(self.weapon.main_length, 8) * self.weapon.avg_main_width
self.mech_adv = (self.weapon.main_depth/2) / self.weapon.main_width
elif t == 'p':
if self.striker == 'main':
shape = self.weapon.main_shape
length = self.weapon.main_length
depth = self.weapon.main_depth
width = self.weapon.main_width
else:
shape = 'point'
if self.striker == 'shaft':
length = min(self.weapon.shaft_length, 8)
depth = width = 1
else:
length = depth = width = 1
if shape in ['point', 'blade']:
wedge1 = length / width
wedge2 = width / depth
#Double each (since there are two wedges per side) and multiply for full MA of tip
self.mech_adv = (wedge1*2)*(wedge2*2)
self.main_area = depth * length * width
else:
if self.main_shape == 'hook':
wedge1 = self.weapon.main_length / self.weapon.main_width
wedge2 = self.weapon.main_width / self.weapon.main_depth
#Double each (since there are two wedges per side) and multiply for full MA of tip
self.mech_adv = (wedge1*2)*(wedge2*2)
self.main_area = self.weapon.main_depth * self.weapon.main_width
if self.damage_type in ['s','b']:
self.stamina += self.weapon.weight + int(self.weapon.weight*self.weapon.axis_vs_com)
self.base_ap += min(self.weapon.weight/10, (self.weapon.weight * 10)/self.weapon.axis_vs_com)
self.added_mass = self.weapon.weight / self.weapon.com_perc
self.attack_mod += (20 - ((self.weapon.weight*10) * self.weapon.com_perc))
self.parry_mod -= ((self.weapon.weight*10) * self.weapon.com_perc)
if self.weapon.main_num > 1:
self.attack_mod += self.weapon.main_num * 5
self.parry_mod -= self.weapon.main_num * 20
else:
self.stamina += self.weapon.weight/2
self.base_ap += self.weapon.weight * 5
self.added_mass = self.weapon.weight/10
if self.damage_type == 'p':
self.attack_mod -= self.weapon.weight/10
self.parry_mod -= self.weapon.weight * 5
else:
self.attack_mod += -5 + (self.weapon.main_num * 5)
self.parry_mod -= self.weapon.main_num * 20
More code by request. Here is the code for the weapon class (which the attack references) and the materials class (which weapon references):
class Weapon:
def __init__(self, **kwargs):
self.name = ''
self.shafted = False #Used to determine if wpn has a shaft
self.allowed_main_materials = [] # List of materials applicable for the main surface. Young's modulus prevents copper and bronze swords longer than 24", for example
self.main_material = m_steel #Damage component (blade, head, etc) material
self.shaft_material = m_wood
self.grip_material = m_leather
self.accent_material = m_steel
self.attack_mod = 0
self.parry_mod = 0 #Mod to weilder's ability to parry with weapon
self.b_striker = 'accent' #Striking surface for damage type. Can be main, shaft, accent, or none
self.s_striker = 'main'
self.p_striker = 'main'
self.t_striker = 'none'
self.hands = [1] #List can include 0,1,2
self.quality = 'Average'
self.base_name = 'Weapon'
self.bname_variants = [] #A list of variant names for the weapon
self.skill = None #This is the default skill used for the weapon. String
self.length = 1
self.shaft_length = 0 #Also used as tethers for flail and whip like weapons
self.shaft_diameter = 0
self.shaft_num = 0
self.pre_load = False #Used to account for weapons that can be preloaded with velocity, like flails or staves
self.avg_main_width = 1 #1.25 average longsword
self.main_width = 1 #Absolute width at widest point
self.avg_main_depth = .1 #.14 is average for a sword blade
self.main_depth = .2 #Absolute depth at deepest point
self.main_shape = 'de blade' #Acceptable values: de blade, blade, point, wedge, round, flat, hook
self.main_num = 1 #Number of main attack surfaces, mostly used for flails/flogs
self.accent_cuin = 1 #Cubic inches of accent material, such as the crossguard and pommel on a sword
self.main_com = .5 #Center of mass for the main weapon component
self.main_loc = .1 #Location along the total length for the main weapon component
self.accent_loc = .05 #Location along the total length for the accent component
self.grip_loc = .03 #location along the total length for the grip
self.main_weight = 0
self.shaft_weight = 0
self.accent_weight = 0
self.grip_weight = 0
self.weight = 0
self.main_length = 0
self.added_mass = 0
self.damage_type = 'b'
self.stamina = 0
#Maximums; used to procedurally gen weapons
self.main_len_range = (0,0) #Tuple containing min and max range for acceptable lengths
self.main_depth_range = (0,0)
self.main_avg_depth_range = (0,0)
self.main_width_range = (0,0)
self.main_avg_width_range = (0,0)
self.length_range = (0,0)
self.shaft_length_range = (0,0)
self.shaft_diameter_range = (0,0)
self.max_main_num = 1
self.max_shaft_num = 1
self.main_only_com = 0
self.shaft_only_com = 0
self.accent_only_com = 0
self.com = 0 #Center of mass for the whole weapon
self.com_perc = 0 #com as a percentage
self.axis_vs_com = 0 #Shows COM relative to grip location (axis for swings). Used to determine AP/stamina costs.
self.main_hits = 0
self.staff_hits = 0
self.accent_hits = 0
self.min_pwr_1h = 0 #1 pwr = 1 ft/lb; accelleration = 40 f/s2; weight of average hand = .86 lb
self.min_pwr_2h = 0 #1 pwr = 1.5 ft/lb; accelleration = 40 f/s2; weight of average hand = .86 lb
self.solidness = 1 #Used in damage calc
self.sharpness = 1
self.pointedness = 1
self.base_attacks = []
self.attacks = []
self.base_maneuvers = []
self.maneuvers = []
self.guards = []
self.cost = 0
self.normality = 1
for k in kwargs:
for key in self.__dict__:
if k == key:
self.__dict__.update(kwargs)
self.set_dynamic_attributes()
def set_dynamic_attributes(self):
#Determine which variant name to use
if len(self.bname_variants) > 1:
n_idx = roll_dice(1, len(self.bname_variants))
n_idx -= 1
self.base_name = self.bname_variants[n_idx]
#Name weapon using quality and material, if applicable
if self.main_material is not m_tissue:
if self.quality != 'Average':
self.name = self.quality + ' ' + self.main_material.name + ' ' + self.base_name
else:
self.name = self.main_material.name + ' ' + self.base_name
else:
self.name = self.base_name
self.attack_mod = 20 * quality_dict.get(self.quality)
self.parry_mod = 20 * quality_dict.get(self.quality)
self.main_weight = ((self.main_length * self.avg_main_depth * self.avg_main_width)*self.main_num) * (self.main_material.density * .03)
self.shaft_weight = (self.shaft_length * self.shaft_diameter * (self.shaft_material.density * .03)) * self.shaft_num
self.accent_weight = self.accent_cuin * (self.accent_material.density * .03)
self.grip_weight = self.grip_material.density * (.3 * max(self.hands))
self.weight = self.main_weight + self.shaft_weight + self.accent_weight
self.main_only_com = ((self.main_length*self.main_com)+(self.main_loc*self.length))*self.main_weight
self.shaft_only_com = (self.shaft_length*.5)*self.shaft_weight
self.accent_only_com = (self.accent_loc*self.length)*self.accent_weight
self.com = (self.main_only_com + self.shaft_only_com + self.accent_only_com)/self.weight #Center of mass for the whole weapon
self.com_perc = self.com / self.length #com as a percentage
self.axis_vs_com = self.com_perc - self.grip_loc #Shows COM relative to grip location (axis for swings). Used to determine AP/stamina costs.
self.main_hits = (self.main_material.elasticity * 1450000) * (self.main_weight/(self.main_material.density*.03)) * self.main_material.toughness
self.min_pwr_1h = ((self.added_mass + .86) * 40)/1 #1 pwr = 1 ft/lb/s; accelleration = 40 f/s2; weight of average hand = .86 lb
self.min_pwr_2h = ((self.added_mass + 1.72) * 40)/1.5 #1 pwr = 1.5 ft/lb/s; accelleration = 40 f/s2; weight of average hand = .86 lb
if self.main_material.elasticity < 1: self.solidness = self.main_material.elasticity
if self.main_material.hardness < 1:
self.sharpness = self.main_material.hardness
self.pointedness = self.main_material.hardness
else:
self.sharpness = sqrt(self.main_material.hardness)
self.pointedness = self.main_material.hardness
#Damage calc = ((((added_mass + fist mass) * velocity) / main_area) * mech_adv) * sharpness or hardness or pointedness
main_materials_cost = self.main_material.cost * self.main_weight
shaft_materials_cost = self.shaft_material.cost * self.shaft_weight
grip_materials_cost = self.grip_material.cost * self.grip_weight
accent_materials_cost = self.accent_material.cost * self.accent_weight
#Crafting costs. 1 day skilled labor = ~5 material cost
main_crafting_cost = self.main_material.craft_diff * self.main_weight
shaft_crafting_cost = self.shaft_material.craft_diff * self.shaft_weight
grip_crafting_cost = self.grip_material.craft_diff * self.grip_weight
accent_crafting_cost = self.accent_material.craft_diff * self.accent_weight
self.cost = main_crafting_cost + main_materials_cost + shaft_crafting_cost + shaft_materials_cost + grip_crafting_cost + grip_materials_cost + accent_crafting_cost + accent_materials_cost
self.normality = self.main_material.normality * self.shaft_material.normality * self.grip_material.normality * self.accent_material.normality
class Material():
_allMaterials = []
def __init__(self, **kwargs):
#Below is to create a list of instances of this class
self._allMaterials.append(self)
self.name = None
#Goal: Hardness and elasticity set the hits for the material, toughness then scales them up or down.
self.hardness = 1 #Scalar. 1 = ~7 Brinell, roughly very hard wood. Copper 85, Bronze 180, Iron 300, Steel 400-600
self.elasticity = 1 #Scalar. Basically resistance to deformation. 1 = Wood, average modulus of elasticity ~10 GPa. Bone 18, Gold 79, Copper 100, Bronze 120, Iron 210, Steel 200
self.toughness = 1 #Scalar. Resistance to breaking when deformed. 1 = Wood, very non-brittle. 10 = Gold, very malliable. .5 = Steel, somewhat brittle. 0.1 = stone
self.p_ratio = .5 #Poisson's ratio. Non-scaled, use actual values
self.normality = 1 #Scalar. How rare the material is. 1 = Iron, very common. Gold is 4 million times rarer than Iron, but probably it will be ~.01 in game
self.cost = 1 #Scalar. Raw cost of the material (taking into account refining costs). 1 = Wood, cheap $0.23/lb. 10 = Copper, $2.50/lb. 82,000 = Pure Gold
self.craft_diff = 1 #Scalar. Difficulty of crafting. 1 = 1 day of crafting per lb of finished material. Cost of 1 day of a craftsman = 5
# For metals, hardness modifies it. Hardened Steel takes 5 days to craft 1 pound of weaponry.
self.density = 1 #Scalar. Relative density per cubic inch. 1 = Wood, .03 lb/in3. Copper = .32, Silver = .38, Gold = .7, Iron = .28, Bronze = .3, Steel = .28
for key in self.__dict__:
for k in kwargs:
if k == key:
self.__dict__.update(kwargs)
self.set_dynamic_attr()
def set_dynamic_attr(self):
if self.craft_diff == 0:
self.craft_diff = .078 * self.hardness
self.craft_diff = round(self.craft_diff, 2)
Fixed it; The issue was as user10987432 suspected, it was creating copies of all objects up the tree. I fixed it by removing the weapon reference in the constructor and passing each individual attribute as a kwarg (ugh). Makes for a very ugly 4-line call, but it works and is fast. I'll see if I can shorten it once it gets on my nerves enough; for now I can just copy/paste and only change relevant fields.
Edit: I fixed the problem much more cleanly by making the individual attacks all child classes of the Attack class and just creating instances of them for each actor. Not sure why I didn't do that originally, I'm doing it for maneuvers already, but it's much easier to read and debug now. Thanks again to everyone for the help.
I'm trying to figure out why the while loop in one of my functions is still running even after the points in my graphics are equal, which is when I set it to stop. Is there anything I'm doing wrong? I've tried to switch other things around to get it to work but no luck.
It's for a game--when the character reaches the endbox the loop needs to break, but it isn't doing that after I explicitly coded it to. It's in the second function I have:
from graphics import *
def field():
#creating the window
win = GraphWin('The Field',400,400)
win.setBackground('white')
#drawing the grid
boxlist = []
for i in range(0,400,40):
for j in range(0,400,40):
box = Rectangle(Point(i,j),Point(i+40,j+40))
box.setOutline('light gray')
box.draw(win)
boxlist.append(box)
#creating other boxes
startbox = Rectangle(Point(0,0),Point(40,40))
startbox.setFill('lime')
startbox.setOutline('light gray')
startbox.draw(win)
endbox = Rectangle(Point(360,360),Point(400,400))
endbox.setFill('red')
endbox.setOutline('light gray')
endbox.draw(win)
boxlist.append(startbox)
boxlist.append(endbox)
#creating Pete
pete = Rectangle(Point(2,2),Point(38,38))
pete.setFill('gold')
pete.draw(win)
return win,boxlist,pete
def move(win2,boxlist,pete,endbox):
peteloc = pete.getCenter()
#creating loop to move pete
while peteloc != endbox.getCenter():
click = win2.getMouse()
x = click.getX()
y = click.getY()
peteloc = pete.getCenter()
petex = peteloc.getX()
petey = peteloc.getY()
#moving pete
if x>=petex+20 and y<=petey+20 and y>=petey-20:
pete.move(40,0)
elif x<=petex-20 and y<=petey+20 and y>=petey-20:
pete.move(-40,0)
elif y>=petey+20 and x<=petex+20 and x>=petex-20:
pete.move(0,40)
elif y<=petey-20 and x<=petex+20 and x>=petex-20:
pete.move(0,-40)
peteloc = pete.getCenter()
# The main function
def main():
win2,boxlist,pete = field()
endbox = boxlist[len(boxlist)-1]
move(win2,boxlist,pete,endbox)
main()
I think maybe it is caused by precision of float. I guess pete.getCenter() and endbox.getCenter() are something like [float, float], you should avoid using != between float, such as 1.0000001 is not equal to 1.
So even if the character reaches the endbox, the position will still get a little float bias.
So you can change a != b to abs(a - b) > acceptable_error when the error is acceptable. Sample code is like:
# while peteloc != endbox.getCenter():
while abs(peteloc.getX() - endbox.getCenter().getX()) > 0.01 and abs(peteloc.getY() - endbox.getCenter().getY()) > 0.01:
Hope that will help you.
Zelle graphics Point objects don't appear to ever compare as equal:
>>> from graphics import *
>>> a = Point(100, 100)
>>> b = Point(100, 100)
>>> a == b
False
>>>
We have to extract coordinates and do our own comparison. Although #recnac provides a workable solution (+1), I'm going to suggest a more general one. We'll create a distance() method that's valid for any object that inherits from _BBox, which includes Rectangle, Oval, Circle and Line:
def distance(bbox1, bbox2):
c1 = bbox1.getCenter()
c2 = bbox2.getCenter()
return ((c2.getX() - c1.getX()) ** 2 + (c2.getY() - c1.getY()) ** 2) ** 0.5
We can now measure the distance between objects, horizontally, vertically and diagonally. Since your boxes are moving twenty pixels at a time, we can assume that if they are withing 1 pixel of each other, they are in the same location. Your code rewritten to use the distance() method and other tweaks:
from graphics import *
def field(win):
# drawing the grid
boxlist = []
for i in range(0, 400, 40):
for j in range(0, 400, 40):
box = Rectangle(Point(i, j), Point(i + 40, j + 40))
box.setOutline('light gray')
box.draw(win)
boxlist.append(box)
# creating other boxes
startbox = Rectangle(Point(0, 0), Point(40, 40))
startbox.setFill('lime')
startbox.setOutline('light gray')
startbox.draw(win)
boxlist.append(startbox)
endbox = Rectangle(Point(360, 360), Point(400, 400))
endbox.setFill('red')
endbox.setOutline('light gray')
endbox.draw(win)
boxlist.append(endbox)
# creating Pete
pete = Rectangle(Point(2, 2), Point(38, 38))
pete.setFill('gold')
pete.draw(win)
return boxlist, pete
def distance(bbox1, bbox2):
c1 = bbox1.getCenter()
c2 = bbox2.getCenter()
return ((c2.getX() - c1.getX()) ** 2 + (c2.getY() - c1.getY()) ** 2) ** 0.5
def move(win, pete, endbox):
# creating loop to move pete
while distance(pete, endbox) > 1:
click = win.getMouse()
x, y = click.getX(), click.getY()
peteloc = pete.getCenter()
petex, petey = peteloc.getX(), peteloc.getY()
# moving pete
if x >= petex + 20 and petey - 20 <= y <= petey + 20:
pete.move(40, 0)
elif x <= petex - 20 and petey - 20 <= y <= petey + 20:
pete.move(-40, 0)
elif y >= petey + 20 and petex - 20 <= x <= petex + 20:
pete.move(0, 40)
elif y <= petey - 20 and petex - 20 <= x <= petex + 20:
pete.move(0, -40)
# The main function
def main():
# creating the window
win = GraphWin('The Field', 400, 400)
win.setBackground('white')
boxlist, pete = field(win)
endbox = boxlist[-1]
move(win, pete, endbox)
main()
I'm attempting to program my own connect four game using Python. I'm trying to sort the circles that I have drawn into a 2d array. However when I try to assign my shape object to the array it gives me an index error. I can't really see an issue with counterrow and countrercolumn, can anyone else? Btw my space class just has an initialiser setting x1, x2, y1, y2, taken, and id
from tkinter import *
from space import *
master = Tk();
w = Canvas(master, width = 600, height = 500)
w.pack()
spaceList = []
for i in range(7):
spaceList.append([0] * 6)
currentmove = 'PLAYER1'
won = False
counterrow = 0
countercolumn = 0
for i in range(0,560,80):
for j in range(0,480,80):
w.create_oval(10+i, 10+j, 90+i, 90+j)
newspace = Space(10+i, 10+j, 90+i, 90+j, False, 'EMPTY')
spaceList[counterrow][countercolumn] = newspace
countercolumn = countercolumn + 1
counterrow = counterrow + 1
while(not won):
movecol = int(input("Please select a column!"))
def move(column):
for i in spaceList:
return 0
mainloop()
You have to reset the countercolumn:
for i in range(0,560,80):
# add this:
countercolumn = 0
for j in range(0,480,80):
# omitted
Otherwise it becomes seven and larger and you get an overflow.
I would like to animate multiple balls bouncing on the boundaries but not on each other. The height boundary should be moving at a constant speed determined by the user.
So I tried to first make a ball move in the boundaries I set for it.
This is the script:
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import animation
from matplotlib.widgets import Slider, Button, RadioButtons
import numpy as np
from time import sleep
import numpy.random as rnd
def norme ( vecteur ):
return np.sqrt( np.dot(vecteur,vecteur) )
B = int(input( "How many balls: " ) )
N=1000
C=1
sx = 1000
sy = 200
Vmax = 50000
VY = 50000
class ball:
def __init__(self):
self.x = rnd.randint(1,sx)
self.y = rnd.randint(1,sy)
self.vx = rnd.randint(1,Vmax)
self.vy = rnd.randint(1,VY)
b = ball()
p, v, balls = [], [], []
nv=np.sqrt(b.vx**2+b.vy**2)
dt = 100/ nv
for i in range(N):
b.x += b.vx * dt
b.y += b.vy * dt
if( b.x >= 1000 or b.x <=0 ):
b.vx = - b.vx/C
if( b.y >= 200 or b.y < 0 ):
b.vy = -b.vy*C
p.append( (b.x,b.y) )
v.append( (b.vx,b.vy) )
fig = plt.figure()
mes_axes = plt.axes(xlim=(0, sx), ylim=(0, sy) )
mes_axes.set_title("Balle")
TexteX = " Paroi amorphe si C = " + str(C) + " est < 1 "
TexteY = " Paroi impulsive si 1/C = " + str(1./C) + " est > 1 "
plt.xlabel(TexteX)
plt.ylabel(TexteY)
balle, = mes_axes.plot( [], [], marker = 'o', markersize = 15.0 )
def initialise():
balle.set_data( [], [] )
return balle,
def AvanceBalle(n,mes_axes,fig):
x, y = p[n]
vx, vy = v[n]
balle.set_data( p[n] )
return balle,
anim = animation.FuncAnimation(fig,AvanceBalle,init_func=initialise,interval=100,frames=N, fargs=(mes_axes,fig), blit=True )
plt.show()
Then I tried to add to my loop
for i in range(N):
b.x += b.vx * dt
b.y += b.vy * dt
if( b.x >= 1000 or b.x <=0 ):
b.vx = - b.vx/C
if( b.y >= 200 or b.y < 0 ):
b.vy = -b.vy*C
p.append( (b.x,b.y) )
v.append( (b.vx,b.vy) )
a new loop to make multiple balls.
This is the new loop and it's not working
for i in range(B):
b = ball()
balls.append(b)
for i in range(B):
x,y,vx,vy=balls[i].x,balls[i].y,balls[i].vx,balls[i].vy
xax.append((x,y))
yax.append((vx,vy))
for i in range(N):
x += vx * dt
y += vy * dt
if( x >= 1000 or x <=0 ):
vx = - vx/C
if( y >= 200 or y < 0 ):
vy = -vy*C
p.append( (x,y) )
v.append( (vx,vy) )
The program keeps animating 1 ball and not the others.
P.S. I'm a beginner in python and most of the animating part, I took from a ready script that was handed to me by my professor.
Please note that some of these values are put so that I can change them later like: C=1