Skip to content

uk_Expression and Spreadsheet

Kuzemko Alexsandr edited this page May 2, 2022 · 2 revisions

Загальна інформація

Починаючи з версії 0.9, існує значне оновлення FreeCAD Expression Engine і Spreadsheet Workbench. У цій статті висвітлюються відмінності та покращення, зроблені в моїй гілці у порівнянні з офіційною(upstream). Передбачається, що читач вже знайомий з основами використанням виразів і електронних таблиць. Якщо ні, спершу перегляньте посилання вище, а також розділ цієї книги.

Короткий список покращень,

  • Синтаксис виразів був значно розширений, щоб стати повноцінною мовою сценаріїв. Синтаксис запозичений з Python з кількома розширеннями для підтримки системи модулів FreeCAD, посилання на об’єкт документа тощо. Він здебільшого зворотно сумісний із синтаксисом upstream із дуже невеликими винятками.

  • Завдяки розширеному синтаксису вираз тепер може перетворюватися на будь-який тип Python об’єкта. І механізм виразів був розширений, щоб підтримувати прив’язування до будь-якого типу властивості. Додаткову інформацію див. тут.

  • Обидва PropertyExpressionEngine та PropertySheet були модифіковані, щоб вони поводилися аналогічно до властивостей посилання, яке підтримує зовнішні об’єкти. Це означає,

  • Нова альтернативна підтримка режиму редагування в Електронній таблиці, включаючи кнопку, поле зі списком і мітку, що дає змогу користувачеві створювати простий графічний інтерфейс за допомогою електронної таблиці.

  • Підтримка відносних та абсолютних посилань на комірку або діапазон комірок електронної таблиці під час копіювання та вставлення. Додаткову інформацію див. тут.

Для тих, кому не дуже важливі деталі й хочеться перейти безпосередньо до справи, ви можете перейти сюди для демонстрації.

Турбота про безпеку

Синтаксис мови виразів запозичений з Python, за винятком визначення класів. Перш ніж ми перейдемо до деталей, зауважте те що, хоча сценарії виглядають як Python, інтерпретатор Python не інтерпретує їх. З міркувань безпеки класи виразів і синтаксичний аналізатор FreeCAD змінені, щоб інтерпретувати сценарії самостійно. Таким чином, ми маємо більше контролю над тим, що може або не може робити сценарій. За замовчуванням блокуються такі вбудовані функції Python, як

  • eval()
  • execfile()
  • exec()
  • __import__()
  • file()
  • open()
  • input()

Жоден з Python модулів не доступний, крім

  • builtins (або __builtin__ для Python 2)
  • math
  • re
  • collections
  • FreeCAD (тобто App модуль)
  • FreeCADGui (тобто Gui модуль. Однак, Gui.docommand() заблокований)
  • Base (FreeCAD вбудований Базовий модуль)
  • Units (вбудований модуль FreeCAD)
  • __FreeCADConsole__ (FreeCAD вбудований Console модуль)
  • Selection (вбудований модуль FreeCAD)
  • Sketcher (вбудований модуль FreeCAD)
  • Spreadsheet (вбудований модуль FreeCAD)
  • Part (вбудований модуль FreeCAD)
  • PartDesign (вбудований модуль FreeCAD)
  • freecad.fc_cadquery (вбудована та модифікована версія CadQuery)

Користувач може вручну ввести модуль в білий (і чорний) список, додавши логічний параметр у розділ BaseApp/Preferences/Expression/PyModules з абсолютним посиланням на модуль як ім’я параметра, як показано нижче. Внаслідок свого спеціального призначення ця група параметрів захищена від запису за допомогою виразів.

Оновлення: У ранніх версіях білий/чорний список застосовується лише до прямого імпорту модуля за допомогою оператора import_py. Немає обмежень щодо виклику будь-якого методу існуючого об’єкта. У поточній версії (2020.11.24) список застосовується перед будь-яким викликом. Білий або чорний (якщо для логічного параметра встановлено значення False) застосовується до даного модуля та будь-якого з його підмодулів. Це також означає створення екземплярів класів модулів, оскільки клас є типом викликаного об’єкта. Зауважте, що доступ для читання та запису атрибутів не обмежений.

Навіть з усіма перерахованими вище застереженнями, нова потужність Expression все ще може наражати на потенційну небезпеку для кінцевого користувача, оскільки тепер вона може звертатися до іншого коду Python, що може мати невідомий ризик для безпеки. У майбутньому буде запроваджено більше заходів безпеки, можливі кандидати,

  • Явно показувати користувачеві використання кожного виразу перед завантаженням файлу
  • Реалізувати більш детальний контроль доступу, наприклад,
    • Заборонити розширені сценарії (але все ще дозволяти використання виразів у upstream),
    • Заборонити імпортування,
    • Заборонити виклик,
    • Заборонити запис властивостей.
  • Підтримка цифрового підпису файлів, яка не тільки може заборонити виконання ненадійних файлів, але й полегшить надання прав доступу довіреним, наприклад, з власним підписом користувача.

