What's new

Add icon image from the Alibre file to the parts list

stepalibre

Alibre Super User
The program processes what's included in the assembly. You can try GenAI with the current script, I can help later today if that doesn't work. Having a well thought-out features/requirements list goes a long way.
 

Helioc

Member
Python:
# ------------------------------------------------------------
# Assembly and Part Thumbnail Generator (IronPython 2.7)
# ------------------------------------------------------------
# This script:
#   1. Prompts the user for thumbnail size and output folder.
#   2. Detects whether the current document is an Assembly or Part.
#   3. Explores the assembly/subassemblies to:
#       - Count identical parts (group by normalized name).
#       - Save part/subassembly thumbnails as .jpg (unique only).
#   4. Creates an Excel workbook (Images_NET.xlsx) that embeds
#      all images found in the chosen folder, placing each image
#      inside a cell sized 300×300 pixels (approx).
# ------------------------------------------------------------

import clr
import sys
import re
import os
from collections import defaultdict

# Add references for .NET interop with Excel
clr.AddReference("System")
clr.AddReference("Microsoft.Office.Interop.Excel")

from Microsoft.Office.Interop import Excel

# Minimal MsoTriState "enum" to avoid referencing Microsoft.Office.Core
class MsoTriState:
    msoFalse = 0
    msoTrue = -1

# ------------------------------------------------------------
# STEP 1: Prompt user for inputs (thumbnail size + save folder)
# ------------------------------------------------------------

Win = Windows()  # Provided by your CAD/automation environment
Option = [
    ['Thumbnail Size', WindowsInputTypes.Real, 100],
    ['Save Folder',   WindowsInputTypes.Folder, None],
]
Values = Win.OptionsDialog("Image from Assembly", Option, 100)

if Values is None:
    sys.exit()

dimension_thumb = Values[0]
save_path = Values[1]

# ------------------------------------------------------------
# STEP 2: Detect if we have an Assembly or a Part
# ------------------------------------------------------------

def detect_type():
    """
    Detect if the object is an assembly or a part based on common attributes.
    """
    try:
        if hasattr(CurrentAssembly(), 'Parts'):
            obj = CurrentAssembly()
            obj_type = 'Assembly'
        else:
            raise Exception("Invalid assembly")
    except:
        if hasattr(CurrentPart(), 'Name'):
            obj = CurrentPart()
            obj_type = 'Part'
        else:
            raise Exception("Invalid part")
    return obj, obj_type

# ------------------------------------------------------------
# STEP 3: Generate thumbnails for each Part/Subassembly
# ------------------------------------------------------------

def normalize_part_name(part_name):
    """
    Remove unique identifiers like <1>, <2>, etc. from part names.
    """
    return re.sub(r"<\d+>", "", part_name)

def clean_file_name(name):
    """
    Remove invalid characters for Windows file names.
    """
    invalid_chars = r'<>:"/\\|?*'
    return re.sub('[{}]'.format(re.escape(invalid_chars)), '_', name)

def ListPartsInAssembly(assembly, save_path):
    """
    Explore the assembly (and subassemblies), group identical parts,
    count quantities, and save unique thumbnails for parts/subassemblies.
    """
    parts_count = defaultdict(int)
    saved_thumbnails = set()  # Track saved thumbnails to avoid duplicates

    def process_assembly(asm):
        for part in asm.Parts:
            part_name = part.Name if hasattr(part, 'Name') else str(part)
            normalized_name = normalize_part_name(part_name)
            cleaned_name = clean_file_name(normalized_name)

            # Ensure thumbnails are unique by combining normalized name and geometry hash
            geometry_hash = getattr(part, 'GeometryHash', id(part))  # Use a hash if available
            unique_identifier = (cleaned_name, geometry_hash)

            if unique_identifier not in saved_thumbnails:
                thumbnail_path = os.path.join(save_path, cleaned_name + '.jpg')
                part.SaveThumbnail(
                    thumbnail_path,
                    dimension_thumb,
                    dimension_thumb
                )
                saved_thumbnails.add(unique_identifier)

            parts_count[normalized_name] += 1

        for sub_asm in asm.SubAssemblies:
            sub_asm_name = sub_asm.Name if hasattr(sub_asm, 'Name') else str(sub_asm)
            normalized_name = normalize_part_name(sub_asm_name)
            cleaned_name = clean_file_name(normalized_name)

            # Ensure thumbnails are unique by combining normalized name and geometry hash
            geometry_hash = getattr(sub_asm, 'GeometryHash', id(sub_asm))  # Use a hash if available
            unique_identifier = (cleaned_name, geometry_hash)

            if unique_identifier not in saved_thumbnails:
                thumbnail_path = os.path.join(save_path, cleaned_name + '.jpg')
                sub_asm.SaveThumbnail(
                    thumbnail_path,
                    dimension_thumb,
                    dimension_thumb
                )
                saved_thumbnails.add(unique_identifier)

            # Recurse into subassembly
            process_assembly(sub_asm)

    process_assembly(assembly)

    # Print consolidated part list
    print("\nConsolidated Part List:")
    for part, quantity in parts_count.items():
        print("- {}: {}".format(part, quantity))

