Browse Source

Added button for faster rendering and fixed problem with overlay not recomputing

dev/better-annotations
Youen 2 years ago
parent
commit
3c7bdc2a4c
  1. 7
      InitGui.py
  2. 4
      ahb_cmd_view_annotate_detail.py
  3. 4
      ahb_cmd_view_refresh.py
  4. 22
      ahb_cmd_view_refresh_fast.py
  5. 198
      ahb_raster_view.py
  6. 38
      ahb_techdraw_extensions.py

7
InitGui.py

@ -78,8 +78,8 @@ class AssemblyHandbookWorkbench(Gui.Workbench):
#self.importModule('ahb_cmd_export_csv')
#toolbox.append("AHB_exportCsv")
self.importModule('ahb_cmd_render')
toolbox.append("AHB_render")
#self.importModule('ahb_cmd_render')
#toolbox.append("AHB_render")
self.importModule('ahb_cmd_new_step')
toolbox.append("AHB_new_step")
@ -98,6 +98,9 @@ class AssemblyHandbookWorkbench(Gui.Workbench):
toolbox.append("AHB_view_add_source_parts")
toolbox.append("AHB_view_remove_source_parts")
self.importModule('ahb_cmd_view_refresh_fast')
toolbox.append("AHB_view_refresh_fast")
self.importModule('ahb_cmd_view_refresh')
toolbox.append("AHB_view_refresh")

4
ahb_cmd_view_annotate_detail.py