Режим синтаксису Python

Було докладено великих зусиль, щоб зробити розширений синтаксис, що імітує синтаксис Python, і все ж таки зворотно сумісний з upstream FreeCAD. Однак є винятки. На момент написання цього тексту єдине, що мені відомо, це те, що in як одиниця дюймів більше не розпізнається, оскільки він сильно конфліктує з ключовим словом Python in. Ви все ще можете використовувати " для позначення дюйма.

Розширений синтаксис повністю визначено в цьому файлі.

За замовчуванням розпізнаються всі основні ключові слова (крім in), зокрема для одиниць, вбудованих функцій і констант. Це додає близько сотні ключових слів і, отже, сильно перешкоджає коду сценарію, схожому на Python. І не кажучи вже про незручний спосіб взяття строки в лапки за допомогою << та >>, тому що ' та " відповідають одиницям футів і дюймів. Щоб полегшити цю проблему, введено псевдооператор для перемикання синтаксису в сумісний з Python режим

#@pybegin

Хоча він виглядає як коментар Python, синтаксичний аналізатор виразів розпізнає його як інструкцію, аналогічно pass або return, тому ви повинні дотримуватися звичайних правил відступів Python. Крім того, цей оператор діє лише під час виконання, а не під час розбору. Це означає, що якщо ви додасте цей оператор за межами визначенням функції, код всередині функції може не виконуватися в режимі Python залежно від контексту її виклику.

