Save Picture Not Working from Macro

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Post Reply
SunMesa
Posts: 8
Joined: Fri May 19, 2017 3:45 pm

Save Picture Not Working from Macro

Post by SunMesa »

Hello all,

I've been using FreeCAD over the last 4-5 yrs, but sporadically, and would still have to consider myself a beginner. I've recently run into a problem trying to save a scene image from a macro.

The basic goal is to run a macro that produces a large set (thousands) of images of an evolving scene, with each including a text timestamp. These will ultimately be distilled into an animation with a separate video editing application.

The following macro, called TestSavePicture.FCMacro, is a simplified version of the actual one, and is only attempting to save a single image, but it reproduces the problem. It is launched immediately after opening a FreeCAD project called TestPictureSave, which is a completely empty project except for a colored background that by default opens in the Part workbench. The macro simply creates a sphere, adds a text timestamp and an axis cross, and saves the image:

Code: Select all

import Part
import Draft
import os
#Get the pathname of the current working directory
pathnm = os.getcwd()
#Create an object
App.ActiveDocument.addObject("Part::Sphere","Sphere")
#Create a text timestamp for this snapshot, then size, color, and position it in the scene
_text_ = Draft.make_text(["t = 0.00 msec"], placement=FreeCAD.Vector(20.0, 20.0, 0.0))
FreeCADGui.getDocument('TestPictureSave').getObject('Text').DisplayMode = u"2D text"
FreeCADGui.getDocument('TestPictureSave').getObject('Text').FontSize = '40 mm'
FreeCADGui.getDocument('TestPictureSave').getObject('Text').TextColor = (1.00000000,1.00000000,1.00000000)
#Display the coordinate axes
Gui.ActiveDocument.ActiveView.setAxisCross(True)
App.ActiveDocument.recompute()
#Take the snapshot and save it as a jpeg file
Gui.activeDocument().activeView().saveImage(pathnm + '/PictureSavedFromMacro.jpg',1505,618,'Current')
When I execute the macro, the scene in FreeCAD develops exactly as expected. But the saved image ("PictureSavedFromMacro.jpg") shows only the blank colored background! The same thing happens if I execute the macro by copying it in its entirety into the Python console. This is where it gets even more weird...if I instead copy everything except the last line (the picture-saving command) into the Python console, the scene is again generated as expected, and if I then copy and execute that last macro line in the Python console, it saves the image correctly!

Does anyone have any idea why this macro doesn't work when executed all at once? Thanks for any info or guidance!

I am using FreeCAD 0.19, revision 24291 (15 Apr 2021), on Windows 7 64-bit.
heda
Veteran
Posts: 1348
Joined: Sat Dec 12, 2015 5:49 pm

Re: Save Picture Not Working from Macro

Post by heda »

just made a bit less repeat typing and was going to confirm that it did not save the updated scene, but it did on my linux-box, so a bit hard to imagine why it is not at your side.

Code: Select all

import Part
import Draft
import os
# Get the pathname of the current working directory
pathnm = os.path.expanduser('~/Desktop')
doc = App.ActiveDocument
gdoc = Gui.ActiveDocument
# Create an object
doc.addObject("Part::Sphere", "Sphere")
# Create a text timestamp for this snapshot, then size, color, and position it in the scene
_text_ = Draft.make_text(["t = 0.00 msec"], placement=App.Vector(20.0, 20.0, 0.0))
gdoc.getObject('Text').DisplayMode = "2D text"
gdoc.getObject('Text').FontSize = '40 mm'
gdoc.getObject('Text').TextColor = (1., 1., 1.)
# Display the coordinate axes
gdoc.ActiveView.setAxisCross(True)
doc.recompute()
# Take the snapshot and save it as a jpeg file
picpath = os.path.join(pathnm, 'PictureSavedFromMacro.jpg')
gdoc.activeView().saveImage(picpath, 1505, 618, 'Current')
but you could try

Code: Select all

Gui.updateGui()
after the doc.recompute()
SunMesa
Posts: 8
Joined: Fri May 19, 2017 3:45 pm

Re: Save Picture Not Working from Macro

Post by SunMesa »

@heda,

Thanks for your suggestion, and I like your more concise formulation of the macro. I tried it myself exactly as you wrote it, both with and without the Gui.updateGui() line, and the behavior is identical to my original... executing as a full macro results in a blank (colored background only) saved image, whereas pasting all but the last line into the Python console, followed by execution of the last (save image command) line, produces the correct saved image! Very bizarre-
openBrain
Veteran
Posts: 9034
Joined: Fri Nov 09, 2018 5:38 pm
Contact:

Re: Save Picture Not Working from Macro