# ------------------------------------------------------------
# STEP 4: Create Excel workbook and embed images with 300×300 cells
# ------------------------------------------------------------

def GenerateExcelWithImagesNET(image_directory):
    """
    Creates an Excel workbook that lists all images in `image_directory`
    in two columns:
        1. Image Name
        2. Embedded Image (300×300 px, approx)
    """
    # Start Excel
    excel = Excel.ApplicationClass()
    excel.Visible = False  # Change to True if you want to see Excel UI

    # Create a new Workbook (which has one blank worksheet by default)
    workbook = excel.Workbooks.Add()
    sheet = workbook.Worksheets[1]

    # Header cells
    sheet.Cells[1, 1].Value2 = "Image Name"
    sheet.Cells[1, 2].Value2 = "Image"

    # Convert 300 px to points (approx: 1 px ≈ 0.75 pt at 96 DPI)
    cell_size_points = 225

    # Set entire column B's width so it can hold ~300 px
    sheet.Columns("B").ColumnWidth = 42.5

    row = 2
    valid_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.gif')

    for filename in os.listdir(image_directory):
        if filename.lower().endswith(valid_extensions):
            image_path = os.path.join(image_directory, filename)

            # Write file name in column A
            sheet.Cells[row, 1].Value2 = filename

            # Set the row height to match 300 px (~225 points)
            sheet.Rows(row).RowHeight = cell_size_points

            # Calculate the cell's top-left in points
            left = sheet.Cells[row, 2].Left
            top = sheet.Cells[row, 2].Top

            # Insert the image as a shape that fits ~300×300 px
            picture = sheet.Shapes.AddPicture(
                Filename=image_path,
                LinkToFile=MsoTriState.msoFalse,      # Do not link to file
                SaveWithDocument=MsoTriState.msoTrue, # Embed in the workbook
                Left=left,
                Top=top,
                Width=cell_size_points,
                Height=cell_size_points
            )

            row += 1

    # Auto-fit Column A for better readability
    sheet.Columns("A").AutoFit()

    # Save workbook in the same folder
    excel_file_path = os.path.join(image_directory, "Images_NET.xlsx")
    workbook.SaveAs(excel_file_path)
    workbook.Close(False)
    excel.Quit()

    print("\nExcel file with images created at: {}".format(excel_file_path))

# ------------------------------------------------------------
# MAIN EXECUTION
# ------------------------------------------------------------

def Main():
    obj, obj_type = detect_type()

    if obj_type == 'Part':
        # Generate a single thumbnail for this part
        print("Current document is a Part; generating thumbnail.")
        cleaned_name = clean_file_name(obj.Name)
        obj.SaveThumbnail(
            os.path.join(save_path, cleaned_name + '.jpg'),
            dimension_thumb,
            dimension_thumb
        )
    else:
        # Treat it as an Assembly
        print("Current document is an Assembly; generating thumbnails.")
        ListPartsInAssembly(obj, save_path)

    # Finally, create the Excel workbook with 300×300 embedded images
    GenerateExcelWithImagesNET(save_path)

Main()
 

albie0803

Alibre Super User
So I have tweaked this slightly (using GPT):

to add a quantity column by adding a semicolon ; and quantity to the filename and then read and then strip out this info, leaving just the partname.
eg
Outer Flange;4.jpg
Col A = Name = Outer Flange
Col B = Picture
Col C = Qty = 4

Python:
import clr
import sys
import re
import os
from collections import defaultdict

# Add references for .NET interop with Excel
clr.AddReference("System")
clr.AddReference("Microsoft.Office.Interop.Excel")