@ -3,8 +3,8 @@ import FreeCAD as App
class AHB_View_Annotate_Detail:
def GetResources(self):
return {"MenuText": "Add annotation details",
"ToolTip": "Annotates each part of a sub-assembly",
return {"MenuText": "Annotate sub-assembly",
"ToolTip": "Annotates each part of selected sub-assembly balloons",
"Pixmap": ""
}

4
ahb_cmd_view_refresh.py

@ -3,7 +3,7 @@ import FreeCAD as App
class AHB_RefreshView:
def GetResources(self):
return {"MenuText": "Refresh page",
return {"MenuText": "Refresh page (final quality)",
"ToolTip": "Redraws the current page",
"Pixmap": ""
}
@ -16,7 +16,7 @@ class AHB_RefreshView:
workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench
page = workbench.techDrawExtensions.getActivePage()
if page is not None:
workbench.techDrawExtensions.forceRedrawPage(page)
workbench.techDrawExtensions.forceRedrawPage(page, fast_render = False)
from ahb_command import AHB_CommandWrapper
AHB_CommandWrapper.addGuiCommand('AHB_view_refresh', AHB_RefreshView())

22
ahb_cmd_view_refresh_fast.py

@ -0,0 +1,22 @@
import FreeCADGui as Gui
import FreeCAD as App
class AHB_RefreshViewFast:
def GetResources(self):
return {"MenuText": "Refresh page (fast)",
"ToolTip": "Redraws the current page",
"Pixmap": ""
}
def IsActive(self):
workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench
return workbench.techDrawExtensions.getActivePage() is not None
def Activated(self):
workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench
page = workbench.techDrawExtensions.getActivePage()
if page is not None:
workbench.techDrawExtensions.forceRedrawPage(page, fast_render = True)
from ahb_command import AHB_CommandWrapper
AHB_CommandWrapper.addGuiCommand('AHB_view_refresh_fast', AHB_RefreshViewFast())

198
ahb_raster_view.py

@ -99,7 +99,7 @@ class RasterView:
def _should_render(self, obj):
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
import os
from PIL import Image, ImageDraw, ImageChops
@ -141,26 +141,27 @@ class RasterView:
is_new_part = workbench.techDrawExtensions.isNewPartInView(view, part)
is_conflicting = False
if link.LinkedObject in duplicated_parts.keys():
link.LinkedObject = duplicated_parts[link.LinkedObject]
else:
other_parts = prev_parts if is_new_part else new_parts
for other_part in other_parts:
other_objects = self._flatten_objects_tree([other_part])
for obj in self._flatten_objects_tree([link]):
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)
#print("conflict: " + link.LinkedObject.Document.Name + '#' + link.LinkedObject.Label)
shape_copy = Part.getShape(link.LinkedObject,'',needSubElement=False,refine=False)
part_copy = tmp_doc.addObject('Part::Feature','ShapeCopy')
part_copy.Shape = shape_copy
part_copy.Label = part.Label
duplicated_parts[link.LinkedObject] = part_copy
link.LinkedObject = part_copy
if not fast_render:
is_conflicting = False
if link.LinkedObject in duplicated_parts.keys():
link.LinkedObject = duplicated_parts[link.LinkedObject]
else:
other_parts = prev_parts if is_new_part else new_parts
for other_part in other_parts:
other_objects = self._flatten_objects_tree([other_part])
for obj in self._flatten_objects_tree([link]):
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)
#print("conflict: " + link.LinkedObject.Document.Name + '#' + link.LinkedObject.Label)
shape_copy = Part.getShape(link.LinkedObject,'',needSubElement=False,refine=False)
part_copy = tmp_doc.addObject('Part::Feature','ShapeCopy')
part_copy.Shape = shape_copy
part_copy.Label = part.Label
duplicated_parts[link.LinkedObject] = part_copy
link.LinkedObject = part_copy
if is_new_part:
new_parts.append(link)
@ -173,16 +174,17 @@ class RasterView:
continue
if self._should_render(obj):
objects_to_reset[obj] = (
obj.ViewObject.Visibility,
obj.ViewObject.LineColor,
obj.ViewObject.ShapeMaterial.AmbientColor,
obj.ViewObject.ShapeMaterial.DiffuseColor,
obj.ViewObject.ShapeMaterial.SpecularColor,
obj.ViewObject.ShapeMaterial.EmissiveColor,
obj.ViewObject.LineWidth,
obj.ViewObject.DisplayMode
)
if not fast_render:
objects_to_reset[obj] = (
obj.ViewObject.Visibility,
obj.ViewObject.LineColor,
obj.ViewObject.ShapeMaterial.AmbientColor,
obj.ViewObject.ShapeMaterial.DiffuseColor,
obj.ViewObject.ShapeMaterial.SpecularColor,
obj.ViewObject.ShapeMaterial.EmissiveColor,
obj.ViewObject.LineWidth,
obj.ViewObject.DisplayMode
)
else:
objects_to_reset[obj] = (
obj.ViewObject.Visibility,
@ -218,16 +220,18 @@ class RasterView:
resolution[0] = int(resolution[0] * max_res / resolution[1])
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), [])
if fast_render:
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
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)
new_parts_img = self._render_lines(tmp_doc, resolution, new_parts, (0.0, 0.0, 0.0), prev_parts)
# 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)
#composite_img = new_parts_img.copy()
# create the composite image
composite_img = prev_parts_img.copy()
composite_img.paste(new_parts_img, None, new_parts_img)
finally:
# 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.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
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
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.ShapeMaterial.AmbientColor = (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]
alpha_img = lines_bands[0].point(lambda p: 255 - p)
# 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
step = 8
r = step
g = step
b = step
for link in doc.findObjects():
if link in parts or link in masking_parts:
for obj in self._flatten_objects_tree([link]):
if self._should_render(obj) and obj.TypeId != 'Part::Part2DObjectPython':
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 link in parts else (1.0, 1.0, 1.0, 0.0)
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
generate_outlines = not fast_render
if generate_outlines:
# 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
step = 8
r = step
g = step
b = step
for link in doc.findObjects():
if link in parts or link in masking_parts:
for obj in self._flatten_objects_tree([link]):
if self._should_render(obj) and obj.TypeId != 'Part::Part2DObjectPython':
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 link in parts else (1.0, 1.0, 1.0, 0.0)
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
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:
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:
outlines_img.paste(partial_outlines, None, partial_outlines.point(lambda p: 0 if p == 255 else 255))
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()
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)
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)
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
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[1] * p + line_color[1] * (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

38
ahb_techdraw_extensions.py

@ -82,8 +82,8 @@ class TechDrawExtensions:
workbench.docObserver.onObjectTypeChanged('balloon_changed', 'TechDraw::DrawViewBalloon', lambda obj, prop: self.onBalloonChanged(obj, prop))
workbench.docObserver.onObjectTypeSelected('balloon_selected', 'TechDraw::DrawViewBalloon', lambda operation, obj, sub, point: self.onBalloonSelected(operation, obj, sub, point))
def repaint(self, view):
self.views_to_repaint[view] = True
def repaint(self, view, fast_render = True):
self.views_to_repaint[view] = fast_render
QTimer.singleShot(10, self._do_repaint)
def _do_repaint(self):
@ -93,10 +93,10 @@ class TechDrawExtensions:
selection = Gui.Selection.getSelection()
to_repaint = self.views_to_repaint.keys()
to_repaint = self.views_to_repaint.copy()
self.views_to_repaint = {}
for view in to_repaint:
for view, fast_render in to_repaint.items():
if '_overlay' in view.Label:
continue
#print("Repainting " + view.Name)
@ -116,7 +116,7 @@ class TechDrawExtensions:
print("Rasterizing view " + view.Label + "...")
raster_view = RasterView(view)
raster_view.render()
raster_view.render(fast_render)
view.Visibility = False
overlayName = view.Label + "_overlay"
@ -583,16 +583,17 @@ class TechDrawExtensions:
return obj
return None
def forceRedrawPage(self, page, callback = None):
needPageUpdate = False
def forceRedrawPage(self, page, callback = None, fast_render = True):
for view in page.Views:
if view.TypeId == 'TechDraw::DrawViewPart' and 'Assembly_handbook_PreviousStepView' in view.PropertiesList:
if not 'Assembly_handbook_RasterView' in view.PropertiesList:
view.addProperty("App::PropertyBool", "Assembly_handbook_RasterView", "Assembly_handbook")
view.Assembly_handbook_RasterView = True
if 'Assembly_handbook_RasterView' not in view.PropertiesList or not view.Assembly_handbook_RasterView:
needPageUpdate = True
if '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)
else:
view.touch()
self.refreshView(view)
elif view.TypeId == 'TechDraw::DrawViewBalloon':
if view.ViewObject.Visibility:
@ -608,23 +609,34 @@ class TechDrawExtensions:
view.ViewObject.Visibility = True
view.ViewObject.Visibility = False
if page.KeepUpdated or not needPageUpdate:
if page.KeepUpdated:
for view in page.Views:
if view.TypeId != 'TechDraw::DrawViewPart':
view.recompute()
for view in page.Views:
if view.TypeId == 'TechDraw::DrawViewPart':
view.recompute()
self.repaint(view)
self.repaint(view, fast_render)
if callback is not None:
callback()
else:
page.KeepUpdated = True
def restoreKeepUpdated():
page.KeepUpdated = False
for view in page.Views:
if view.TypeId == 'TechDraw::DrawViewPart':
self.repaint(view)
if view.Name.endswith('_overlay'):
view.touch()
view.recompute()
for sub_view in page.Views:
try:
if sub_view.SourceView == view:
sub_view.recompute()
except:
pass
else:
view.recompute()
self.repaint(view, fast_render)
page.KeepUpdated = False
if callback is not None:
callback()
QTimer.singleShot(10, restoreKeepUpdated)

Loading…
Cancel
Save