Compare commits

..

23 Commits

Author SHA1 Message Date
Youen ed487eab3d Increased line width for better readability when drawings are printed 6 months ago
Youen dfe5d329b3 Improved part metadata system 7 months ago
Youen f92482eb46 Fixed error when a document has multiple "main" objects 10 months ago
Youen cbf1d1b275 Added code to increase near/far distance during rendering to avoid cutting objects 10 months ago
Youen 46698319f6 fixed CSV export 10 months ago
Youen b63643b153 Added system to export part list in CSV file 2 years ago
Youen 731b203c5e Added possibility to render multiple pages in batch 2 years ago
Youen 85b680d2f4 Added possibility to remove transparency from rasterized images 2 years ago
Youen 4bb23e5684 Added code to hide points in rasterized images (only lines should be visible) 2 years ago
Youen a7201934be fixed bug related to clipping in fast render mode 2 years ago
Youen c06af9db24 Added code to handle clipping view 2 years ago
Youen 5e65e68c13 raster rendering optimizations 2 years ago
Youen ec97d10a51 Added possibility to hide specific objects in the rasterized image 2 years ago
Youen a8e10a2b99 fixed rasterization issues, and added possibility to render Draft objects (dimensions, lines, etc.) 2 years ago
Youen ff8b904dc4 Added code to optimize PNG images file size (indexed colors with all-or-nothing alpha) 2 years ago
Youen 8a9c6d8069 Added possibility to manually define a view volume, and fixed some bugs 2 years ago
Youen f180c72f81 New Step button must always be active (to be able to create the first step) 2 years ago
Youen 20895bbf4b fixed error when a document is being loaded (balloons source object are not available yet) 2 years ago
Youen 13ff11293a Added code to initialize pages when loading a document (fix for balloons visibility and image file path) 2 years ago
Youen f62ab9eb70 fixed bugs for link branch 2 years ago
youen 8bea399e48 Merge pull request 'Divers fixes mineurs et annotations générées en cercle' (#2) from AndreasL/assembly_handbook:dev/better-annotations into master 2 years ago
youen 078ce07b86 Merge pull request 'Part name used in sub assembly are now properly display' (#1) from AndreasL/assembly_handbook:master into master 2 years ago
Andréas Livet 959b2fef0a Part name used in sub assembly are now properly display 2 years ago
  1. 1
      .gitignore
  2. 3
      InitGui.py
  3. 109
      ahb_cmd_export_parts_list.py
  4. 3
      ahb_cmd_new_step.py
  5. 22
      ahb_cmd_view_annotate.py
  6. 19
      ahb_cmd_view_refresh.py
  7. 22
      ahb_document_observer.py
  8. 24
      ahb_material.py
  9. 180
      ahb_raster_view.py
  10. 139
      ahb_techdraw_extensions.py

1
.gitignore vendored

@ -3,3 +3,4 @@
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
/pydev/

3
InitGui.py

@ -103,6 +103,9 @@ class AssemblyHandbookWorkbench(Gui.Workbench):
self.importModule('ahb_cmd_view_refresh') self.importModule('ahb_cmd_view_refresh')
toolbox.append("AHB_view_refresh") toolbox.append("AHB_view_refresh")
self.importModule('ahb_cmd_export_parts_list')
toolbox.append("AHB_export_parts_list")
if self.dev: if self.dev:
self.importModule('ahb_cmd_reload') self.importModule('ahb_cmd_reload')

109
ahb_cmd_export_parts_list.py

@ -0,0 +1,109 @@
import FreeCADGui as Gui
import FreeCAD as App
import ahb_utils
from ahb_material import Material
class PartInfo:
def __init__(self, workbench, document, obj):
self.document = document
self.reference = obj.Label
if len(self.reference) == 3 and self.reference[0:1] in ['L', 'M', 'T', 'R', 'E']:
self.reference = 'TB_' + self.reference
workbench.techDrawExtensions.initPartMetadata(obj)
self.material = 'Unknown'
try:
self.material = obj.Assembly_handbook_Material
except:
pass
self.size = [obj.Shape.BoundBox.XLength, obj.Shape.BoundBox.YLength, obj.Shape.BoundBox.ZLength] # in mm
self.volume = obj.Shape.Volume # in mm3
self.mass = -1 # in g (negative means unknown mass)
density = -1 # in g/cm3
material = Material.Get(self.material)
if material is not None:
density = material.density
if density >= 0: self.mass = density * (self.volume / 1000)
try:
part_mass = obj.Assembly_handbook_Weight
if part_mass >= 0:
self.mass = part_mass
except:
pass
self.count = 0
class AHB_ExportPartsList:
def GetResources(self):
return {"MenuText": "Export parts list (CSV)",
"ToolTip": "Exports all parts of the selected assembly in CSV format",
"Pixmap": ""
}
def IsActive(self):
obj = Gui.Selection.getSelection()
if len(obj) == 1:
obj = obj[0]
return obj.TypeId == 'App::Part' and 'AssemblyType' in obj.PropertiesList
return False
def Activated(self):
all_parts = {}
rootAssembly = Gui.Selection.getSelection()[0]
workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench
def add_part(part):
#print(part.Label + " (" + part.LinkedObject.Label + ")")
try:
info = all_parts[part.LinkedObject]
except:
info = PartInfo(workbench, part.LinkedObject.Document.Name, part.LinkedObject)
all_parts[part.LinkedObject] = info
info.count += 1
def process_group(group):
for part in group.Group:
if not part.Visibility:
continue
isPartLink = part.TypeId == 'App::Link' or (part.TypeId == 'Part::FeaturePython' and hasattr(part, 'LinkedObject'))
partType = None
isSinglePart = None
try:
partType = part.Type
isSinglePart = part.Assembly_handbook_IsSinglePart
except:
pass
if isPartLink:
if isSinglePart != True and (partType == 'Assembly' or (part.Group[1].Label == 'Constraints' and part.Group[2].Label == 'Variables' and part.Group[3].Label == 'Configurations')):
process_group(part.LinkedObject)
else:
add_part(part)
else:
if part.TypeId != 'App::DocumentObjectGroup' and part.TypeId != 'Spreadsheet::Sheet' and partType != 'App::PropertyContainer':
print("??? " + part.Label)
pass
print("Exporting all parts contained in assembly: " + rootAssembly.Label + "...")
process_group(rootAssembly)
file_name = rootAssembly.Document.FileName.replace('.FCStd', '') + '_list.csv'
with open(file_name, "w") as f:
f.write("Document, Reference, Material, SizeX (mm), SizeY (mm), SizeZ (mm), Volume (cm3), Mass (g), Count\n")
for part in all_parts.values():
mass_str = ''
if part.mass >= 10:
mass_str = str(int(part.mass + 0.5))
elif part.mass >= 0:
mass_str = str(int(part.mass*10 + 0.5) / 10.0)
f.write(part.document + "," + part.reference + "," + part.material + "," + str(int(part.size[0]+0.5)) + "," + str(int(part.size[1]+0.5)) + "," + str(int(part.size[2]+0.5)) + "," + str(int(part.volume/100+0.5)/10.0) + "," + mass_str + ", " + str(part.count) + "\n")
print("Part list exported to " + file_name)
from ahb_command import AHB_CommandWrapper
AHB_CommandWrapper.addGuiCommand('AHB_export_parts_list', AHB_ExportPartsList())

3
ahb_cmd_new_step.py

@ -10,7 +10,7 @@ class AHB_New_Step:
} }
def IsActive(self): def IsActive(self):
return ahb_utils.getCurrentView() is not None return True
def Activated(self): def Activated(self):
import re import re
@ -74,6 +74,7 @@ class AHB_New_Step:
view.CoarseView = True view.CoarseView = True
view.addProperty("App::PropertyString", "Assembly_handbook_PreviousStepView", "Assembly_handbook") view.addProperty("App::PropertyString", "Assembly_handbook_PreviousStepView", "Assembly_handbook")
view.addProperty("App::PropertyBool", "Assembly_handbook_RasterView", "Assembly_handbook") view.addProperty("App::PropertyBool", "Assembly_handbook_RasterView", "Assembly_handbook")
view.addProperty("App::PropertyXLinkList", "Assembly_handbook_HideParts", "Assembly_handbook")
view.Assembly_handbook_RasterView = raster_view view.Assembly_handbook_RasterView = raster_view
if raster_view: if raster_view:
view.Visibility = False view.Visibility = False