from Microsoft.Office.Interop import Excel

# Minimal MsoTriState "enum" to avoid referencing Microsoft.Office.Core
class MsoTriState:
    msoFalse = 0
    msoTrue = -1

# STEP 1: Prompt user for inputs (thumbnail size + save folder)
Win = Windows()  # Provided by your CAD/automation environment
Option = [
    ['Thumbnail Size', WindowsInputTypes.Real, 100],
    ['Save Folder',   WindowsInputTypes.Folder, None],
]
Values = Win.OptionsDialog("Image from Assembly", Option, 100)

if Values is None:
    sys.exit()

dimension_thumb = Values[0]
save_path = Values[1]

# STEP 2: Detect if we have an Assembly or a Part
def detect_type():
    try:
        if hasattr(CurrentAssembly(), 'Parts'):
            obj = CurrentAssembly()
            obj_type = 'Assembly'
        else:
            raise Exception("Invalid assembly")
    except:
        if hasattr(CurrentPart(), 'Name'):
            obj = CurrentPart()
            obj_type = 'Part'
        else:
            raise Exception("Invalid part")
    return obj, obj_type

# STEP 3: Count Parts
def normalize_part_name(part_name):
    return re.sub(r"<\d+>", "", part_name)

def clean_file_name(name):
    invalid_chars = r'<>:"/\\|?*'
    cleaned_name = re.sub('[{}]'.format(re.escape(invalid_chars)), '_', name)
    return cleaned_name + ';'

def count_parts_in_assembly(assembly):
    parts_count = defaultdict(int)

    def process_assembly(asm):
        for part in asm.Parts:
            part_name = part.Name if hasattr(part, 'Name') else str(part)
            normalized_name = normalize_part_name(part_name)
            parts_count[normalized_name] += 1

        for sub_asm in asm.SubAssemblies:
            process_assembly(sub_asm)

    process_assembly(assembly)
    return parts_count

# STEP 4: Generate Thumbnails
def generate_thumbnails_with_quantities(assembly, parts_count, save_path):
    saved_thumbnails = set()

    def process_assembly(asm):
        for part in asm.Parts:
            part_name = part.Name if hasattr(part, 'Name') else str(part)
            normalized_name = normalize_part_name(part_name)
            cleaned_name = clean_file_name(normalized_name)
            quantity = parts_count[normalized_name]
            cleaned_name_with_quantity = "{}{}".format(cleaned_name, quantity)

            geometry_hash = getattr(part, 'GeometryHash', id(part))
            unique_identifier = (cleaned_name_with_quantity, geometry_hash)

            if unique_identifier not in saved_thumbnails:
                thumbnail_path = os.path.join(save_path, cleaned_name_with_quantity + '.jpg')
                part.SaveThumbnail(thumbnail_path, dimension_thumb, dimension_thumb)
                saved_thumbnails.add(unique_identifier)

        for sub_asm in asm.SubAssemblies:
            sub_asm_name = sub_asm.Name if hasattr(sub_asm, 'Name') else str(sub_asm)
            normalized_name = normalize_part_name(sub_asm_name)
            cleaned_name = clean_file_name(normalized_name)
            quantity = parts_count[normalized_name]
            cleaned_name_with_quantity = "{}{}".format(cleaned_name, quantity)

            geometry_hash = getattr(sub_asm, 'GeometryHash', id(sub_asm))
            unique_identifier = (cleaned_name_with_quantity, geometry_hash)

            if unique_identifier not in saved_thumbnails:
                thumbnail_path = os.path.join(save_path, cleaned_name_with_quantity + '.jpg')
                sub_asm.SaveThumbnail(thumbnail_path, dimension_thumb, dimension_thumb)
                saved_thumbnails.add(unique_identifier)

            process_assembly(sub_asm)

    process_assembly(assembly)

