Skip to content

Commit 53879ab

Browse files
committed
translation window, custom save, bounds lock, selection bounds, corner lock
1 parent 1c2d37e commit 53879ab

File tree

1 file changed

+175
-44
lines changed

1 file changed

+175
-44
lines changed

rectmaker.py

Lines changed: 175 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,6 @@ def __init__(self, master):
4646
"h": tk.StringVar()
4747
}
4848

49-
self.scale_vars = {
50-
"X Scale": tk.StringVar(),
51-
"Y Scale": tk.StringVar()
52-
}
53-
5449
for i, key in enumerate(["x","y","w","h"]):
5550
tk.Label(self.sidebar,text=key.upper()).grid(row=i,column=0,sticky="w")
5651
entry = tk.Entry(self.sidebar,textvariable=self.coord_vars[key],width=10)
@@ -68,6 +63,8 @@ def __init__(self, master):
6863
self.unsaved_changes = False
6964
self.redraw_background = False
7065

66+
self.output_scale = 1.0
67+
7168
self.image = None
7269
self.tk_image = None
7370
self.scale = 1.0
@@ -109,8 +106,10 @@ def create_menu(self):
109106
#file_menu.add_command(label="Open .rect", command=self.import_rectangles)
110107
#file_menu.add_command(label="Open .vmt",command=self.open_vmt)
111108
file_menu.add_command(label="Open",command= self.open_file)
112-
file_menu.add_command(label="Save", command=self.save)
109+
file_menu.add_separator()
110+
file_menu.add_command(label="Save", command=self.save,accelerator="Ctrl+S")
113111
file_menu.add_command(label="Save As...",command=self.export_rectangles)
112+
114113
file_menu.add_separator()
115114
file_menu.add_command(label="Exit", command=self.master.quit)
116115

@@ -120,7 +119,8 @@ def create_menu(self):
120119
menu.add_cascade(label="Edit",menu=edit_menu)
121120

122121
tools_menu = tk.Menu(menu,tearoff=0)
123-
tools_menu.add_command(label="Rescale Rectangles",command=self.open_scale_window,accelerator = "Ctrl+M")
122+
tools_menu.add_command(label="Move/Translate",command=self.open_scale_window,accelerator = "Ctrl+M")
123+
tools_menu.add_command(label="Special Save",command=self.open_custom_save_window)
124124
menu.add_cascade(label="Tools",menu=tools_menu)
125125

126126
def bind_events(self):
@@ -152,43 +152,114 @@ def bind_events(self):
152152
self.master.bind("<plus>",self.increase_grid)
153153
self.master.bind("<KP_Subtract>",self.decrease_grid)
154154
self.master.bind("<Key>", self.debug_key)
155-
self.master.bind("<Control-m>",self.open_scale_window)
155+
self.master.bind("<Control-m>",lambda e: self.open_scale_window())
156156

157157
def open_image_error_window(self):
158158
messagebox.showinfo("Message","You tried to open a .rect file without an image loaded. Open an image first!")
159159

160+
def open_custom_save_window(self):
161+
window = tk.Toplevel(self.master)
162+
window.title = "Custom Save"
163+
window.wm_attributes("-topmost",True)
164+
savelabel = tk.Label(window,text="Save As:")
165+
savelabel.grid(row=0,column=0,sticky="w")
166+
file_path = tk.StringVar()
167+
tk.Entry(window,textvariable=file_path,width=40).grid(row=0,column=1)
168+
def browse_file():
169+
path = filedialog.asksaveasfilename(defaultextension=".rect", filetypes=[("Rect Files", "*.rect")])
170+
if path:
171+
file_path.set(path)
172+
173+
tk.Button(window,text="Browse...",command=browse_file).grid(row=0,column=3,sticky="w")
174+
175+
tk.Label(window,text="Output Scale:").grid(row=1,column=0,sticky="w")
176+
output_scale = tk.StringVar(value="1.0")
177+
tk.Entry(window,textvariable=output_scale).grid(row=1,column=1,sticky="w")
178+
179+
def on_custom_save():
180+
try:
181+
path = file_path.get()
182+
scale = float(output_scale.get())
183+
self.output_scale = scale
184+
if path:
185+
self.export_rectangles_to_path(path)
186+
self.current_rect_file = path
187+
self.update_window_title()
188+
window.destroy()
189+
except ValueError:
190+
tk.messagebox.showerror("Invalid Scale","Please enter a valid float value.")
191+
192+
tk.Button(window,text="Save",command=on_custom_save).grid(row=2,column=1,pady=10)
160193