В режимі Python,

  • Ключові слова одиниць більше не розпізнаються;

  • Вбудовані функції для виразів все ще розпізнаються, але можуть бути перевизначені змінними та власними визначеннями функцій.

  • Пошук імені змінної включає вбудований модуль Python і має пріоритет над вбудованими функціями для виразів.

  • Текстовий літерал тепер підтримує звичайні ' і ". Зверніть увагу, що << та >>, а також довгий рядок ''' та """ завжди підтримуються незалежно від поточного режиму.

  • , не можна використовувати як десятковий розділювач. У режимі за замовчуванням вираз 1,2 інтерпретується як число 1.2, тоді як 1, 2 означає два цілих числа. У режимі Python вони обидва інтерпретуються як два цілих числа.

  • Коментарі Python працюють без змін. Однак у режимі за замовчуванням коментар розпізнається, лише якщо після # стоїть принаймні один пробіл. Це дозволяє використовувати синтаксис посилань на зовнішні об’єкти, document#object. Варто зазначити, що після того, як ви ввели сценарій у комірку електронної таблиці, він негайно аналізується, а всі коментарі видаляються. Тому коментарі не залишаться, під час повторного редагування сценарію. Ви завжди можете використати рядковий вираз як коментар, який залишиться після аналізу, головним чином довгий рядок ''' або """. Однак, оскільки строковий вираз вважається оператором, він повинен підкорятися звичайним правилам відступів. Якщо ви дійсно хочете використовувати і зберігати звичайні коментарі, ви можете ввести сценарій як звичайний рядок (почавши сценарій з одного ' замість =). Потім ви можете запустити сценарій за допомогою вбудованої функції eval() (яка не відповідає eval у Python, а є власною реалізацією FreeCAD) або func(). Додаткову інформацію див. тут.

Для закінчення режиму Python, введіть

#@pyend

Зауважте, що оператор перемикання режимів не враховується, тому його можна завершити одним #@pyend незалежно від того, скільки #@pybegin попереду. Під час написання виразу в електронній таблиці ви можете використовувати нову властивість електронної таблиці Режим Python для керування режимом за замовчуванням.

Синтаксична різниця у порівнянні з Python

Як згадувалося вище, розширений синтаксис виразів запозичений з Python, а точніше Python 3. Цей розділ підкреслює різницю між обома.

Ось діаграма, що показує зв’язок між синтаксисом Python, розширеним синтаксисом для виразів та синтаксисом виразів з upstream.

expression-engine-overall-map

Не реалізований

Ось що є в Python, але не реалізовано (поки що) аналізатором виразів FreeCAD,

  • Немає підтримки визначення class, decorator чи annotation.

  • Немає реалізації async, yield, with або assert операторів.

  • Відсутні побітові або матричні оператори. Причина непідтримки побітового оператора тому, що оператор зсуву << і >> конфліктує з лапками для рядка.

  • Обмежений строковий літеральний префікс. Підтримуються лише r та u, іншими словами, не підтримується двійковий або відформатований рядковий літерал.

  • Обмежена підтримка розмітки рядка. Підтримка тільки \t, \r, \n \\, \', \". А оскільки вираз підтримує рядок у лапках за допомогою << та >>, \> також підтримується. Немає підтримки вісімкового, шістнадцяткового або Unicode розмітки.

  • Оголошення та присвоєння змінних можна використовувати лише всередині тіла функції або за сценарієм, викликаним через eval() або func(). Це обмеження також стосується неявного призначення змінної з оператором for, але не застосовується до list/set/dict, які можна використовувати де завгодно.

  • Змінні, з областю дії global, трактуються як змінні, визначені оператором верхнього рівня. А local означає поточну виконувану функцію або eval(). Область дії змінної, або, точніше, стеки обчислення ізольовані від інтерпретатора Python, що працює у FreeCAD. Оператори global, local, nonlocal і del підтримуються, але зі зміненим визначенням області дії, описаним вище.

  • Оператор from ... import ... підтримує лише абсолютний імпорт.

Все, що не згадано вище, має підтримуватися розширеним синтаксисом для виразів і працювати приблизно так само, як і в Python.

Ключові слова

У режимі за замовчуванням вирази FreeCAD підтримують понад 60 ключових слів. І ви можете створити більше одиниць, виконуючи з ними арифметичні дії, наприклад м/с. Повний список підтримуваних одиниць можна знайти тут. Вони, в основному, аналогічні до upstream FreeCAD.

Визначення функцій

Іменовані функції визначаються аналогічно Python def name(args...). Однак, на відміну від Python, цей оператор повертає викликаний об’єкт (callable object) Python, подібний до виразу lambda. Це робиться для того, щоб користувачеві було легко перетворити комірку електронної таблиці на виклик, до якого можуть отримати доступ інші, наприклад нещодавно визначений метод екземпляра. На додаток, якщо комірка містить лише визначення функції, ім’я функції автоматично використовується як її псевдонім.

Ще одна незначна відмінність від Python полягає в тому, що якщо функція досягає кінця без оператора return, вона повертає результат останнього оператора замість None. Однак краще завжди використовувати явний оператор return, щоб уникнути будь-яких несподіванок. Наприклад, якщо останній оператор є рядковим виразом, він поверне None замість рядка. Це оптимізація для використання рядкового виразу як коментаря.

Посилання на властивість об’єкта документа

Вираз FreeCAD має спеціальний синтаксис для посилання на властивість об’єкта документа. Слово identifier, що використовується нижче, визначається як зазвичай у більшості мов програмування, тобто текстовий рядок, що починається з літер алфавіту або _, за яким слідують будь-які цифри або _. Існує кілька додаткових схем посилань, описаних нижче, які підтримуються у порівнянні з upstream

  • identifier. Окремий ідентифікатор можна використовувати для посилання на властивість виразу власного об’єкта документа, який ми будемо називати посиланням на локальну властивість. Однак у тілі функції або в скрипті, викликаному eval(), пошук імені спочатку буде починатись від внутрішньої локальної змінної до глобальної області. А в режимі Python, якщо не буде знайдено відповідну змінну, відбудеться пошук по вбудованим модулям. Якщо відповідності не буде знайдено, то буде здійснено пошук у списку властивостей об’єкта власника.

  • .identifier. Введено новий синтаксис, щоб спростити для користувача пряме посилання на локальну властивість без двозначності, поставивши перед identifier ., аналогічно. до синтаксису відносного імпорту Python.

    Для зручності нижче наведено список псевдовластивостей, які можна використовувати в будь-якій схемі посилання на об’єкт, не обмежуючись посиланням на локальні властивості.

    • _shape, використовуючи Part.getShape() для отримання Shape об’єкта, на який посилаються. Part.getShape() підтримує отримання форми з багатьох об'єктів без властивості Shape, включаючи App::Link, App::LinkGroup, App::Part та App::Group.

    • _pla, використовуючи DocumentObject.getSubObject(), щоб отримати накопичене розміщення посилання на підоб’єкт.

    • _matrix, те саме, що й вище, але повертає App.Matrix

    • __pla, подібний до _pla, за винятком того, що якщо підоб’єкт є App::Link, то він використовуватиме DocumentObject. getLinkedObject(), щоб також накопичити розташування зв'язаного об'єкта.

    • __matrix, те саме, що __pla, але повертає App.Matrix

    • _self, посилається на сам об'єкт. Оскільки жодна зі схеми посилань не може посилатися на об’єкт без властивості, користувач може використовувати цю псевдовластивість для доступу до атрибутів будь-якого об’єкта, що не є властивістю.

    • _app, посилається на модуль App.

    • _gui, посилається на модуль Gui.

    • _part, посилається на модуль Part.

    • _re, посилається на модуль Python regex.

    • _py, посилається на модуль Python builtins.

    • _math, посилається на модуль Python math.

    • _coll, посилається на модуль Python collections.

    • _cq, посилається на вбудований модуль CadQuery.

  • <<label>>.identifier. Посилання на об’єкт за допомогою мітки, яку користувач може змінювати. Зауважте, що мітка має бути рядком зі FreeCAD стилем цитування виразів. У разі зміни мітки об’єкта, цей вираз буде автоматично оновлено. Цей синтаксис не має двозначності, на відміну від наведеного нижче.

  • idenfifier1.identifier2. Це складний синтаксис, успадкований від upstream FreeCAD. Для нього існує багато способів його інтерпретації, як показано нижче

    • local_property.sub_property
    • object_name.property_name
    • object_label.property_name
    • І через розширений синтаксис це також може бути посиланням на локальну змінну.

    Щоб зменшити неоднозначність, новий синтаксичний аналізатор виразів намагатиметься вирішити ідентифікатор під час аналізу та за можливістю змінити вхідний вираз на альтернативну і менш неоднозначну форму, із застосуванням наступних правил у зазначеному порядку

    • Якщо є об’єкт з внутрішньою назвою identifier1 та властивістю з назвою identifier2, то не шукати зміну під час виконання.

    • Якщо є об’єкт з міткою identifier1 і має властивість з назвою identifier2, то вираз буде змінено на <<identifier1>>.identifier2

    • Якщо є локальна властивість з іменем identifier1, то вираз буде змінено на .identifier1.identifier2

    • Якщо жодне з наведених вище правил не застосовне, буде виконано пошук під час виконання серед усіх визначених змінних.

  • identifier1.<<subname>>.identifier2, посилання на підоб'єкт із використанням посилання subname, де identifier1 посилається на батьківський об’єкт за його внутрішнім ім’ям, а identifier2 – ім’я властивості підоб’єкта. Ви можете використовувати посилання на мітку всередині subname, поставивши перед міткою $. Ці мітки також автоматично оновлюються, якщо їх змінить користувач. Крім того, ви також можете включити посилання на геометрію всередині subname. Воно буде автоматично оновлено, якщо модель геометрії, на яку посилається, буде оновлена ​​завдяки новій функції Топологічна назва.

    Наприклад, вираз Assembly.<<Parts.$Fusion.Edge10>>._shape дасть вам Python об’єкт ребра форми, перетворений у глобальний простір координат. $ всередині означає, що підоб’єкт Fusion посилається за своєю міткою. Отже, якщо користувач змінить мітку об’єкта на, скажімо, Fuse, тоді вираз буде автоматично оновлено до Assembly.<<Parts.$Fuse.Edge10>>._shape. І якщо об’єкт злиття було модифіковано так, що Edge10 переміщено на Edge9, то вираз також буде автоматично оновлено відповідно.

    Зауважте, що завершувач виразів намагається автоматично виправити деякі поширені помилки в посиланнях subname. Він спробує інтерпретувати мітки, якщо названий об’єкт не може бути знайдено. Це створює деяку двозначність посилання на subname який містить лише посилання на елемент геометрії, наприклад Box.<<Face1>>._shape. Щоб вирішити цю неоднозначність, ви завжди повинні уточнювати посилання на елемент геометрії за допомогою ., тобто Box.<<.Face1>>._Shape

  • <<label>>.<<subname>>.identifier, дивись вище, за винятком того, що на батьківський об’єкт посилається його мітка.

  • identifier1#identifier2.identifier3, цей синтаксис використовується для посилання на властивість identifier3 об'єкта з внутрішнім ім'ям identifier2, який належить зовнішньому документу з назвою identifier1. На відміну від upstream, ви повинні зберегти як посилання, так і документи, на які посилаються принаймні один раз, перш ніж використовувати цей тип посилання, інакше буде створено exception. Щоразу, коли відкривається документ, що містить посилання такого типу, зовнішній документ, на який посилається, буде також автоматично відкриватися.

    identifier1 і identifier2 також можуть бути рядками в лапках для посилання на документ та/або об’єкт за їх міткою. Також може бути посилання за subname. Наприклад, <<asm>>#<<top assembly>>.<<Parts.Body.Pad>>.Length.

Посилання на Комірку і Діапазон Комірок

FreeCAD має Робоче середовище Spreadsheet, який дозволяє користувачеві створювати електронні таблиці, подібно до Microsoft Excel або Google Sheet. Комірки всередині електронної таблиці називаються за однаковою умовою, тобто одна або дві літери алфавіту для посилання на стовпець і ціле число, починаючи з одиниці, для посилання на рядок, наприклад, A1, AB1234. Максимальний підтримуваний індекс рядка становить 16384.

Правила назви комірок відповідає визначенню identifier. Оскільки об’єкт електронної таблиці створює властивість для кожної непорожньої комірки, на комірку можна посилатися у виразі, як на будь-які інші властивості. Для підтримки концепції відносного та абсолютного посилання на комірку реалізовано нову функцію. Усі звичайні посилання на комірку розглядаються як відносні. Це означає, що коли ви копіюєте комірку (або діапазон комірок) і вставляєте її в інше місце, усі відносні посилання на комірку будуть відповідно зміщені. Наприклад, якщо клітинка B1 посилається на A1, а користувач копіює B1 на B2, то посилання на A1 буде автоматично змінено на A2.

Якщо автоматичне зміщення не потрібне, користувач може використовувати абсолютне посилання на комірку, поставивши перед стовпцем, або рядком символ $. Наприклад, $A1 означає абсолютний стовпець A, але відносний перший рядок, тоді як $A$1 належить до абсолютної комірки. Хоча абсолютне посилання не відповідає звичайному визначенню identifier, його спеціально дозволено використовувати всередині посилання на властивість об’єкта, як і відносне посилання на комірку.

Діапазон клітинок може називатися як A1:C2. Деякі вбудовані функції сприймають посилання на діапазон безпосередньо як вхідний аргумент, наприклад sum(A1:C2). Однак діапазон не можна використовувати всередині посилання на властивість об’єкта. Однак його можна розпакувати в список, наприклад [*A1:C2] або (*A1:C2,). Той самий синтаксис розпакування можна використовувати для передачі діапазону комірок у звичайну функцію в якості аргументу, наприклад test(*A1:C2).

Посилання на діапазон за межами електронної таблиці

Наведене вище посилання на діапазон працює лише для посилань на власні комірки електронної таблиці. Вам знадобиться інший спосіб посилатися на діапазон комірок за межами електронної таблиці або з іншої електронної таблиці.

Кожна електронна таблиця у FreeCAD подібна до інших об’єктів, які мають безліч властивостей. Електронна таблиця автоматично трактує всі непорожні клітинки як властивість, використовуючи адресу клітинки в якості її назви. Ось як ви можете посилатися на комірку за межами електронної таблиці, використовуючи звичайний синтаксис, наприклад Sheet.A1. Електронна таблиця також зберігає всі свої комірки у властивості типу PropertySheet з назвою cells. І ця спеціальна властивість дає можливість іншим об’єктам динамічно посилатися на діапазон комірок із таким синтаксисом.

# повертає список вмісту комірки в порядку A1, B1, A2, B2. Порожні клітинки відповідатимуть елементу None.
Sheet.cells[<<A1:B2>>]

# повертає вміст рядка A, починаючи з A1 до першої порожньої комірки
Sheet.cells[<<A1:->>]

# повертає вміст стовпця A, починаючи з A1 до першої порожньої комірки
Sheet.cells[<<A1:|>>]

IDict (словник ідентифікатора)

На додаток до звичайного dict, вирази підтримують іншу зручну форму під назвою idict, що означає словник ідентифікатора, з таким синтаксисом

{ identifier=obj, ... }

Ключ має бути дійсним ідентифікатором, а значення може бути будь-яким. Вираз обчислюється як звичайний Python dict із рядковими ключами. Ця зручна форма призначена для того, щоб позбавити користувача необхідності цитувати ключ, використовуючи незручні << та >>.

Спеціальні Вбудовані функції

На додаток до всіх вбудованих функцій в Upstream FreeCAD, існують деякі додаткові.

eval

Не плутати з аналогічною вбудованою Python функцією. Ця функція виконує сценарії з розширеним синтаксисом виразу FreeCAD. Причина, чому eval() Python небезпечна, полягає в тому, що системні модулі Python надають скрипту доступ до локальної системи користувача. І eval() дозволяє зловмиснику запускати будь-який код сценарію. Механізм виразів FreeCAD за замовчуванням не має доступу до локальної файлової системи користувача. Таким чином, дозвіл виконання eval() спричиняє не більше небезпеки, ніж дозвіл користувачеві вводити вираз у комірку електронної таблиці.

Використання eval() є,

eval(cmd, ...)

cmd може бути одним рядком або послідовністю рядків. Ви можете передати один або кілька необов'язкових вхідних аргументів як попередньо визначені змінні перед виконанням сценарію. Аргумент підтримує позиційний аргумент і аргумент ключового слова, а також розпакування послідовності та словника. Позиційний аргумент називається _index_, де index – це позиція аргументу, яка починається з одиниці. Наприклад,

# припустимо
c = [ 3, 2 ]
d = { 'g':4 }

# виклик eval з такими аргументами
eval(cmd, 5, 6, a=[1,2,3], *c, **d)

# еквівалентно попередньому визначенню наступної змінної
_1_ = 5
_2_ = 6
a = [1,2,3]
_3_ = 3
_4_ = 2
g = 4

Однією з переваг eval() перед визначенням функції є те, що сценарій зберігається як рядок, а всі коментарі всередині залишаються недоторканими.

func/func_d

func() and func_d() має такий самий синтаксис виклику, як eval(). Різниця полягає в тому, що func/func_d() повертає викликаний об’єкт замість того, щоб обчислювати вхідні сценарії. Ви можете думати, що він компілює сценарій, але не запускає його. d у func_d означає затримку, тобто обчислення аргументу, переданий у func_d(), не відбувається під час компіляції, а відкладається до часу виконання,

import_py

Подібно до Python функції __import__(), import_py(name) повертає об’єкт модуля із заданою назвою. Різниця полягає в тому, що import_py() дозволяє імпортувати лише модулі, які явно ввімкнено користувачем. Перелік модулів знаходиться у BaseApp/Preferences/Expression/PyModules.

href

href() означає приховане посилання, яке приймає один аргумент, що містить посилання на властивість. Ця функція приховує будь-яке посилання на об’єкт всередині аргументу, щоб обійти помилку циклічної залежності. Використовуючи href(), потрібно бути обережним, оскільки пропуск перевірки залежностей може призвести до нестабільного порядку переобчислення і, таким чином, отримати несподіваний результат. Як правило, буде безпечно, якщо href() посилається (прямо чи опосередковано) на деяку властивість, яка буде змінена лише користувачем, а не обчислюється об’єктом (наприклад, з іншими виразами). При правильному використанні href() може бути потужним інструментом. Ви можете, наприклад, надати параметри модуля в батьківському контейнері (наприклад, App::Part або PartDesign::Body) і посилатися на ці параметри всередині будь-якого дочірнього параметра, напр. href(Part001.Length).

dbind

dbind() означає подвійну прив'язку. Він приймає один аргумент, що містить посилання на властивість, і дозволяє прив’язуватися до цієї властивості обома способами. На відміну від звичайного одностороннього зв’язування виразів, де воно зчитує значення лише з пов’язаної властивості, dbind() також дозволяє записувати до пов’язаної властивості. Іншими словами, він дозволяє зв’язувати властивості багатьох різних об’єктів і розміщувати їх в одному місці ( це можуть бути будь-який тип об’єкта, не обмежений електронною таблицею) для легкого налаштування. Водночас ви все ще можете редагувати властивість у вихідному об’єкті, а зміни будуть показуватися з іншого боку. Як і href(), посилання на властивість приховано від перевірки залежностей.

У випадку електронної таблиці, вам потрібно буде вибрати для однієї із комірок edit mode для редагування. У випадку звичайних об’єктах, перегляд властивостей дозволить вам редагувати властивість, навіть якщо вона пов’язана з виразом, як показано нижче.

Прив'язування виразів

Завдяки розширеному синтаксису вираз тепер може обчислювати будь-який тип об’єктів Python, що, своєю чергою, дозволяє прив’язувати будь-який тип властивостей. Перегляд властивостей було змінено, щоб увімкнути таку можливість.

За замовчуванням у режимі перегляду властивостей буде показано лише деякі властивості вибраного об’єкта. Деякі типи властивостей дозволяють прив’язувати вирази, натиснувши маленьку синю кнопку f(x), показану нижче.

Тепер ви можете відкрити всі властивості, клацнувши правою кнопкою миші в будь-якому місці перегляду властивостей і вибравши Show all. Як показано нижче, ви навіть можете використовувати вираз, щоб створити фігуру та прив’язати її до властивості Shape, використовуючи дію меню Expression.... Однак будьте обережні, деякі властивості приховані з певних причин. Розгляньте це як розширення функціоналу, та переконайтесь, що знаєте, що робите.

Інші пункти меню використовуються для динамічного контролю доступу до властивостей. Додаткову інформацію див. тут. Одним зі статусів властивостей, особливо важливим для ExpressionEngine, є статус Output. Коли об’єкт повторно обчислюється, його ExpressionEngine спочатку обчислює всі прив’язки виразів non-Output властивостей, потім викликає функцію execute() об’єкта і, нарешті, обчислює будь-яку прив’язку виразу з властивістю Output. Іншими словами, статус властивості Output контролює порядок обчислення прив'язаних виразів в об’єкті.

Прив’язки діапазону комірок електронної таблиці

У кожній комірці електронної таблиці передбачена підтримка прив'язки виразу. Крім того, також є можливість прив’язати діапазон комірок до іншого діапазону клітинок у тій самій чи іншій електронній таблиці за допомогою дії Зв’язати... у контекстному меню електронної таблиці. Після зв’язування комірки в цільовому діапазоні будуть показувати те, що знаходиться в комірках джерела. Зв’язані клітинки показує з синьою рамкою, і їх не можна редагувати. Двічі клацніть на будь-які зв’язані комірки, щоб відредагувати або скасовувати прив’язку.

Режим редагування електронних таблиць

Окрім вищезгаданої функції відносної комірки, ще однією важливою новою функцією електронної таблиці є введення альтернативного режиму редагування. Він дає змогу користувачеві створювати простий GUI за допомогою електронної таблиці. Щоб активувати цю функцію, просто клацніть правою кнопкою миші комірку та виберіть один із режимів редагування в контекстному меню. Режим Звичайний – це режим за замовчуванням, який безпосередньо редагує вміст комірки. Постійно – це додаткова опція для всіх інших режимів редагування, при якій завжди показується елемент керування редагуванням. В іншому випадку він показується лише під час редагування комірки, наприклад, подвійним клацанням. Інші режими описані нижче.

Режим редагування кнопок

Будь-яку комірку з функцією, можна перевести в режим редагування кнопки, де віджет кнопки показується в позиції комірки. Текст кнопки шукається в наступних місцях у зазначеному порядку,

  • doc строка функції,
  • Ім'я функції
  • Псевдонім комірки,
  • Адреса комірки.

Функція викликається при натисканні кнопки, після чого весь поточний документ повторно обчислюється.

Режим редагування Combo Box

Для режиму редагування ComboBox потрібна комірка із list або tuple виразом з двох або більше елементів. Якщо перший елемент є відображенням рядка на що-небудь, то другий елемент має бути ключем рядка зіставлення. Якщо перший елемент є послідовністю рядків, то другий елемент може бути або рядком, або цілочисельним індексом послідовності. Коли активовано режим редагування ComboBox, у комірці буде показано лише другий елемент рядка як вміст. Коли комірку редагують подвійним клацанням миші або за допомогою клавіатури, вона показує віджет ComboBox, заповнений ключами рядка першого елемента, якщо це зіставлення, або просто перший елемент, якщо це список рядків. Коли користувач вибирає елемент, другому елементу буде призначено вибраний рядок. Якщо у виразі комірки є третій викликаний елемент, він буде викликаний при зміні значення з аргументами callable(sheet, cell_address, seq), де sheet – електронна таблиця, cell_address – це поточна адреса комірки в текстовій формі, а seq – це обчислений об'єкт типу list або tuple.

Режим редагування міток

Для режиму редагування мітки необхідна клітинка з виразом типу list або tuple з мінімум одним елементом. Першим елементом має бути рядок. Решта елементів може бути будь-якою. Їх не чіпають. Якщо активовано, комірка показує лише перший елемент рядка як вміст, і його можна редагувати як звичайно. Інші елементи приховані та не використовуються. Перегляньте наступну демонстрацію для прикладу використання цього режиму.

Цей режим редагування також можна використовувати для редагування властивості рядка з іншого об’єкта за допомогою функції подвійного зв’язування, напр. dbind(Box.Label2).

Режим редагування кількості

Цей режим редагування забезпечує простий спосіб редагування значень за допомогою SpinBox. Це схоже на те, як перегляд властивостей дозволяє редагувати властивість Кількість об’єкта, але пропонує більше налаштувань із виразом.

У цьому режимі вважаеться, що комірка міститить просте число, quantity (тобто число з одиницею виміру) або tuple/list(quantity, dict). dict містить додаткові рядкові ключі 'step', 'max', 'min', 'unit' для налаштування SpinBox. Очікується, що всі значення ключів мають тип double, за винятком unit, який має бути string (рядком). Якщо параметр unit не знайдено, буде використано параметр display unit поточної комірки. Зауважте, що параметр unit всередині виразу не впливає на відображення комірки, коли не редагується. Ви, звичайно, можете увімкнути режим редагування Постійно, щоб завжди показувати вміст комірки в SpinBox.

Режим редагування CheckBox

Редагує комірку використовуючи CheckBox(Прапорець). Очікується, що комірка містить будь-яке значення, яке можна перетворити на логічне значення. Якщо ви хочете, щоб прапорець мав назву, використовуйте tuple/list(boolean, title).

Для всіх режимів редагування, окрім виразу комірки, згаданого вище, вони також приймають вираз виклику спеціальної функції dbind(). Як згадувалося раніше, dbind() працює так само, як App::Link на рівні об’єкта. Його можна використовувати для посилання на властивість. А режим редагування можна використовувати для пересилання редагування до пов’язаної властивості.

Демонстрація

Наведу демонстрацію, що використовує таблицю для заповнення простого списку BOM. Електронна таблиця пропонує користувачеві вибрати документ і збірку, а потім заповнює список інформацією про деталі з вибраного документа.

Щоб відкрити сценарій комірки з режимом редагування не за замовчуванням, ви можете клацнути її правою кнопкою миші та вибрати Режим редагування -> Звичайний. Потім ви можете редагувати комірку як завжди. Як варіант, ви можете клацнути правою кнопкою миші об’єкт електронної таблиці в ієрархії документа та вибрати Дії з виразом -> Копіювати вибране. Потім ви можете вставити сценарії у свій улюблений текстовий редактор. Це дасть вам вирази для кожної комірки в електронній таблиці. Інші пункти меню в Дії з виразом можуть дозволити вам копіювати вирази активного документа або всіх відкритих документів не тільки з електронної таблиці, а й з усіх прив’язок виразів у будь-яких об’єктах. Ви можете змінити сценарій у своєму редакторі та скопіювати його назад за допомогою Дії з виразом -> Вставити. Ви можете вставити лише частину сценаріїв, якщо включите маркер ##@@ виразу, який потрібно змінити. Переконайтеся, що ваш скопійований текст починається з одного з маркерів ##@@.

Ось сценарій,

##@@ A1 bom#Spreadsheet.cells (Spreadsheet)
##@@
<<Please select document:>>

##@@ A2 bom#Spreadsheet.cells (Spreadsheet)
##@@
<<Please select assembly:>>

##@@ A3 bom#Spreadsheet.cells (Spreadsheet)
##@@<Cell alias="getDocuments" />
def getDocuments():
    #@pybegin
    docs = {}
    for name, doc in ._app.listDocuments().items() :
        if doc.Label != name:
            name = '%s (%s)' % (doc.Label, name)
        else:
            name = doc.Label

        docs[name] = doc

    return docs

##@@ A4 bom#Spreadsheet.cells (Spreadsheet)
##@@<Cell foregroundColor="#ffffffff" backgroundColor="#000080ff" editMode="3" editModeName="Label" />
[<<Document>>, ._self.column(), lambda obj : obj.Document.Label]

##@@ B1 bom#Spreadsheet.cells (Spreadsheet)
##@@<Cell alias="curDoc" editMode="2" editModeName="Combo" editPersistent="1" />
[.getDocuments(), <<bom>>]

##@@ B2 bom#Spreadsheet.cells (Spreadsheet)
##@@<Cell alias="curAssembly" editMode="2" editModeName="Combo" editPersistent="1" />
[.getAssemblies(.curDoc), <<None>>, .getParts]

##@@ B3 bom#Spreadsheet.cells (Spreadsheet)
##@@<Cell alias="getAssemblies" />
def getAssemblies(docMap):
    #@pybegin
    objs = []
    try:
        doc = docMap[0].get(docMap[1])
        for obj in doc.Objects :
            if str(getattr(obj, 'Proxy', None)).startswith('<freecad.asm3.assembly.Assembly '):
                objs.append(obj)
    except:
        pass

    if  not objs:
        return {'None':None}

    return { obj.Label : obj for obj in objs }

##@@ B4 bom#Spreadsheet.cells (Spreadsheet)
##@@<Cell foregroundColor="#ffffffff" backgroundColor="#000080ff" editMode="3" editModeName="Label" />
[<<Label>>, ._self.column(), lambda obj : obj.Label]

##@@ C1 bom#Spreadsheet.cells (Spreadsheet)
##@@<Cell alias="Refresh" editMode="1" editModeName="Button" />
def Refresh(asm=.curAssembly):
    ._self.touchCells(<<curDoc>>)
    ._self.recompute()
    .getParts(None, None, asm)

##@@ C3 bom#Spreadsheet.cells (Spreadsheet)
##@@<Cell alias="getParts" />
def getParts(_obj, _addr, asm, template=.template):
    #@pybegin
    rstart = 5
    r = rstart
    count = 0
    try:
        while ._self.get('A' + str(r)) :
            count += 1
            r += 1
    except:
        pass

    if count:
        ._self.removeRows(str(rstart), count)

    r = rstart
    try:
        asm = asm[0].get(asm[1], None)
        if  not asm:
            ._app.Console.PrintError('No parts found\n')
            return

        parts = asm.Group[2]
        for obj in parts.Group :
            for _, col, func in template[1] :
                addr = col + str(r)
                ._self.set(addr, "'" + func(obj))

            r += 1
    except Exception as e:
        ._app.Console.PrintError('Failed to get parts: ' + str(e) + '\n')

##@@ C4 bom#Spreadsheet.cells (Spreadsheet)
##@@<Cell foregroundColor="#ffffffff" backgroundColor="#000080ff" editMode="3" editModeName="Label" />
[<<Part No.>>, ._self.column(), lambda obj : .partInfo(obj, 0)]

##@@ D3 bom#Spreadsheet.cells (Spreadsheet)
##@@<Cell alias="template" editMode="3" editModeName="Label" />
[<<template>>, [*A4:D4]]

##@@ D4 bom#Spreadsheet.cells (Spreadsheet)
##@@<Cell foregroundColor="#ffffffff" backgroundColor="#000080ff" editMode="3" editModeName="Label" />
[<<Description>>, ._self.column(), lambda obj : .partInfo(obj, 1)]

##@@ E3 bom#Spreadsheet.cells (Spreadsheet)
##@@<Cell alias="partInfo" />
def partInfo(obj, idx):
    #@pybegin
    try:
        res = obj.Label2.split(':')[idx]
        return res ? res : '?'
    except:
        return '?'

Третій рядок електронної таблиці використовується для визначення кількох допоміжних функцій для підрахунку документів, збірок, деталей тощо. Ви можете за бажанням приховати цей рядок для кращого вигляду, як показано на відео. Просто клацніть правою кнопкою миші на індексі рядка та виберіть Toggle rows. Щоб знову показати приховані рядки, виберіть Показати всі рядки.

Номер частини і Опис заповнюються функцією partInfo(), яка зчитує властивість об’єкта частини Label2, тобто текст, показаний у стовпці Опис ієрархії документа. Як показано на відео, ви можете редагувати стовпець в ієрархії документа, натиснувши клавішу F2. Демо-код просто розбиває рядок за допомогою роздільника :. Звичайно, ви можете використовувати складнішу структуру даних або додати спеціальну властивість до об’єкта деталі для отримання додаткової інформації.

Стовпці списку BOM (A4:D4) визначаються за допомогою режиму редагування Label, де перший елемент списку є заголовком стовпця, другий елемент зберігає поточну адресу стовпця., а третій елемент — це лямбда для вилучення інформації про відповідну частину.

D3 (тобто template) визначається як зручний спосіб об'єднання всіх визначень стовпців. Він використовує режим редагування Мітки, щоб зменшити детальність відображення комірок.

Зверніть увагу на те, що B1 містить список поточних відкритих документів, які користувач може вибрати. Немає механізму автоматичного переобчислення у разі створення нового чи видалення документа. Кнопка Оновити визначає функцію з такою ж назвою для повторного сканування всіх відкритих документів і повторного заповнення списку збірок поточного документа.

Clone this wiki locally