Перетворіть вихідне фото на повнокольорову кастомну клавішу механічної клавіатури
в два етапи: прототип генерує рендер дизайну "готової клавіші"
з вашого вхідного фото. Після підтвердження цього рендера, збірка перетворює його
на текстуровану 3D модель клавіші в одному запуску — генерація білої моделі, автоматичне
розміщення та різання на каліброваній стандартній позі, повне фарбування моделі та
остаточна збірка відбуваються в межах одного завдання збірки. Два етапи пов'язані
через input_task_id плюс candidate_id.
POST /openapi/creative-lab/keycap/v1/prototype
POST /openapi/creative-lab/keycap/v1/build
Обидва POST endpoints вимагають платного плану підписки. Запити від
акаунтів з безкоштовним планом відхиляються з 402 Payment Required.
Генеруйте рендер дизайну готової клавіші з вихідного фото. Результат завдання містить масив image_urls (рендер дисплея готової клавіші) і паралельний масив candidate_ids; обидва містять один запис. Викличте цю кінцеву точку знову для іншого рендера, якщо результат не відповідає вашим очікуванням — кожен виклик оплачується окремо. Передайте candidate_id разом з ідентифікатором завдання прототипу до кінцевої точки збірки. Зверніться до
Об'єкт завдання прототипу клавіші
для форми відповіді.
Параметри
Name
image_url
Type
string
Обов'язковий
Description
Вихідне фото для Meshy, щоб перетворити його на зображення дизайну клавіші. Наразі ми підтримуємо формати .jpg, .jpeg, .png та .webp.
Формат визначається шляхом декодування даних зображення, а не з розширення файлу URL — URL без розширення або той, що перенаправляє, працює, якщо байти декодуються в підтримуваний формат. HTTP-перенаправлення підтримуються. Орієнтація EXIF нормалізується, тому повернуте фото з телефону використовується так, як воно виглядає.
Обмеження: щонайменше 32 пікселі з кожного боку, не більше 178,956,970 пікселів загалом і не більше 20,000,000 байтів після завантаження. Для Data URI обмеження застосовується до декодованих байтів, тому вихідний файл може бути до цього розміру — це текст base64, який приблизно на третину більший, що важливо для вашого тіла запиту, а не для цього обмеження. Data URI повинен оголошувати тип вмісту image/* і ;base64.
Існує два способи надати зображення:
Публічно доступний URL: URL, доступний з публічного інтернету.
Data URI: закодований у base64 Data URI зображення. Приклад Data URI: data:image/jpeg;base64,<your base64-encoded image data>.
Name
name
Type
string
Description
Необов'язкова назва завдання для відображення. Максимум 100 символів.
Name
remove_background
Type
boolean
за замовчуванням false
Description
Якщо встановлено значення true, рендер дисплея, повернутий у image_urls, є прозорим RGBA PNG з видаленим фоном, щоб ви могли скласти його на будь-який фон.
Це стосується лише рендера дисплея. Кандидат, який споживає кінцева точка збірки, не змінюється, тому 3D-результат ідентичний у будь-якому випадку.
Повертає
Властивість result відповіді містить ідентифікатор завдання id новоствореного завдання прототипу клавіші. Опитуйте Отримати завдання кінцеву точку або підпишіться на потік до тих пір, поки завдання не досягне SUCCEEDED, потім візьміть запис з candidate_ids і передайте його разом з ідентифікатором завдання до кінцевої точки збірки.
Режими відмови
Name
400 - Bad Request
Description
Запит був неприйнятним. Поширені причини:
Відсутній параметр: image_url є обов'язковим.
Неприпустимий формат зображення: Наданий image_url не є підтримуваним форматом (.jpg, .jpeg, .png, .webp).
Розміри зображення поза діапазоном: Зображення занадто мале, перевищує максимальний розмір файлу або перевищує максимальну кількість пікселів.
Недоступний URL: image_url не вдалося завантажити (404 або timeout).
Неприпустимий Data URI: Рядок base64 пошкоджений.
Вміст позначено: Вхідне зображення було позначено модерацією NSFW.
Name
401 - Unauthorized
Description
Автентифікація не вдалася. Будь ласка, перевірте ваш API key.
Name
402 - Payment Required
Description
Обліковий запис знаходиться на безкоштовному плані (потрібен платний план для створення завдань) або має недостатньо кредитів.
Name
403 - Forbidden
Description
Вхідне зображення було позначено модерацією інтелектуальної власності.
Name
429 - Too Many Requests
Description
Ви перевищили ваше обмеження частоти.
Name
500 - Internal Server Error
Description
Виникла несподівана помилка на стороні сервера — наприклад, служба модерації вмісту була недоступна, підготовка вхідного зображення не вдалася або завдання не вдалося створити. У цьому випадку завдання не створюється, тому повторна спроба безпечна.
Запит
POST
/openapi/creative-lab/keycap/v1/prototype
# Stage 1: generate a finished-keycap design rendercurlhttps://api.meshy.ai/openapi/creative-lab/keycap/v1/prototype \-XPOST \-H"Authorization: Bearer ${YOUR_API_KEY}" \-H'Content-Type: application/json' \-d'{ "image_url": "<your publicly accessible image url or base64-encoded data URI>" }'
Генерація фінальної текстурованої 3D моделі кейкапа з успішного прототипу
завдання та одного з його кандидатів. Одне завдання на побудову виконує весь
процес від початку до кінця — генерація білої моделі з обраного дизайну,
автоматичне розміщення та вирізання на основі кейкапа з використанням каліброваної
стандартної пози (інтерактивне налаштування не потрібне), повне забарвлення моделі,
фінальна збірка та експорт. Побудова зазвичай займає 3–7 хвилин, ближче до верхньої межі, коли кілька побудов виконуються одночасно.
Дивіться Об'єкт завдання на побудову кейкапа
для форми відповіді.
Параметри
Name
input_task_id
Type
string
Обов'язковий
Description
Ідентифікатор завдання прототипу, створеного через цю ж кінцеву точку OpenAPI. Прототип повинен бути створений тим самим обліковим записом Meshy, досягти SUCCEEDED і створити принаймні одного кандидата.
Прототипи, створені через веб-додаток, не приймаються — кінцева точка побудови приймає лише прототипи, створені POST /openapi/creative-lab/keycap/v1/prototype і відхиляє будь-яке інше джерело з 404.
Name
candidate_id
Type
string
Обов'язковий
Description
Кандидат для побудови, взятий з масиву candidate_ids успішного завдання прототипу. Повинен належати цьому завданню; будь-яке інше значення відхиляється з 400.
Name
name
Type
string
Description
Необов'язкова назва завдання для відображення. Максимум 100 символів.
options
Необов'язкове налаштування геометрії. Кожне поле має каліброване значення за замовчуванням — надсилайте лише ті, які хочете перевизначити.
Name
base_model
Type
string
за замовчуванням cherry-mx-1x1-r1
Description
Основа кейкапа для побудови. Наразі єдине доступне значення —
cherry-mx-1x1-r1 — стандартний профіль Cherry MX 1u кейкап. Планується 3–5
додаткових стандартних розмірів; нестандартні розміри не підтримуються.
Name
head_size_mm
Type
number
за замовчуванням 23
Description
Цільовий розмір скульптурної голови в міліметрах: її найдовший вимір масштабується до цього значення. Діапазон: [10, 40]. Значення понад приблизно 32.9 можуть бути зменшені, щоб голова все ще вписувалася в обмежувальний паралелепіпед основи, тому доставлений найдовший вимір може бути меншим, ніж запитуваний. Застосоване значення сьогодні не відображається в об'єкті завдання — якщо вам потрібно підтвердити розмір, який ви фактично отримали, виміряйте обмежувальний паралелепіпед сітки keycap-head у завантаженій моделі.
Name
vertical_offset_mm
Type
number
за замовчуванням 0
Description
Вертикальне зміщення, застосоване до голови перед її розміщенням на основі, в міліметрах. Діапазон: [-5, 5].
Повернення
Властивість result відповіді містить ідентифікатор завдання id новоствореного завдання на побудову кейкапа. Опитуйте Отримати завдання кінцеву точку або підпишіться на потік, поки завдання не досягне SUCCEEDED, потім завантажте артефакти з model_urls.glb та model_urls.obj_zip.
І GLB, і OBJ пакет експортуються в реальному масштабі міліметрів, з Y-вгору координатною системою і передньою частиною кейкапа, що дивиться на +Z.
Режими відмови
Name
400 - Bad Request
Description
Запит був неприйнятним. Поширені причини:
Відсутній параметр: input_task_id і candidate_id є обов'язковими.
Недійсний UUID: input_task_id не є дійсним UUID.
Батьківське завдання не успішне: Зазначене завдання прототипу ще не досягло SUCCEEDED.
Немає кандидатів: Завдання прототипу було успішним, але не створило кандидатів.
Невідомий кандидат: candidate_id не є одним з кандидатів вхідного завдання.
Параметри поза діапазоном: Одне з полів options вийшло за межі дозволеного діапазону або набору перерахувань.
Name
401 - Unauthorized
Description
Автентифікація не вдалася. Будь ласка, перевірте ваш API-ключ.
Name
402 - Payment Required
Description
Обліковий запис на безкоштовному плані (для створення завдань потрібен платний план) або недостатньо кредитів.
Name
404 - Not Found
Description
Зазначене завдання прототипу не існує, належить іншому користувачу або було створено через веб-додаток (лише прототипи в режимі API переходять у побудову).
Name
429 - Too Many Requests
Description
Ви перевищили своє обмеження частоти.
Name
500 - Internal Server Error
Description
Виникла несподівана помилка на стороні сервера — наприклад, служба модерації контенту була недоступна, не вдалося підготувати вхідне зображення або завдання не могло бути створено. У цьому випадку завдання не створюється, тому повторна спроба безпечна.
Запит
POST
/openapi/creative-lab/keycap/v1/build
# Stage 2: build the chosen candidate into a 3D keycapcurlhttps://api.meshy.ai/openapi/creative-lab/keycap/v1/build \-XPOST \-H"Authorization: Bearer ${YOUR_API_KEY}" \-H'Content-Type: application/json' \-d'{ "input_task_id": "019c9a4e-2b31-7f6a-8c44-5d2a87b3c1ef", "candidate_id": "0198c1b2-33f5-7d21-a4b7-8e9d0c1f2a3b", "options": { "base_model": "cherry-mx-1x1-r1", "head_size_mm": 23, "vertical_offset_mm": 0 } }'
Отримайте прототип або завдання на створення, вказавши дійсний id завдання. Шлях URL
має відповідати стадії завдання — запит завдання на створення через
/prototype/:id поверне 404, і навпаки.
Скасувати завдання keycap. Якщо завдання ще PENDING, кредити, використані під час створення, повертаються. Завдання, які вже IN_PROGRESS, скасовуються без повернення (працівник може вже використовувати ресурси). Завдання, які вже досягли кінцевого стану (SUCCEEDED, FAILED, CANCELED), не можуть бути скасовані.
Шлях URL повинен відповідати стадії завдання — DELETE на
/prototype/:buildId повертає 404.
Параметри шляху
Name
id
Type
path
Description
Унікальний ідентифікатор завдання keycap для скасування.
Повертає
Повертає 204 No Content у разі успіху з порожнім тілом.
Режими відмови
Name
400 - Bad Request
Description
Завдання вже знаходиться в кінцевому стані і не може бути скасоване.
Name
404 - Not Found
Description
Завдання не існує, належить іншому користувачу або його стадія не відповідає шляху URL.
Name
500 - Internal Server Error
Description
Виникла несподівана помилка на сервері під час скасування. Завдання могло бути скасоване або ні — перечитайте його, щоб підтвердити перед повторною спробою.
Транслюйте оновлення в реальному часі для завдання keycap через Server-Sent Events (SSE).
Шлях URL повинен відповідати етапу завдання — відкриття потоку на
/prototype/:buildId/stream видає єдине event: error корисне навантаження з
status_code: 404 і закриває потік.
Параметри
Name
id
Type
path
Description
Унікальний ідентифікатор для завдання keycap, яке потрібно транслювати.
Повертає
Повертає потік об'єктів завдань Keycap Prototype
або Keycap Build як
Server-Sent Events. Для завдань PENDING або IN_PROGRESS, потік відповіді
включатиме лише необхідні поля progress і status.
Отримайте пагінований список ваших завдань keycap для одного етапу. Шлях URL
вибирає етап — /prototype повертає завдання прототипу; /build
повертає завдання збірки. Завдання з іншого етапу не включаються в жодну з відповідей.
Параметри шляху
Name
stage
Type
path
Обов'язковий
Description
Або prototype, або build. Колекція повертає лише завдання,
етап яких відповідає URL — запит /prototype ніколи не повертає
завдання збірки і навпаки.
Параметри запиту
Name
page_num
Type
integer
за замовчуванням 1
Description
Номер сторінки для пагінації.
Name
page_size
Type
integer
за замовчуванням 10
Description
Обмеження розміру сторінки. Максимально дозволено 100 елементів.
Name
sort_by
Type
string
за замовчуванням -created_at
Description
Поле для сортування. Доступні значення:
+created_at: Сортувати за часом створення у зростаючому порядку.
-created_at: Сортувати за часом створення у спадаючому порядку.
Об'єкт Завдання Прототипу Клавіші — це робоча одиниця, яку Meshy відстежує для
генерації одного зображення готового дизайну клавіші з вихідного фото.
Вихід цього етапу зв'язується з етапом створення
через input_task_id плюс candidate_id.
Властивості
Name
id
Type
string
Description
Унікальний ідентифікатор для завдання. Хоча ми використовуємо k-сортований UUID для ідентифікаторів завдань як деталь реалізації, ви не повинні робити жодних припущень щодо формату id.
Name
type
Type
string
Description
Тип завдання. Значення — creative-lab-keycap-prototype.
Name
name
Type
string
Description
Ім'я завдання, надане при створенні завдання. Порожній рядок, якщо ім'я не було надано.
Name
status
Type
string
Description
Статус завдання. Можливі значення: PENDING, IN_PROGRESS, SUCCEEDED, FAILED, CANCELED.
Name
progress
Type
integer
Description
Прогрес завдання. Якщо завдання ще не розпочато, ця властивість буде 0. Після успішного завершення завдання це значення стане 100.
Name
created_at
Type
timestamp
Description
Мітка часу створення завдання, у мілісекундах.
Мітка часу представляє кількість мілісекунд, що минули з 1 січня 1970 року UTC, відповідно до
стандарту RFC 3339.
Наприклад, п'ятниця, 1 вересня 2023 року, 12:00:00 GMT представлена як 1693569600000. Це стосується
всіх міток часу в Meshy API.
Name
started_at
Type
timestamp
Description
Мітка часу початку завдання, у мілісекундах. Якщо завдання ще не розпочато, ця властивість буде 0.
Name
finished_at
Type
timestamp
Description
Мітка часу завершення завдання, у мілісекундах. Якщо завдання ще не завершено, ця властивість буде 0.
Name
expires_at
Type
timestamp
Description
Мітка часу, коли результат завдання закінчується, у мілісекундах.
Name
preceding_tasks
Type
integer
Description
Кількість попередніх завдань.
Значення цього поля має сенс лише якщо статус завдання — PENDING.
Name
task_error
Type
object
Description
Деталі помилки для невдалих завдань. Дивіться Помилки для повної довідки по об'єкту task_error.
Name
consumed_credits
Type
integer
Description
Кількість кредитів, спожитих цим завданням. Завдання, яке досягає SUCCEEDED, стягує повну суму за свій етап. Завдання, яке ніколи не створюється (помилка 4xx під час запиту, включаючи відхилення модерації), не стягується взагалі. Завдання, яке досягає FAILED, повертає 0 — плата повертається, включаючи асинхронне блокування модерації. Скасування через DELETE повертає плату лише поки завдання ще PENDING; завдання, яке вже IN_PROGRESS, залишається стягнутим, оскільки робота вже виконана.
Name
image_urls
Type
array of strings
Description
Завантажувана URL-адреса рендеру готового дизайну клавіші — як кандидат виглядає як готова клавіша. Містить один запис; image_urls[i] відповідає candidate_ids[i]. Порожній до тих пір, поки завдання не досягне SUCCEEDED. URL-адреса призначена лише для відображення; кінцева точка створення споживає candidate_ids, а не ці URL-адреси. Такий самий життєвий цикл URL-адреси, як у model_urls: підписаний, без заголовка Authorization, дійсний до expires_at, і стабільний при повторному читанні завдання.
Name
candidate_ids
Type
array of strings
Description
Непрозорі ідентифікатори кандидатів, паралельні image_urls. Передайте запис, що відповідає вашому обраному дизайну, як candidate_id запиту на створення. Не робіть жодних припущень щодо формату цих ідентифікаторів.
Об'єкт Завдання Побудови Клавіші — це робоча одиниця, за якою Meshy стежить для
генерації фінальної текстурованої 3D клавіші з успішного прототипного завдання та
обраного кандидата. Одна побудова запускає повний конвеєр — генерацію білого
моделя, автоматичне розміщення та різання, фарбування, складання та експорт.
Властивості
Name
id
Type
string
Description
Унікальний ідентифікатор для завдання.
Name
type
Type
string
Description
Тип завдання. Значення — creative-lab-keycap-build.
Name
name
Type
string
Description
Ім'я завдання, надане при створенні завдання. Порожній рядок, якщо ім'я не було надано.
Name
status
Type
string
Description
Статус завдання. Можливі значення: PENDING, IN_PROGRESS, SUCCEEDED, FAILED, CANCELED.
Name
progress
Type
integer
Description
Прогрес завдання. Якщо завдання ще не розпочато, ця властивість буде 0. Після успішного завершення завдання це значення стане 100.
Name
created_at
Type
timestamp
Description
Мітка часу створення завдання, в мілісекундах.
Name
started_at
Type
timestamp
Description
Мітка часу початку завдання, в мілісекундах.
Name
finished_at
Type
timestamp
Description
Мітка часу завершення завдання, в мілісекундах.
Name
expires_at
Type
timestamp
Description
Мітка часу, коли результат завдання закінчується, в мілісекундах.
Name
preceding_tasks
Type
integer
Description
Кількість попередніх завдань. Має сенс лише коли статус PENDING.
Name
task_error
Type
object
Description
Деталі помилки для невдалих завдань. Дивіться Помилки для повної довідки по об'єкту task_error.
Name
consumed_credits
Type
integer
Description
Кількість кредитів, спожитих цим завданням. Завдання, яке досягає SUCCEEDED, стягується повна сума за його етап. Завдання, яке ніколи не створюється (помилка 4xx під час запиту, включаючи відхилення модерації), не стягується взагалі. Завдання, яке досягає FAILED, повертає 0 — плата повертається, включаючи асинхронне блокування модерації. Скасування через DELETE повертає плату лише поки завдання ще PENDING; завдання, яке вже IN_PROGRESS, залишається стягнутим, оскільки робота вже виконана.
Name
model_urls
Type
object
Description
Завантажувані URL-адреси для згенерованих артефактів моделі. І GLB, і пакет OBJ експортуються в реальному масштабі міліметрів, Y-вгору, з передньою частиною клавіші, що дивиться +Z. Сітки називаються keycap-head і keycap-base; коли основа повертається до заповнення шаблоном, також присутня третя сітка keycap-base-interior для порожнини стебла. Не припускайте точно дві сітки.
Це підписані URL-адреси: отримуйте їх без заголовка Authorization. Вони залишаються дійсними до expires_at, що становить 3 дні після finished_at, і повторне читання завдання в цьому вікні повертає ідентичну URL-адресу, а не нову підписану. Завантажте та збережіть файли самостійно до цього часу — немає способу оновити прострочене посилання.
Name
glb
Type
string
Description
Завантажувана URL-адреса до фінальної текстурованої model.glb.
Name
obj_zip
Type
string
Description
Завантажувана URL-адреса до zip-пакету, що містить model.obj, model.mtl і текстурні PNG, на які посилається його MTL. Однотонна основа постачається лише з keycap-head.png; шаблонна основа також постачається з keycap-base.png.
Name
process_image_urls
Type
object
Description
Завантажувані URL-адреси для проміжних зображень процесу, згруповані за типом. Такий же життєвий цикл URL, як у model_urls: підписані, без заголовка Authorization, дійсні до expires_at і стабільні при повторному читанні завдання. Наразі випущені типи:
head_design — зображення дизайну обраного кандидата, яке було використано у побудові (завжди присутнє).
composite — рендер відображення завершеної клавіші обраного кандидата (присутнє, коли доступне).
base_canvas — полотно пофарбованої основи клавіші (присутнє, коли доступне).
Розглядайте набір ключів як відкритий; нові типи можуть бути додані без порушення сумісності.
Повний процес: створити прототип з фото, перевірити його до
SUCCEEDED, вибрати кандидата з candidate_ids, створити збірку з
цим кандидатом, перевірити збірку до SUCCEEDED, потім завантажити GLB та
пакет OBJ з model_urls.
Приклад програмно вибирає першого кандидата. У реальній інтеграції ви б відобразили запис image_urls кінцевому користувачу і дозволили б йому вибрати; обраний індекс відображається 1:1 на candidate_ids.
Повний процес
POST
/openapi/creative-lab/keycap/v1
#!/usr/bin/env bashset-euopipefail# Requires curl and jq. Point IMAGE_PATH at a local photo, or IMAGE_URL at a public one:# export MESHY_API_KEY=msy_...# export IMAGE_PATH=./portrait.jpg # or: export IMAGE_URL=https://...:"${MESHY_API_KEY:?export MESHY_API_KEY first}"if [[ -z"${IMAGE_PATH:-}"&&-z"${IMAGE_URL:-}" ]]; thenecho"export IMAGE_PATH (local file) or IMAGE_URL (public url) first">&2exit1fiBASE="https://api.meshy.ai/openapi/creative-lab/keycap/v1"AUTH="Authorization: Bearer $MESHY_API_KEY"# api METHOD URL [curl args...] -> prints the response body, non-zero on failure.# Note we do not use -f/--fail: it discards the body, and the body is the only# place the reason appears.api() {local method=$1 url=$2 out http_code bodyshift2 out=$(curl--silent--show-error--max-time60--write-out$'\n%{http_code}' \-X"$method""$url"-H"$AUTH""$@") ||return1 http_code=${out##*$'\n'} body=${out%$'\n'*}if ((http_code >=400)); thenecho"HTTP $http_code for $url: $body">&2return1fiprintf'%s'"$body"}# Each task gets its own 40-minute budget.poll() {local kind=$1 id=$2 delay=5 task_status deadline deadline=$(($(date+%s) + 2400))while:; doif (($(date +%s) >= deadline)); thenecho"gave up waiting for $kind $id">&2return1fi task_status=$(apiGET"$BASE/$kind/$id"|jq-r'.status')echo"$kind: $task_status"case"$task_status"inSUCCEEDED)return0 ;;FAILED|CANCELED)return1 ;;esacsleep"$delay" delay=$((delay*2>30?30:delay*2))done}# Build the request body in a file. A base64 data URI must never go on the# command line or into an exported variable - a photo of any real size will# exceed the OS argument limit.BODY=$(mktemp)trap'rm -f "$BODY"'EXITif [[ -n"${IMAGE_PATH:-}" ]]; then# Declare the real type: the API accepts JPEG, PNG and WebP.case"$(printf '%s' "${IMAGE_PATH##*.}" |tr 'A-Z' 'a-z')"inpng) MIME=image/png ;;webp) MIME=image/webp ;;*) MIME=image/jpeg ;;esac {printf'{"image_url":"data:%s;base64,'"$MIME"base64<"$IMAGE_PATH"|tr-d'\n'printf'"}' } >"$BODY"elseprintf'{"image_url":"%s"}'"$IMAGE_URL">"$BODY"fi# 1. Create the prototype taskPROTO_ID=$(apiPOST"$BASE/prototype" \-H'Content-Type: application/json'--data-binary@"$BODY"|jq-r'.result')# 2. Wait for the design renderpollprototype"$PROTO_ID"# 3. Pick a candidate (first one here; show image_urls to a user in production)CANDIDATE_ID=$(apiGET"$BASE/prototype/$PROTO_ID"|jq-r'.candidate_ids[0]')# 4. Create the build taskjq-n--argp"$PROTO_ID"--argc"$CANDIDATE_ID" \'{input_task_id: $p, candidate_id: $c}'>"$BODY"BUILD_ID=$(apiPOST"$BASE/build" \-H'Content-Type: application/json'--data-binary@"$BODY"|jq-r'.result')# 5. Wait for the model (a build usually takes 3-7 minutes)pollbuild"$BUILD_ID"# 6. Download the artifacts. These are signed URLs: no Authorization header,# and they stay valid for 3 days after the task finishes.TASK=$(apiGET"$BASE/build/$BUILD_ID")curl--silent--show-error--fail--max-time900 \-okeycap.glb"$(jq-r '.model_urls.glb' <<<"$TASK")"curl--silent--show-error--fail--max-time900 \-okeycap-obj.zip"$(jq-r '.model_urls.obj_zip' <<<"$TASK")"echo"Done: keycap.glb + keycap-obj.zip"