# STEP 5: Create Excel workbook and embed images with 300×300 cells
def GenerateExcelWithImagesNET(image_directory):
    excel = Excel.ApplicationClass()
    excel.Visible = False

    workbook = excel.Workbooks.Add()
    sheet = workbook.Worksheets[1]

    sheet.Cells[1, 1].Value2 = "Part Name"
    sheet.Cells[1, 2].Value2 = "Image"
    sheet.Cells[1, 3].Value2 = "Quantity"

    cell_size_points = 75
    sheet.Columns("B").ColumnWidth = 15

    row = 2
    valid_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.gif')

    for filename in os.listdir(image_directory):
        if filename.lower().endswith(valid_extensions):
            image_path = os.path.join(image_directory, filename)

            # Extract the part of the filename before the semicolon
            file_name_before_semicolon = filename.split(';')[0]

            # Extract the part of the filename after the semicolon
            part_after_semicolon = filename.split(';')[1]

            # Remove the file extension from the part after the semicolon
            numeric_value_str = os.path.splitext(part_after_semicolon)[0]

            # Convert the numeric value to an integer
            numeric_value = int(numeric_value_str)

            # Write file name in column A
            sheet.Cells[row, 1].Value2 = file_name_before_semicolon

            # Write numeric value in column C (if needed)
            sheet.Cells[row, 3].Value2 = numeric_value

            # Set the row height to match 300 px (~225 points)
            sheet.Rows[row].RowHeight = cell_size_points

            # Calculate the cell's top-left in points
            left = sheet.Cells[row, 2].Left
            top = sheet.Cells[row, 2].Top

            # Insert the image as a shape that fits ~300×300 px
            picture = sheet.Shapes.AddPicture(
                Filename=image_path,
                LinkToFile=MsoTriState.msoFalse,
                SaveWithDocument=MsoTriState.msoTrue,
                Left=left,
                Top=top,
                Width=cell_size_points,
                Height=cell_size_points
            )

            row += 1

    sheet.Columns("A").AutoFit()

    excel_file_path = os.path.join(image_directory, "Images_NET.xlsx")
    workbook.SaveAs(excel_file_path)
    workbook.Close(False)
    excel.Quit()

    print("\nExcel file with images created at: {}".format(excel_file_path))

# MAIN EXECUTION
def Main():
    obj, obj_type = detect_type()

    if obj_type == 'Part':
        print("Current document is a Part; generating thumbnail.")
        cleaned_name = clean_file_name(obj.Name)
        obj.SaveThumbnail(os.path.join(save_path, cleaned_name + ';1.jpg'), dimension_thumb, dimension_thumb)
    else:
        print("Current document is an Assembly; counting parts.")
        parts_count = count_parts_in_assembly(obj)

        print("Generating thumbnails with quantities.")
        generate_thumbnails_with_quantities(obj, parts_count, save_path)

    GenerateExcelWithImagesNET(save_path)

Main()
 
Last edited:

albie0803

Alibre Super User
So I've modified the above code so that the excel file name is 'Document Number' 'Assembly Name'.xlsx eg "H-2172 Counter Assembly.xlsx"

But I've reached my limit. I want to pull the drawing number for each part and attach it to the thumbnail name and then strip it out later to add into the excel file.
I can do the strip out bit but I can't work out where in the code to store the info and then retrieve it when the thumbnail is created.
Any help would be appreciated.

It may also need a default value if an empty field is not acceptable as fasteners, bearings, etc most likely won't have a drawing number.

drawing_number = part.DocumentNumber

Current Code:
Python:
import clr
import sys
import re
import os
from collections import defaultdict

# Add references for .NET interop with Excel
clr.AddReference("System")
clr.AddReference("Microsoft.Office.Interop.Excel")

from Microsoft.Office.Interop import Excel

# Minimal MsoTriState "enum" to avoid referencing Microsoft.Office.Core
class MsoTriState:
    msoFalse = 0
    msoTrue = -1

# STEP 1: Prompt user for inputs (thumbnail size + save folder)
Win = Windows()  # Provided by your CAD/automation environment
Option = [
    ['Thumbnail Size', WindowsInputTypes.Real, 100],
    ['Save Folder',   WindowsInputTypes.Folder, None],
]
Values = Win.OptionsDialog("Image from Assembly", Option, 100)

if Values is None:
    sys.exit()

dimension_thumb = Values[0]
save_path = Values[1]

# STEP 2: Detect if we have an Assembly or a Part
def detect_type():
    try:
        if hasattr(CurrentAssembly(), 'Parts'):
            obj = CurrentAssembly()
            obj_type = 'Assembly'
        else:
            raise Exception("Invalid assembly")
    except:
        if hasattr(CurrentPart(), 'Name'):
            obj = CurrentPart()
            obj_type = 'Part'
        else:
            raise Exception("Invalid part")
    return obj, obj_type