161-
def open_scale_window(self,event):
194+
def open_scale_window(self):
162195
if self.scale_window is not None:
163196
return
164-
window = tk.Toplevel(self.master,width = 150)
165-
window.title = "Scale Rectangles"
166-
for i, key in enumerate(["X Scale","Y Scale"]):
167-
tk.Label(window,text=key).grid(row=i,column=0,sticky="w")
168-
entry = tk.Entry(window,textvariable=self.scale_vars[key],width=10)
169-
entry.grid(row=i, column=1, pady=2, padx=5)
170-
window.bind("<Return>",self.rescale_rectangles)
197+
198+
xscale = tk.StringVar(value="1.0")
199+
yscale = tk.StringVar(value="1.0")
200+
xtranslate = tk.StringVar(value="0")
201+
ytranslate = tk.StringVar(value="0")
202+
203+
window = tk.Toplevel(self.master)
204+
window.title = "Move/Scale"
205+
window.attributes("-topmost",True)
206+
tk.Label(window,text="X Scale:").grid(row=0,column=0,sticky="w")
207+
tk.Entry(window,textvariable=xscale).grid(row=0,column=1)
208+
tk.Label(window,text="Y Scale:").grid(row=1,column=0,sticky="w")
209+
tk.Entry(window,textvariable=yscale).grid(row=1,column=1)
210+
tk.Label(window,text="X Move:").grid(row=0,column=2,sticky="w")
211+
tk.Entry(window,textvariable=xtranslate).grid(row=0,column=3)
212+
tk.Label(window,text="Y Move:").grid(row=1,column=2,sticky="w")
213+
tk.Entry(window,textvariable=ytranslate).grid(row=1,column=3)
214+
215+
checkvar = tk.IntVar()
216+
217+
tk.Checkbutton(window,text="Apply to all",variable=checkvar,onvalue=1,offvalue=0).grid(row=2,column=1)
218+
219+
def apply():
220+
self.handle_translation(float(xscale.get()),float(yscale.get()),int(xtranslate.get()),int(ytranslate.get()),checkvar.get())
221+
222+
tk.Button(window,text="Apply",command=apply).grid(row=2,column=2)
171223
self.scale_window = window
172224

173225
window.protocol("WM_DELETE_WINDOW", self.on_close_scale_window())
174226

175227
def on_close_scale_window(self):
176228
self.scale_window = None
229+
def apply_translations(self,rect,xscale,yscale,xmove,ymove,all):
230+
x0,y0,x1,y1,fill,image,scaled_image,scale = self.rectangles[rect]
231+
if all:
232+
x0 *= xscale
233+
x1 *= xscale
234+
y0 *= yscale
235+
y1 *= yscale
236+
else:
237+
width = x1 - x0
238+
height = y1 - y0
239+
y1 = y0 + height * yscale
240+
x1 = x0 + width * xscale
177241

178-
def rescale_rectangles(self,event):
179-
for idx, r in enumerate(self.rectangles):
180-
x0,y0,x1,y1,fill,image,scaled_image,scale = r
181-
x = float(self.scale_vars["X Scale"].get())
182-
y = float(self.scale_vars["Y Scale"].get())
183-
x0 *= x
184-
x1 *= x
185-
y0 *= y
186-
y1 *= y
187-
self.rectangles[idx] = (int(x0),int(y0),int(x1),int(y1),fill,image,scaled_image,scale)
188-
self.redraw()
242+
x0 += xmove
243+
x1 += xmove
244+
y0 += ymove
245+
y1 += ymove
189246

247+
self.rectangles[rect] = (int(x0),int(y0),int(x1),int(y1),fill,image,scaled_image,scale)
190248

191249

