How to get a family type preview image from a Family Document with Dynamo?

How to get a family type preview image from a Family Document with Dynamo?

I tried to place all the family types loaded in a project and creating an isolated view of each of them and then exporting as image the view but this was a huge pain and loads of memory RAM

is there nowadays a more direct way to do that? could I get an image as the family preview that we see in the properties panel of a family element in the project for example?

Have you looked into this: GetPreviewImage Method

1 Like

I do not know how to get the element type class of a family document to get this imagepreview from it

So you are looking for the thumbnail from .rfa files not the families in a project?

yes, I am getting from rfa document the ownerFamily and then the FamilyManager so I get the family types but those are not element types class, so how to find the element type from the family type of the document so I could get this: GetPreviewImage Method

If you already have .rfa files then it’s likely best to utilize the previews which Windows already has associated to the file. I am not sure how to extract them but I know it can be done via windows automations. Harder lift with Python though.

Short of that, or if you want a preview for every family type, I would load the families into the active document, extract the preview image, and save that to disc. Delete the family from the document as you go.

2 Likes

I am trying other approach: creating temprary view and export the view to image file, but do not even know if it is allowed by revit

Open the file, get the types, for each type (create the view, generate the view, export the view), close the file…

Should work. Loading into a project and exporting might perform better though.

1 Like

Hi,
a test using “olefile” python library (compatible with CPython3 and IronPython3)

get image preview family

Explanation

  • Autodesk Revit files contain COM structured storage, also known as OLE structured storage.
    In this structure we find in particular the data concerning the information of the Revit file “RevitPreview4.0”

  • With each save, Revit stores (as thumbnail )the rendering of the first 3d View found (from the current Type)

to be confirmed, but I’m not sure if it’s faster than exporting the image of a 3D view directly with the Revit API


# Load the Python Standard and DesignScript Libraries
import sys
import clr
import System
from System.IO import *
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB

#import net library
from System import Array
from System.Collections.Generic import List, IList, Dictionary


#import transactionManager and DocumentManager (RevitServices is specific to Dynamo)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
uiapp = DocumentManager.Instance.CurrentUIApplication
uidoc = uiapp.ActiveUIDocument
doc = DocumentManager.Instance.CurrentDBDocument

clr.AddReference('System.Drawing')
from System.Drawing import Image, Bitmap

clr.AddReference('Python.Included')
import Python.Included as pyInc
path_py3_lib = pyInc.Installer.EmbeddedPythonHome
sys.path.append(path_py3_lib + r'\Lib\site-packages')

import os
import os.path as op
import olefile
import base64

def get_rvt_preview(rvt_file):
    newpreview = None
    if op.exists(rvt_file):
        if olefile.isOleFile(rvt_file):
            rvt_ole = None
            try:
                # Open ole file
                rvt_ole = olefile.OleFileIO(rvt_file)
                bfi = rvt_ole.openstream("RevitPreview4.0")
                readed = bfi.read()
                # Find png signature
                readed_hex = readed.hex()
                pos = readed_hex.find('89504e470d0a1a0a')
                png_hex = readed_hex[pos:]
                data = bytes.fromhex(png_hex)
                netBytes = System.Array[System.Byte](data)
                ms = MemoryStream(netBytes)
                newpreview = Bitmap(ms)
                ms.Close()
            except Exception as ex:
                print(ex)
            finally:
                if rvt_ole is not None:
                    rvt_ole.close()
    return newpreview
    
def create3d_view(famdoc, v3d_name = "Preview"):
    #
    filterView3d = System.Predicate[System.Object](lambda x : x.ViewType  == ViewType.ThreeD and "{" not in x.Name)
    view3D = List[DB.Element](FilteredElementCollector(famdoc).OfClass(DB.View).ToElements()).Find(filterView3d)
    #
    if view3D is None:
        filterV3d = System.Predicate[System.Object](lambda x : x.ViewFamily == ViewFamily.ThreeDimensional)
        viewFamilyType = List[DB.Element](FilteredElementCollector(famdoc).OfClass(ViewFamilyType).ToElements()).Find(filterV3d)
        view3D = View3D.CreateIsometric(famdoc, viewFamilyType.Id)
    #
    view3D.SetOrientation(ViewOrientation3D(XYZ(0, 0, 0), XYZ(-1, 1, 2), XYZ(-1, 1, -1)))
    view3D.Name = "Preview"
    view3D.DetailLevel = ViewDetailLevel.Fine
    view3D.DisplayStyle = DisplayStyle.FlatColors
    return view3D
    