# STEP 3: Count Parts
def normalize_part_name(part_name):
    return re.sub(r"<\d+>", "", part_name)

def clean_file_name(name):
    invalid_chars = r'<>:"/\\|?*'
    cleaned_name = re.sub('[{}]'.format(re.escape(invalid_chars)), '_', name)
    return cleaned_name + ';'

def count_parts_in_assembly(assembly):
    parts_count = defaultdict(int)

    def process_assembly(asm):
        global Assembly_Name
        global Assembly_DNum
        Assembly_Name = asm.Name
        Assembly_DNum = asm.DocumentNumber
        for part in asm.Parts:
            part_name = part.Name if hasattr(part, 'Name') else str(part)
            normalized_name = normalize_part_name(part_name)
            parts_count[normalized_name] += 1

        for sub_asm in asm.SubAssemblies:
            process_assembly(sub_asm)

    process_assembly(assembly)
    return parts_count

# STEP 4: Generate Thumbnails
def generate_thumbnails_with_quantities(assembly, parts_count, save_path):
    saved_thumbnails = set()

    def process_assembly(asm):
        for part in asm.Parts:
            part_name = part.Name if hasattr(part, 'Name') else str(part)
            normalized_name = normalize_part_name(part_name)
            cleaned_name = clean_file_name(normalized_name)
            quantity = parts_count[normalized_name]
            cleaned_name_with_quantity = "{}{}".format(cleaned_name, quantity)

            geometry_hash = getattr(part, 'GeometryHash', id(part))
            unique_identifier = (cleaned_name_with_quantity, geometry_hash)

            if unique_identifier not in saved_thumbnails:
                thumbnail_path = os.path.join(save_path, cleaned_name_with_quantity + '.jpg')
                part.SaveThumbnail(thumbnail_path, dimension_thumb, dimension_thumb)
                saved_thumbnails.add(unique_identifier)

        for sub_asm in asm.SubAssemblies:
            sub_asm_name = sub_asm.Name if hasattr(sub_asm, 'Name') else str(sub_asm)
            normalized_name = normalize_part_name(sub_asm_name)
            cleaned_name = clean_file_name(normalized_name)
            quantity = parts_count[normalized_name]
            cleaned_name_with_quantity = "{}{}".format(cleaned_name, quantity)

            geometry_hash = getattr(sub_asm, 'GeometryHash', id(sub_asm))
            unique_identifier = (cleaned_name_with_quantity, geometry_hash)

            if unique_identifier not in saved_thumbnails:
                thumbnail_path = os.path.join(save_path, cleaned_name_with_quantity + '.jpg')
                sub_asm.SaveThumbnail(thumbnail_path, dimension_thumb, dimension_thumb)
                saved_thumbnails.add(unique_identifier)

            process_assembly(sub_asm)

    process_assembly(assembly)

# STEP 5: Create Excel workbook and embed images with 300×300 cells
def GenerateExcelWithImagesNET(image_directory):
    excel = Excel.ApplicationClass()
    excel.Visible = False

    workbook = excel.Workbooks.Add()
    sheet = workbook.Worksheets[1]

    sheet.Cells[1, 1].Value2 = "Part Name"
    sheet.Cells[1, 2].Value2 = "Image"
    sheet.Cells[1, 3].Value2 = "Quantity"
    sheet.Cells[1, 4].Value2 = "Purchased"
    sheet.Cells[1, 5].Value2 = "Ready"

    cell_size_points = 75
    sheet.Columns("B").ColumnWidth = 15

    row = 2
    valid_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.gif')

    for filename in os.listdir(image_directory):
        if filename.lower().endswith(valid_extensions):
            image_path = os.path.join(image_directory, filename)

            # Extract the part of the filename before the semicolon
            file_name_before_semicolon = filename.split(';')[0]

            # Extract the part of the filename after the semicolon
            part_after_semicolon = filename.split(';')[1]

            # Remove the file extension from the part after the semicolon
            numeric_value_str = os.path.splitext(part_after_semicolon)[0]

            # Convert the numeric value to an integer
            numeric_value = int(numeric_value_str)

            # Write file name in column A
            sheet.Cells[row, 1].Value2 = file_name_before_semicolon

            # Write numeric value in column C (if needed)
            sheet.Cells[row, 3].Value2 = numeric_value

            # Set the row height to match 300 px (~225 points)
            sheet.Rows[row].RowHeight = cell_size_points

            # Calculate the cell's top-left in points
            left = sheet.Cells[row, 2].Left
            top = sheet.Cells[row, 2].Top

            # Insert the image as a shape that fits ~300×300 px
            picture = sheet.Shapes.AddPicture(
                Filename=image_path,
                LinkToFile=MsoTriState.msoFalse,
                SaveWithDocument=MsoTriState.msoTrue,
                Left=left,
                Top=top,
                Width=cell_size_points,
                Height=cell_size_points
            )

            row += 1

    sheet.Columns("A").AutoFit()

    excel_file_path = os.path.join(image_directory, "{} {}.xlsx".format(Assembly_DNum, Assembly_Name))
    workbook.SaveAs(excel_file_path)
    workbook.Close(False)
    excel.Quit()

    print("\nExcel file with images created at: {}".format(excel_file_path))

