I have a function that takes the names of image files and creates a grid of them as buttons using the image attribute, which is where the issue arises, since I need the button object to create the image due to another function of mine fit_image() (which in this case fits the image inside the object completely, hence full=True). The result of running this without adding the images to these buttons is fine and all the buttons are clickable:
self.image_buttons = {}
count = 0
# goes by column
for y in range(height_divisor):
# row
for x in range(width_divisor):
self.image_buttons[count] = tk.Button(self.preview_frame)
self.image_buttons[count].place(
relx=0 + (1 / width_divisor * x),
rely=0 + (1 / height_divisor * y),
relwidth=1 / width_divisor,
relheight=1 / height_divisor,
anchor="nw",
)
# self.current_image = fit_image(
# Image.open(names_of_files[count]), self.image_buttons[count], full=True
# )
# self.image_buttons[count].configure(image=self.current_image)
count += 1
print(self.image_buttons)
Result of print statement:
{0: <tkinter.Button object .!toplevel2.!frame2.!button>, 1: <tkinter.Button object .!toplevel2.!frame2.!button2>, 2: <tkinter.Button object .!toplevel2.!frame2.!button3>, 3: <tkinter.Button object .!toplevel2.!frame2.!button4>}
However, once I uncomment this code, only the last button is clickable and does have the image on it, but all the others are blank and not clickable buttons. I have tried putting the commented (image configuration) lines in a separate for loop afterwards to go through and configure each button to no avail. Though I've previously had this work and even tried combing through my repo's commits (nothing before Aug 8th should be relevant) to see how it worked before, it must've been working then I most likely broke it before committing.
This was solved thanks to #jasonharper 's comment by changing these two lines and adding a new dictionary:
self.image_buttons, self.images = {}, {}
#for loops here
self.images[count] = fit_image(
Image.open(names_of_files[count]), self.image_buttons[count], full=True
)
self.image_buttons[count].configure(image=self.images[count])
Related
I am writing a Python script to automatically adjust cell borders in LibreOffice Calc. I think I know what property I need to change, however when I assign a new value to this property, the value does not change.
For instance, I wrote this code to change the TopLine.LineWidth of a single Cell from 0 to 10.
# Access the current calc document
model = desktop.getCurrentComponent()
# Access the active sheet
active_sheet = model.CurrentController.ActiveSheet
# Get the cell and change the value of LineWidth
cell = active_sheet.getCellByPosition(2, 2)
cell.TableBorder2.TopLine.LineWidth = 10
I don't get any errors after running this code. And I have also made sure that I am accessing the cell I wish to modify. However, this code does not change the cell's border width.
I tried doing some debugging by printing the value before and after the assignment:
# This first print statement returns 0 because the cell has no borders
print(cell.TableBorder2.TopLine.LineWidth)
cell.TableBorder2.TopLine.LineWidth = 10
# This second print statement still returns 0, but I was expecting it to return 10
print(cell.TableBorder2.TopLine.LineWidth)
Does anyone know what I am doing wrong?
You need to set the cell property to a changed border object. From https://ask.libreoffice.org/en/question/145885/border-macro-no-longer-works/:
aThinBorder = oRange.TopBorder2
aThinBorder.LineWidth = 1
oRange.TopBorder2 = aThinBorder
So, after doing a lot of research, I found at least three methods to change border settings. Because it took me so much effort, I figured I should leave them here so in the future other people may find the answer more easily.
In all examples I'll set the LineWidth of the TopBorder of a single cell to 10.
Method 1: Using getPropertyValue() and setPropertyValue()
cell = active_sheet.getCellByPosition(1, 1)
border_prop = cell.getPropertyValue("TopBorder")
border_prop.LineWidth = 10
cell.setPropertyValue("TopBorder", border_prop)
Method 2 (derived from Jim K's answer)
cell = active_sheet.getCellByPosition(1, 1)
border_prop = cell.TopBorder2
border_prop.LineWidth = 10
cell.TopBorder2 = border_prop
Method 3: Using a BorderLine2 struct
border_prop = uno.createUnoStruct("com.sun.star.table.BorderLine2")
border_prop.LineWidth = 10
cell = active_sheet.getCellByPosition(1, 1)
cell.setPropertyValue("TopBorder", border_prop)
I'm currently using Bokeh to present a multi_line plot, that has several static lines and one line, that is live updated. This runs fine with only few lines but, depending on the resolution of the lines (usually 2000-4000 points per line), the refreshing rate drops significantly when having 50+ lines in the plot. The CPU usage of the browser is pretty high at that moment.
This is how the the plot is initialized and the live update is triggered:
figure_opts = dict(plot_width=750,
plot_height=750,
x_range=(0, dset_size),
y_range=(0, np.iinfo(dtype).max),
tools='pan,wheel_zoom')
line_opts = dict(
line_width=5, line_color='color', line_alpha=0.6,
hover_line_color='color', hover_line_alpha=1.0,
source=profile_lines
)
profile_plot = figure(**figure_opts)
profile_plot.toolbar.logo = None
multi_line_plot = profile_plot.multi_line(xs='x', ys='y', **line_opts)
profile_plot.xaxis.axis_label = "x"
profile_plot.yaxis.axis_label = "y"
ds = multi_line_plot.data_source
def update_live_plot():
random_arr = np.random.random_integers(65535 * (i % 100) / (100 + 100 / 4), 65535 * (i % 100 + 1) / 100, (2048))
profile = random_arr.astype(np.uint16)
if profile is not None:
profile_lines["x"][i] = x
profile_lines["y"][i] = profile
profile_lines["color"][i] = Category20_20[0]
ds.data = profile_lines
doc.add_periodic_callback(update_live_plot, 100)
Is there any way to make this better performing?
Is it, for example, possible to only update the one line, that needs to get updated, instead of ds.data = profile_lines?
Edit: The one line that needs to be updated has to be updated in its full length. I.e. I'm not streaming data at one end, but instead I have a full new set of 2000-4000 values and want to show those, instead of the old live line.
Currently the live line is the element at i in the arrays in the profile_lines dictionary.
You are in luck, updating a single line with all new elements while keeping the same length is something that can be accomplished with the CDS patch method. (Streaming would not help here, since streaming to the end of a CDS for a multi_line means adding an entire new line, and the other case of streaming to the end of each sub-line does not have a good solution at all.)
There is a patch_app.py example in the repository that shows how to use patch to update one line of a multi_line. The example only updates a single point in the line, but it's possible to update the entire line at once using slices:
source.patch({ 'ys' : [([i, slice(None)], new_y)]})
That will update the ith line in source.data['ys'], as long as new_y has the same length as the old line.
I'm creating an instance python command where the primary purpose is to generate objects in neat horizontal rows. Even though I can randomize rotation and set the range, I still can't figure out how to get the objects to appear in horizontal rows.
I already tried to use the xform command to get the objects to move along the x coordinates.
import maya.cmds as MC
import random as RN
def ChainmailGenerator():
thing = MC.ls(sl=True)
print thing
if not thing:
MC.error (" ***Error - you need to select an object *** ")
# create a group node
grp = MC.group(empty=True, name=thing[0] + '_grp#')
#Loop though the items below with the range of a
for i in range (0,25):
instanceObj = MC.instance(thing, name=thing[0]+'instance#', smartTransform=True)
rx = RN.uniform(-1,1)*5
ry = RN.uniform(-1,1)*5
rz = RN.uniform(-1,1)*5
MC.rotate (rx,ry,rz, instanceObj)
MC.xform (r=True, ro=(90, 0, 0) )
tx = 5
MC.xform ( instanceObj, t=(0,15+1,0))
MC.parent (instanceObj,grp)
print "*** chainmail ***"
ChainmailGenerator()
The expectations are for the objects to generate in horizontal increments as if they're forming neat rows.
here is an example to create 10 spheres along x, moving them with xform :
step = 1
tx = 0
for x in range(10):
sphere = cmds.polySphere()[0]
cmds.xform(sphere, t=[tx,0,0])
tx+= step
The reason yours is placing everything in the same place now is because you aren't multiplying it against a value that keeps increasing. Normally you could hard-code some random value to space each one out, but this would yield inconsistent results.
Here's a generic way to go about it that seems to work with any object.
The idea is to use the mesh's bounding box to determine what the spacing should be by looking at the size of its x axis. You can also move it in place with xform, but you do need to include its worldspace parameter so that it doesn't move it relative to its current position.
import maya.cmds as cmds
def cloneInRow(count):
# Get selection.
thing = cmds.ls(sl=True)
if not thing:
cmds.error("You need to select an object")
# Get selection's mesh shape.
mesh = cmds.listRelatives(thing[0], shapes=True, f=True, ni=True, type="mesh")
if not mesh:
cmds.error("Unable to find a mesh on the selected object")
# Determine spacing by looking at object's bounding box. Use its `x` axis size.
bb_min = cmds.getAttr(mesh[0] + ".boundingBoxMin")[0]
bb_max = cmds.getAttr(mesh[0] + ".boundingBoxMax")[0]
spacing = bb_max[0] - bb_min[0]
# Create a root transform to parent to.
grp = cmds.group(empty=True, name=thing[0] + '_grp#')
# Create instance, and move it in place.
for i in range (0, count):
instanceObj = cmds.instance(thing[0], name=thing[0] + 'instance' + str(i), smartTransform=True)
cmds.xform(instanceObj, ws=True, t=(i * spacing, 0, 0))
cmds.parent(instanceObj, grp)
cmds.select(grp)
cloneInRow(10)
With this I can take this crossbow:
And clone any of its objects and get nice spacing:
The only catch is rotation. If your pivot isn't centered to the mesh, then randomizing its rotation will lose its place in space (since rotating would also effects its position!) So if you got weird pivots then it won't look nice when you add back on rotations.
max_page_name = self.ui.p_tree.sizeHintForColumn(0) + 2*self.ui.p_tree.frameWidth()
The above code gives the size of the Tree Widget from contents but considers only the top level items. How can I get the size considering all the items including sub-items?
Right now, I am using a work-around by doing
self.ui.p_tree.expandAll()
max_page_name = self.ui.p_tree.sizeHintForColumn(0) + 2*self.ui.p_tree.frameWidth()
self.ui.p_tree.collapseAll()
self.ui.p_tree.setMinimumWidth(max_page_name)
I have 4 directories with images for an animation. I would like to take the set of images and generate a single image with the 4 images arranged into a 2x2 grid for each frame of the animation.
My code so far is:
import Image
fluid64 = "Fluid64_half_size/00"
fluid128 = "Fluid128_half_size/00"
fluid512 = "Fluid512_half_size/00"
fluid1024 = "Fluid1024_half_size/00"
out_image = "Fluid_all/00"
for pic in range(1, 26):
blank_image = Image.open("blank.jpg")
if pic < 10:
image_num = "0"+str(pic)
else:
image_num = str(pic)
image64 = Image.open(fluid64+image_num+".jpg")
image128 = Image.open(fluid128+image_num+".jpg")
image512 = Image.open(fluid512+image_num+".jpg")
image1024 = Image.open(fluid1024+image_num+".jpg")
out = out_image + image_num + ".jpg"
blank_image.paste(image64, (0,0)).paste(fluid128, (400,0)).paste(fluid512, (0,300)).paste(fluid1024, (400,300)).save(out)
Not sure why it's not working. I'm getting the error:
Traceback (most recent call last):
File "C:\Users\Casey\Desktop\Image_composite.py", line 24, in <module>
blank_image.paste(image64, (0,0)).paste(fluid128, (400,0)).paste(fluid512, (
ste(fluid1024, (400,300)).save(out)
AttributeError: 'NoneType' object has no attribute 'paste'
shell returned 1
Any help would be awesome. Thanks!
The only problem there is that "paste" does not return an image object - it rather modifies the "blank" image inplace.
So, when the second paste is called (the one that uses the fuild128 image), it tries to be applied on "None" - which is the return value of the first image.
If that is the only problem you are having, just make one paste call per line, like this:
blank_image.paste(image64, (0,0))
blank_image.paste(fluid128, (400,0))
blank_image.paste(fluid512, (0,300))
blank_image.paste(fluid1024, (400,300))
blank_image.save(out)
Although it looks likely you'd need to scale each image so that their format match as well.
And your code for the "image_num" variable is unecessary. Python is really good with strings - just do something like this:
image64 = Image.open(fluid64 + "%02d.jpg" % pic)
You may want to be using something along the lines of :
blank_image = Image.new("RGB", (800, 600))
This will create a new area in memory in which you can generate your image. You should then be able to paste you images into that.
Then you'll need to save it out again later on with:
blank_image.save("blank.jpg")
Read the error message:
AttributeError: 'NoneType' object has no attribute 'paste'
This means you tried to call .paste on something that was of type NoneType, i.e. on the None object.
Image.paste returns None. You can't "chain" together calls like that except when the functions are specifically designed to support it, and Image.paste is not. (Support for this sort of thing is accomplished by having the function return self. You get an error that talks about NoneType because the function is written not to return anything, and everything in Python returns None by default if nothing else is returned explicitly.) This is considered Pythonic: methods either return a new value, or modify self and return None. Thus, so-called "fluent interfaces" are not used when the functions have side effects - Pythonistas consider that harmful. Returning None is a warning that the function has side effects. :)
Just do four separate .paste calls.
Tiling figures in a 2-by-2 grid would be easy to achieve with the append_images function defined in this reply
https://stackoverflow.com/a/46623632/8738113
For example:
img1 = append_images([image64, image128], direction='horizontal')
img2 = append_images([image512, image1024], direction='horizontal')
final = append_images([img1, img2], direction='vertical')
final.save("Fluid_all/00.jpg")
Unlike PIL APIs copy, crop, resize or rotate which return an Image object, paste returns None which prevents chained method calls. Not so convenient API design.