input_index = IN[0]
img_preview = None
curent_type_Name = None
#TransactionManager.Instance.ForceCloseTransaction()
if doc.IsFamilyDocument :
    TransactionManager.Instance.EnsureInTransaction(doc)
    fam = doc.FamilyManager 
    lstTypes = sorted(fam.Types, key = lambda x : x.Name)
    for idx, f_type in enumerate(lstTypes):
        if idx == input_index:
            fam.CurrentType  = f_type
            curent_type_Name = f_type.Name
            break
    view3D = create3d_view(doc)
    TransactionManager.Instance.TransactionTaskDone()
    TransactionManager.Instance.ForceCloseTransaction()
    
    doc.Save()
    img_preview = get_rvt_preview(doc.PathName )
    
    OUT = curent_type_Name, img_preview
6 Likes

Certainly faster; the issue is ‘do you need the image from one type or all types’.

1 Like

I would like all the types and code compatible with ironpython 2.7 if possible because wanting to run script from Revit 2019. But let say just getting the predefined 3d view as image from a document family opened in background looks a challenge i am still trying to get it done.

Obligatory reminder that 2019 isn’t supported. Please don’t become another cautionary tale for the information security world - upgrade your stuff to keep everyone safe. And if you aren’t in position to make such calls on upgrading, spread the word about why it needs to be done to those who are making the decisions (and their superiors / clients) to influence the best practices. First time you tell the client ‘you are putting us all at risk for an information breach’ they tend to change their tune.

Since all you want to do is extract images, any version will do, and 2024 will likely upgrade, open, and export faster than 2019 will open and export, so keep that in mind as you go. That said I still think importing the family into a project is the way to go.

2 Likes

For IronPython2.7 I think this should be feasible either:

3 Likes

I understand olefile is a package that must be downloaded and installed manually in computer and the second option is a way to install packages of python automatically ? I find very interesting the second :thinking: :eyes:

I believe by reading your blog and resources that the pip installer works in IronPython 2.7.5 which it is already installed with Dynamo for Revit ?

I just tested there is a problem with pip + ironpython2.7 + olefile, try to copy paste the source source code into a py file and import it into Dynamo Ironpython2.7 as a module (not tested)

1 Like

I am trying to get the ironpython 2.7.11 version as you indicate in the blog post downloading from here Release IronPython 2.7.12 ¡ IronLanguages/ironpython2 ¡ GitHub. I tried to install it but it indicates I have already installed a later version but what I got is version 2.7.3.
image
I uninstalled ironpython 2.7.3 and then I can install the newer version, I can see pip is there which is great:
image
but it has been installed in C:\Program Files and not in C:\Program Files (x86) as was the other version
I tried to install the library olefile and mpmath and did not work:

Hi @RubenVivancos

I think the pip install mechanism is no longer compatible with IronPython2.7. support was probably dropped in favor of IronPython3 (where it works perfectly)

IronPython2.7 vs IronPython3 pip command

1 Like

Try installing from source with this:

ipy -X:Frames -m pip install https://github.com/decalage2/olefile/zipball/master

Then add the path to the location of the olefile folder for example

C:\Users\<username>\AppData\Local\Programs\Python\IronPython.2.7.11\Lib\site-packages

You can find the executable by running python in a terminal

> ipy
>>> import sys
>>> sys.executable
'C:\\Users\\...
4 Likes

@Mike.Buttery nice workaround!, thanks !

1 Like

interesting, I tried and does not work, not sure why. I installed ironpython 2.7.12 :


I tried also this script in dynamo:

import sys
import clr
clr.AddReference('System')
from System.Diagnostics import Process

def run_command(command):
    process = Process()
    process.StartInfo.UseShellExecute = False
    process.StartInfo.RedirectStandardOutput = True
    process.StartInfo.FileName = r"C:\Program Files\IronPython 2.7\ipy.exe"
    process.StartInfo.Arguments = command
    process.Start()
    process.WaitForExit()
    output = process.StandardOutput.ReadToEnd()
    return output.strip()

# Construct the pip install command for olefile from GitHub
pip_command = " -X:Frames -m pip install https://github.com/decalage2/olefile/zipball/master"

# Run the command and get the result
installation_result = run_command(pip_command)

# Output the result to Dynamo
OUT = installation_result

although I dowloaded the library package from the website and extracted in my computer and added total control administraction rights in the folder site-packages of the ironpython installation folder and I believe it has been installed with this other code but I cannot load the library later in a script:

import sys
import clr
clr.AddReference('System')
from System.Diagnostics import Process
from System.IO import Path