# MAIN EXECUTION
def Main():
    obj, obj_type = detect_type()

    if obj_type == 'Part':
        print("Current document is a Part; generating thumbnail.")
        cleaned_name = clean_file_name(obj.Name)
        obj.SaveThumbnail(os.path.join(save_path, cleaned_name + ';1.jpg'), dimension_thumb, dimension_thumb)
    else:
        print("Current document is an Assembly; counting parts.")
        parts_count = count_parts_in_assembly(obj)

        print("Generating thumbnails with quantities.")
        generate_thumbnails_with_quantities(obj, parts_count, save_path)

    GenerateExcelWithImagesNET(save_path)

Main()
 
Last edited:

stepalibre

Alibre Super User
So I've modified the above code so that the excel file name is 'Document Number' 'Assembly Name'.xlsx eg "H-2172 Counter Assembly.xlsx"

But I've reached my limit. I want to pull the drawing number for each part and attach it to the thumbnail name and then strip it out later to add into the excel file.
I can do the strip out bit but I can't work out where in the code to store the info and then retrieve it when the thumbnail is created.
Any help would be appreciated.

It may also need a default value if an empty field is not acceptable as fasteners, bearings, etc most likely won't have a drawing number.

drawing_number = asm.DocumentNumber

Current Code:
Python:
import clr
import sys
import re
import os
from collections import defaultdict

# Add references for .NET interop with Excel
clr.AddReference("System")
clr.AddReference("Microsoft.Office.Interop.Excel")

from Microsoft.Office.Interop import Excel

# Minimal MsoTriState "enum" to avoid referencing Microsoft.Office.Core
class MsoTriState:
    msoFalse = 0
    msoTrue = -1

# STEP 1: Prompt user for inputs (thumbnail size + save folder)
Win = Windows()  # Provided by your CAD/automation environment
Option = [
    ['Thumbnail Size', WindowsInputTypes.Real, 100],
    ['Save Folder',   WindowsInputTypes.Folder, None],
]
Values = Win.OptionsDialog("Image from Assembly", Option, 100)

if Values is None:
    sys.exit()

dimension_thumb = Values[0]
save_path = Values[1]

# STEP 2: Detect if we have an Assembly or a Part
def detect_type():
    try:
        if hasattr(CurrentAssembly(), 'Parts'):
            obj = CurrentAssembly()
            obj_type = 'Assembly'
        else:
            raise Exception("Invalid assembly")
    except:
        if hasattr(CurrentPart(), 'Name'):
            obj = CurrentPart()
            obj_type = 'Part'
        else:
            raise Exception("Invalid part")
    return obj, obj_type

# STEP 3: Count Parts
def normalize_part_name(part_name):
    return re.sub(r"<\d+>", "", part_name)

def clean_file_name(name):
    invalid_chars = r'<>:"/\\|?*'
    cleaned_name = re.sub('[{}]'.format(re.escape(invalid_chars)), '_', name)
    return cleaned_name + ';'

def count_parts_in_assembly(assembly):
    parts_count = defaultdict(int)

    def process_assembly(asm):
        global Assembly_Name
        global Assembly_DNum
        Assembly_Name = asm.Name
        Assembly_DNum = asm.DocumentNumber
        for part in asm.Parts:
            part_name = part.Name if hasattr(part, 'Name') else str(part)
            normalized_name = normalize_part_name(part_name)
            parts_count[normalized_name] += 1

        for sub_asm in asm.SubAssemblies:
            process_assembly(sub_asm)

    process_assembly(assembly)
    return parts_count

