@@ -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 ("\t rectangle\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