From f9d58c66fc1a119e35a8c97114436b53485c3a3c Mon Sep 17 00:00:00 2001
From: Youen <youen.toupin@youb.fr>
Date: Mon, 26 Dec 2022 14:08:41 +0100
Subject: [PATCH] fixed bugs

---
 ahb_cmd_view_annotate.py   | 10 +++----
 ahb_raster_view.py         | 51 +++++++++++++++++++++++--------
 ahb_techdraw_extensions.py | 61 +++++++++++++++++++++++++++++++++++++-
 3 files changed, 102 insertions(+), 20 deletions(-)

diff --git a/ahb_cmd_view_annotate.py b/ahb_cmd_view_annotate.py
index 6157a9f..8af647f 100644
--- a/ahb_cmd_view_annotate.py
+++ b/ahb_cmd_view_annotate.py
@@ -21,6 +21,8 @@ class AHB_View_Annotate:
 		if view.TypeId != 'TechDraw::DrawViewPart':
 			raise Exception("Selected object is not a TechDraw view")
 		
+		overlay_view = workbench.techDrawExtensions.getOverlayView(view)
+		
 		doc = view.Document
 		
 		page = workbench.techDrawExtensions.getViewPage(view)
@@ -30,7 +32,7 @@ class AHB_View_Annotate:
 		# Remove balloons referencing missing objects
 		for balloon in page.Views:
 			if balloon.TypeId == 'TechDraw::DrawViewBalloon' and "Assembly_handbook_Source" in balloon.PropertiesList:
-				if balloon.SourceView != view: continue
+				if balloon.SourceView != view and balloon.SourceView != overlay_view: continue
 				partLink = workbench.techDrawExtensions.getBalloonSourcePart(balloon)
 				if partLink is None or partLink not in view.XSource:
 					ref_name = "<no ref>"
@@ -47,13 +49,9 @@ class AHB_View_Annotate:
 			# Search an existing balloon to update
 			for obj in page.Views:
 				if obj.TypeId == 'TechDraw::DrawViewBalloon' and workbench.techDrawExtensions.getBalloonSourcePart(obj) == partLink:
-					if obj.SourceView != view: continue
+					if obj.SourceView != overlay_view: continue
 					balloon = obj
 			
-			overlay_view = doc.getObject(view.Name + '_overlay')
-			if overlay_view is None:
-				overlay_view = view
-			
 			# Create a new balloon if needed
 			if balloon is None:
 				partName = partLink.Name
diff --git a/ahb_raster_view.py b/ahb_raster_view.py
index cd7539a..0ac8789 100644
--- a/ahb_raster_view.py
+++ b/ahb_raster_view.py
@@ -7,6 +7,21 @@ class RasterView:
 		doc = view.Document
 		self.image_file_name = doc.FileName.replace('.FCStd', '') + '_raster/' + view.Name + '.png'
 		
+	def init_image_projection(self):
+		doc = self.source_view.Document
+		image_name = self.source_view.Label + "_raster"
+		image = doc.getObject(image_name)
+		if image is None:
+			return False
+		
+		self.image_view = image
+		
+		if image.Assembly_handbook_ViewVolumeWidth > 0:
+			self._precompute_image_projection()
+			return True
+		
+		return False
+		
 	def init_image(self):
 		workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench
 		
@@ -34,9 +49,9 @@ class RasterView:
 		self.image_view = image
 		
 		if image.Assembly_handbook_ViewVolumeWidth > 0:
-			self.init_image_projection()
+			self._precompute_image_projection()
 			
-	def init_image_projection(self):
+	def _precompute_image_projection(self):
 		YDirection = self.source_view.Direction.cross(self.source_view.XDirection)
 		
 		self.image_x_dir = self.source_view.XDirection / self.image_view.Assembly_handbook_ViewVolumeWidth
@@ -51,6 +66,15 @@ class RasterView:
 		offset = self.image_view.Assembly_handbook_ViewVolumeOffset
 		return App.Vector(self.image_x_dir.dot(point3d) + offset.x, self.image_y_dir.dot(point3d) + offset.y, self.image_z_dir.dot(point3d) + offset.z)
 	