250+
def handle_translation(self,xscale,yscale,xmove,ymove,all):
251+
if all:
252+
for idx, r in enumerate(self.rectangles):
253+
self.apply_translations(idx,xscale,yscale,xmove,ymove,all)
254+
else:
255+
if self.selected_rect is not None:
256+
self.apply_translations(self.selected_rect,xscale,yscale,xmove,ymove,all)
257+
258+
self.save_undo_state()
259+
self.redraw()
260+
self.update_rectangle_list()
261+
self.update_window_title()
262+
192263
def debug_key(self,event):
193264
print(f"Key pressed: keysym={event.keysym}, keycode={event.keycode}, char={event.char}")
194265

@@ -240,14 +311,24 @@ def export_rectangles_to_path(self,file):
240311
return
241312

242313
with open(file, "w") as f:
314+
write_custom_data = self.output_scale != 1.0
315+
if write_custom_data:
316+
f.write(f"output_scale {self.output_scale}\n")
243317
f.write("Rectangles\n{\n")
244318
for r in self.rectangles:
245319
x0, y0, x1, y1,*_ = r
320+
if write_custom_data:
321+
x0 = int(x0 * self.output_scale)
322+
y0 = int(y0 * self.output_scale)
323+
x1 = int(x1 * self.output_scale)
324+
y1 = int(y1 * self.output_scale)
325+
246326
f.write("\trectangle\n\t{\n")
247327
f.write(f"\t\t\"min\" \"{x0} {y0}\"\n")
248328
f.write(f"\t\t\"max\" \"{x1} {y1}\"\n")
249329
f.write("\t}\n")
250330
f.write("}")
331+
251332
self.current_rect_file = file
252333
self.update_window_title()
253334

@@ -339,6 +420,15 @@ def import_rectangles_from_path(self,file):
339420

340421
while i < len(lines):
341422
line = lines[i].strip()
423+
if line.startswith("output_scale"):
424+
parts = line.split()
425+
if len(parts) > 1:
426+
scale = float(parts[1])
427+
print(f"scale {scale}")
428+
i += 1
429+
else:
430+
scale = 1.0
431+
342432
if line == "rectangle":
343433
if i + 1 < len(lines) and lines[i + 1].strip() == "{":
344434
min_line = lines[i + 2].strip()
@@ -350,6 +440,11 @@ def import_rectangles_from_path(self,file):
350440
x0, x1 = sorted((x0, x1))
351441
y0, y1 = sorted((y0, y1))
352442

443+
x0 = int(x0/scale)
444+
x1 = int(x1/scale)
445+
y0 = int(y0/scale)
446+
y1 = int(y1/scale)
447+
353448
fill = self.random_color()
354449
width = x1 - x0
355450
height = y1 - y0
@@ -368,6 +463,7 @@ def import_rectangles_from_path(self,file):
368463
self.selected_rect = None
369464
self.redraw_background = True
370465
self.current_rect_file = file
466+
self.output_scale = 1.0
371467
self.redraw()
372468
print(f"Imported {len(self.rectangles)} rectangles from file.")
373469
except Exception as e:
@@ -433,8 +529,30 @@ def on_middle_mouse_drag(self, event):
433529
def on_middle_mouse_release(self,event):
434530
self.canvas.config(cursor="arrow")
435531

532+
533+
def within_selection_bounds(self,x,y,rect):
534+
x0,y0,x1,y1 = rect
535+
536+
w = x1 - x0
537+
h = y1 - y0
538+
539+
margin_x = (w / 2) * 0.25
540+
margin_y = (h / 2) * 0.25
541+
542+
inner_x0 = x0 + margin_x
543+
inner_x1 = x1 - margin_x
544+
inner_y0 = y0 + margin_y
545+
inner_y1 = y1 - margin_y
546+
547+
print(f"Margin: {margin_x} {margin_y}")
548+
print(f"Safe Coords: {inner_x0} {inner_y0} x {inner_x1} {inner_y1}")
549+
print(f"mouse pos {x} {y}")
550+
551+
return inner_x0 <= x <= inner_x1 and inner_y0 <= y <= inner_y1
552+
436553
def on_left_mouse_down(self, event):
437554
x, y = self.to_image_coords(event.x, event.y)
555+
print(f"Clickpos {event.x} {event.y}")
438556