22
ahb_cmd_view_annotate.py

@ -69,12 +69,14 @@ class AHB_View_Annotate:
print(balloon.Name + " references missing object " + ref_name + ", removing balloon") print(balloon.Name + " references missing object " + ref_name + ", removing balloon")
doc.removeObject(balloon.Name) doc.removeObject(balloon.Name)
balloonsCreated = []
for partLink in view.XSource: for partLink in view.XSource:
balloonsCreated = workbench.techDrawExtensions.add_or_update_balloon(view, partLink, '') balloonsCreated.extend(workbench.techDrawExtensions.add_or_update_balloon(view, partLink, ''))
if len(balloonsCreated) > 0:
regroupedBalloons = self.RegroupNearestSimilarBalloons(balloonsCreated) if len(balloonsCreated) > 0:
self.PlaceBalloonsInCircle(regroupedBalloons) regroupedBalloons = self.RegroupNearestSimilarBalloons(balloonsCreated)
#self.PlaceBalloonsInCircle(balloonsCreated) #self.PlaceBalloonsInCircle(regroupedBalloons)
self.PlaceBalloonsInCircle(balloonsCreated)
workbench.techDrawExtensions.refreshOverlays(page) workbench.techDrawExtensions.refreshOverlays(page)
@ -86,13 +88,13 @@ class AHB_View_Annotate:
realBalloon = balloon[0] if type(balloon) is list else balloon realBalloon = balloon[0] if type(balloon) is list else balloon
totalX = totalX + int(realBalloon.OriginX) totalX = totalX + int(realBalloon.OriginX)
totalY = totalY + int(realBalloon.OriginY) totalY = totalY + int(realBalloon.OriginY)
return App.Base.Vector2d(totalX / len(balloons), totalY / len(balloons)) return App.Vector(totalX / len(balloons), totalY / len(balloons))
def IsSimilarBalloonNear(self, balloonA, balloonB): def IsSimilarBalloonNear(self, balloonA, balloonB):
MAX_DISTANCE_BETWEEN_REGROUPED_BALLOONS = 50 MAX_DISTANCE_BETWEEN_REGROUPED_BALLOONS = 50
if balloonA.Text == balloonB.Text: if balloonA.Text == balloonB.Text:
pos = App.Base.Vector2d(balloonA.OriginX, balloonA.OriginY) pos = App.Vector(balloonA.OriginX, balloonA.OriginY)
dist = pos.distance(App.Base.Vector2d(balloonB.OriginX, balloonB.OriginY)) dist = pos.distanceToPoint(App.Vector(balloonB.OriginX, balloonB.OriginY))
return dist < MAX_DISTANCE_BETWEEN_REGROUPED_BALLOONS return dist < MAX_DISTANCE_BETWEEN_REGROUPED_BALLOONS
else: else:
return False return False
@ -119,13 +121,13 @@ class AHB_View_Annotate:
for i in range(nbBalloons): for i in range(nbBalloons):
xPos = round(center.x + 600 * math.cos(balloonPosStep * i)) xPos = round(center.x + 600 * math.cos(balloonPosStep * i))
yPos = round(center.y + 600 * math.sin(balloonPosStep * i)) yPos = round(center.y + 600 * math.sin(balloonPosStep * i))
balloonPos = App.Base.Vector2d(xPos, yPos) balloonPos = App.Vector(xPos, yPos)
# Find nearest arrow to avoid arrow crossing each other # Find nearest arrow to avoid arrow crossing each other
smallestDistance = 0 smallestDistance = 0
balloonToUse = None balloonToUse = None
for balloon in balloons: for balloon in balloons:
realBalloon = balloon[0] if type(balloon) is list else balloon realBalloon = balloon[0] if type(balloon) is list else balloon
dist = balloonPos.distance(App.Base.Vector2d(realBalloon.OriginX, realBalloon.OriginY)) dist = balloonPos.distanceToPoint(App.Vector(realBalloon.OriginX, realBalloon.OriginY))
if smallestDistance == 0 or dist < smallestDistance: if smallestDistance == 0 or dist < smallestDistance:
smallestDistance = dist smallestDistance = dist
balloonToUse = balloon balloonToUse = balloon

