@ -14,66 +14,89 @@ class AHB_Render:
def IsActive ( self ) :
return True
def set_render_lines ( self , line_color = ( 0.0 , 0.0 , 0.0 , 0.0 ) , background_color = ( 1.0 , 1.0 , 1.0 , 0.0 ) , mask_stages_below : Optional [ int ] = None , mask_color = ( 1.0 , 1.0 , 1.0 ) ) :
def flatten_objects_tree ( self , obj_list ) :
result = [ ]
for obj in obj_list :
if obj . TypeId == ' Part::FeaturePython ' and hasattr ( obj , ' LinkedObject ' ) : # variant link
result . extend ( self . flatten_objects_tree ( obj . Group ) )
elif obj . TypeId in [ ' App::Link ' ] :
result . extend ( self . flatten_objects_tree ( [ obj . LinkedObject ] ) )
elif obj . TypeId in [ ' App::Part ' , ' App::DocumentObjectGroup ' ] :
result . extend ( self . flatten_objects_tree ( obj . Group ) )
elif obj . TypeId in [ ' Part::Feature ' , ' Part::FeaturePython ' , ' PartDesign::Body ' , ' PartDesign::CoordinateSystem ' , ' PartDesign::Line ' , ' Part::Mirroring ' , ' Part::Cut ' ] :
result . append ( obj )
if hasattr ( obj , ' Group ' ) :
result . extend ( self . flatten_objects_tree ( obj . Group ) )
return result
def renderable_objects ( self ) :
doc = App . activeDocument ( )
return self . flatten_objects_tree ( doc . Objects )
def should_render ( self , obj ) :
return obj . TypeId in [ ' Part::Feature ' , ' Part::FeaturePython ' , ' PartDesign::Body ' , ' Part::Mirroring ' , ' Part::Cut ' ]
def should_mask ( self , obj ) :
return False
def set_render_lines ( self , line_color = ( 0.0 , 0.0 , 0.0 , 0.0 ) , background_color = ( 1.0 , 1.0 , 1.0 , 0.0 ) , mask_color = ( 1.0 , 1.0 , 1.0 ) ) :
doc = App . activeDocument ( )
for obj in doc . Objects :
if obj . TypeId == ' Part::Feature ' :
if ' AssemblyHandbook_Stage ' in obj . PropertiesList :
masked = mask_stages_below is not None and obj . AssemblyHandbook_Stage < mask_stages_below
if ' AssemblyHandbook_RenderLines ' in obj . PropertiesList and not obj . AssemblyHandbook_RenderLines :
obj . ViewObject . LineColor = background_color
else :
obj . ViewObject . LineColor = line_color
obj . ViewObject . DisplayMode = ' Flat Lines ' if not masked else ' Shaded '
obj . ViewObject . ShapeMaterial . AmbientColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . ShapeMaterial . DiffuseColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . ShapeMaterial . SpecularColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . ShapeMaterial . EmissiveColor = background_color if not masked else mask_color
def set_render_outlines ( self , mask_stages_below : Optional [ int ] = None ) :
for obj in self . renderable_objects ( ) :
if self . should_render ( obj ) :
#print(obj.Name, obj.TypeId, hasattr(obj, 'LinkedObject'), obj)
masked = self . should_mask ( obj )
obj . ViewObject . LineColor = line_color
obj . ViewObject . DisplayMode = ' Flat Lines ' if not masked else ' Shaded '
obj . ViewObject . ShapeMaterial . AmbientColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . ShapeMaterial . DiffuseColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . ShapeMaterial . SpecularColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . ShapeMaterial . EmissiveColor = background_color if not masked else mask_color
obj . ViewObject . Visibility = True
else :
obj . ViewObject . Visibility = False
def set_render_outlines ( self ) :
doc = App . activeDocument ( )
step = 8
r = step
g = step
b = step
for obj in doc . Objects :
if obj . TypeId == ' Part::Feature ' :
if ' AssemblyHandbook_Stage ' in obj . PropertiesList :
masked = mask_stages_below is not None and obj . AssemblyHandbook_Stage < mask_stages_below
obj . ViewObject . DisplayMode = ' Shaded '
obj . ViewObject . ShapeMaterial . AmbientColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . ShapeMaterial . DiffuseColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . ShapeMaterial . SpecularColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
#obj.ViewObject.ShapeMaterial.EmissiveColor = (r/255.0, g/255.0, b/255.0, 0.0) if not masked else (1.0,1.0,1.0,0.0)
obj . ViewObject . ShapeMaterial . EmissiveColor = ( r / 255.0 , g / 255.0 , b / 255.0 , 0.0 )
if masked :
obj . ViewObject . Visibility = False
r = r + step
if r > = 256 - step :
r = step
g = g + step
if g > = 256 - step :
g = step
b = b + step
if b > = 256 - step :
b = step
for obj in self . renderable_objects ( ) :
if self . should_render ( obj ) :
masked = self . should_mask ( obj )
obj . ViewObject . DisplayMode = ' Shaded '
obj . ViewObject . ShapeMaterial . AmbientColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . ShapeMaterial . DiffuseColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . ShapeMaterial . SpecularColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
#obj.ViewObject.ShapeMaterial.EmissiveColor = (r/255.0, g/255.0, b/255.0, 0.0) if not masked else (1.0,1.0,1.0,0.0)
obj . ViewObject . ShapeMaterial . EmissiveColor = ( r / 255.0 , g / 255.0 , b / 255.0 , 0.0 )
if masked :
obj . ViewObject . Visibility = False
r = r + step
if r > = 256 - step :
r = step
g = g + step
if g > = 256 - step :
g = step
b = b + step
if b > = 256 - step :
b = step
def reset_display ( self ) :
doc = App . activeDocument ( )
for obj in doc . Objects :
if obj . TypeId == ' Part::Feature ' :
if ' AssemblyHandbook_Stage ' in obj . PropertiesList :
obj . ViewObject . LineColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . DisplayMode = ' Flat Lines '
obj . ViewObject . ShapeMaterial . AmbientColor = ( 0.3 , 0.3 , 0.3 , 0.0 )
obj . ViewObject . ShapeMaterial . DiffuseColor = ( 1.0 , 1.0 , 1.0 , 0.0 )
obj . ViewObject . ShapeMaterial . SpecularColor = ( 0.5 , 0.5 , 0.5 , 0.0 )
obj . ViewObject . ShapeMaterial . EmissiveColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
workbench = Gui . getWorkbench ( " AssemblyHandbookWorkbench " )
workbench . context . onPartStageChanged ( None )
for obj in self . renderable_objects ( ) :
if self . should_render ( obj ) :
obj . ViewObject . LineColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . DisplayMode = ' Flat Lines '
obj . ViewObject . ShapeMaterial . AmbientColor = ( 0.3 , 0.3 , 0.3 , 0.0 )
obj . ViewObject . ShapeMaterial . DiffuseColor = ( 1.0 , 1.0 , 1.0 , 0.0 )
obj . ViewObject . ShapeMaterial . SpecularColor = ( 0.5 , 0.5 , 0.5 , 0.0 )
obj . ViewObject . ShapeMaterial . EmissiveColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
else :
#obj.ViewObject.Visibility = True
pass
def render ( self , resolution , filename : str , line_color = ( 0.0 , 0.0 , 0.0 , 0.0 ) , fill_color = ( 1.0 , 1.0 , 1.0 , 0.0 ) , mask_stages_below : Optional [ int ] = None ) :
import time
@ -89,10 +112,10 @@ class AHB_Render:
# render lines in black, background in red, fill shapes in green
# the green band contains the lines images, the red band contains the inverted alpha layer
self . set_render_lines ( ( 0.0 , 0.0 , 0.0 ) , ( 0.0 , 1.0 , 0.0 ) , mask_stages_below = mask_stages_below , mask_ color = ( 1.0 , 0.0 , 1.0 ) )
self . set_render_lines ( ( 0.0 , 0.0 , 0.0 ) , ( 0.0 , 1.0 , 0.0 ) , mask_color = ( 1.0 , 0.0 , 1.0 ) )
Gui . ActiveDocument . ActiveView . saveImage ( temp_lines_file_name , resolution [ 0 ] + 2 , resolution [ 1 ] + 2 , " #ff0000 " )
self . set_render_outlines ( mask_stages_below = mask_stages_below )
self . set_render_outlines ( )
Gui . ActiveDocument . ActiveView . saveImage ( temp_shapes_file_name , ( resolution [ 0 ] + 2 ) * 2 , ( resolution [ 1 ] + 2 ) * 2 , " #ffffff " )
lines_bands = Image . open ( temp_lines_file_name ) . split ( )
@ -159,15 +182,12 @@ class AHB_Render:
import math
from pivy import coin
import re
render_main = False
render_stages = True
render_parts = False
Image . MAX_IMAGE_PIXELS = 9999999999 # allow very high resolution images
Gui . Selection . clearSelection ( )
Gui . activeDocument ( ) . activeView ( ) . setCameraType ( " Orthographic " )
workbench = Gui . getWorkbench ( " AssemblyHandbookWorkbench " )
doc = App . activeDocument ( )
doc_file_name : str = doc . FileName
if doc_file_name is None :
@ -176,157 +196,13 @@ class AHB_Render:
filename = os . path . splitext ( doc_file_name ) [ 0 ] + " .png "
dir = os . path . dirname ( filename )
if render_main :
resolution = ( 3000 , 3000 )
workbench . context . setAllStagesVisible ( True )
self . render ( resolution , filename )
self . reset_display ( )
img_full = Image . new ( ' RGB ' , resolution , ( 255 , 255 , 255 ) )
img = Image . open ( filename )
img_full . paste ( img , None , img . getchannel ( ' A ' ) )
img_full . save ( filename )
if render_stages :
shutil . rmtree ( dir + " /stages " , ignore_errors = True )
os . makedirs ( dir + " /stages " , exist_ok = True )
all_stages = workbench . context . getAllStages ( )
prev_stage_id : Optional [ int ] = None
for stage_id in all_stages :
stage_name = str ( stage_id )
while len ( stage_name ) < 6 :
stage_name = " 0 " + stage_name
resolution = ( 3000 , 3000 )
if prev_stage_id is not None :
workbench . context . setActiveStage ( prev_stage_id )
workbench . context . setAllStagesVisible ( False )
self . render ( resolution , dir + " /stages/ " + stage_name + " -bg.png " , ( 0.7 , 0.7 , 0.7 ) , ( 1 , 1 , 1 ) )
workbench . context . setActiveStage ( stage_id )
workbench . context . setAllStagesVisible ( False )
self . render ( resolution , dir + " /stages/ " + stage_name + " .png " , mask_stages_below = stage_id )
# merge images
bg = Image . new ( ' RGB ' , resolution , ( 255 , 255 , 255 ) )
if prev_stage_id is not None :
prev_stage = Image . open ( dir + " /stages/ " + stage_name + " -bg.png " )
prev_stage . load ( )
os . remove ( dir + " /stages/ " + stage_name + " -bg.png " )
bg . paste ( prev_stage , None , prev_stage . getchannel ( ' A ' ) )
fg = Image . open ( dir + " /stages/ " + stage_name + " .png " )
bg . paste ( fg , None , fg . getchannel ( ' A ' ) )
bg . save ( dir + " /stages/ " + stage_name + " .png " )
if prev_stage_id is not None :
pass
prev_stage_id = stage_id
self . reset_display ( )
if render_parts :
rendered_references = [ ]
max_length = 1500
max_resolution = 800
min_resolution = 350
shutil . rmtree ( dir + " /parts " , ignore_errors = True )
os . makedirs ( dir + " /parts " , exist_ok = True )
count = 0
for part_to_render in doc . Objects :
if part_to_render . TypeId in [ ' Part::Feature ' , ' App::Part ' ] :
if ' Base_Reference ' not in part_to_render . PropertiesList or part_to_render . Base_Reference == " " or part_to_render . Base_Reference in rendered_references :
continue
parent = part_to_render . Parents [ 0 ] [ 0 ] if len ( part_to_render . Parents ) > 0 else None
if parent is not None and parent . TypeId != ' App::Part ' :
parent = None
if parent is not None and ' Base_Reference ' in parent . PropertiesList and parent . Base_Reference != " " :
continue
#if not part_to_render.Label.startswith("L") and not part_to_render.Label.startswith("M") and not part_to_render.Label.startswith("T") and not part_to_render.Label.startswith("R"):
# continue
rendered_references . append ( part_to_render . Base_Reference )
for part_to_hide in doc . Objects :
if part_to_hide . TypeId == ' Part::Feature ' :
part_to_hide . ViewObject . Visibility = False
part_to_render . ViewObject . Visibility = True
if part_to_render . TypeId == ' App::Part ' :
for feature in part_to_render . Group :
feature . ViewObject . Visibility = True
dimensions = [ part_to_render . Shape . BoundBox . XLength , part_to_render . Shape . BoundBox . YLength , part_to_render . Shape . BoundBox . ZLength ]
main_axis : int = 0 ;
main_length : float = part_to_render . Shape . BoundBox . XLength
if part_to_render . Shape . BoundBox . YLength > main_length :
main_axis = 1
main_length = part_to_render . Shape . BoundBox . YLength
if part_to_render . Shape . BoundBox . ZLength > main_length :
main_axis = 2
main_length = part_to_render . Shape . BoundBox . ZLength
center = coin . SbVec3f ( part_to_render . Shape . BoundBox . Center )
offset = coin . SbVec3f ( 800 , - 2000 , 800 )
up = coin . SbVec3f ( 0 , 0 , 1 )
if main_axis == 1 :
offset [ 0 ] , offset [ 1 ] = - offset [ 1 ] , offset [ 0 ]
elif main_axis == 2 :
offset [ 0 ] , offset [ 2 ] = - offset [ 2 ] , offset [ 0 ]
up = coin . SbVec3f ( - 1 , 0 , 0 )
cam = Gui . ActiveDocument . ActiveView . getCameraNode ( )
cam . position = center + offset
cam . pointAt ( center , up )
Gui . ActiveDocument . ActiveView . fitAll ( )
cross_length = 0
if main_axis == 0 :
cross_length = max ( dimensions [ 1 ] * 0.5 , dimensions [ 2 ] )
elif main_axis == 1 :
cross_length = max ( dimensions [ 0 ] * 0.5 , dimensions [ 2 ] )
elif main_axis == 2 :
cross_length = max ( dimensions [ 0 ] , dimensions [ 1 ] * 0.5 )
cross_length = max ( cross_length , main_length * 0.25 + cross_length * 0.8 )
main_length_pixels = max ( min_resolution , int ( math . pow ( min ( 1.0 , main_length / max_length ) , 0.3 ) * max_resolution ) )
cross_length_pixels = int ( cross_length * main_length_pixels / main_length )
cam . scaleHeight ( 0.05 + 0.95 * cross_length_pixels / main_length_pixels )
resolution = ( main_length_pixels , cross_length_pixels )
part_filename = dir + " /parts/ " + re . sub ( ' [^A-Z0-9_] ' , ' - ' , part_to_render . Base_Reference . upper ( ) ) + " .png "
self . render ( resolution , part_filename )
image = Image . new ( ' RGB ' , resolution , ( 255 , 255 , 255 ) )
fg = Image . open ( part_filename )
image . paste ( fg , None , fg . getchannel ( ' A ' ) )
#image.save(part_filename)
final_apsect_ratio = 2.0 # width/height
final_resolution = ( max_resolution , int ( max_resolution / final_apsect_ratio ) )
final_size = image . size
if final_size [ 0 ] > final_resolution [ 0 ] :
final_size = ( final_size [ 0 ] * final_resolution [ 0 ] / final_size [ 0 ] , final_size [ 1 ] * final_resolution [ 0 ] / final_size [ 0 ] )
if final_size [ 1 ] > final_resolution [ 1 ] :
final_size = ( final_size [ 0 ] * final_resolution [ 1 ] / final_size [ 1 ] , final_size [ 1 ] * final_resolution [ 1 ] / final_size [ 1 ] )
final_size = ( int ( final_size [ 0 ] ) , int ( final_size [ 1 ] ) )
if final_size [ 0 ] < image . size [ 0 ] or final_size [ 1 ] < image . size [ 1 ] :
image . thumbnail ( final_size , Image . ANTIALIAS )
final_image = Image . new ( ' RGB ' , final_resolution , ( 255 , 255 , 255 ) )
final_image . paste ( image , ( final_resolution [ 0 ] - final_size [ 0 ] , int ( final_resolution [ 1 ] / 2 - final_size [ 1 ] / 2 ) ) )
final_image . save ( part_filename )
count = count + 1
if count == 10 :
pass
self . reset_display ( )
workbench . context . setAllStagesVisible ( True )
resolution = ( 6000 , 6000 )
self . render ( resolution , filename )
self . reset_display ( )
img_full = Image . new ( ' RGB ' , resolution , ( 255 , 255 , 255 ) )
img = Image . open ( filename )
img_full . paste ( img , None , img . getchannel ( ' A ' ) )
img_full . save ( filename )
from ahb_command import AHB_CommandWrapper
AHB_CommandWrapper . addGuiCommand ( ' AHB_render ' , AHB_Render ( ) )