forked from youen/assembly_handbook
raster rendering optimizations
This commit is contained in:
parent
ec97d10a51
commit
5e65e68c13
@ -1,5 +1,13 @@
|
||||
import FreeCAD as App
|
||||
import FreeCADGui as Gui
|
||||
from datetime import datetime
|
||||
|
||||
def print_verbose(msg):
|
||||
verbose = False
|
||||
if verbose:
|
||||
now = datetime.now()
|
||||
current_time = now.strftime("%H:%M:%S")
|
||||
print(current_time, msg)
|
||||
|
||||
class RasterView:
|
||||
def __init__(self, view):
|
||||
@ -116,7 +124,7 @@ class RasterView:
|
||||
|
||||
self.init_image()
|
||||
|
||||
print('Rasterizing ' + view.Label + " to " + self.image_file_name + "...")
|
||||
print_verbose('Rasterizing ' + view.Label + " to " + self.image_file_name + "...")
|
||||
|
||||
dir = os.path.dirname(self.image_file_name)
|
||||
if not os.path.exists(dir):
|
||||
@ -127,6 +135,7 @@ class RasterView:
|
||||
objects_to_reset = {}
|
||||
duplicated_parts = {}
|
||||
try:
|
||||
print_verbose("Preparing scene...")
|
||||
# construct new scene with links to the parts we want
|
||||
sceneGroup = tmp_doc.addObject('App::DocumentObjectGroup', 'Scene')
|
||||
prev_parts = []
|
||||
@ -259,20 +268,25 @@ class RasterView:
|
||||
resolution[1] = int(max_res)
|
||||
|
||||
if fast_render:
|
||||
print_verbose("Fast rasterization...")
|
||||
composite_img = self._render_lines(tmp_doc, resolution, prev_parts + new_parts, (0.0, 0.0, 0.0), [])
|
||||
else:
|
||||
# render old parts in gray lines
|
||||
print_verbose("Rendering old parts (gray)...")
|
||||
prev_parts_img = self._render_lines(tmp_doc, resolution, prev_parts, (0.6, 0.6, 0.6), [], fast_render)
|
||||
|
||||
# render new parts in black lines (old parts can mask them)
|
||||
print_verbose("Rendering new parts (black)...")
|
||||
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
|
||||
print_verbose("Compositing images...")
|
||||
composite_img = prev_parts_img.copy()
|
||||
composite_img.paste(new_parts_img, None, new_parts_img)
|
||||
|
||||
# Optimize the image to reduce storage size
|
||||
if not fast_render:
|
||||
print_verbose("Optimizing PNG size...")
|
||||
num_colors = 32
|
||||
|
||||
# All-or-nothing alpha: we use a white background and only make pixels fully transparent where alpha is zero, to not loose antialiasing
|
||||
@ -286,6 +300,7 @@ class RasterView:
|
||||
composite_img = composite_img.quantize(colors=num_colors, dither=Image.Dither.NONE)
|
||||
|
||||
finally:
|
||||
print_verbose("Cleaning scene...")
|
||||
#raise Exception("test")
|
||||
# restore properties on objects we have modified
|
||||
for obj, props in objects_to_reset.items():
|
||||
@ -302,6 +317,8 @@ class RasterView:
|
||||
# remove the temporary document
|
||||
App.closeDocument(tmp_doc.Name)
|
||||
|
||||
print_verbose("Finalizing view...")
|
||||
|
||||
# Crop the image, which is also used to deduce the center of the source view
|
||||
original_size = composite_img.size
|
||||
|
||||
@ -350,6 +367,8 @@ class RasterView:
|
||||
image.Height = composite_img.size[1] * image_scale / 10.0 * 1.01
|
||||
image.recompute()
|
||||
|
||||
print_verbose("Done")
|
||||
|
||||
def _render_lines(self, doc, resolution, parts, line_color, masking_parts, fast_render = True):
|
||||
import tempfile
|
||||
from PIL import Image, ImageDraw, ImageFilter
|
||||
@ -358,12 +377,17 @@ class RasterView:
|
||||
|
||||
# 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
|
||||
configured = []
|
||||
print_verbose('Preparing objects for line rendering...')
|
||||
for link in doc.findObjects():
|
||||
if link in parts or link in masking_parts:
|
||||
link.ViewObject.Visibility = True
|
||||
|
||||
# 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]):
|
||||
if obj in configured: continue
|
||||
configured.append(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.ShapeMaterial.AmbientColor = (0.0, 0.0, 0.0, 0.0)
|
||||
@ -377,6 +401,7 @@ class RasterView:
|
||||
else:
|
||||
link.ViewObject.Visibility = False
|
||||
|
||||
print_verbose('Rendering lines...')
|
||||
temp_file_name = tempfile.gettempdir() + "/ahb_temp_image.png"
|
||||
doc_view.saveImage(temp_file_name, resolution[0]+2, resolution[1]+2, "#ff0000") # we add 1 pixel border that we will need to crop later
|
||||
lines_bands_img = self._read_image(temp_file_name)
|
||||
@ -390,14 +415,20 @@ class RasterView:
|
||||
# Render all shapes with different colors, in order to extract outlines (where color changes)
|
||||
# This is needed because FreeCAD does not render lines on the boundary of curve shapes, such as spheres or cylinders
|
||||
# The technique could be improved by using the depth buffer instead, in order to detect boundaries within the same object
|
||||
print_verbose('Preparing objects for outline rendering...')
|
||||
step = 8
|
||||
r = step
|
||||
g = step
|
||||
b = step
|
||||
configured = []
|
||||
for link in doc.findObjects():
|
||||
if link in parts or link in masking_parts:
|
||||
for obj in self._flatten_objects_tree([link]):
|
||||
if obj in configured: continue
|
||||
configured.append(obj)
|
||||
|
||||
if self._should_render(obj) and obj.TypeId != 'Part::Part2DObjectPython':
|
||||
configured.append(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)
|
||||
@ -416,9 +447,11 @@ class RasterView:
|
||||
else:
|
||||
obj.ViewObject.Visibility = False
|
||||
|
||||
print_verbose('Rendering shapes...')
|
||||
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)
|
||||
|
||||
print_verbose('Extracting outlines...')
|
||||
outlines_img = None
|
||||
for x in range(0, 3):
|
||||
for y in range(0, 3):
|
||||
@ -434,6 +467,7 @@ class RasterView:
|
||||
else:
|
||||
outlines_img.paste(partial_outlines, None, partial_outlines.point(lambda p: 0 if p == 255 else 255))
|
||||
|
||||
print_verbose('Combining lines and outlines...')
|
||||
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))
|
||||
@ -448,6 +482,7 @@ class RasterView:
|
||||
alpha_img = alpha_img.point(lambda p: 0 if p == 0 else 255)
|
||||
|
||||
# colorize final image
|
||||
print_verbose('Colorizing image...')
|
||||
fill_color = (1.0, 1.0, 1.0)
|
||||
result = Image.merge("RGBA", [
|
||||
all_lines.point(lambda p: int(fill_color[0] * p + line_color[0] * (255.0 - p))),
|
||||
|
@ -640,6 +640,10 @@ class TechDrawExtensions:
|
||||
def refreshOverlays(self, page, callback = None):
|
||||
import os
|
||||
|
||||
for view in page.Views:
|
||||
if view.TypeId == 'TechDraw::DrawViewPart' and 'Assembly_handbook_RasterView' in view.PropertiesList and view.Assembly_handbook_RasterView:
|
||||
view.purgeTouched() # make sure we don't trigger rendering of source views (this is awfully slow and doesn't even work for a lot of models)
|
||||
|
||||
doc = page.Document
|
||||
for image in page.Views:
|
||||
if image.TypeId == 'TechDraw::DrawViewImage':
|
||||
|
Loading…
Reference in New Issue
Block a user