19
ahb_cmd_view_refresh.py

@ -4,7 +4,7 @@ import FreeCAD as App
class AHB_RefreshView: class AHB_RefreshView:
def GetResources(self): def GetResources(self):
return {"MenuText": "Refresh page (final quality)", return {"MenuText": "Refresh page (final quality)",
"ToolTip": "Redraws the current page", "ToolTip": "Redraws the current page, or if one or more pages are selected, redraw all the selected pages",
"Pixmap": "" "Pixmap": ""
} }
@ -14,9 +14,20 @@ class AHB_RefreshView:
def Activated(self): def Activated(self):
workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench
page = workbench.techDrawExtensions.getActivePage()
if page is not None: selection = Gui.Selection.getSelection()
workbench.techDrawExtensions.forceRedrawPage(page, fast_render = False) has_selected_pages = len(selection) > 0
for s in selection:
if s.TypeId != 'TechDraw::DrawPage':
has_selected_pages = False
if has_selected_pages:
for page in selection:
workbench.techDrawExtensions.forceRedrawPage(page, fast_render = False)
else:
page = workbench.techDrawExtensions.getActivePage()
if page is not None:
workbench.techDrawExtensions.forceRedrawPage(page, fast_render = False)
from ahb_command import AHB_CommandWrapper from ahb_command import AHB_CommandWrapper
AHB_CommandWrapper.addGuiCommand('AHB_view_refresh', AHB_RefreshView()) AHB_CommandWrapper.addGuiCommand('AHB_view_refresh', AHB_RefreshView())

22
ahb_document_observer.py

@ -43,11 +43,28 @@ class DocLinkObserver:
class DocObserver: class DocObserver:
changed_object_by_type = {} changed_object_by_type = {}
selection_by_type = {} selection_by_type = {}
doc_callbacks = {}
was_selected = [] was_selected = []
def __init__(self): def __init__(self):
Gui.Selection.addObserver(self) Gui.Selection.addObserver(self)
def slotActivateDocument(self, doc):
#print('slotActivateDocument', doc.Name)
self._triggerDocumentEvent(doc, 'activate')
def slotCreatedDocument(self, doc):
#print('slotCreatedDocument', doc.Name)
self._triggerDocumentEvent(doc, 'created')
def slotDeletedDocument(self, doc):
#print('slotDeletedDocument', doc.Name)
self._triggerDocumentEvent(doc, 'deleted')
def _triggerDocumentEvent(self, doc, event):
for callback in self.doc_callbacks.values():
callback(doc, event)
def slotChangedObject(self, obj, prop): def slotChangedObject(self, obj, prop):
#print("object changed: " + str(obj).replace('<', '').replace(' object>', '') + " " + obj.Name + " : " + str(prop)) #print("object changed: " + str(obj).replace('<', '').replace(' object>', '') + " " + obj.Name + " : " + str(prop))
@ -94,4 +111,7 @@ class DocObserver:
if callbacks is None: if callbacks is None:
callbacks = {} callbacks = {}
self.selection_by_type[type_id] = callbacks self.selection_by_type[type_id] = callbacks
callbacks[callback_id] = callback callbacks[callback_id] = callback
def onDocumentEvent(self, callback_id: str, callback):
self.doc_callbacks[callback_id] = callback

24
ahb_material.py

@ -0,0 +1,24 @@
class Material:
def __init__(self, ID, density):
self.ID = ID
self.density = density
DB = []
@staticmethod
def GetMaterialIDs():
result = []
for m in Material.DB:
result.append(m.ID)
return result
@staticmethod
def Get(ID):
for m in Material.DB:
if m.ID == ID: return m
return None
Material.DB.append(Material('Stainless steel', density = 8.00))
Material.DB.append(Material('Aluminium', density = 2.71))
Material.DB.append(Material('Wood (pine)', density = 0.55))
Material.DB.append(Material('Plywood', density = 0.6))

180
ahb_raster_view.py