# Function to run a command in the IronPython subprocess
def run_command(command, working_directory):
    process = Process()
    process.StartInfo.UseShellExecute = False
    process.StartInfo.RedirectStandardOutput = True
    process.StartInfo.RedirectStandardError = True
    process.StartInfo.FileName = r"C:\Program Files\IronPython 2.7\ipy.exe"
    process.StartInfo.Arguments = command
    process.StartInfo.WorkingDirectory = working_directory
    process.Start()
    process.WaitForExit()
    output = process.StandardOutput.ReadToEnd()
    errors = process.StandardError.ReadToEnd()
    return output + errors

# The directory where the 'setup.py' for 'olefile' is located
# Replace this with the actual path where you have extracted the 'olefile' package
package_directory = r"B:\Descargas\olefile-0.46"

# The command to run the 'setup.py' script
setup_command = "setup.py install"

# Run the command and get the result
installation_result = run_command(setup_command, package_directory)

# Output the result to Dynamo
OUT = installation_result

the result says:

[0] running install
running bdist_egg
running egg_info
writing olefile.egg-info\PKG-INFO
writing top-level names to olefile.egg-info\top_level.txt
writing dependency_links to olefile.egg-info\dependency_links.txt
reading manifest file ‘olefile.egg-info\SOURCES.txt’
reading manifest template ‘MANIFEST.in’
writing manifest file ‘olefile.egg-info\SOURCES.txt’
installing library code to build\bdist.cli\egg
running install_lib
running build_py
creating build\bdist.cli\egg
creating build\bdist.cli\egg\olefile
copying build\lib\olefile\olefile.py → build\bdist.cli\egg\olefile
copying build\lib\olefile_init_.py → build\bdist.cli\egg\olefile
creating build\bdist.cli\egg\EGG-INFO
copying olefile.egg-info\PKG-INFO → build\bdist.cli\egg\EGG-INFO
copying olefile.egg-info\SOURCES.txt → build\bdist.cli\egg\EGG-INFO
copying olefile.egg-info\dependency_links.txt → build\bdist.cli\egg\EGG-INFO
copying olefile.egg-info\top_level.txt → build\bdist.cli\egg\EGG-INFO
creating ‘dist\olefile-0.46-py2.7.egg’ and adding ‘build\bdist.cli\egg’ to it
removing ‘build\bdist.cli\egg’ (and everything under it)
Processing olefile-0.46-py2.7.egg
removing ‘c:\program files\ironpython 2.7\lib\site-packages\olefile-0.46-py2.7.egg’ (and everything under it)
creating c:\program files\ironpython 2.7\lib\site-packages\olefile-0.46-py2.7.egg
Extracting olefile-0.46-py2.7.egg to c:\program files\ironpython 2.7\lib\site-packages
olefile 0.46 is already the active version in easy-install.pth

Installed c:\program files\ironpython 2.7\lib\site-packages\olefile-0.46-py2.7.egg
Processing dependencies for olefile==0.46
Finished processing dependencies for olefile==0.46
no previously-included directories found matching ‘doc_build’
warning: build_py: byte-compiling is disabled, skipping.

warning: install_lib: byte-compiling is disabled, skipping.

zip_safe flag not set; analyzing archive contents…
Unable to analyze compiled code on this platform.
Please ask the author to include a ‘zip_safe’ setting (either True or False) in the package’s setup.py
[1] Failed to import olefile: No module named olefile

I tried to import the library and the output tell me there is no module named future?!!! this is asking me for other library wow. I used this script:

[0] Failed to import olefile: No module named future
[1] [0] .
[1] [1] C:\Program Files\IronPython 2.7\Lib\site-packages\olefile-0.46-py2.7.egg
[1] [2] C:\Program Files\IronPython 2.7\Lib\site-packages\olefile-0.46-py2.7.egg\olefile

import sys
import clr
clr.AddReference('System.IO')
from System.IO import Directory, Path

# Ensure that IronPython can see the olefile module by adding it to sys.path
egg_path = r'C:\Program Files\IronPython 2.7\Lib\site-packages\olefile-0.46-py2.7.egg'
olefile_module_path = Path.Combine(egg_path, 'olefile')

# Append both the egg directory and the olefile module directory to sys.path
if egg_path not in sys.path:
    sys.path.append(egg_path)
if olefile_module_path not in sys.path:
    sys.path.append(olefile_module_path)

# Now try to import the olefile module
try:
    import olefile
    import_message = "Successfully imported olefile!"
except Exception as e:
    import_message = "Failed to import olefile: {0}".format(e.message)

# Output the result to Dynamo
OUT = import_message, sys.path  # Output sys.path for debugging purposes

why so complicated?