diff --git a/InitGui.py b/InitGui.py index 3e80716..0cbd9c2 100644 --- a/InitGui.py +++ b/InitGui.py @@ -1,3 +1,7 @@ +#sys.path.append('/usr/local/lib/python3.9/dist-packages/') +#import pydevd +#pydevd.settrace() + import os import FreeCADGui as Gui diff --git a/ahb_cmd_view_annotate.py b/ahb_cmd_view_annotate.py index da613be..f4b5870 100644 --- a/ahb_cmd_view_annotate.py +++ b/ahb_cmd_view_annotate.py @@ -1,5 +1,7 @@ import FreeCADGui as Gui import FreeCAD as App +import numpy as np +from pickle import NONE class AHB_View_Annotate: updating_balloon = False @@ -27,17 +29,14 @@ class AHB_View_Annotate: if view.TypeId != 'TechDraw::DrawViewPart': raise Exception("Selected object is not a TechDraw view") - page = None - for obj in doc.Objects: - if obj.TypeId == 'TechDraw::DrawPage': - if view in obj.Views: - page = obj + page = self.getViewPage(view) if page is None: raise Exception("Can't find page in which the selected view is located") # Remove balloons referencing missing objects for balloon in page.Views: if balloon.TypeId == 'TechDraw::DrawViewBalloon' and "AssemblyHandbook_PartName" in balloon.PropertiesList: + if balloon.SourceView != view: continue partLink = doc.getObject(balloon.AssemblyHandbook_PartName) if partLink is None or partLink not in view.XSource: print(balloon.Name + " references missing object " + balloon.AssemblyHandbook_PartName + ", removing balloon") @@ -49,6 +48,7 @@ class AHB_View_Annotate: # Search an existing balloon to update for obj in page.Views: if obj.TypeId == 'TechDraw::DrawViewBalloon' and "AssemblyHandbook_PartName" in obj.PropertiesList and obj.AssemblyHandbook_PartName == partLink.Name: + if obj.SourceView != view: continue balloon = obj # Create a new balloon if needed @@ -63,8 +63,8 @@ class AHB_View_Annotate: self.updateBalloon(balloon) - balloon.X = int(balloon.OriginX) + 100 - balloon.Y = int(balloon.OriginY) + 100 + balloon.X = int(balloon.OriginX) + 20 + balloon.Y = int(balloon.OriginY) + 20 else: self.updateBalloon(balloon) @@ -79,34 +79,193 @@ class AHB_View_Annotate: self.updateBalloon(obj) self.updating_balloon = False - def onBalloonSelected(self, operation, obj, sub, point): + def onBalloonSelected(self, operation, balloon, sub, point): + import time + #print(operation, obj.Name, sub, point) - if "AssemblyHandbook_PartName" in obj.PropertiesList: + if "AssemblyHandbook_PartName" in balloon.PropertiesList: if operation == 'added': - print("Selected balloon of " + obj.AssemblyHandbook_PartName) + print("Selected balloon of " + balloon.AssemblyHandbook_PartName) + + doc = balloon.Document + view = balloon.SourceView + page = self.getViewPage(view) + + partLink = doc.getObject(balloon.AssemblyHandbook_PartName) + + 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() + + # 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): + hasEdge = False + try: + edge = view.getEdgeByIndex(edgeIdx) + hasEdge = True + except: + pass + if not hasEdge: + break + + 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 + 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 + ] + v0 = App.Vector(edgeData[0], edgeData[1]) + v1 = App.Vector(edgeData[2], edgeData[3]) + #print(edgeData) + for line in cachedLines: + 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) + + 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() + elif operation == 'removed': - print("Deselected balloon of " + obj.AssemblyHandbook_PartName) + print("Deselected balloon of " + balloon.AssemblyHandbook_PartName) def updateBalloon(self, balloon): doc = App.activeDocument() view = balloon.SourceView - partLink = doc.getObject(balloon.AssemblyHandbook_PartName) + obj = doc.getObject(balloon.AssemblyHandbook_PartName) - # Get object center in view space - objectCenterWorld = partLink.LinkPlacement.Matrix.multiply(partLink.LinkedObject.Shape.CenterOfGravity) - vertId = view.makeCosmeticVertex3d(objectCenterWorld) - vert = view.getCosmeticVertex(vertId) - objectCenterView = vert.Point - view.removeCosmeticVertex(vertId) + objectCenterView = self.computePartCenter(view, obj) balloon.OriginX = objectCenterView.x balloon.OriginY = objectCenterView.y - balloon.Text = partLink.LinkedObject.Document.Name + balloon.Text = obj.LinkedObject.Document.Name if obj.TypeId == 'App::Link' else obj.Name balloon.ViewObject.Font = 'DejaVu Sans' balloon.ViewObject.Fontsize = 4 balloon.BubbleShape = 'Inspection' balloon.EndTypeScale = 4 + + def getViewPage(self, view): + for obj in view.InList: + if obj.TypeId == 'TechDraw::DrawPage': + if view in obj.Views: + return obj + return None + + 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 from ahb_command import AHB_CommandWrapper AHB_CommandWrapper.addGuiCommand('AHB_view_annotate', AHB_View_Annotate())