Jump to content

Talk:Macro Alias For Table For Object

Add topic
From FreeCAD Documentation
Latest comment: Thursday at 04:56 by DonnerWolfBach in topic Does not create aliases on freecad 1.1.1

Does not create aliases on freecad 1.1.1[edit source]

The gui shows up, but no visible changes are made DonnerWolfBach (talk) 04:26, 28 May 2026 (UTC)Reply

Vibe coded fix: DonnerWolfBach (talk) 04:56, 28 May 2026 (UTC)Reply

Broken in Freecad 1.1.1, vibe coded rewrite[edit source]

The pop up showed up for me, but nothing happened

Vibe coded this with claude:

```

#!/usr/bin/env python

# -*- coding: utf-8 -*-

# ========================================================================

# Macro Alias for table for object

# file name : Alias_For_Table_For_Object.FCMacro

# Updated for FreeCAD 1.1.1 — uses SheetViewPy.selectedCells() API

# ========================================================================

__Name__    = "Alias for table for object"

__Comment__ = "Automatically creates aliases in a two-dimensional table using the names of the rows and columns"

__Author__  = "2cv001 (updated for FreeCAD 1.1.1)"

__Title__   = "Alias For Table For Object"

__Date__    = "2024/02/20"

__Version__ = __Date__

__License__ = "Apache-2.0"

__Wiki__    = "https://wiki.freecad.org/Macro_Alias_For_Table_For_Object"

import FreeCAD as App

import FreeCADGui as Gui

import re

try:

    from PySide2 import QtWidgets

except ImportError:

    try:

        from PySide6 import QtWidgets

    except ImportError:

        from PySide import QtGui as QtWidgets

# ========================================================================

# User-adjustable parameters

# ========================================================================

addNumberIfOther            = True   # append _2, _3 … on alias name conflicts

separateur                  = " "    # word separator in source cell text

nouveauCaract               = ''     # replaces separator in alias (e.g. "_")

majuscule                   = True   # capitalise each word after a separator

premierCaractereEnMinuscule = False  # force first character lower-case

caracEquivalents = [

    ['é','e'], ['è','e'], ['ê','e'], ['à','a'], ['@','a'], ['&','e'],

    ['ç','c'], ['²','2'], ["'",''],  ['?',''],  ['"',''],  ['(',''],

    [')',''],  ['#',''],  ['.',''],  [',',''],  [';',''],  ['$',''],

    ['+',''],  ['-',''],  ['*',''],  ['/',''],  ['\\',''],

    ['[',''],  [']',''],

]

# ========================================================================

# Cell address utilities

# ========================================================================

def cell_to_rowcol(cell):

    """'AB12' -> (row=11, col=27)  — both zero-based."""

    m = re.match(r'^([A-Z]+)(\d+)$', cell.upper())

    if not m:

        raise ValueError(f"Invalid cell address: {cell!r}")

    col_str, row_str = m.group(1), m.group(2)

    col = 0

    for ch in col_str:

        col = col * 26 + (ord(ch) - ord('A') + 1)

    col -= 1   # make 0-based

    row = int(row_str) - 1

    return row, col

def rowcol_to_cell(row, col):

    """(row=11, col=27) -> 'AB12'  — both zero-based."""

    col_index = col

    col_str   = ''

    while True:

        col_str   = chr(col_index % 26 + ord('A')) + col_str

        col_index = col_index // 26 - 1

        if col_index < 0:

            break

    return col_str + str(row + 1)

# ========================================================================

# Active sheet + selection helpers

# ========================================================================

def get_active_sheet():

    """Return the active Spreadsheet::Sheet or None."""

    try:

        sheet = Gui.ActiveDocument.ActiveView.getSheet()

        if sheet and sheet.TypeId == 'Spreadsheet::Sheet':

            return sheet

    except Exception:

        pass

    for obj in Gui.Selection.getSelection():

        if getattr(obj, 'TypeId', '') == 'Spreadsheet::Sheet':

            return obj

    if App.ActiveDocument:

        for obj in App.ActiveDocument.Objects:

            if obj.TypeId == 'Spreadsheet::Sheet':

                return obj

    App.Console.PrintError(

        "Alias_For_Table: No spreadsheet found. "

        "Please open a spreadsheet first.\n"

    )

    return None

def get_selected_cells():

    """

    Return selected cell addresses as a list of strings, e.g. ['A1','B2',...].

    Uses SheetViewPy.selectedCells() — the native API in FreeCAD 1.1.1.

    """

    try:

        cells = Gui.ActiveDocument.ActiveView.selectedCells()

        if cells:

            return cells

    except Exception as e:

        App.Console.PrintError(f"Alias_For_Table: selectedCells() failed: {e}\n")

    App.Console.PrintError(

        "Alias_For_Table: No cells selected. "

        "Select a range in the spreadsheet and run the macro again.\n"

    )

    return []

def safe_get_contents(sheet, cell):

    """Return raw cell content as str, or '' — handles API name variation."""

    for method_name in ('getContents', 'getContent'):

        fn = getattr(sheet, method_name, None)

        if fn is not None:

            try:

                return str(fn(cell))

            except Exception:

                pass

    return ''

# ========================================================================

# Alias-string processing

# ========================================================================

def traitementChaineSource(text):

    """Build a valid alias string from cell text.

    e.g. "Diametre du cylindre" -> "DiametreDuCylindre"

    """

    def replaceEquiv(ch):

        for pair in caracEquivalents:

            if pair[0] == ch:

                return pair[1]

        return ch

    def cleanWord(word):

        return ''.join(replaceEquiv(c) for c in word)

    result = ''

    first  = True

    for word in text.split(separateur):

        word = cleanWord(word)

        if first:

            result = word

            first  = False

        else:

            if majuscule:

                result += nouveauCaract + word[:1].upper() + word[1:]

            else:

                result += nouveauCaract + word

    if premierCaractereEnMinuscule:

        result = result[:1].lower() + result[1:]

    return result

# ========================================================================

# PART 1 – Create aliases

# ========================================================================

def create_alias_for_table_cells_Selected():

    sheet     = get_active_sheet()

    if sheet is None:

        return

    sel_cells = get_selected_cells()

    if not sel_cells:

        return

    # Convert to (row, col) pairs and find the top-left origin

    rc_list = [cell_to_rowcol(c) for c in sel_cells]

    min_row  = min(r for r, c in rc_list)

    min_col  = min(c for r, c in rc_list)

    changed = 0

    for (row, col) in rc_list:

        # Skip header row and header column

        if row == min_row or col == min_col:

            continue

        cellName      = rowcol_to_cell(row, col)

        rowHeaderCell = rowcol_to_cell(row,     min_col)   # left edge

        colHeaderCell = rowcol_to_cell(min_row, col)       # top edge

        # Read each header independently so one empty header is allowed.

        # An empty/missing cell returns '' rather than causing a skip.

        def read_header(cell):

            try:

                val = sheet.get(cell)

                return str(val) if val is not None else ''

            except Exception:

                return ''

        textRow = read_header(rowHeaderCell)

        textCol = read_header(colHeaderCell)

        # Skip only if BOTH headers are empty (nothing to name the alias with)

        if not textRow and not textCol:

            continue

        # Build alias: if one side is empty, omit it and the separator

        if textRow and textCol:

            rawAlias = textRow + '_' + textCol

        elif textRow:

            rawAlias = textRow

        else:

            rawAlias = textCol

        textAlias = traitementChaineSource(rawAlias)

        if not textAlias:

            continue

        if addNumberIfOther:

            for i in range(1, 201):

                candidate = textAlias if i == 1 else f"{textAlias}_{i - 1}"

                try:

                    sheet.setAlias(cellName, candidate)

                    changed += 1

                    break

                except Exception:

                    continue

        else:

            try:

                sheet.setAlias(cellName, textAlias)

                changed += 1

            except Exception as e:

                App.Console.PrintWarning(

                    f"Alias_For_Table: Could not set alias '{textAlias}' "

                    f"on {cellName}: {e}\n"

                )

    if changed:

        App.ActiveDocument.recompute()

        App.Console.PrintMessage(

            f"Alias_For_Table: {changed} alias(es) created/updated.\n"

        )

    else:

        App.Console.PrintWarning(

            "Alias_For_Table: No aliases were created. "

            "Check that your selection includes both header row/column and data cells.\n"

        )

# ========================================================================

# PART 2 – Table to object

# ========================================================================

dico = {

    "posx"            : "Placement.Base.x",

    "posy"            : "Placement.Base.y",

    "posz"            : "Placement.Base.z",

    "posX"            : "Placement.Base.x",

    "posY"            : "Placement.Base.y",

    "posZ"            : "Placement.Base.z",

    "angle"           : "Placement.Rotation.Angle",

    "ang"             : "Placement.Rotation.Angle",

    "axisx"           : "Placement.Rotation.Axis.x",

    "axisy"           : "Placement.Rotation.Axis.y",

    "axisz"           : "Placement.Rotation.Axis.z",

    "axeX"            : "Placement.Rotation.Axis.x",

    "axeY"            : "Placement.Rotation.Axis.y",

    "axeZ"            : "Placement.Rotation.Axis.z",

    "attachementX"    : "AttachmentOffset.Base.x",

    "attachementY"    : "AttachmentOffset.Base.y",

    "attachementZ"    : "AttachmentOffset.Base.z",

    "attachementAngle": "AttachmentOffset.Rotation.Angle",

    "attachementAxisX": "AttachmentOffset.Rotation.Axis.x",

    "attachementAxisY": "AttachmentOffset.Rotation.Axis.y",

    "attachementAxisZ": "AttachmentOffset.Rotation.Axis.z",

    "Length"          : "Length",

    "Length2"         : "Length2",

    "Radius"          : "Radius",

    "Height"          : "Height",

    "FirstAngle"      : "FirstAngle",

    "SecondAngle"     : "SecondAngle",

    "Angle1"          : "Angle1",

    "Angle2"          : "Angle2",

    "Angle3"          : "Angle3",

}

def set_constraint_expression(sketchLabel, constraint_name, expression):

    found    = False

    sketches = App.ActiveDocument.getObjectsByLabel(sketchLabel)

    if len(sketches) > 1:

        App.Console.PrintWarning(

            f"Alias_For_Table: Multiple sketches share label '{sketchLabel}'.\n"

        )

    for sketch in sketches:

        for i in range(sketch.ConstraintCount):

            if sketch.Constraints[i].Name == constraint_name:

                sketch.setExpression(f'Constraints[{i}]', expression)

                found = True

                break

    return found

def set_property_based_on_alias(sheet, cell):

    alias = sheet.getAlias(cell)

    if safe_get_contents(sheet, cell) == '':

        return

    if alias is None:

        App.Console.PrintWarning(

            f"Alias_For_Table: Cell {cell} has no alias – skipping.\n"

        )

        return

    parts = alias.rsplit('_', 1)

    if len(parts) != 2:

        App.Console.PrintWarning(

            f"Alias_For_Table: Alias '{alias}' is not 'ObjectLabel_Property' – skipping.\n"

        )

        return

    objLabel, property_keyword = parts

    matches = App.ActiveDocument.getObjectsByLabel(objLabel)

    if not matches:

        App.Console.PrintWarning(

            f"Alias_For_Table: No object with label '{objLabel}'.\n"

        )

        return

    obj           = matches[0]

    property_name = dico.get(property_keyword, property_keyword)

    expression    = f"<<{sheet.Label}>>.{alias}"

    if obj.TypeId == 'Sketcher::SketchObject':

        if set_constraint_expression(objLabel, property_keyword, expression):

            return

    top_level = property_name.split('.')[0]

    if hasattr(obj, top_level):

        try:

            obj.setExpression(property_name, expression)

        except Exception as e:

            App.Console.PrintError(

                f"Alias_For_Table: Cannot set expression on "

                f"'{objLabel}.{property_name}': {e}\n"

            )

    else:

        App.Console.PrintWarning(

            f"Alias_For_Table: Object '{objLabel}' has no property '{property_name}'.\n"

        )

def tableToObject():

    sheet     = get_active_sheet()

    if sheet is None:

        return

    sel_cells = get_selected_cells()

    if not sel_cells:

        return

    changed = 0

    for cell in sel_cells:

        if safe_get_contents(sheet, cell) != '':

            set_property_based_on_alias(sheet, cell)

            changed += 1

    if changed:

        App.ActiveDocument.recompute()

        App.Console.PrintMessage(

            f"Alias_For_Table: Expressions set for {changed} cell(s).\n"

        )

# ========================================================================

# Dialog

# ========================================================================

class CheckBoxDialog(QtWidgets.QDialog):

    def __init__(self, parent=None):

        super().__init__(parent)

        self.setWindowTitle("Alias For Table For Object")

        self.createAliasCheckBox = QtWidgets.QCheckBox("Create aliases")

        self.createAliasCheckBox.setToolTip(

            "Creates an alias in each selected cell based on\n"

            "the first selected row and column.\n"

            "Format: 'RowHeader_ColumnHeader'"

        )

        self.tableToObjectCheckBox = QtWidgets.QCheckBox("Fill object properties from aliases")

        self.tableToObjectCheckBox.setToolTip(

            "Sets expressions on object properties\n"

            "based on aliases in the format 'ObjectLabel_Property'."

        )

        buttons = QtWidgets.QDialogButtonBox(

            QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel

        )

        buttons.accepted.connect(self.accept)

        buttons.rejected.connect(self.reject)

        layout = QtWidgets.QVBoxLayout()

        layout.addWidget(self.createAliasCheckBox)

        layout.addWidget(self.tableToObjectCheckBox)

        layout.addWidget(buttons)

        self.setLayout(layout)

# ========================================================================

# Entry point

# ========================================================================

def main():

    App.Console.PrintMessage(

        f"Alias_For_Table: Running on FreeCAD "

        f"{'.'.join(str(v) for v in App.Version()[:3])}\n"

    )

    if App.ActiveDocument is None:

        QtWidgets.QMessageBox.warning(

            None, "Alias For Table",

            "No document is open. Please open a FreeCAD document first."

        )

        return

    dialog = CheckBoxDialog()

    result = dialog.exec()

    if result == QtWidgets.QDialog.Accepted:

        if dialog.createAliasCheckBox.isChecked():

            create_alias_for_table_cells_Selected()

        if dialog.tableToObjectCheckBox.isChecked():

            tableToObject()

if __name__ == '__main__':

    main()

``` DonnerWolfBach (talk) 04:55, 28 May 2026 (UTC)Reply