+	def project3DPointToSourceView(self, point3d):
+		offset = self.image_view.Assembly_handbook_ViewVolumeOffset
+		offset = App.Vector((offset.x-0.5) * self.image_view.Assembly_handbook_ViewVolumeWidth, (offset.y-0.5) * self.image_view.Assembly_handbook_ViewVolumeHeight, (offset.z-0.5) * self.image_view.Assembly_handbook_ViewVolumeDepth)
+		#image_view_point = App.Vector(self.image_x_dir.dot(point3d), self.image_y_dir.dot(point3d), self.image_z_dir.dot(point3d))
+		#return App.Vector(image_view_point.x * self.image_view.Assembly_handbook_ViewVolumeWidth, image_view_point.y * self.image_view.Assembly_handbook_ViewVolumeHeight, 0)
+		
+		YDirection = self.source_view.Direction.cross(self.source_view.XDirection)
+		return App.Vector(self.source_view.XDirection.dot(point3d) + offset.x, YDirection.dot(point3d) + offset.y, self.image_z_dir.dot(point3d) + offset.z)
+	
 	def projectImageViewPointTo3D(self, point2d):
 		offset = self.image_view.Assembly_handbook_ViewVolumeOffset
 		p = point2d - offset
@@ -96,25 +120,15 @@ class RasterView:
 		self.image_view.Assembly_handbook_ViewVolumeHeight = viewVolume.getHeight()
 		self.image_view.Assembly_handbook_ViewVolumeDepth = viewVolume.getDepth()
 		
-		sb_offset = viewVolume.projectToScreen(coin.SbVec3f(0,0,0))
-		self.image_view.Assembly_handbook_ViewVolumeOffset = App.Vector(sb_offset[0], sb_offset[1], sb_offset[2])
-		
-		self.init_image_projection()
-		
 		docView.saveImage(self.image_file_name, 4096, 4096, "#ffffff")
 		
 		with Image.open(self.image_file_name) as img:
 			original_size = img.size
 			
-			p2dA = self.project3DPointToImageView(App.Vector(0,0,0))
-			p2dB = self.project3DPointToImageView(view.XDirection)
-			imageScale = view.Scale / (p2dB.x - p2dA.x) / original_size[0] * 10
-			#print('imageScale', imageScale)
-			
 			bg = Image.new(img.mode, img.size, '#ffffff') # fills an image with the background color
 			diff = ImageChops.difference(img, bg) # diff between the actual image and the background color
 			bbox = diff.getbbox() # finds border size (non-black portion of the image)
-			print(bbox)
+			#print(bbox)
 			#image_center = (bbox[0] + (bbox[2] - bbox[0])/2 - img.size[0]/2, bbox[1] + (bbox[3] - bbox[1])/2 - img.size[1]/2)
 			#print(image_center)
 			img = img.crop(bbox)
@@ -135,6 +149,17 @@ class RasterView:
 			#debugPoint(App.Vector(131.23702882966705, -655.0000021095163, 145.21130178331268))
 			
 			img.save(self.image_file_name)
+			
+			sb_offset = viewVolume.projectToScreen(coin.SbVec3f(0,0,0))
+			crop_offset = App.Vector(((bbox[0] + bbox[2])/2 - original_size[0]/2)/original_size[0], ((bbox[1] + bbox[3])/2 - original_size[1]/2)/original_size[1], 0)
+			self.image_view.Assembly_handbook_ViewVolumeOffset = App.Vector(sb_offset[0] - crop_offset.x, sb_offset[1] + crop_offset.y, sb_offset[2])
+			
+			self._precompute_image_projection()
+			
+			p2dA = self.project3DPointToImageView(App.Vector(0,0,0))
+			p2dB = self.project3DPointToImageView(view.XDirection)
+			imageScale = view.Scale / (p2dB.x - p2dA.x) / original_size[0] * 10
+			#print('imageScale', imageScale)
 		
 		App.closeDocument(doc.Name)
 		
diff --git a/ahb_techdraw_extensions.py b/ahb_techdraw_extensions.py
index e763b1d..e2423f5 100644
--- a/ahb_techdraw_extensions.py
+++ b/ahb_techdraw_extensions.py
@@ -16,6 +16,9 @@ class CursorItem(QtGui.QGraphicsItem):
 		self.size = 100.0
 		self.view = view
 		
+	def removeSceneEventFilter(self, a, b):
+		print('removeSceneEventFilter', a, b)
+		
 	def onViewPosChange(self, callback):
 		self.viewPosChangeCallback = callback
 
@@ -173,11 +176,33 @@ class TechDrawExtensions:
 				overlay.Scale = view.Scale
 				overlay.ViewObject.LineWidth = 0.01
 				