439557
if self.image is None:
440558
return
@@ -447,12 +565,8 @@ def on_left_mouse_down(self, event):
447565
self.canvas.config(cursor="cross")
448566
return
449567

450-
451-
self.resize_mode = None
452-
self.selected_rect = None
453-
454-
455-
for idx, (x0,y0,x1,y1,fill,image,scaled_image,zoom) in enumerate(self.rectangles):
568+
if self.selected_rect is not None:
569+
x0,y0,x1,y1,*_ = self.rectangles[self.selected_rect]
456570
corners = {
457571
'nw': (x0,y0),
458572
'ne':(x1,y0),
@@ -462,17 +576,21 @@ def on_left_mouse_down(self, event):
462576
for mode,(cx,cy) in corners.items():
463577
sx,sy = self.to_screen_coords(cx,cy)
464578
if abs(event.x - sx) <= self.handle_size and abs(event.y - sy) <= self.handle_size:
465-
self.selected_rect = idx
579+
print("clicking corner")
466580
self.resize_mode = mode
467581
self.save_undo_state()
468582
return
469583

584+
self.resize_mode = None
585+
self.selected_rect = None
586+
470587
for idx, (x0,y0,x1,y1,fill,image,scale_image,zoom) in enumerate(self.rectangles):
471-
if x0 <= x <= x1 and y0 <= y <= y1:
472-
self.selected_rect = idx
473-
self.drag_offset = (x - x0, y - y0)
474-
self.save_undo_state()
475-
break
588+
print(f"{x0} {y0} x {x1} {y1}")
589+
if self.within_selection_bounds(x,y,(x0,y0,x1,y1)):
590+
self.selected_rect = idx
591+
self.drag_offset = (x - x0, y - y0)
592+
self.save_undo_state()
593+
break
476594

477595

478596

@@ -482,6 +600,7 @@ def on_left_mouse_down(self, event):
482600

483601
def grid_snap_value(self,value):
484602
return math.ceil(self.grid_size * round(value/self.grid_size))
603+
485604
def on_left_mouse_drag(self, event):
486605
if self.image is None:
487606
return
@@ -538,16 +657,33 @@ def on_left_mouse_drag(self, event):
538657
x1 = self.grid_snap_value(x1)
539658
y1 = self.grid_snap_value(y1)
540659
else:
660+
imagewidth = self.image.width
661+
imageheight = self.image.height
541662
dx, dy = self.drag_offset
542663
x0 = x - dx
543664
y0 = y - dy
665+
666+
if x0 <= 0:
667+
x0 = 0
668+
if y0 <= 0:
669+
y0 = 0
670+
544671
if self.draw_grid is True:
545672
x0 = self.grid_snap_value(x0)
546673
y0 = self.grid_snap_value(y0)
547674
width = rect[2] - rect[0]
548675
height = rect[3] - rect[1]
676+
549677
x1 = x0 + width
678+
if x1 >= imagewidth:
679+
x1 = imagewidth
680+
x0 = x1 - width
681+
550682
y1 = y0 + height
683+
if y1 >= imageheight:
684+
y1 = imageheight
685+
y0 = y1 - height
686+
551687
self.rectangles[self.selected_rect] = (x0,y0,x1,y1,fill,image,scaled_image,self.scale)
552688
self.update_rectangle_list()
553689
self.redraw()
@@ -675,11 +811,6 @@ def redraw(self):
675811
if self.transparent_rectangles[idx] is not None:
676812
self.canvas.delete(self.transparent_rectangles[idx])
677813
self.transparent_rectangles[idx] = None
678-
# tags = self.canvas.gettags(self.transparent_rectangles[idx])
679-
# if "rectangles" in tags:
680-
# self.canvas.dtag(self.transparent_rectangles[idx],"rectangles")
681-
# self.transparent_rectangles[idx] = None
682-
# print("Trans Rect outside screen, deleting\n")
683814
continue
684815

685816
w = int(x1 - x0)

0 commit comments

Comments
 (0)