Post by openBrain »

SunMesa wrote: Fri Jan 21, 2022 8:58 pm @heda,

Thanks for your suggestion, and I like your more concise formulation of the macro. I tried it myself exactly as you wrote it, both with and without the Gui.updateGui() line, and the behavior is identical to my original... executing as a full macro results in a blank (colored background only) saved image, whereas pasting all but the last line into the Python console, followed by execution of the last (save image command) line, produces the correct saved image! Very bizarre-
What does happen if you run all the macro except the last line in a macro, then only run the last line in the Python console ?
TheMarkster
Veteran
Posts: 5505
Joined: Thu Apr 05, 2018 1:53 am

Re: Save Picture Not Working from Macro

Post by TheMarkster »

This code works for me. I changed a few things, including a message showing where the file is being saved.

OS: Windows 10 Version 2009
Word size of FreeCAD: 64-bit
Version: 0.20.27129 (Git)
Build type: Release
Branch: master
Hash: fcaa698be874323ae26b20d7adf92214989c2d38
Python version: 3.8.6+
Qt version: 5.15.2
Coin version: 4.0.1
OCC version: 7.5.3
Locale: English/United States (en_US)

Code: Select all

import Part
import Draft
import os
#Get the pathname of the current working directory
pathnm = os.getcwd()
#Create an object
App.ActiveDocument.addObject("Part::Sphere","Sphere")
#Create a text timestamp for this snapshot, then size, color, and position it in the scene
_text_ = Draft.make_text(["t = 0.00 msec"], placement=FreeCAD.Vector(20.0, 20.0, 0.0))
doc = App.ActiveDocument
doc.recompute()
doc.getObject('Text').ViewObject.DisplayMode = u"2D text"
doc.getObject('Text').ViewObject.FontSize = '40 mm'
doc.getObject('Text').ViewObject.TextColor = (1.00000000,1.00000000,1.00000000)
#Display the coordinate axes
Gui.ActiveDocument.ActiveView.setAxisCross(True)
App.ActiveDocument.recompute()
#Take the snapshot and save it as a jpeg file
FreeCAD.Console.PrintMessage(f"Saving to: {pathnm + '/PictureSavedFromMacro.jpg'}\n") 
Gui.activeDocument().activeView().saveImage(pathnm + '/PictureSavedFromMacro.jpg',1505,618,'Current')
SunMesa
Posts: 8
Joined: Fri May 19, 2017 3:45 pm

Re: Save Picture Not Working from Macro

Post by SunMesa »

@openBrain,
What does happen if you run all the macro except the last line in a macro, then only run the last line in the Python console ?
It correctly saves the image (noting that, after running the truncated macro, I then have to first import os and then define pathnm in the Python console before executing the final image-saving command).

@TheMarkster
This code works for me. I changed a few things, including a message showing where the file is being saved.
Executing your version on my end produces the same result as my original macro (saved image is colored background only).

In the interim since the original reply from @heda, I've produced a macro that actually performs correctly when executed all at once, but I haven't quite deduced what the magic ingredient is... I think it may involve what the camera is actually looking at when the image-saving command is executed from the macro environment rather than from the console. I'll update again soon, at least with the working macro, even if I don't know why it works and the original doesn't.
SunMesa
Posts: 8
Joined: Fri May 19, 2017 3:45 pm

Re: Save Picture Not Working from Macro

Post by SunMesa »

Apologies for the delayed update. I can't say the issue is solved, in that I still don't completely understand the strange behavior of my original macro, or why running the final image-saving command from the console makes it work, but after some research and trial and error, I have developed macro(s) that do what I need.

I first tried eliminating the text creation in my original macro to see if that was the problem, but it didn't help. I then tried inserting the Python equivalent of the View - Fit All command into the macro, and hooray, the sphere object and axis cross were properly saved to the image file!

Thus encouraged, i re-inserted the text timestamp code back into the macro, but the saved image did not include the text. However, the text was part of the global scene, which was evident by zooming out a little in the FreeCAD display. It was here I realized that text objects are ignored by the View - Fit All command.

Anyway, to make a long story short(er), I eventually developed two different ways to do what I wanted (save both a rendered object and a text string to an image file from a macro). They include some cosmetic mods relative to the original, but the basic content/function remains unchanged.

The first is the more clunky of the two, but it's nevertheless effective:

Code: Select all