+				# migrate balloons from source view to overlay
 				for balloon in page.Views:
 					if balloon.TypeId == 'TechDraw::DrawViewBalloon' and "Assembly_handbook_Source" in balloon.PropertiesList and balloon.SourceView == view:
 						if balloon.SourceView == view:
+							old_source = balloon.Assembly_handbook_Source
+							old_OriginOffsetX = balloon.Assembly_handbook_OriginOffsetX
+							old_OriginOffsetY = balloon.Assembly_handbook_OriginOffsetY
+							old_X = balloon.X
+							old_Y = balloon.Y
+							old_Visibility = balloon.ViewObject.Visibility
+							balloonName = balloon.Name
+							
+							doc.removeObject(balloon.Name)
+							
+							balloon = doc.addObject("TechDraw::DrawViewBalloon", balloonName)
 							balloon.SourceView = overlay
+							balloon.addProperty("App::PropertyXLink", "Assembly_handbook_Source", "Assembly_handbook")
+							balloon.Assembly_handbook_Source = old_source
+							balloon.addProperty("App::PropertyFloat", "Assembly_handbook_OriginOffsetX", "Assembly_handbook")
+							balloon.addProperty("App::PropertyFloat", "Assembly_handbook_OriginOffsetY", "Assembly_handbook")
+							balloon.Assembly_handbook_OriginOffsetX = old_OriginOffsetX
+							balloon.Assembly_handbook_OriginOffsetY = old_OriginOffsetY
 							page.addView(balloon)
+							self.updateBalloon(balloon)
+							balloon.X = old_X
+							balloon.Y = old_Y
+							balloon.ViewObject.Visibility = old_Visibility
 							balloon.recompute()
 				
 				page.KeepUpdated = True # once we have hidden the source view, there should be no performance issue
@@ -293,6 +318,13 @@ class TechDrawExtensions:
 				selected_balloons.append(obj)
 		
 		cursor = self.view_cursors.get(view, None)
+		if cursor is not None:
+			try:
+				cursor.x() # this can throw an exception if the Qt item has been deleted (for example when closing the page)
+			except Exception as ex:
+				print("Re-generating cursor...")
+				cursor = None
+		
 		if cursor is None:
 			cursor = CursorItem(view = view)
 			TDG.addQGIToView(view, cursor);
@@ -412,7 +444,7 @@ class TechDrawExtensions:
 			view = balloon.SourceView
 			self.updateBalloonCursor(view)
 			if self.enable_selected_part_highlight:
-				self.repaint(view) # disabled for now, for performance reasons
+				self.repaint(view)
 			
 	def onBalloonChanged(self, obj, prop):
 		# Avoid reentry
@@ -496,6 +528,10 @@ class TechDrawExtensions:
 		needPageUpdate = False
 		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
 				self.refreshView(view)
@@ -534,7 +570,24 @@ class TechDrawExtensions:
 					callback()
 			QTimer.singleShot(10, restoreKeepUpdated)
 	
+	def getSourceView(self, view):
+		if view.Name.endswith('_overlay'):
+			view = view.Document.getObject(view.Name[0:-8])
+			if view is None:
+				raise Exception("Can't find source view of " + view.Name)
+		
+		return view
+	
+	def getOverlayView(self, view):
+		if view.Name.endswith('_overlay'):
+			return view
+		
+		overlay = view.Document.getObject(view.Name + '_overlay')
+		return overlay if overlay is not None else view
+	
 	def computePartCenter(self, view, obj):
+		view = self.getSourceView(view)
+		
 		if obj.TypeId == 'App::Link':
 			partLink = obj
 			objectCenterWorld = partLink.LinkPlacement.Matrix.multiply(partLink.LinkedObject.Shape.CenterOfGravity)
@@ -561,6 +614,12 @@ class TechDrawExtensions:
 		return self.projectPoint(view, objectCenterWorld)
 	
 	def projectPoint(self, view, point3d):
+		if 'Assembly_handbook_RasterView' in view.PropertiesList or view.Assembly_handbook_RasterView:
+			from ahb_raster_view import RasterView
+			raster_view = RasterView(view)
+			if raster_view.init_image_projection():
+				return raster_view.project3DPointToSourceView(point3d)
+		
 		# DrawViewPart::projectPoint should be exposed to python in freecad 0.21, but for 0.20 we have to use a workaround
 		view_cache = self.getViewCache(view)
 		if view_cache.projected_origin is None: