forked from youen/assembly_handbook
Added button for faster rendering and fixed problem with overlay not recomputing
This commit is contained in:
parent
d6fb938d96
commit
3c7bdc2a4c
@ -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")
|
||||
|
||||
|
@ -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": ""
|
||||
}
|
||||
|
||||
|
@ -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
Normal file
22
ahb_cmd_view_refresh_fast.py
Normal file
@ -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())
|
@ -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,
|
||||
@ -217,17 +219,19 @@ class RasterView:
|
||||
if resolution[1] > max_res:
|
||||
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), [])
|
||||
|
||||
# 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)
|
||||
|
||||
# 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()
|
||||
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, fast_render)
|
||||
|
||||
# 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
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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
|
||||
|
@ -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…
Reference in New Issue
Block a user