@@ -1595,10 +1595,29 @@ def _do_clay_render(scene, filepath, clay_color):
15951595 scene .display .shading .show_cavity = saved ['show_cavity' ]
15961596
15971597
1598+ class _BatchAborted (Exception ):
1599+ """Stop the batch early without dumping a traceback on the user."""
1600+
1601+
15981602class MV_OT_RenderAll (Operator ):
15991603 bl_idname = "mv.render_all"
16001604 bl_label = "Render All"
16011605
1606+ def _fail (self , failures , done , path , exc ):
1607+ """Record one failed write, aborting the batch if nothing has landed.
1608+
1609+ Collected and reported at the end rather than raised, so a single
1610+ unwritable file cannot throw away a long batch that is otherwise fine.
1611+ Before the first success there is nothing to salvage and the cause is
1612+ almost always the setup, so that case stops immediately instead of
1613+ failing the same way for every remaining image.
1614+ """
1615+ note = f"{ os .path .basename (path )} : { exc } "
1616+ failures .append (note )
1617+ print (f"[MultiView] FAILED { note } " )
1618+ if not done :
1619+ raise _BatchAborted from exc
1620+
16021621 def execute (self , context ):
16031622 scene = context .scene
16041623 s = scene .mv_settings
@@ -1615,7 +1634,11 @@ def execute(self, context):
16151634 return {'CANCELLED' }
16161635
16171636 out_root = bpy .path .abspath (s .render_output_dir )
1618- os .makedirs (out_root , exist_ok = True )
1637+ try :
1638+ os .makedirs (out_root , exist_ok = True )
1639+ except OSError as exc :
1640+ self .report ({'ERROR' }, f"Cannot use output folder '{ out_root } ': { exc } " )
1641+ return {'CANCELLED' }
16191642
16201643 saved_hide = {}
16211644 for pc in products :
@@ -1630,6 +1653,7 @@ def execute(self, context):
16301653 total_per_pair = (2 if both else 1 ) * len (cameras )
16311654 total = len (products ) * total_per_pair
16321655 done = 0
1656+ failures = []
16331657
16341658 try :
16351659 for i , pc in enumerate (products ):
@@ -1640,27 +1664,41 @@ def execute(self, context):
16401664
16411665 label = product_label (s .start_prefix , s .start_number , i , s .products_per_letter )
16421666 product_dir = os .path .join (out_root , safe_name (f"{ label } _{ pc .name } " ))
1643- os .makedirs (product_dir , exist_ok = True )
1667+ try :
1668+ os .makedirs (product_dir , exist_ok = True )
1669+ except OSError as exc :
1670+ self ._fail (failures , done , product_dir , exc )
1671+ continue
16441672
16451673 for cam in cameras :
16461674 scene .camera = cam
16471675 view_name = cam .name .replace ("MV_Cam_" , "" )
16481676 base = safe_name (f"{ label } _{ pc .name } _{ view_name } " )
16491677
1678+ clay = (tuple (s .clay_color ),)
16501679 if mode == 'FULL' :
1651- _do_full_render (scene , os .path .join (product_dir , base ))
1652- done += 1
1680+ attempts = ((_do_full_render , base , ()),)
16531681 elif mode == 'CLAY' :
1654- _do_clay_render (scene , os .path .join (product_dir , base + "_clay" ),
1655- tuple (s .clay_color ))
1656- done += 1
1682+ attempts = ((_do_clay_render , base + "_clay" , clay ),)
16571683 else :
1658- _do_full_render (scene , os .path .join (product_dir , base + "_render" ))
1684+ attempts = ((_do_full_render , base + "_render" , ()),
1685+ (_do_clay_render , base + "_clay" , clay ))
1686+
1687+ for render , name , extra in attempts :
1688+ path = os .path .join (product_dir , name )
1689+ try :
1690+ render (scene , path , * extra )
1691+ except RuntimeError as exc :
1692+ # bpy.ops.render.render raises when the image cannot
1693+ # be written. Letting that escape put a Python
1694+ # traceback in front of the user instead of the name
1695+ # of the file that failed.
1696+ self ._fail (failures , done , path , exc )
1697+ continue
16591698 done += 1
1660- _do_clay_render (scene , os .path .join (product_dir , base + "_clay" ),
1661- tuple (s .clay_color ))
1662- done += 1
1663- print (f"[MultiView] { done } /{ total } : { scene .render .filepath } " )
1699+ print (f"[MultiView] { done } /{ total } : { scene .render .filepath } " )
1700+ except _BatchAborted :
1701+ pass
16641702 finally :
16651703 for pc in products :
16661704 lc = find_layer_coll (context .view_layer .layer_collection , pc .name )
@@ -1669,7 +1707,19 @@ def execute(self, context):
16691707 scene .camera = saved_cam
16701708 scene .render .filepath = saved_path
16711709
1672- self .report ({'INFO' }, f"Wrote { done } image(s)." )
1710+ if not failures :
1711+ self .report ({'INFO' }, f"Wrote { done } image(s)." )
1712+ return {'FINISHED' }
1713+
1714+ if not done :
1715+ # Nothing landed at all, so the batch stopped at the first failure
1716+ # rather than burning render time failing the same way every time.
1717+ self .report ({'ERROR' }, f"Nothing rendered. { failures [0 ]} " )
1718+ return {'CANCELLED' }
1719+
1720+ self .report ({'WARNING' },
1721+ f"Wrote { done } of { total } image(s). { len (failures )} failed, "
1722+ f"first: { failures [0 ]} . Full list in the System Console." )
16731723 return {'FINISHED' }
16741724
16751725
0 commit comments