@ -1,5 +1,13 @@
import FreeCAD as App import FreeCAD as App
import FreeCADGui as Gui import FreeCADGui as Gui
from datetime import datetime
def print_verbose(msg):
verbose = False
if verbose:
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
print(current_time, msg)
class RasterView: class RasterView:
def __init__(self, view): def __init__(self, view):
@ -89,7 +97,7 @@ class RasterView:
result.extend(self._flatten_objects_tree([obj.LinkedObject])) result.extend(self._flatten_objects_tree([obj.LinkedObject]))
elif obj.TypeId in ['App::Part', 'App::DocumentObjectGroup']: elif obj.TypeId in ['App::Part', 'App::DocumentObjectGroup']:
result.extend(self._flatten_objects_tree(obj.Group)) result.extend(self._flatten_objects_tree(obj.Group))
elif obj.TypeId in ['Part::Feature', 'Part::FeaturePython', 'PartDesign::Body', 'PartDesign::CoordinateSystem', 'PartDesign::Line', 'Part::Mirroring', 'Part::Cut', 'Part::Part2DObjectPython']: elif self._should_render(obj) or self._should_render_as_is(obj) or obj.TypeId in ['PartDesign::CoordinateSystem', 'PartDesign::Line']:
result.append(obj) result.append(obj)
if hasattr(obj, 'Group'): if hasattr(obj, 'Group'):
result.extend(self._flatten_objects_tree(obj.Group)) result.extend(self._flatten_objects_tree(obj.Group))
@ -97,7 +105,10 @@ class RasterView:
return result return result
def _should_render(self, obj): def _should_render(self, obj):
return obj.TypeId in ['Part::Feature', 'Part::FeaturePython', 'PartDesign::Body', 'Part::Mirroring', 'Part::Cut', 'Part::Part2DObjectPython'] return obj.TypeId in ['Part::Feature', 'Part::FeaturePython', 'PartDesign::Body', 'Part::Mirroring', 'Part::Cut', 'Part::Part2DObjectPython', 'Part::MultiFuse', 'Part::Loft', 'Part::Torus', 'Part::Cylinder']
def _should_render_as_is(self, obj):
return obj.TypeId in ['App::FeaturePython']
def render(self, fast_render = True): def render(self, fast_render = True):
from pivy import coin from pivy import coin
@ -113,21 +124,45 @@ class RasterView:
self.init_image() self.init_image()
print('Rasterizing ' + view.Label + " to " + self.image_file_name + "...") print_verbose('Rasterizing ' + view.Label + " to " + self.image_file_name + "...")
dir = os.path.dirname(self.image_file_name) dir = os.path.dirname(self.image_file_name)
if not os.path.exists(dir): if not os.path.exists(dir):
os.makedirs(dir) os.makedirs(dir)
if 'Assembly_handbook_RasterSavedView' in view.PropertiesList and view.Assembly_handbook_RasterSavedView is not None:
tmp_doc = view.Assembly_handbook_RasterSavedView.Document
close_tmp_doc = False
else:
tmp_doc = App.newDocument('tmp_raster', hidden=False, temp=False)
close_tmp_doc = True
tmp_doc = App.newDocument('tmp_raster', hidden=False, temp=False) transparent_background = True
if 'Assembly_handbook_TransparentBackground' in view.PropertiesList:
transparent_background = view.Assembly_handbook_TransparentBackground
objects_to_reset = {} objects_to_reset = {}
duplicated_parts = {} duplicated_parts = {}
try: try:
print_verbose("Preparing scene...")
# Clean existing scene (if any)
sceneGroup = tmp_doc.getObject('Scene')
if sceneGroup is not None:
sceneGroup.removeObjectsFromDocument()
tmp_doc.removeObject(sceneGroup.Name)
# construct new scene with links to the parts we want # construct new scene with links to the parts we want
sceneGroup = tmp_doc.addObject('App::DocumentObjectGroup', 'Scene')
prev_parts = [] prev_parts = []
new_parts = [] new_parts = []
for part in view.XSource: all_parts = view.XSource + view.Source
objects_to_hide = []
if 'Assembly_handbook_HideParts' in view.PropertiesList:
objects_to_hide = self._flatten_objects_tree(view.Assembly_handbook_HideParts)
for part in all_parts:
link = tmp_doc.addObject('App::Link', part.Name) link = tmp_doc.addObject('App::Link', part.Name)
link.Label = part.Label link.Label = part.Label
if part.TypeId == 'App::Link': if part.TypeId == 'App::Link':
@ -138,10 +173,14 @@ class RasterView:
link.Placement = part.Placement link.Placement = part.Placement
else: else:
link.LinkedObject = part link.LinkedObject = part
if part.TypeId in ['Part::Part2DObjectPython']:
link.Placement = part.Placement
is_new_part = workbench.techDrawExtensions.isNewPartInView(view, part) is_new_part = workbench.techDrawExtensions.isNewPartInView(view, part)
if not fast_render: if not fast_render:
# check if another part with different render settings will conflict with ours
# a conflict occurs when two parts link to the same object (directly or indirectly), because render settings (such as color) are set at the object level
is_conflicting = False is_conflicting = False
if link.LinkedObject in duplicated_parts.keys(): if link.LinkedObject in duplicated_parts.keys():
link.LinkedObject = duplicated_parts[link.LinkedObject] link.LinkedObject = duplicated_parts[link.LinkedObject]
@ -150,7 +189,7 @@ class RasterView:
for other_part in other_parts: for other_part in other_parts:
other_objects = self._flatten_objects_tree([other_part]) other_objects = self._flatten_objects_tree([other_part])
for obj in self._flatten_objects_tree([link]): for obj in self._flatten_objects_tree([link]):
if obj in other_objects: if self._should_render(obj) and obj in other_objects:
is_conflicting = True is_conflicting = True
if is_conflicting: if is_conflicting:
@ -162,18 +201,21 @@ class RasterView:
part_copy.Label = part.Label part_copy.Label = part.Label
duplicated_parts[link.LinkedObject] = part_copy duplicated_parts[link.LinkedObject] = part_copy
link.LinkedObject = part_copy link.LinkedObject = part_copy
part_copy.ViewObject.Visibility = False
sceneGroup.addObject(link)
if is_new_part: if is_new_part:
new_parts.append(link) new_parts.append(link)
else: else:
prev_parts.append(link) prev_parts.append(link)
# hide objects that we don't want to display ; also make a backup of properties we want to reset after we're done # hide objects that we don't want to display ; also make a backup of properties we want to reset after we're done
for obj in self._flatten_objects_tree([link]): for obj in self._flatten_objects_tree([link]):
if obj in objects_to_reset.keys(): if obj in objects_to_reset.keys():
continue continue
if self._should_render(obj): if self._should_render(obj) and not obj in objects_to_hide:
if not fast_render: if not fast_render:
objects_to_reset[obj] = ( objects_to_reset[obj] = (
obj.ViewObject.Visibility, obj.ViewObject.Visibility,
@ -185,6 +227,12 @@ class RasterView:
obj.ViewObject.LineWidth, obj.ViewObject.LineWidth,
obj.ViewObject.DisplayMode obj.ViewObject.DisplayMode
) )
if not obj.ViewObject.Visibility:
obj.ViewObject.ShapeMaterial.AmbientColor = (0, 0, 0)
obj.ViewObject.ShapeMaterial.DiffuseColor = (0, 0, 0)
obj.ViewObject.ShapeMaterial.SpecularColor = (0, 0, 0)
obj.ViewObject.ShapeMaterial.EmissiveColor = (0, 0, 0)
else: else:
objects_to_reset[obj] = ( objects_to_reset[obj] = (
obj.ViewObject.Visibility, obj.ViewObject.Visibility,
@ -200,14 +248,32 @@ class RasterView:
rot *= coin.SbRotation(coin.SbVec3f(0,0,1), coin.SbVec3f(view.Direction.x,view.Direction.y,view.Direction.z)) rot *= coin.SbRotation(coin.SbVec3f(0,0,1), coin.SbVec3f(view.Direction.x,view.Direction.y,view.Direction.z))
cam.orientation.setValue(rot) cam.orientation.setValue(rot)
tmp_doc_view.fitAll() targetViewVolume = None
try:
targetViewVolume = view.Assembly_handbook_ViewVolume
except:
pass
if targetViewVolume is None:
tmp_doc_view.fitAll()
else:
sceneGroup.ViewObject.Visibility = False
viewVolumeLink = tmp_doc.addObject('App::Link', 'ViewVolume')
viewVolumeLink.LinkedObject = targetViewVolume
viewVolumeLink.Placement = targetViewVolume.Placement
tmp_doc_view.fitAll()
tmp_doc.removeObject(viewVolumeLink.Name)
sceneGroup.ViewObject.Visibility = True
print_verbose("Near=" + str(cam.nearDistance.getValue()) + ", far="+str(cam.farDistance.getValue()))
cam.nearDistance.setValue(cam.nearDistance.getValue() - 1000)
cam.farDistance.setValue(cam.farDistance.getValue() + 1000)
viewVolume = cam.getViewVolume(0.0) viewVolume = cam.getViewVolume(0.0)
self.image_view.Assembly_handbook_ViewVolumeWidth = viewVolume.getWidth() self.image_view.Assembly_handbook_ViewVolumeWidth = viewVolume.getWidth()
self.image_view.Assembly_handbook_ViewVolumeHeight = viewVolume.getHeight() self.image_view.Assembly_handbook_ViewVolumeHeight = viewVolume.getHeight()
self.image_view.Assembly_handbook_ViewVolumeDepth = viewVolume.getDepth() self.image_view.Assembly_handbook_ViewVolumeDepth = viewVolume.getDepth()
max_res = 3200 # todo: keep aspect ratio when we limit max image dimensions max_res = 3200
#max_res = 1500 #max_res = 1500
resolution = [ resolution = [
int(viewVolume.getWidth() * view.Scale * 10), int(viewVolume.getWidth() * view.Scale * 10),
@ -221,23 +287,46 @@ class RasterView:
resolution[1] = int(max_res) resolution[1] = int(max_res)
if fast_render: if fast_render:
print_verbose("Fast rasterization...")
composite_img = self._render_lines(tmp_doc, resolution, prev_parts + new_parts, (0.0, 0.0, 0.0), []) composite_img = self._render_lines(tmp_doc, resolution, prev_parts + new_parts, (0.0, 0.0, 0.0), [])
else: else:
# render old parts in gray lines # render old parts in gray lines
print_verbose("Rendering old parts (gray)...")
prev_parts_img = self._render_lines(tmp_doc, resolution, prev_parts, (0.6, 0.6, 0.6), [], fast_render) 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) # render new parts in black lines (old parts can mask them)
print_verbose("Rendering new parts (black)...")
new_parts_img = self._render_lines(tmp_doc, resolution, new_parts, (0.0, 0.0, 0.0), prev_parts, fast_render) 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 # create the composite image
print_verbose("Compositing images...")
composite_img = prev_parts_img.copy() composite_img = prev_parts_img.copy()
composite_img.paste(new_parts_img, None, new_parts_img) composite_img.paste(new_parts_img, None, new_parts_img)
# Optimize the image to reduce storage size
if not fast_render:
print_verbose("Optimizing PNG size...")
num_colors = 32
# All-or-nothing alpha: we use a white background and only make pixels fully transparent where alpha is zero, to not loose antialiasing
bg_img = Image.new(composite_img.mode, composite_img.size, color = '#ffffff')
bg_img.paste(composite_img.convert('RGB'), composite_img)
final_alpha = composite_img.split()[3].point(lambda p: 0 if p <= int(255/num_colors+0.5) else 255)
if not transparent_background:
final_alpha = final_alpha.point(lambda p: 255)
composite_img = bg_img
composite_img.putalpha(final_alpha)
# Convert to indexed colors
composite_img = composite_img.quantize(colors=num_colors, dither=Image.Dither.NONE)
finally: finally:
print_verbose("Cleaning scene...")
#raise Exception("test")
# restore properties on objects we have modified # restore properties on objects we have modified
for obj, props in objects_to_reset.items(): for obj, props in objects_to_reset.items():
obj.ViewObject.Visibility = props[0] obj.ViewObject.Visibility = props[0]
if self._should_render(obj): if self._should_render(obj) and not obj in objects_to_hide:
obj.ViewObject.LineColor = props[1] obj.ViewObject.LineColor = props[1]
obj.ViewObject.ShapeMaterial.AmbientColor = props[2] obj.ViewObject.ShapeMaterial.AmbientColor = props[2]
obj.ViewObject.ShapeMaterial.DiffuseColor = props[3] obj.ViewObject.ShapeMaterial.DiffuseColor = props[3]
@ -245,14 +334,18 @@ class RasterView:
obj.ViewObject.ShapeMaterial.EmissiveColor = props[5] obj.ViewObject.ShapeMaterial.EmissiveColor = props[5]
obj.ViewObject.LineWidth = props[6] obj.ViewObject.LineWidth = props[6]
obj.ViewObject.DisplayMode = props[7] obj.ViewObject.DisplayMode = props[7]
obj.ViewObject.PointMaterial.Transparency = 0
# remove the temporary document # remove the temporary document
App.closeDocument(tmp_doc.Name) if close_tmp_doc:
App.closeDocument(tmp_doc.Name)
print_verbose("Finalizing view...")
# Crop the image, which is also used to deduce the center of the source view # Crop the image, which is also used to deduce the center of the source view
original_size = composite_img.size original_size = composite_img.size
diff_source_img = composite_img.split()[3] diff_source_img = composite_img.split()[-1]
bg = Image.new(diff_source_img.mode, diff_source_img.size, '#000000') # fills an image with the background color bg = Image.new(diff_source_img.mode, diff_source_img.size, '#000000') # fills an image with the background color
diff = ImageChops.difference(diff_source_img, bg) # diff between the actual image and the background color diff = ImageChops.difference(diff_source_img, bg) # diff between the actual image and the background color
bbox = diff.getbbox() # finds border size (non-black portion of the image) bbox = diff.getbbox() # finds border size (non-black portion of the image)
@ -291,11 +384,13 @@ class RasterView:
image.Scale = image_scale image.Scale = image_scale
image.X = view.X image.X = view.X
image.Y = view.Y image.Y = view.Y
image.ImageFile = self.image_file_name # TODO: see if it's possible to set a relative path image.ImageFile = self.image_file_name
image.ViewObject.Crop = True image.ViewObject.Crop = True
image.Width = composite_img.size[0] * image_scale / 10.0 * 1.01 image.Width = composite_img.size[0] * image_scale / 10.0 * 1.01
image.Height = composite_img.size[1] * image_scale / 10.0 * 1.01 image.Height = composite_img.size[1] * image_scale / 10.0 * 1.01
image.recompute() image.recompute()
print_verbose("Done")
def _render_lines(self, doc, resolution, parts, line_color, masking_parts, fast_render = True): def _render_lines(self, doc, resolution, parts, line_color, masking_parts, fast_render = True):
import tempfile import tempfile
@ -303,48 +398,66 @@ class RasterView:
doc_view = Gui.getDocument(doc.Name).mdiViewsOfType('Gui::View3DInventor')[0] doc_view = Gui.getDocument(doc.Name).mdiViewsOfType('Gui::View3DInventor')[0]
# render lines in black, background in red, fill shapes in green # render lines in blue, background in red, fill shapes in green
# the green band contains the lines images, the red band contains the inverted alpha layer # the green band contains the lines image, the red band contains the inverted alpha layer
# if there is a clipping plane set with "Fill clip plane", the blue band contains the intersection with the clip plane
configured = []
print_verbose('Preparing objects for line rendering...')
for link in doc.findObjects(): for link in doc.findObjects():
if link in parts or link in masking_parts: if link in parts or link in masking_parts:
link.ViewObject.Visibility = True link.ViewObject.Visibility = True
# 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 # 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]): for obj in self._flatten_objects_tree([link]):
if obj in configured: continue
configured.append(obj)
if self._should_render(obj) and not fast_render: 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.DisplayMode = 'Flat Lines'
obj.ViewObject.PointMaterial.Transparency = 1.0 # hide points
obj.ViewObject.LineColor = (0.0, 0.0, 1.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.AmbientColor = (0.0, 0.0, 0.0, 0.0)
obj.ViewObject.ShapeMaterial.DiffuseColor = (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.SpecularColor = (0.0, 0.0, 0.0, 0.0)
obj.ViewObject.ShapeMaterial.EmissiveColor = (0.0, 1.0, 0.0, 0.0) if link in parts else (1.0, 0.0, 1.0) obj.ViewObject.ShapeMaterial.EmissiveColor = (0.0, 1.0, 1.0, 0.0) if link in parts else (1.0, 0.0, 1.0)
# We need to set two different values otherwise freecad does not always update LineWidth of sub-elements # We need to set two different values otherwise freecad does not always update LineWidth of sub-elements
obj.ViewObject.LineWidth = 1.0 obj.ViewObject.LineWidth = 1.0
obj.ViewObject.LineWidth = 2.0 obj.ViewObject.LineWidth = 3.0
else: else:
link.ViewObject.Visibility = False link.ViewObject.Visibility = False
print_verbose('Rendering lines...')
temp_file_name = tempfile.gettempdir() + "/ahb_temp_image.png" temp_file_name = tempfile.gettempdir() + "/ahb_temp_image.png"
doc_view.saveImage(temp_file_name, resolution[0]+2, resolution[1]+2, "#ff0000") # we add 1 pixel border that we will need to crop later #temp_file_name = "/home/youen/tmp/ahb_temp_image.png"
doc_view.saveImage(temp_file_name, resolution[0]+2, resolution[1]+2, "#ff00ff") # we add 1 pixel border that we will need to crop later
lines_bands_img = self._read_image(temp_file_name) lines_bands_img = self._read_image(temp_file_name)
lines_bands = lines_bands_img.split() lines_bands = lines_bands_img.split()
lines_img = lines_bands[1] lines_img = lines_bands[1]
alpha_img = lines_bands[0].point(lambda p: 255 - p) alpha_img = lines_bands[0].point(lambda p: 255 - p)
clip_img = lines_bands[2]
generate_outlines = not fast_render generate_outlines = not fast_render
#generate_outlines = False
if generate_outlines: if generate_outlines:
# Render all shapes with different colors, in order to extract outlines (where color changes) # 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 # 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 # The technique could be improved by using the depth buffer instead, in order to detect boundaries within the same object
print_verbose('Preparing objects for outline rendering...')
step = 8 step = 8
r = step r = step
g = step g = step
b = step b = step
configured = []
for link in doc.findObjects(): for link in doc.findObjects():
if link in parts or link in masking_parts: if link in parts or link in masking_parts:
for obj in self._flatten_objects_tree([link]): for obj in self._flatten_objects_tree([link]):
if obj in configured: continue
configured.append(obj)
if self._should_render(obj) and obj.TypeId != 'Part::Part2DObjectPython': if self._should_render(obj) and obj.TypeId != 'Part::Part2DObjectPython':
configured.append(obj)
obj.ViewObject.DisplayMode = 'Shaded' obj.ViewObject.DisplayMode = 'Shaded'
obj.ViewObject.ShapeMaterial.AmbientColor = (0.0, 0.0, 0.0, 0.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) obj.ViewObject.ShapeMaterial.DiffuseColor = (0.0, 0.0, 0.0, 0.0)
@ -363,16 +476,19 @@ class RasterView:
else: else:
obj.ViewObject.Visibility = False obj.ViewObject.Visibility = False
print_verbose('Rendering shapes...')
doc_view.saveImage(temp_file_name, (resolution[0]+2)*2, (resolution[1]+2)*2, "#ffffff") # shapes are rendered at twice the resolution for antialiasing 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) shapes_img = self._read_image(temp_file_name)
print_verbose('Extracting outlines...')
outlines_img = None outlines_img = None
for x in range(0, 3): for x in range(0, 5):
for y in range(0, 3): for y in range(0, 5):
if x == 1 and y == 1: continue if x == 2 and y == 2: continue
kernel = [0, 0, 0, 0, 1, 0, 0, 0, 0] if (x-2)*(x-2) + (y-2)+(y-2) > 4: continue
kernel[y * 3 + x] = -1 kernel = [0,0,0,0,0, 0,0,0,0,0, 0,0,1,0,0, 0,0,0,0,0, 0,0,0,0,0]
partial_outlines = shapes_img.filter(ImageFilter.Kernel((3, 3), kernel, 1, 127)) kernel[y * 5 + x] = -1
partial_outlines = shapes_img.filter(ImageFilter.Kernel((5, 5), kernel, 1, 127))
partial_outlines = partial_outlines.point(lambda p: 255 if p == 127 else 0) partial_outlines = partial_outlines.point(lambda p: 255 if p == 127 else 0)
partial_outlines = partial_outlines.convert("L") partial_outlines = partial_outlines.convert("L")
partial_outlines = partial_outlines.point(lambda p: 255 if p == 255 else 0) partial_outlines = partial_outlines.point(lambda p: 255 if p == 255 else 0)
@ -381,6 +497,7 @@ class RasterView:
else: 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))
print_verbose('Combining lines and outlines...')
lines_fullres = lines_img.resize(outlines_img.size, Image.NEAREST) 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(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)) #lines_fullres.paste(255, alpha_fullres.point(lambda p: 255 if p == 0 else 0))
@ -395,6 +512,7 @@ class RasterView:
alpha_img = alpha_img.point(lambda p: 0 if p == 0 else 255) alpha_img = alpha_img.point(lambda p: 0 if p == 0 else 255)
# colorize final image # colorize final image
print_verbose('Colorizing image...')
fill_color = (1.0, 1.0, 1.0) fill_color = (1.0, 1.0, 1.0)
result = Image.merge("RGBA", [ result = Image.merge("RGBA", [
all_lines.point(lambda p: int(fill_color[0] * p + line_color[0] * (255.0 - p))), all_lines.point(lambda p: int(fill_color[0] * p + line_color[0] * (255.0 - p))),
@ -403,6 +521,16 @@ class RasterView:
alpha_img alpha_img
]) ])
# set clip color
if not fast_render:
clip_color = (0.5, 0.5, 0.5)
colorized_clip_img = Image.merge("RGB", [
clip_img.point(lambda p: int(clip_color[0] * (255.0 - p))),
clip_img.point(lambda p: int(clip_color[1] * (255.0 - p))),
clip_img.point(lambda p: int(clip_color[2] * (255.0 - p)))
])
result.paste(colorized_clip_img, clip_img.point(lambda p: 255 - p))
# crop 1px borders # crop 1px borders
result = result.crop((1, 1, result.size[0] - 1, result.size[1] - 1)) result = result.crop((1, 1, result.size[0] - 1, result.size[1] - 1))

139
ahb_techdraw_extensions.py

@ -7,6 +7,8 @@ import TechDraw, TechDrawGui
from PySide import QtGui, QtCore from PySide import QtGui, QtCore
TDG = TechDrawGui TDG = TechDrawGui
from ahb_material import Material
class CursorItem(QtGui.QGraphicsItem): class CursorItem(QtGui.QGraphicsItem):
def __init__(self, parent = None, view = None): def __init__(self, parent = None, view = None):
super().__init__(parent) super().__init__(parent)
@ -77,10 +79,16 @@ class TechDrawExtensions:
enable_selected_part_highlight = False # disable for now, for performance reasons enable_selected_part_highlight = False # disable for now, for performance reasons
initialized_documents = []
def __init__(self): def __init__(self):
workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench
workbench.docObserver.onObjectTypeChanged('balloon_changed', 'TechDraw::DrawViewBalloon', lambda obj, prop: self.onBalloonChanged(obj, prop)) 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)) workbench.docObserver.onObjectTypeSelected('balloon_selected', 'TechDraw::DrawViewBalloon', lambda operation, obj, sub, point: self.onBalloonSelected(operation, obj, sub, point))
workbench.docObserver.onDocumentEvent('techdrawext_doc_event', lambda doc, event: self.onDocumentEvent(doc, event))
if App.ActiveDocument is not None:
self.onDocumentEvent(App.ActiveDocument, 'activate')
def repaint(self, view, fast_render = True): def repaint(self, view, fast_render = True):
self.views_to_repaint[view] = fast_render self.views_to_repaint[view] = fast_render
@ -88,6 +96,7 @@ class TechDrawExtensions:
def _do_repaint(self): def _do_repaint(self):
from ahb_raster_view import RasterView from ahb_raster_view import RasterView
import Draft
workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench
@ -127,44 +136,28 @@ class TechDrawExtensions:
overlay_frame_name = view.Label + "_frame" overlay_frame_name = view.Label + "_frame"
overlay_frame = doc.getObject(overlay_frame_name) overlay_frame = doc.getObject(overlay_frame_name)
if overlay_frame is not None:
if overlay_frame is None: doc.removeObject(overlay_frame.Name)
import Draft #overlay_frame = Draft.makeWire(points, closed=False, face=False, support=None)
''' overlay_frame = doc.addObject("Part::Part2DObjectPython", overlay_frame_name)
points = [App.Vector(0.0, 0.0, 0.0), App.Vector(1.01, 0, 0)] Draft.Wire(overlay_frame)
obj1 = Draft.makeWire(points, closed=False, face=False, support=None) pos = raster_view.projectImageViewPointTo3D(App.Vector(0,0,0))
pos2 = raster_view.projectImageViewPointTo3D(App.Vector(0.001,0.001,1))
points = [App.Vector(200.0, 0.0, 0.0), App.Vector(201.01, 0, 0)] overlay_frame.Points = [pos, pos2]
obj2 = Draft.makeWire(points, closed=False, face=False, support=None) Draft.ViewProviderWire(overlay_frame.ViewObject)
overlay_frame.recompute()
#overlay_frame = Draft.upgrade([], delete=True)
#overlay_frame.Label = overlay_frame_name
overlay_frame = App.ActiveDocument.addObject("Part::Part2DObjectPython", overlay_frame_name)
Draft.Block(overlay_frame)
overlay_frame.Components = [obj1, obj2]
Draft.ViewProviderDraftPart(overlay_frame.ViewObject)
doc.removeObject(obj1.Name)
doc.removeObject(obj2.Name)'''
#overlay_frame = Draft.makeWire(points, closed=False, face=False, support=None)
overlay_frame = App.ActiveDocument.addObject("Part::Part2DObjectPython", overlay_frame_name)
Draft.Wire(overlay_frame)
pos = raster_view.projectImageViewPointTo3D(App.Vector(0,0,0))
pos2 = raster_view.projectImageViewPointTo3D(App.Vector(0.001,0.001,1))
overlay_frame.Points = [pos, pos2]
Draft.ViewProviderWire(overlay_frame.ViewObject)
overlay_frame.recompute()
overlay_frame2_name = view.Label + "_frame2" overlay_frame2_name = view.Label + "_frame2"
overlay_frame2 = doc.getObject(overlay_frame2_name) overlay_frame2 = doc.getObject(overlay_frame2_name)
if overlay_frame2 is None: if overlay_frame2 is not None:
overlay_frame2 = App.ActiveDocument.addObject("Part::Part2DObjectPython", overlay_frame2_name) doc.removeObject(overlay_frame2.Name)
Draft.Wire(overlay_frame2) overlay_frame2 = doc.addObject("Part::Part2DObjectPython", overlay_frame2_name)
pos = raster_view.projectImageViewPointTo3D(App.Vector(1,1,0)) Draft.Wire(overlay_frame2)
pos2 = raster_view.projectImageViewPointTo3D(App.Vector(1.001,1.001,1)) pos = raster_view.projectImageViewPointTo3D(App.Vector(1,1,0))
overlay_frame2.Points = [pos, pos2] pos2 = raster_view.projectImageViewPointTo3D(App.Vector(1.001,1.001,1))
Draft.ViewProviderWire(overlay_frame2.ViewObject) overlay_frame2.Points = [pos, pos2]
overlay_frame2.recompute() Draft.ViewProviderWire(overlay_frame2.ViewObject)
overlay_frame2.recompute()
overlay.Source = [overlay_frame, overlay_frame2] overlay.Source = [overlay_frame, overlay_frame2]
@ -515,14 +508,15 @@ class TechDrawExtensions:
obj = self.getBalloonSourcePart(balloon) obj = self.getBalloonSourcePart(balloon)
path = self.getBalloonSourcePartPath(balloon) path = self.getBalloonSourcePartPath(balloon)
partDisplayName = 'Inconnu' if obj is None else self.getPartDisplayName(obj) if obj is not None:
objectCenterView = workbench.techDrawExtensions.computePartCenter(view, obj, path)
objectCenterView = workbench.techDrawExtensions.computePartCenter(view, obj, path)
balloon.OriginX = objectCenterView.x + balloon.Assembly_handbook_OriginOffsetX balloon.OriginX = objectCenterView.x + balloon.Assembly_handbook_OriginOffsetX
balloon.OriginY = objectCenterView.y + balloon.Assembly_handbook_OriginOffsetY balloon.OriginY = objectCenterView.y + balloon.Assembly_handbook_OriginOffsetY
partDisplayName = 'Inconnu' if obj is None else self.getPartDisplayName(obj)
balloon.Text = partDisplayName
balloon.Text = partDisplayName
balloon.ViewObject.Font = 'DejaVu Sans' balloon.ViewObject.Font = 'DejaVu Sans'
balloon.ViewObject.Fontsize = 4 balloon.ViewObject.Fontsize = 4
balloon.BubbleShape = 'Inspection' balloon.BubbleShape = 'Inspection'
@ -646,8 +640,24 @@ class TechDrawExtensions:
QTimer.singleShot(10, restoreKeepUpdated) QTimer.singleShot(10, restoreKeepUpdated)
def refreshOverlays(self, page, callback = None): def refreshOverlays(self, page, callback = None):
import os
for view in page.Views:
if view.TypeId == 'TechDraw::DrawViewPart' and '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)
doc = page.Document
for image in page.Views:
if image.TypeId == 'TechDraw::DrawViewImage':
folder_name = '/' + os.path.basename(doc.FileName).replace('.FCStd', '') + '_raster/'
if folder_name in image.ImageFile:
full_path = doc.FileName.replace('.FCStd', '') + '_raster/' + image.ImageFile.split(folder_name)[1]
if image.ImageFile != full_path:
image.ImageFile = full_path
if page.KeepUpdated: if page.KeepUpdated:
callback() if callback:
callback()
else: else:
page.KeepUpdated = True page.KeepUpdated = True
def restoreKeepUpdated(): def restoreKeepUpdated():
@ -772,3 +782,48 @@ class TechDrawExtensions:
cache = ViewCache() cache = ViewCache()
self.view_cache[view] = cache self.view_cache[view] = cache
return cache return cache
def onDocumentEvent(self, doc, event):
if event == 'activate':
if doc not in self.initialized_documents:
self.initialized_documents.append(doc)
self.initializeDocument(doc)
elif event == 'deleted':
if doc in self.initialized_documents:
self.initialized_documents.remove(doc)
def initializeDocument(self, doc):
def doInit():
main_part = None
try:
for obj in doc.Objects:
if obj.TypeId == 'TechDraw::DrawPage':
self.onPageLoaded(obj)
main_parts = doc.getObjectsByLabel(doc.Name)
if len(main_parts) == 1:
main_part = main_parts[0]
except:
pass
if main_part is not None:
self.initPartMetadata(main_part)
QTimer.singleShot(0, doInit)
def initPartMetadata(self, part):
current_material = 'Unknown'
if 'Assembly_handbook_Material' in part.PropertiesList:
current_material = part.Assembly_handbook_Material
else:
part.addProperty("App::PropertyEnumeration", "Assembly_handbook_Material", "Assembly_handbook")
material_list = ['Unknown'] + Material.GetMaterialIDs()
part.Assembly_handbook_Material = material_list
part.Assembly_handbook_Material = material_list.index(current_material) if current_material in material_list else 0
if 'Assembly_handbook_Weight' not in part.PropertiesList:
part.addProperty("App::PropertyFloat", "Assembly_handbook_Weight", "Assembly_handbook", 'Part weight in grams. Set a negative number if weight is unknown.')
part.Assembly_handbook_Weight = -1
def onPageLoaded(self, page):
self.refreshOverlays(page)

Loading…
Cancel
Save