@ -99,7 +99,7 @@ class RasterView:
def _should_render ( self , obj ) :
def _should_render ( self , obj ) :
return obj . TypeId in [ ' Part::Feature ' , ' Part::FeaturePython ' , ' PartDesign::Body ' , ' Part::Mirroring ' , ' Part::Cut ' , ' Part::Part2DObjectPython ' ]
return obj . TypeId in [ ' Part::Feature ' , ' Part::FeaturePython ' , ' PartDesign::Body ' , ' Part::Mirroring ' , ' Part::Cut ' , ' Part::Part2DObjectPython ' ]
def render ( self ) :
def render ( self , fast_render = True ) :
from pivy import coin
from pivy import coin
import os
import os
from PIL import Image , ImageDraw , ImageChops
from PIL import Image , ImageDraw , ImageChops
@ -141,26 +141,27 @@ class RasterView:
is_new_part = workbench . techDrawExtensions . isNewPartInView ( view , part )
is_new_part = workbench . techDrawExtensions . isNewPartInView ( view , part )
is_conflicting = False
if not fast_render :
if link . LinkedObject in duplicated_parts . keys ( ) :
is_conflicting = False
link . LinkedObject = duplicated_parts [ link . LinkedObject ]
if link . LinkedObject in duplicated_parts . keys ( ) :
else :
link . LinkedObject = duplicated_parts [ link . LinkedObject ]
other_parts = prev_parts if is_new_part else new_parts
else :
for other_part in other_parts :
other_parts = prev_parts if is_new_part else new_parts
other_objects = self . _flatten_objects_tree ( [ other_part ] )
for other_part in other_parts :
for obj in self . _flatten_objects_tree ( [ link ] ) :
other_objects = self . _flatten_objects_tree ( [ other_part ] )
if obj in other_objects :
for obj in self . _flatten_objects_tree ( [ link ] ) :
is_conflicting = True
if obj in other_objects :
is_conflicting = True
if is_conflicting :
# We must copy the part because otherwise we can't control the emissive color (link material override does not work for emissive color)
if is_conflicting :
#print("conflict: " + link.LinkedObject.Document.Name + '#' + link.LinkedObject.Label)
# We must copy the part because otherwise we can't control the emissive color (link material override does not work for emissive color)
shape_copy = Part . getShape ( link . LinkedObject , ' ' , needSubElement = False , refine = False )
#print("conflict: " + link.LinkedObject.Document.Name + '#' + link.LinkedObject.Label)
part_copy = tmp_doc . addObject ( ' Part::Feature ' , ' ShapeCopy ' )
shape_copy = Part . getShape ( link . LinkedObject , ' ' , needSubElement = False , refine = False )
part_copy . Shape = shape_copy
part_copy = tmp_doc . addObject ( ' Part::Feature ' , ' ShapeCopy ' )
part_copy . Label = part . Label
part_copy . Shape = shape_copy
duplicated_parts [ link . LinkedObject ] = part_copy
part_copy . Label = part . Label
link . LinkedObject = part_copy
duplicated_parts [ link . LinkedObject ] = part_copy
link . LinkedObject = part_copy
if is_new_part :
if is_new_part :
new_parts . append ( link )
new_parts . append ( link )
@ -173,16 +174,17 @@ class RasterView:
continue
continue
if self . _should_render ( obj ) :
if self . _should_render ( obj ) :
objects_to_reset [ obj ] = (
if not fast_render :
obj . ViewObject . Visibility ,
objects_to_reset [ obj ] = (
obj . ViewObject . LineColor ,
obj . ViewObject . Visibility ,
obj . ViewObject . ShapeMaterial . AmbientColor ,
obj . ViewObject . LineColor ,
obj . ViewObject . ShapeMaterial . DiffuseColor ,
obj . ViewObject . ShapeMaterial . AmbientColor ,
obj . ViewObject . ShapeMaterial . SpecularColor ,
obj . ViewObject . ShapeMaterial . DiffuseColor ,
obj . ViewObject . ShapeMaterial . EmissiveColor ,
obj . ViewObject . ShapeMaterial . SpecularColor ,
obj . ViewObject . LineWidth ,
obj . ViewObject . ShapeMaterial . EmissiveColor ,
obj . ViewObject . DisplayMode
obj . ViewObject . LineWidth ,
)
obj . ViewObject . DisplayMode
)
else :
else :
objects_to_reset [ obj ] = (
objects_to_reset [ obj ] = (
obj . ViewObject . Visibility ,
obj . ViewObject . Visibility ,
@ -217,17 +219,19 @@ class RasterView:
if resolution [ 1 ] > max_res :
if resolution [ 1 ] > max_res :
resolution [ 0 ] = int ( resolution [ 0 ] * max_res / resolution [ 1 ] )
resolution [ 0 ] = int ( resolution [ 0 ] * max_res / resolution [ 1 ] )
resolution [ 1 ] = int ( max_res )
resolution [ 1 ] = int ( max_res )
# render old parts in gray lines
prev_parts_img = self . _render_lines ( tmp_doc , resolution , prev_parts , ( 0.6 , 0.6 , 0.6 ) , [ ] )
# render new parts in black lines (old parts can mask them)
if fast_render :
new_parts_img = self . _render_lines ( tmp_doc , resolution , new_parts , ( 0.0 , 0.0 , 0.0 ) , prev_parts )
composite_img = self . _render_lines ( tmp_doc , resolution , prev_parts + new_parts , ( 0.0 , 0.0 , 0.0 ) , [ ] )
else :
# create the composite image
# render old parts in gray lines
composite_img = prev_parts_img . copy ( )
prev_parts_img = self . _render_lines ( tmp_doc , resolution , prev_parts , ( 0.6 , 0.6 , 0.6 ) , [ ] , fast_render )
composite_img . paste ( new_parts_img , None , new_parts_img )
#composite_img = new_parts_img.copy()
# render new parts in black lines (old parts can mask them)
new_parts_img = self . _render_lines ( tmp_doc , resolution , new_parts , ( 0.0 , 0.0 , 0.0 ) , prev_parts , fast_render )
# create the composite image
composite_img = prev_parts_img . copy ( )
composite_img . paste ( new_parts_img , None , new_parts_img )
finally :
finally :
# restore properties on objects we have modified
# restore properties on objects we have modified
@ -289,7 +293,7 @@ class RasterView:
image . ImageFile = self . image_file_name # TODO: see if it's possible to set a relative path
image . ImageFile = self . image_file_name # TODO: see if it's possible to set a relative path
image . recompute ( )
image . recompute ( )
def _render_lines ( self , doc , resolution , parts , line_color , masking_parts ) :
def _render_lines ( self , doc , resolution , parts , line_color , masking_parts , fast_render = True ) :
import tempfile
import tempfile
from PIL import Image , ImageDraw , ImageFilter
from PIL import Image , ImageDraw , ImageFilter
@ -303,7 +307,7 @@ class RasterView:
# in current version of freecad, link override material does not allow to override all material properties, for example emissive color, so we have to change material of the linked object
# in current version of freecad, link override material does not allow to override all material properties, for example emissive color, so we have to change material of the linked object
for obj in self . _flatten_objects_tree ( [ link ] ) :
for obj in self . _flatten_objects_tree ( [ link ] ) :
if self . _should_render ( obj ) :
if self . _should_render ( obj ) and not fast_render :
obj . ViewObject . LineColor = ( 0.0 , 0.0 , 0.0 , 0.0 ) if link in parts else ( 1.0 , 0.0 , 1.0 )
obj . ViewObject . LineColor = ( 0.0 , 0.0 , 0.0 , 0.0 ) if link in parts else ( 1.0 , 0.0 , 1.0 )
obj . ViewObject . ShapeMaterial . AmbientColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
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 . DiffuseColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
@ -325,62 +329,66 @@ class RasterView:
lines_img = lines_bands [ 1 ]
lines_img = lines_bands [ 1 ]
alpha_img = lines_bands [ 0 ] . point ( lambda p : 255 - p )
alpha_img = lines_bands [ 0 ] . point ( lambda p : 255 - p )
# Render all shapes with different colors, in order to extract outlines (where color changes)
generate_outlines = not fast_render
# This is needed because FreeCAD does not render lines on the boundary of curve shapes, such as spheres or cylinders
if generate_outlines :
# The technique could be improved by using the depth buffer instead, in order to detect boundaries within the same object
# Render all shapes with different colors, in order to extract outlines (where color changes)
step = 8
# This is needed because FreeCAD does not render lines on the boundary of curve shapes, such as spheres or cylinders
r = step
# The technique could be improved by using the depth buffer instead, in order to detect boundaries within the same object
g = step
step = 8
b = step
r = step
for link in doc . findObjects ( ) :
g = step
if link in parts or link in masking_parts :
b = step
for obj in self . _flatten_objects_tree ( [ link ] ) :
for link in doc . findObjects ( ) :
if self . _should_render ( obj ) and obj . TypeId != ' Part::Part2DObjectPython ' :
if link in parts or link in masking_parts :
obj . ViewObject . DisplayMode = ' Shaded '
for obj in self . _flatten_objects_tree ( [ link ] ) :
obj . ViewObject . ShapeMaterial . AmbientColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
if self . _should_render ( obj ) and obj . TypeId != ' Part::Part2DObjectPython ' :
obj . ViewObject . ShapeMaterial . DiffuseColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . DisplayMode = ' Shaded '
obj . ViewObject . ShapeMaterial . SpecularColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . ShapeMaterial . AmbientColor = ( 0.0 , 0.0 , 0.0 , 0.0 )
obj . ViewObject . ShapeMaterial . EmissiveColor = ( r / 255.0 , g / 255.0 , b / 255.0 , 0.0 ) if link in parts else ( 1.0 , 1.0 , 1.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 )
r = r + step
obj . ViewObject . ShapeMaterial . EmissiveColor = ( r / 255.0 , g / 255.0 , b / 255.0 , 0.0 ) if link in parts else ( 1.0 , 1.0 , 1.0 , 0.0 )
if r > = 256 - step :
r = step
r = r + step
g = g + step
if r > = 256 - step :
if g > = 256 - step :
r = step
g = step
g = g + step
b = b + step
if g > = 256 - step :
if b > = 256 - step :
g = step
b = step
b = b + step
if b > = 256 - step :
b = step
else :
obj . ViewObject . Visibility = False
doc_view . saveImage ( temp_file_name , ( resolution [ 0 ] + 2 ) * 2 , ( resolution [ 1 ] + 2 ) * 2 , " #ffffff " ) # shapes are rendered at twice the resolution for antialiasing
shapes_img = self . _read_image ( temp_file_name )
outlines_img = None
for x in range ( 0 , 3 ) :
for y in range ( 0 , 3 ) :
if x == 1 and y == 1 : continue
kernel = [ 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 ]
kernel [ y * 3 + x ] = - 1
partial_outlines = shapes_img . filter ( ImageFilter . Kernel ( ( 3 , 3 ) , kernel , 1 , 127 ) )
partial_outlines = partial_outlines . point ( lambda p : 255 if p == 127 else 0 )
partial_outlines = partial_outlines . convert ( " L " )
partial_outlines = partial_outlines . point ( lambda p : 255 if p == 255 else 0 )
if outlines_img is None :
outlines_img = partial_outlines
else :
else :
obj . ViewObject . Visibility = False
outlines_img . paste ( partial_outlines , None , partial_outlines . point ( lambda p : 0 if p == 255 else 255 ) )
doc_view . saveImage ( temp_file_name , ( resolution [ 0 ] + 2 ) * 2 , ( resolution [ 1 ] + 2 ) * 2 , " #ffffff " ) # shapes are rendered at twice the resolution for antialiasing
shapes_img = self . _read_image ( temp_file_name )
outlines_img = None
for x in range ( 0 , 3 ) :
for y in range ( 0 , 3 ) :
if x == 1 and y == 1 : continue
kernel = [ 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 ]
kernel [ y * 3 + x ] = - 1
partial_outlines = shapes_img . filter ( ImageFilter . Kernel ( ( 3 , 3 ) , kernel , 1 , 127 ) )
partial_outlines = partial_outlines . point ( lambda p : 255 if p == 127 else 0 )
partial_outlines = partial_outlines . convert ( " L " )
partial_outlines = partial_outlines . point ( lambda p : 255 if p == 255 else 0 )
if outlines_img is None :
outlines_img = partial_outlines
else :
outlines_img . paste ( partial_outlines , None , partial_outlines . point ( lambda p : 0 if p == 255 else 255 ) )
lines_fullres = lines_img . resize ( outlines_img . size , Image . NEAREST )
lines_fullres . paste ( outlines_img , None , outlines_img . point ( lambda p : 255 if p == 0 else 0 ) )
#lines_fullres.paste(255, alpha_fullres.point(lambda p: 255 if p == 0 else 0))
all_lines = lines_fullres . resize ( lines_img . size , Image . BILINEAR )
#all_lines = lines_img.copy()
alpha_fullres = alpha_img . resize ( outlines_img . size , Image . NEAREST )
lines_fullres = lines_img . resize ( outlines_img . size , Image . NEAREST )
alpha_fullres . paste ( outlines_img . point ( lambda p : 255 ) , None , outlines_img . point ( lambda p : 255 if p == 0 else 0 ) )
lines_fullres . paste ( outlines_img , None , outlines_img . point ( lambda p : 255 if p == 0 else 0 ) )
alpha_img = alpha_fullres . resize ( all_lines . size , Image . BILINEAR )
#lines_fullres.paste(255, alpha_fullres.point(lambda p: 255 if p == 0 else 0))
all_lines = lines_fullres . resize ( lines_img . size , Image . BILINEAR )
#all_lines = lines_img.copy()
alpha_fullres = alpha_img . resize ( outlines_img . size , Image . NEAREST )
alpha_fullres . paste ( outlines_img . point ( lambda p : 255 ) , None , outlines_img . point ( lambda p : 255 if p == 0 else 0 ) )
alpha_img = alpha_fullres . resize ( all_lines . size , Image . BILINEAR )
else :
all_lines = lines_img
# colorize final image
# colorize final image
fill_color = ( 1.0 , 1.0 , 1.0 )
fill_color = ( 1.0 , 1.0 , 1.0 )
@ -388,7 +396,7 @@ class RasterView:
all_lines . point ( lambda p : int ( fill_color [ 0 ] * p + line_color [ 0 ] * ( 255.0 - p ) ) ) ,
all_lines . point ( lambda p : int ( fill_color [ 0 ] * p + line_color [ 0 ] * ( 255.0 - p ) ) ) ,
all_lines . point ( lambda p : int ( fill_color [ 1 ] * p + line_color [ 1 ] * ( 255.0 - p ) ) ) ,
all_lines . point ( lambda p : int ( fill_color [ 1 ] * p + line_color [ 1 ] * ( 255.0 - p ) ) ) ,
all_lines . point ( lambda p : int ( fill_color [ 2 ] * p + line_color [ 2 ] * ( 255.0 - p ) ) ) ,
all_lines . point ( lambda p : int ( fill_color [ 2 ] * p + line_color [ 2 ] * ( 255.0 - p ) ) ) ,
alpha_img
alpha_img . point ( lambda p : 0 if p == 0 else 255 )
] )
] )
# crop 1px borders
# crop 1px borders