# STEP 4: Generate Thumbnails
def generate_thumbnails_with_quantities(assembly, parts_count, save_path):
    saved_thumbnails = set()

    def process_assembly(asm):
        for part in asm.Parts:
            part_name = part.Name if hasattr(part, 'Name') else str(part)
            normalized_name = normalize_part_name(part_name)
            cleaned_name = clean_file_name(normalized_name)
            quantity = parts_count[normalized_name]
            cleaned_name_with_quantity = "{}{}".format(cleaned_name, quantity)

            geometry_hash = getattr(part, 'GeometryHash', id(part))
            unique_identifier = (cleaned_name_with_quantity, geometry_hash)

            if unique_identifier not in saved_thumbnails:
                thumbnail_path = os.path.join(save_path, cleaned_name_with_quantity + '.jpg')
                part.SaveThumbnail(thumbnail_path, dimension_thumb, dimension_thumb)
                saved_thumbnails.add(unique_identifier)

        for sub_asm in asm.SubAssemblies:
            sub_asm_name = sub_asm.Name if hasattr(sub_asm, 'Name') else str(sub_asm)
            normalized_name = normalize_part_name(sub_asm_name)
            cleaned_name = clean_file_name(normalized_name)
            quantity = parts_count[normalized_name]
            cleaned_name_with_quantity = "{}{}".format(cleaned_name, quantity)

            geometry_hash = getattr(sub_asm, 'GeometryHash', id(sub_asm))
            unique_identifier = (cleaned_name_with_quantity, geometry_hash)

            if unique_identifier not in saved_thumbnails:
                thumbnail_path = os.path.join(save_path, cleaned_name_with_quantity + '.jpg')
                sub_asm.SaveThumbnail(thumbnail_path, dimension_thumb, dimension_thumb)
                saved_thumbnails.add(unique_identifier)

            process_assembly(sub_asm)

    process_assembly(assembly)

# STEP 5: Create Excel workbook and embed images with 300×300 cells
def GenerateExcelWithImagesNET(image_directory):
    excel = Excel.ApplicationClass()
    excel.Visible = False

    workbook = excel.Workbooks.Add()
    sheet = workbook.Worksheets[1]

    sheet.Cells[1, 1].Value2 = "Part Name"
    sheet.Cells[1, 2].Value2 = "Image"
    sheet.Cells[1, 3].Value2 = "Quantity"
    sheet.Cells[1, 4].Value2 = "Purchased"
    sheet.Cells[1, 5].Value2 = "Ready"

    cell_size_points = 75
    sheet.Columns("B").ColumnWidth = 15

    row = 2
    valid_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.gif')

    for filename in os.listdir(image_directory):
        if filename.lower().endswith(valid_extensions):
            image_path = os.path.join(image_directory, filename)

            # Extract the part of the filename before the semicolon
            file_name_before_semicolon = filename.split(';')[0]

            # Extract the part of the filename after the semicolon
            part_after_semicolon = filename.split(';')[1]

            # Remove the file extension from the part after the semicolon
            numeric_value_str = os.path.splitext(part_after_semicolon)[0]

            # Convert the numeric value to an integer
            numeric_value = int(numeric_value_str)

            # Write file name in column A
            sheet.Cells[row, 1].Value2 = file_name_before_semicolon

            # Write numeric value in column C (if needed)
            sheet.Cells[row, 3].Value2 = numeric_value

            # Set the row height to match 300 px (~225 points)
            sheet.Rows[row].RowHeight = cell_size_points

            # Calculate the cell's top-left in points
            left = sheet.Cells[row, 2].Left
            top = sheet.Cells[row, 2].Top

            # Insert the image as a shape that fits ~300×300 px
            picture = sheet.Shapes.AddPicture(
                Filename=image_path,
                LinkToFile=MsoTriState.msoFalse,
                SaveWithDocument=MsoTriState.msoTrue,
                Left=left,
                Top=top,
                Width=cell_size_points,
                Height=cell_size_points
            )

            row += 1

    sheet.Columns("A").AutoFit()

    excel_file_path = os.path.join(image_directory, "{} {}.xlsx".format(Assembly_DNum, Assembly_Name))
    workbook.SaveAs(excel_file_path)
    workbook.Close(False)
    excel.Quit()

    print("\nExcel file with images created at: {}".format(excel_file_path))

