diff --git a/InitGui.py b/InitGui.py index 0cbd9c2..c7cd6e2 100644 --- a/InitGui.py +++ b/InitGui.py @@ -26,6 +26,8 @@ class AssemblyHandbookWorkbench(Gui.Workbench): context = None docObserver = None + + partsCache = None def GetClassName(self): return "Gui::PythonWorkbench" @@ -43,10 +45,14 @@ class AssemblyHandbookWorkbench(Gui.Workbench): if self.context is None: self.initializeContext() - import ahb_document_observer if self.docObserver is None: + import ahb_document_observer self.docObserver = ahb_document_observer.DocObserver() App.addDocumentObserver(self.docObserver) + + if self.partsCache is None: + import ahb_parts_cache + self.partsCache = ahb_parts_cache.PartsCache() def Deactivated(self): """ @@ -99,6 +105,10 @@ class AssemblyHandbookWorkbench(Gui.Workbench): import importlib import ahb_context importlib.reload(ahb_context) + + import ahb_parts_cache + importlib.reload(ahb_parts_cache) + self.partsCache = ahb_parts_cache.PartsCache() self.initializeContext() self.registerCommands() diff --git a/ahb_cmd_view_annotate.py b/ahb_cmd_view_annotate.py index f4b5870..a8f58b3 100644 --- a/ahb_cmd_view_annotate.py +++ b/ahb_cmd_view_annotate.py @@ -1,7 +1,5 @@ import FreeCADGui as Gui import FreeCAD as App -import numpy as np -from pickle import NONE class AHB_View_Annotate: updating_balloon = False @@ -85,8 +83,9 @@ class AHB_View_Annotate: #print(operation, obj.Name, sub, point) if "AssemblyHandbook_PartName" in balloon.PropertiesList: if operation == 'added': - print("Selected balloon of " + balloon.AssemblyHandbook_PartName) + #print("Selected balloon of " + balloon.AssemblyHandbook_PartName) + workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench doc = balloon.Document view = balloon.SourceView page = self.getViewPage(view) @@ -95,85 +94,15 @@ class AHB_View_Annotate: cache_edges_start_time = time.perf_counter() - # create temporary view of part - tmpView = None - for v in page.Views: - if v.TypeId == 'TechDraw::DrawViewPart' and len(v.XSource) == 1 and v.XSource[0] == partLink: - tmpView = v - if tmpView is None: - keepUpdated = page.KeepUpdated - page.KeepUpdated = True - tmpView = doc.addObject("TechDraw::DrawViewPart", "View") - tmpView.Direction = view.Direction - tmpView.XDirection = view.XDirection - tmpView.ScaleType = 'Custom' - tmpView.Scale = 1.0 - tmpView.XSource = partLink - page.addView(tmpView) - tmpView.recompute() - page.KeepUpdated = keepUpdated - - cache_edges_end_render_time = time.perf_counter() - - #tmpView.clearCosmeticEdges() - #tmpView.recompute() + part_view = workbench.partsCache.getPart2DView(view, partLink) - # copy edges relative to center - # TODO: in-memory cache - tmpCenter = self.computePartCenter(tmpView, partLink) - #print('tmpCenter', tmpCenter) - tmpEdges = tmpView.getVisibleEdges() - # count lines - numLines = 0 - for edge in tmpEdges: - if not hasattr(edge.Curve, 'Degree') or edge.Curve.Degree == 1: numLines += 1 - # store all lines in a packed array of floats (for each line: X1, Y1, X2, Y2) - cachedLines = np.empty([numLines, 4]) - lineIdx = 0 - for edge in tmpEdges: - degree = 1 - try: - degree = edge.Curve.Degree - except: - pass - if degree == 1: - #print('tmpEdge', edge.Vertexes[0].Point.x, edge.Vertexes[0].Point.y, edge.Vertexes[1].Point.x, edge.Vertexes[1].Point.y) - sx = 1.0 - sy = -1.0 - cachedLines[lineIdx] = [ - edge.Vertexes[0].Point.x*sx - tmpCenter.x, - edge.Vertexes[0].Point.y*sy - tmpCenter.y, - edge.Vertexes[1].Point.x*sx - tmpCenter.x, - edge.Vertexes[1].Point.y*sy - tmpCenter.y - ] - - #print('tmpLine', cachedLines[lineIdx]) - - lineIdx = lineIdx + 1 - cache_edges_end_time = time.perf_counter() - - '''tmpView.makeCosmeticLine(tmpCenter - App.Vector(0,3), tmpCenter + App.Vector(0,3)) - tmpView.makeCosmeticLine(tmpCenter - App.Vector(3,0), tmpCenter + App.Vector(3,0)) - drawCenter = tmpCenter + App.Vector(-1,0) - for line in cachedLines: - p0 = drawCenter + App.Vector(line[0], line[1]) - p1 = drawCenter + App.Vector(line[2], line[3]) - tmpView.makeCosmeticLine(p0, p1) - tmpView.recompute()''' - - # delete temporary view - doc.removeObject(tmpView.Name) - - #view.clearCosmeticEdges() - #view.recompute() update_final_edges_start_time = time.perf_counter() # iterate edges of actual view and highlight matching edges center = self.computePartCenter(view, partLink) - #print('center', center) - for edgeIdx in range(1000): + for edgeIdx in range(10000): hasEdge = False try: edge = view.getEdgeByIndex(edgeIdx) @@ -183,53 +112,53 @@ class AHB_View_Annotate: if not hasEdge: break - view.formatGeometricEdge(edgeIdx,1,0.25,0,True) + # reset edge format + #view.formatGeometricEdge(edgeIdx,1,0.25,0,True) - degree = 1 - try: - degree = edge.Curve.Degree - except: - pass - - if degree == 1: - #print('edge', edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[1].X, edge.Vertexes[1].Y) - sx = 1 - sy = 1 + if not hasattr(edge.Curve, 'Degree') or edge.Curve.Degree == 1: edgeData = [ - (edge.Vertexes[0].X - center.x)*sx, - (edge.Vertexes[0].Y - center.y)*sy, - (edge.Vertexes[1].X - center.x)*sx, - (edge.Vertexes[1].Y - center.y)*sy + edge.Vertexes[0].X - center.x, + edge.Vertexes[0].Y - center.y, + edge.Vertexes[1].X - center.x, + edge.Vertexes[1].Y - center.y ] v0 = App.Vector(edgeData[0], edgeData[1]) v1 = App.Vector(edgeData[2], edgeData[3]) - #print(edgeData) - for line in cachedLines: + + for line in part_view.cached_lines: l0 = App.Vector(line[0], line[1]) l1 = App.Vector(line[2], line[3]) #d = abs(edgeData[0] - line[0]) + abs(edgeData[1] - line[1]) + abs(edgeData[2] - line[2]) + abs(edgeData[3] - line[3]) d = v0.distanceToLineSegment(l0, l1).Length + v1.distanceToLineSegment(l0, l1).Length if d < 0.001: view.formatGeometricEdge(edgeIdx,1,0.25,(0,0.85,0),True) + + view.requestPaint() update_final_edges_end_time = time.perf_counter() - print("Part render time: " + str(round((cache_edges_end_render_time - cache_edges_start_time) * 1000) / 1000) + "s") - print("Part read time: " + str(round((cache_edges_end_time - cache_edges_end_render_time) * 1000) / 1000) + "s") - print("Update view time: " + str(round((update_final_edges_end_time - update_final_edges_start_time) * 1000) / 1000) + "s") - - '''view.makeCosmeticLine(center - App.Vector(0,3), center + App.Vector(0,3)) - view.makeCosmeticLine(center - App.Vector(3,0), center + App.Vector(3,0)) - drawCenter = center + App.Vector(1,0) - for line in cachedLines: - p0 = drawCenter + App.Vector(line[0], line[1]) - p1 = drawCenter + App.Vector(line[2], line[3]) - view.makeCosmeticLine(p0, p1)''' - - #view.recompute() + #print("Part render time: " + str(round((cache_edges_end_time - cache_edges_start_time) * 1000) / 1000) + "s") + #print("Update view time: " + str(round((update_final_edges_end_time - update_final_edges_start_time) * 1000) / 1000) + "s") elif operation == 'removed': - print("Deselected balloon of " + balloon.AssemblyHandbook_PartName) + #print("Deselected balloon of " + balloon.AssemblyHandbook_PartName) + + view = balloon.SourceView + + # reset edges format + for edgeIdx in range(10000): + hasEdge = False + try: + edge = view.getEdgeByIndex(edgeIdx) + hasEdge = True + except: + pass + if not hasEdge: + break + + view.formatGeometricEdge(edgeIdx,1,0.25,0,True) + + view.requestPaint() def updateBalloon(self, balloon): doc = App.activeDocument() diff --git a/ahb_parts_cache.py b/ahb_parts_cache.py new file mode 100644 index 0000000..06b7ac2 --- /dev/null +++ b/ahb_parts_cache.py @@ -0,0 +1,87 @@ +import FreeCAD as App +import FreeCADGui as Gui + +class PartCachedView: + def __init__(self, direction, x_direction, obj): + self.direction = direction + self.x_direction = x_direction + self.doc_name = obj.Document.Name + self.obj_name = obj.Name + self.cached_lines = None + + def render(self): + import numpy as np + import os + + doc = App.getDocument(self.doc_name) #: :type doc: App.Document + obj = doc.getObject(self.obj_name) #: :type obj: App.DocumentObject + + # create temporary view + page = doc.addObject('TechDraw::DrawPage', 'TmpPage') + if not page.KeepUpdated: page.KeepUpdated = True + template = doc.addObject('TechDraw::DrawSVGTemplate', 'Template') + import ahb_locator + template.Template = os.path.join(os.path.dirname(ahb_locator.__file__), "resources/A4_Landscape_blank.svg") + page.Template = template + + tmpView = doc.addObject('TechDraw::DrawViewPart', 'TmpView') + tmpView.Direction = self.direction + tmpView.XDirection = self.x_direction + tmpView.Perspective = False + tmpView.ScaleType = 'Custom' + tmpView.Scale = 1.0 + tmpView.XSource = [obj] + page.addView(tmpView) + tmpView.recompute() + + # copy edges relative to center + tmpCenter = self.computePartCenter(tmpView, obj) + + # count lines + tmpEdges = tmpView.getVisibleEdges() + numLines = 0 + for edge in tmpEdges: + if not hasattr(edge.Curve, 'Degree') or edge.Curve.Degree == 1: numLines += 1 + + # store all lines in a packed array of floats (for each line: X1, Y1, X2, Y2) + self.cached_lines = np.empty([numLines, 4]) + lineIdx = 0 + for edge in tmpEdges: + if not hasattr(edge.Curve, 'Degree') or edge.Curve.Degree == 1: + sx = 1.0 + sy = -1.0 + self.cached_lines[lineIdx] = [ + edge.Vertexes[0].Point.x*sx - tmpCenter.x, + edge.Vertexes[0].Point.y*sy - tmpCenter.y, + edge.Vertexes[1].Point.x*sx - tmpCenter.x, + edge.Vertexes[1].Point.y*sy - tmpCenter.y + ] + lineIdx = lineIdx + 1 + + # delete temporary view + doc.removeObject(page.Name) + + def computePartCenter(self, view, obj): + if obj.TypeId == 'App::Link': + partLink = obj + objectCenterWorld = partLink.LinkPlacement.Matrix.multiply(partLink.LinkedObject.Shape.CenterOfGravity) + else: + objectCenterWorld = obj.Shape.CenterOfGravity + + vertId = view.makeCosmeticVertex3d(objectCenterWorld) + vert = view.getCosmeticVertex(vertId) + objectCenterView = vert.Point + view.removeCosmeticVertex(vertId) + return objectCenterView + +class PartsCache: + part_views = {} + + def getPart2DView(self, view, obj): + key = (view.Direction.x, view.Direction.y, view.Direction.z, view.XDirection.x, view.XDirection.y, view.XDirection.z, obj.Document.Name, obj.Name) + part_view = self.part_views.get(key, None) + if part_view is None: + part_view = PartCachedView(view.Direction, view.XDirection, obj) + part_view.render() + self.part_views[key] = part_view + return part_view \ No newline at end of file diff --git a/resources/A4_Landscape_blank.svg b/resources/A4_Landscape_blank.svg new file mode 100644 index 0000000..1accfd8 --- /dev/null +++ b/resources/A4_Landscape_blank.svg @@ -0,0 +1,9 @@ + + + +