import Part
import Draft
import os
#Create a new document
FreeCAD.newDocument('TestPictureSave')
#Create an object
App.ActiveDocument.addObject("Part::Sphere","Sphere")
FreeCAD.getDocument('TestPictureSave').getObject('Sphere').Radius = '20 mm'
#Set Draw Style to Shaded
Gui.runCommand('Std_DrawStyle',5)
#Create a camera-facing text timestamp for this snapshot, then position, size, and color it
_text_ = Draft.make_text(["t = 0.00 msec"], placement=FreeCAD.Vector(50.0, 50.0, 0.0),screen=True)
FreeCADGui.getDocument('TestPictureSave').getObject('Text').FontSize = '40 mm'
FreeCADGui.getDocument('TestPictureSave').getObject('Text').TextColor = (1.00000000,1.00000000,1.00000000)
#Now create a temporary shape string and place it in the vicinity of the text above
str = "."
fontfile = "C:/Windows/winsxs/amd64_microsoft-windows-font-truetype-arial_31bf3856ad364e35_6.1.7601.17514_none_d0a9759ec3fa9e2d/arial.ttf"
ss=Draft.makeShapeString(str,fontfile)
plm=FreeCAD.Placement()
plm.Base=FreeCAD.Vector(50.0, 50.0, 0.0)
plm.Rotation.Q=(0.0, 0.0, 0.0, 1.0)
ss.Placement=plm
FreeCADGui.getDocument('TestPictureSave').getObject('ShapeString').Transparency = 100
######## TEST
App.ActiveDocument.recompute()
######## TEST
Gui.Selection.clearSelection()
#Set view to Fit All
FreeCADGui.ActiveDocument.ActiveView.fitAll()
#Remove the ShapeString
App.getDocument('TestPictureSave').removeObject('ShapeString')
#Display the coordinate axes
Gui.ActiveDocument.ActiveView.setAxisCross(True)
#Take the snapshot and save it as a jpeg file
deskpath = os.path.expanduser('~/Desktop')
picpath = os.path.join(deskpath, 'PictureSavedFromMacro.jpg')
Gui.ActiveDocument.activeView().saveImage(picpath,1505,618,'Current')
The temporary ShapeString is recognized by the Fit All command, and thus the desired timestamp text is effectively pulled into the scene on the 'coattails' of the ShapeString. So, why not just use a ShapeString for the timestamp in the first place? The reason is that the "screen=True" option in the Draft.make_text command forces the text to face the camera, and it maintains that orientation regardless of whatever other reorientations you subsequently apply to the scene... this is handy for the ultimate video-generating process this is supporting later.

The second method is more concise and general... it explicitly specifies the camera parameters before saving the image, although it requires a preliminary determination of what those camera parameters should be. These can be obtained by rendering the object/text in the FreeCAD display, then orienting/navigating until the desired scene composition is achieved, then capturing the current camera settings with the Gui.ActiveDocument.ActiveView.getCamera() command from the Python console, then inserting these data into the macro, specifically supplying them to the Gui.ActiveDocument.ActiveView.setCamera() command:

Code: Select all

import Part
import Draft
import os
FreeCAD.newDocument('TestPictureSave')
# Create an object
App.ActiveDocument.addObject("Part::Sphere", "Sphere")
FreeCAD.getDocument('TestPictureSave').getObject('Sphere').Radius = '20 mm'
#Set Draw Style to Shaded
Gui.runCommand('Std_DrawStyle',5)
# Display the coordinate axes
Gui.ActiveDocument.ActiveView.setAxisCross(True)
# Create a text timestamp, then position, size, and color it
_text_ = Draft.make_text(["t = 0.00 msec"], placement=FreeCAD.Vector(50.0, 50.0, 0.0),screen=True)
FreeCADGui.getDocument('TestPictureSave').getObject('Text').FontSize = '40 mm'
FreeCADGui.getDocument('TestPictureSave').getObject('Text').TextColor = (1.0,1.0,1.0)
App.ActiveDocument.recompute()
Gui.Selection.clearSelection()
camdat = '#Inventor V2.1 ascii\n\n\nOrthographicCamera {\n  viewportMapping ADJUST_CAMERA\n  position 8.8219976 8.4894114 100\n  orientation 0 0 1  0\n  nearDistance 42.657673\n  farDistance 120.12\n  aspectRatio 1\n  focalDistance 100\n  height 149.18246\n\n}\n'
Gui.ActiveDocument.ActiveView.setCamera(camdat)
#Take the snapshot and save it as a jpeg file
deskpath = os.path.expanduser('~/Desktop')
picpath = os.path.join(deskpath, 'PictureSavedFromMacro.jpg')
Gui.ActiveDocument.activeView().saveImage(picpath,1505,618,'Current')
So I guess I'm going to declare victory, in the sense that I have a working method, although as noted above I'm not sure I fully understand all the subtleties, particularly as to whether/when commands executed from a macro are equivalent to the same commands executed in the Python console.
Post Reply