# MAIN EXECUTION
def Main():
    obj, obj_type = detect_type()

    if obj_type == 'Part':
        print("Current document is a Part; generating thumbnail.")
        cleaned_name = clean_file_name(obj.Name)
        obj.SaveThumbnail(os.path.join(save_path, cleaned_name + ';1.jpg'), dimension_thumb, dimension_thumb)
    else:
        print("Current document is an Assembly; counting parts.")
        parts_count = count_parts_in_assembly(obj)

        print("Generating thumbnails with quantities.")
        generate_thumbnails_with_quantities(obj, parts_count, save_path)

    GenerateExcelWithImagesNET(save_path)

Main()
I'll see what I can do. I have a backlog growing, after ChatGPT Tasks released.

 

NateLiquidGravity

Alibre Super User
@albie0803 I'm confused why you need to store that info into the filename of the image just to try and retrieve it later. Just store it all info into a variable, return the variable from the function and pass the variable into the next function.

Also FYI these scripts include suppressed parts and subassemblies instead of ignoring them and the thumbnails for those are broke.
 

stepalibre

Alibre Super User
Also FYI these scripts include suppressed parts and subassemblies instead of ignoring them and the thumbnails for those are broke.
Exactly why I asked for a detailed requirement/feature list. My tools (GPTs in general) follow instructions. There are many things I suspect aren't covered since they aren't in the instructions. It's essentially a rules engine.
 

NateLiquidGravity

Alibre Super User
Exactly why I asked for a detailed requirement/feature list. My tools (GPTs in general) follow instructions. There are many things I suspect aren't covered since they aren't in the instructions. It's essentially a rules engine.
I agree. It's a good proof of concept but there are many loose ends (if it is even the intended direction)

AFAIK it's not possible to check if occurrences are suppressed using stock Alibre Script. It will have to use the Alibre API functions for that.
 

albie0803

Alibre Super User
@albie0803 I'm confused why you need to store that info into the filename of the image just to try and retrieve it later. Just store it all info into a variable, return the variable from the function and pass the variable into the next function.

Also FYI these scripts include suppressed parts and subassemblies instead of ignoring them and the thumbnails for those are broke.
I understand what you mean, but I don't know how to do it. My programming ability is fairly limited, I just did what came to mind.
When I tried to read the DocumentNumber for each part I just couldn't follow through the for loops well enough to know the right place(s) to pull the values I was after and wasn't sure how to store them correctly either.

What I did up to that point worked. It may have been a kludgy way of doing it but it worked.

As a tool for creating an illustrated complete parts list for a project, I thought it was great, and while I didn't know about the suppressed issue, I don't see it being an issue for me as I would want everything listed.

There is an array that holds the quantity value, can it be made to hold 2 values? and how do I read both values back using the image name when inserting the images into the excel file?
 
Last edited:

stepalibre

Alibre Super User
AFAIK it's not possible to check if occurrences are suppressed using stock Alibre Script. It will have to use the Alibre API functions for that.
I'm trying to build a system to generate code for IronPython and C# for Alibre Script advanced API scripting.

For the other work I'll share ideas later. I have a few Alibre Script related projects I'm trying to finish soon.
 

Cator

Senior Member
Hi @albie0803,
can you share an example of the excel you want to get? I couldn't understand well. Regarding suppressed subassemblies @NateLiquidGravity , the failure to generate the image could be used either to manually delete it from the excel through uba worksheet rule or once again through the same script through a conditional loop of the type "if there is no image delete row of the corresponding part". What do you think?
 

albie0803

Alibre Super User
So I've got a script that does what I wanted.
Thanks Nate for pushing me to rethink how I was going about it and Stephen for coding ideas.
With references back to other scripts and help from GPT I was able to get it working without playing with extended filenames.
 
Last edited:
1. Can this code be adjusted so the the images are place in the cell so that the can be retrieved with an excel formula such as VLookup?

2. Can the image files be renamed after the excel file is created to remove the ";{Quantity}"?

Did I say thanks for the work you have done. This is great as is. Just hoping for some little adjustments.
 

stepalibre

Alibre Super User
1. What would be used by vlookup from the image? There are several ways to embed image in a cell but they aren't exactly accessible to formulas. I would instead get the path to the image and perform work on the actual image file not an embed cell image.
2. Yes
 
Top