@@ -95,12 +95,10 @@ def estimate_memory(sheet_size, defect_sizes, defect_positions):
9595 try :
9696 from guillotine .core import _solver
9797 from guillotine .core .geometry import SheetGeometry
98- # build defects_arr the same way geometry does
9998 geom = SheetGeometry (sheet_size , defect_sizes , defect_positions )
10099 t1dx , d1dx , t1dy , d1dy , t2d , d2d = _solver .estimate_slab (
101100 W0 , H0 , geom .defects_arr , n_defects )
102101
103- # apply same selection logic as the C solver
104102 min_data = min (d1dx , d1dy , d2d )
105103 tolerance = 1.20
106104 candidates = [
@@ -124,7 +122,6 @@ def estimate_memory(sheet_size, defect_sizes, defect_positions):
124122 result ["total" ] = f_tables + g_tables + prefix + best_data * 4
125123
126124 except ImportError :
127- # fall back to dense estimate if C extension not available
128125 dense = (W0 + 1 )** 2 * (H0 + 1 )** 2 * 4
129126 result ["slab_data" ] = dense
130127 result ["slab_mb" ] = dense / 1024 ** 2
@@ -212,6 +209,7 @@ def parse_args(argv=None):
212209 " guillotine solve --sheet 27x27 --items 5x5 10x10 --defects 9,9,2x2\n "
213210 " guillotine solve problem.json --dry-run\n "
214211 " guillotine solve problem.json --plot\n "
212+ " guillotine solve problem.json --allow-rotation\n "
215213 " guillotine plot output/solution.json\n "
216214 "Run 'guillotine <command> --help' for more information."
217215 ),
@@ -250,6 +248,12 @@ def parse_args(argv=None):
250248 action = "store_true" ,
251249 help = "Print problem summary and memory estimates without solving"
252250 )
251+ bench_parser .add_argument (
252+ "--allow-rotation" ,
253+ action = "store_true" ,
254+ default = False ,
255+ help = "Allow 90-degree rotation of items (adds rotated variants automatically)"
256+ )
253257
254258 # ---- solve command ----
255259 solve_parser = subparsers .add_parser (
@@ -301,6 +305,12 @@ def parse_args(argv=None):
301305 action = "store_true" ,
302306 help = "Print problem summary and memory estimates without solving"
303307 )
308+ solve_parser .add_argument (
309+ "--allow-rotation" ,
310+ action = "store_true" ,
311+ default = False ,
312+ help = "Allow 90-degree rotation of items (adds rotated variants automatically)"
313+ )
304314
305315 # ---- plot command ----
306316 plot_parser = subparsers .add_parser (
@@ -335,7 +345,8 @@ def parse_args(argv=None):
335345# Solver functions
336346# -------------------------
337347def run_solver (item_sizes , defect_sizes , defect_positions , sheet_size ,
338- output_file , profile_file = None , plot_file = None ):
348+ output_file , profile_file = None , plot_file = None ,
349+ allow_rotation = False ):
339350 """Run the solver, save results, and optionally plot.
340351
341352 Plotting is done AFTER saving — the solution JSON is loaded back from
@@ -347,17 +358,19 @@ def run_solver(item_sizes, defect_sizes, defect_positions, sheet_size,
347358 def solve ():
348359 geom = SheetGeometry (sheet_size , defect_sizes , defect_positions )
349360 patterns = CutPatternGenerator (item_sizes , geom )
350- dp = GuillotineDP (item_sizes , geom , patterns )
361+ dp = GuillotineDP (item_sizes , geom , patterns , allow_rotation = allow_rotation )
351362 start = time .time ()
352363 value , sequence = dp .solve ()
353364 solve_time = time .time () - start
354- return value , sequence , solve_time
365+ expanded_item_sizes = [dp .item_w .tolist (), dp .item_h .tolist ()]
366+ n_items_orig = len (item_sizes [0 ])
367+ return value , sequence , solve_time , expanded_item_sizes , n_items_orig
355368
356369 if profile_file :
357370 print (f"Profiling enabled, output: { profile_file } " )
358371 profiler = cProfile .Profile ()
359372 profiler .enable ()
360- value , sequence , solve_time = solve ()
373+ value , sequence , solve_time , expanded_item_sizes , n_items_orig = solve ()
361374 profiler .disable ()
362375 profile_file = ensure_output_path (profile_file )
363376 with open (profile_file , 'w' ) as f :
@@ -366,36 +379,38 @@ def solve():
366379 stats .print_stats (50 )
367380 print (f"Profile saved to: { profile_file } " )
368381 else :
369- value , sequence , solve_time = solve ()
382+ value , sequence , solve_time , expanded_item_sizes , n_items_orig = solve ()
370383
371384 output_file = ensure_output_path (output_file )
372385 save_solution_json (output_file , value , sequence ,
373- item_sizes , defect_sizes , defect_positions , sheet_size )
386+ expanded_item_sizes , defect_sizes , defect_positions ,
387+ sheet_size , n_items_orig = n_items_orig )
374388
375389 print (f"Solved in { solve_time :.3f} s" )
376390 print (f"Value: { value } /{ sheet_size [0 ]* sheet_size [1 ]} "
377391 f"({ value / (sheet_size [0 ]* sheet_size [1 ])* 100 :.1f} %)" )
378392 print (f"Output saved to: { output_file } " )
379393
380- # Plot from the saved JSON — solver memory is no longer referenced
381- # and can be collected before matplotlib loads.
382394 if plot_file :
383395 plot_file = ensure_output_path (plot_file )
384396 run_plot (output_file , plot_file )
385397
386398
387- def run_benchmark (output_file , profile_file = None , plot_file = None ):
399+ def run_benchmark (output_file , profile_file = None , plot_file = None ,
400+ allow_rotation = False ):
388401 """Run the paper benchmark case."""
389402 print ("Running paper benchmark (27x27 sheet)..." )
390403 item_sizes = [[5 , 10 , 12 , 15 ], [5 , 10 , 12 , 15 ]]
391404 defect_sizes = [[2 ], [2 ]]
392405 defect_positions = [[9 ], [9 ]]
393406 sheet_size = (27 , 27 )
394407 run_solver (item_sizes , defect_sizes , defect_positions , sheet_size ,
395- output_file , profile_file , plot_file )
408+ output_file , profile_file , plot_file ,
409+ allow_rotation = allow_rotation )
396410
397411
398- def run_from_json (input_file , output_file , profile_file = None , plot_file = None ):
412+ def run_from_json (input_file , output_file , profile_file = None , plot_file = None ,
413+ allow_rotation = False ):
399414 """Run solver from JSON input file."""
400415 print (f"Loading problem from { input_file } ..." )
401416 problem = load_problem_json (input_file )
@@ -406,7 +421,8 @@ def run_from_json(input_file, output_file, profile_file=None, plot_file=None):
406421 problem ["sheet_size" ],
407422 output_file ,
408423 profile_file ,
409- plot_file
424+ plot_file ,
425+ allow_rotation = allow_rotation ,
410426 )
411427
412428
@@ -429,7 +445,8 @@ def run_plot(solution_file, output_file):
429445 data ["item_sizes" ],
430446 data ["defect_sizes" ],
431447 data ["defect_positions" ],
432- data ["sheet_size" ]
448+ data ["sheet_size" ],
449+ n_items_orig = data .get ("n_items_orig" , len (data ["item_sizes" ][0 ])),
433450 )
434451
435452 output_file = ensure_output_path (output_file )
@@ -449,11 +466,9 @@ def load_problem_for_dry_run(args):
449466 "sheet_size" : (27 , 27 ),
450467 }
451468
452- # JSON input
453469 if args .input :
454470 return load_problem_json (args .input )
455471
456- # Inline input
457472 if args .items and args .sheet :
458473 try :
459474 w , h = map (int , args .sheet .lower ().split ("x" ))
@@ -515,13 +530,15 @@ def main(argv=None):
515530
516531 # ---- benchmark ----
517532 if args .command == "benchmark" :
518- run_benchmark (args .output , args .profile , args .plot )
533+ run_benchmark (args .output , args .profile , args .plot ,
534+ allow_rotation = args .allow_rotation )
519535 return 0
520536
521537 # ---- solve ----
522538 elif args .command == "solve" :
523539 if args .input :
524- run_from_json (args .input , args .output , args .profile , args .plot )
540+ run_from_json (args .input , args .output , args .profile , args .plot ,
541+ allow_rotation = args .allow_rotation )
525542 return 0
526543
527544 if args .items and args .sheet :
@@ -560,7 +577,8 @@ def main(argv=None):
560577 (w , h ),
561578 args .output ,
562579 args .profile ,
563- args .plot
580+ args .plot ,
581+ allow_rotation = args .allow_rotation ,
564582 )
565583 return 0
566584
0 commit comments