Редактор трехмерной графики Maya сегодня – это общепризнанный стандарт 3D графики в кино и телевидении. Книга Дэвида Гоулда представляет собой всестороннее описание возможностей программирования в этой системе. В начале книги читателя знакомят с базовыми принципами функционирования Maya. Постепенно уровень предлагаемой информации усложняется: уделяется повышенное внимание встроенному языку программирования MEL (Maya Embedded Language) и интерфейсу прикладного программирования (API) на языке C++. Детальное, основательное описание языка сценариев MEL сделано на таком высоком уровне, что книга с уверенностью может называться не только руководством по программированию Maya, но и руководством по программированию MEL. Ясность MEL и сходство его синтаксиса с языком C++ способствуют быстрому освоению этого языка даже начинающим пользователям Maya. В книге также приведены уникальные методики создания подключаемых модулей и нестандартных команд и узлов с использованием C++ API, что позволяет существенно расширить возможности пакета Maya. Все методики и приемы проиллюстрированы практическими примерами, в которых, при этом, проведен подробный анализ исходного кода. Руководство рекомендуется начинающим пользователям Maya, а также и подготовленным специалистам.
Написал для всех начинающих руководство по программированию на MEL, постарался максимально сжато и понятно объяснить часть своих знаний, которые можно сразу же применять для написания собственных скриптов. Интерфейса пока не касался, это примерно столько же писанины, да и сложновато будет все это сразу усвоить поэтому о нем напишу попозже. Пока руководство есть только на форуме, но скоро появится и на сайте.
У кого есть какие-то замечания, дополнения или пожелания пишитездесь или в теме на форуме, буду исправляться и дополнять. Кто захочет поделиться своими скриптами опять же только скажу спасибо, чуть позже выложу те скрипты, что сам писал, их у меня правда не так уж и много но тем не менее
Когда вы начнете работу со скриптами в Maya, вам понадобится доступ к документации по командам. В MEL существуют сотни, возможно даже тысячи команд, и каждая из них имеет большое количество параметров. Запомнить их все не представляется возможным, поэтому вам постоянно понадобится обращаться к документации. Есть несколько способов, как получить к ней доступ. Если мы перейдем в Script Editor, то вы увидите, что здесь можно получить подсказку о том, какие команды доступны.
Если вы начнете печатать в окне ввода буквы «mo» из слова move, то вы можете получить подсказку. Вам необходимо нажать клавиши Ctrl и пробел, после этого вы увидите все команды, название которых начинается со слога «mo», вы можете прокрутить этот список и найти в нем move. Затем вам нужно выделить его и нажать клавишу Enter. И теперь выбранное слово стало командой. Как мы уже упоминали ранее, каждая команда имеет большое количество опций.
Так что мы можем найти опции для команды, просмотрев документацию для нее. Нам нужно для этого выделить команду, кликнуть по ней правой клавишей мышки, и зайти в раздел Command Documentation (командная документация). Когда мы сделали это, то у нас откроется окно браузера с документацией для этой команды. В данном случае — для команды Move. Как вы можете видеть, перед нами оказываются все варианты различных опций для выбранной команды. У нас есть absolute (-a) и relative (-r), которые мы уже использовали.
У нас есть опция reflection (-rfl) для отражения, и много другого. Если мы пробежимся по этому списку, то сможем найти некоторые опции, которые будут очень полезными при работе со скриптом. И это полная документация для этой команды. Теперь, если вы хотите получить более общую документацию, вы можете вернуться в Script Editor, затем в разделе «Help» вы можете выбрать «MEL Command Reference». При этом также откроется окно веб-браузера со всеми командами и документацией.
Итак, если вы хотели найти, к примеру, команду Move, вы могли бы прокрутить этот список в поисках нужной вам документации. Но вы можете также найти здесь и другие типы движений, такие как PixelMove и так далее. Если вы просмотрите список команд, то сможете найти ту, которая необходима вам. В дополнение, вы можете попробовать поискать по категориям или типам команд, таким как General, Modeling, Animation, Rendering, Effects и так далее. Этот способ позволяет сузить поисковые рамки до категории, к которой ваша команда принадлежит.
Итак, теперь вы познакомились с документацией MEL в Maya. Вы можете и дальше углубиться в написание скриптов и использовать полученные знания при необходимости.
Mel
Mel is an asychronous event-driven jobs processing engine designed to scale. Mel simplifies jobs management by abstracting away the nuances of scheduling and running jobs.
In Mel, a scheduled job is called a task. A single job may be scheduled in multiple ways, yielding multiple tasks from the same job.
Mel schedules all tasks in Redis, as a set of task id
s sorted by their times of next run. For recurring tasks, the next run is scheduled in Redis right after the current run completes.
This makes Redis the source of truth for schedules, allowing to easily scale out Mel to multiple instances (called workers), or replace or stop workers without losing schedules.
Mel supports bulk scheduling of jobs as a single atomic unit. There’s also support for sequential scheduling to track a series of jobs and perform some action after they are all complete.
Types of tasks
-
Instant Tasks: These are tasks that run only once after they are scheduled, either immediately or at some specified time in the future.
-
Periodic Tasks: These are tasks that run regularly at a specified interval. They may run forever, or till some specified time in the future.
-
Cron Tasks: These are tasks that run according to a specified schedule in Unix Cron format. They may run forever, or till some specified time in the future.
Installation
-
Add the dependency to your
shard.yml
:dependencies: mel: github: GrottoPress/mel
-
Run
shards update
-
Require and configure Mel:
# ->>> src/app/config.cr # ... require "mel" require "../jobs/**" Mel.configure do |settings| settings.error_handler = ->(error : Exception) { puts error.message } settings.redis_pool_size = 25 settings.redis_url = "redis://localhost:6379/0" settings.redis_key_prefix = "mel" settings.timezone = Time::Location.load("Africa/Accra") end Log.setup(Mel.log.source, :info, Log::IOBackend.new) Redis::Connection::LOG.level = :info # ...
Usage
-
Define job:
# ->>> src/jobs/do_some_work.cr struct DoSomeWork include Mel::Job # <= Required def initialize(@arg_1 : Int32, @arg_2 : String) end # <= Instance vars must be JSON-serializable # (Required) # # Main operation to be performed. # Called in a new fiber. def run # << Do work here >> end # Called in the main fiber, before spawning the fiber # that calls the `#run` method above. def before_run # ... end # Called in the same fiber that calls `#run`. # `success` is `true` only if the run succeeded. def after_run(success) if success # ... else # ... end end # Called in the main fiber before enqueueing the task in # Redis. def before_enqueue # ... end # Called in the main fiber after enqueueing the task in # Redis. `success` is `true` only if the enqueue succeeded. def after_enqueue(success) if success # ... else # ... end end end
-
Schedule job:
-
Run job now:
# ->>> src/app/some_file.cr DoSomeWork.run(arg_1: 5, arg_2: "value") # <= Alias: DoSomeWork.run_now(...)
-
Run job after given delay:
# ->>> src/app/some_file.cr DoSomeWork.run_in(5.minutes, arg_1: 5, arg_2: "value")
The given
Time::Span
can be negative. Eg:DoSomeWork.run_in(-5.minutes, ...)
. This may be useful for prioritizing certain tasks. -
Run job at specific time:
# ->>> src/app/some_file.cr DoSomeWork.run_at(10.minutes.from_now, arg_1: 5, arg_2: "value")
The specified
Time
can be in the past. Eg:DoSomeWork.run_at(-10.minutes.from_now, ...)
. This may be useful for prioritizing certain tasks. -
Run periodically:
# ->>> src/app/some_file.cr DoSomeWork.run_every(10.minutes, for: 1.hour, arg_1: 5, arg_2: "value")
Instead of
for:
, you may usetill:
and specify aTime
. Leave those out to run forever. -
Run on a Cron schedule:
# ->>> src/app/some_file.cr DoSomeWork.run_on("0 */2 * * *", for: 6.hours, arg_1: 5, arg_2: "value")
Instead of
for:
, you may usetill:
and specify aTime
. Leave those out to run forever.
The
DoSomeWork.run_*
methods accept the following additional arguments:retries
: Number of times to attempt a task after it fails, before giving up. Default:2
. Eg:DoSomeWork.run(... retries: 1, ...)
. A task fails when any exception is raised during run.
-
-
Start Mel:
-
As its own process (compiled separately):
# ->>> src/worker.cr require "mel/worker" require "./app/**" Mel.configure do |settings| settings.batch_size = 10 settings.poll_interval = 3.seconds settings.worker_id = ENV["WORKER_ID"].to_i end Mel.start # <= Blocks forever, polls for due tasks and runs them. # <= You may stop Mel by sending `Signal::INT` or `Signal::TERM`. # <= Mel will wait for all running tasks to complete before exiting.
-
As part of your app (useful for testing):
# ->>> spec/spec_helper.cr # ... require "mel/spec" Mel.configure do |settings| settings.batch_size = -1 settings.poll_interval = 1.millisecond settings.worker_id = 1 end Spec.before_each { Mel::Task::Query.truncate } Spec.after_suite do Mel.stop Mel::Task::Query.truncate end # <= `Mel.stop` waits for all running tasks to complete before exiting Mel.start_async # ...
-
-
Configure compile targets:
# ->>> shard.yml # ... targets: app: main: src/app.cr worker: main: src/worker.cr # ...
Job templates
A job’s .run_*
methods allow scheduling that single job in multiple ways. However, there may be situations where you need to schedule a job the same way, every time.
Mel comes with Mel::Job::Now
, Mel::Job::In
, Mel::Job::At
, Mel::Job::Every
and Mel::Job::On
templates to do exactly this:
# Define job struct DoSomeWorkNow include Mel::Job::Now # <= Required def initialize(@arg_1 : Int32, @arg_2 : String) end # (Required) def run # << Do work here >> end end # Schedule job DoSomeWorkNow.run(arg_1: 5, arg_2: "value") # <= Alias: `DoSomeWorkNow.run_now(...)`
# Define job struct DoSomeWorkIn include Mel::Job::In # <= Required def initialize(@arg_1 : Int32, @arg_2 : String) end # (Required) def run # << Do work here >> end end # Schedule job DoSomeWorkIn.run_in(10.minutes, arg_1: 5, arg_2: "value")
# Define job struct DoSomeWorkAt include Mel::Job::At # <= Required def initialize(@arg_1 : Int32, @arg_2 : String) end # (Required) def run # << Do work here >> end end # Schedule job DoSomeWorkAt.run_at(Time.local(2021, 6, 9, 5), arg_1: 5, arg_2: "value")
# Define job struct DoSomeWorkEvery include Mel::Job::Every # <= Required def initialize(@arg_1 : Int32, @arg_2 : String) end # (Required) def run # << Do work here >> end end # Schedule job DoSomeWorkEvery.run_every(2.hours, arg_1: 5, arg_2: "value") # <= Overload: `.run_every 2.hours, for: 5.hours` # <= Overload: `.run_every 2.hours, till: 9.hours.from_now`
# Define job struct DoSomeWorkOn include Mel::Job::On # <= Required def initialize(@arg_1 : Int32, @arg_2 : String) end # (Required) def run # << Do work here >> end end # Schedule job DoSomeWorkOn.run_on("0 8 1 * *", arg_1: 5, arg_2: "value") # <= Overload: `.run_on "0 8 1 * *", for: 100.weeks` # <= Overload: `.run_on "0 8 1 * *", till: Time.local(2099, 12, 31)`
A template excludes all methods not relevant to that template. For instance, calling .run_every
or .run_now
for a Mel::Job::At
template won’t compile.
All other methods and callbacks usable in a regular job may be used in a template, including before_*
and after_*
callbacks.
You may include
more than one template in a single job. For instance, including Mel::Job::At
and Mel::Job::Every
in a job means you can call .run_at
and .run_every
methods for that job.
Additionally, Mel comes with two grouped templates: Mel::Job::Instant
and Mel::Job::Recurring
.
Mel::Job::Instant
is equivalent to Mel::Job::Now
, Mel::Job::In
and Mel::Job::At
combined. Mel::Job::Recurring
is the equivalent of Mel::Job::Every
and Mel::Job::On
combined.
Mel::Job
is itself a grouped template that combines all the other templates.
Specifying task IDs
You may specify an ID whenever you schedule a new job, thus: DoSomeWork.run_*(... id: "1001", ...)
. If not specified, Mel automatically generates a unique dynamic ID for the task.
Dynamic task IDs may be OK for triggered jobs (jobs triggered by some kind of user interaction), such as a job that sends an email notification whenever a user logs in.
However, there may be jobs that are scheduled unconditionally when your app starts (global jobs). For example, sending invoices at the beginning of every month. You should specify unique static IDs for such tasks.
Otherwise, every time the app (re)starts, jobs are scheduled again, each time with a different set of IDs. Redis would accept the new schedules because the IDs are different, resulting in duplicated scheduling of the same jobs.
This is particularly important if you run multiple instances of your app. Hardcoding IDs for global jobs means that all instances hold the same IDs, so cannot reschedule a job that has already been scheduled by another instance.
A task ID may be a mixture of static and dynamic parts. For instance, you may include the current month and year for a global job that runs once a month, to ensure it is never scheduled twice within the same month.
Bulk scheduling
A common pattern is to break up long-running tasks into smaller tasks. For example:
struct SendAllEmails include Mel::Job def initialize(@users : Array(User)) end def run @users.each { |user| send_email(user) } end private def send_email(user) # Send email end end # Schedule job users = # ... SendAllEmails.run(users: users)
The above job would run in a single fiber, managed by whichever worker pulls this task at run time. This could mean too much work for a single worker if the number of users is sufficiently large.
Moreover, some mails may be sent multiple times if the task is retried as a result of failure. Ideally, jobs should be idempotent, and as atomic as possible.
The preferred approach is to define a job that sends email to one user, and schedule that job for as many users as needed:
struct SendAllEmails include Mel::Job def initialize(@users : Array(User)) end def run return if @users.empty? # Pushes all jobs atomically, at the end of the block. # # There's also `redis#pipeline(&)`, if you do not need the atomicity. redis.multi do |redis| # Pass `redis` to `.run_*`. @users.each { |user| SendEmail.run(redis: redis, user: user) } end end struct SendEmail include Mel::Job def initialize(@user : User) end def run send_email(@user) end private def send_email(user) # Send email end end end # Schedule job users = # ... SendAllEmails.run(users: users) # <= Any `.run_*` method could be called here, as with any job.
Sequential scheduling
Bulk scheduling works OK as a fire-and-forget mechanism. However, you may need to keep track of a series of jobs as a single unit, and perform some action only after the last job is done.
This is where sequential scheduling comes in handy. Mel‘s event-driven design allows chaining jobs, by scheduling the next after the current one completes:
struct SendAllEmails include Mel::Job def initialize(@users : Array(User)) end def run @users[0]?.try do |user| send_email(user) # <= Send first email end end def after_run(success) return unless success if @users[1]? self.class.run(users: @users[1..]) # <= Schedule next email else # <= All emails have been sent # Do something end end private def send_email(user) # Send email end end # Schedule job users = # ... SendAllEmails.run(users: users)
Although the example above involves a single job, sequential scheduling can be applied to multiple different jobs, each representing a step in a workflow, with each job scheduling the next job in its #after_run
callback:
struct SomeJob include Mel::Job def run # Do something end def after_run(success) SomeStep.run if success end struct SomeStep include Mel::Job def run # Do something end def after_run(success) SomeOtherStep.run if success end end struct SomeOtherStep include Mel::Job def run # Do something end def after_run(success) # All done; do something end end end
Tracking progress
Mel provides a progress tracker for jobs. This is particularly useful for tracking multiple jobs representing a series of steps in a workflow:
# ->>> src/app/config.cr # ... Mel.configure do |settings| settings.progress_expiry = 1.day end # ...
# ->>> src/jobs/some_job.cr struct SomeJob include Mel::Job def initialize @progress = Mel::Progress.start(id: "some_job", description: "Awesome job") end # ... def after_run(success) return @progress.fail unless success SomeStep.run(progress: @progress) @progress.move(50) # <= Move to 50% end struct SomeStep include Mel::Job::Now def initialize(@progress : Mel::Progress) end # ... def after_run(success) return @progress.fail unless success SomeOtherStep.run(progress: @progress) @progress.move(80) # <= Move to 80% end end struct SomeOtherStep include Mel::Job::Now def initialize(@progress : Mel::Progress) end # ... def after_run(success) return @progress.fail unless success @progress.succeed # <= Move to 100% end end end # Schedule job SomeJob.run # Track progress # # This may, for instance, be used in a route in a web application. # Client-side javascipt can query this route periodically, and # show response using a progress tracker UI. # report = Mel::Progress.track("some_job") report.try do |_report| _report.description _report.id _report.value _report.failure? _report.moving? _report.success? _report.started? _report.ended? end
You may delete progress data in specs thus:
# ->>> spec/spec_helper.cr # ... Spec.before_each do # ... Mel::Progress::Query.truncate # ... end Spec.after_suite do # ... Mel::Progress::Query.truncate # ... end # ...
Jobs security
A Mel worker waits for all running tasks to complete before exiting, if it received a Signal::INT
or a Signal::TERM
, or if you called Mel.stop
somewhere in your code. This means jobs are never lost mid-flight.
Jobs are not lost even if there is a force shutdown of the worker process, since Mel does not delete a task from Redis until it is complete. The worker can pick off where it left off when it comes back online.
Mel relies on the worker_id
setting to achieve this. Each worker, therefore, must set a unique, static integer ID, so it knows which pending tasks it owns.
Once a task enters the pending state, only the worker that put it in that state can run it. So if you need to take down a worker permanently, ensure that it completes all pending tasks by sending the appropriate signal.
Scaling out
Because each worker requires it’s own unique .worker_id
, autoscaling as used in classic distributed architectures should not be used, since auto-scaled replicas would inherit the same configuration as the original instance.
This would lead to multiple workers using the same .worker_id
, which could result in pending jobs being run multiple times; once each for each replica that starts up.
Instead, it is recommended that a new service be registered for each worker that is to be deployed, and the appropriate .worker_id
set for each.
-
An example using
Procfile
:# ->> Procfile # ... worker_1: export WORKER_ID=1 && ./bin/worker worker_2: export WORKER_ID=2 && ./bin/worker worker_3: export WORKER_ID=3 && ./bin/worker # ...
-
An example using docker compose for swarm:
# ->> docker-compose.yml # ... services: worker_1: command: ./bin/worker environment: WORKER_ID: "1" deploy: replicas: 1 worker_2: command: ./bin/worker environment: WORKER_ID: "2" deploy: replicas: 1 worker_3: command: ./bin/worker environment: WORKER_ID: "3" deploy: replicas: 1 # ...
Smart polling
Mel‘s batch_size
setting allow setting a limit on the number of due tasks to retrieve and run each poll, and, consequently, the number of fibers spawned to handle those tasks.
If the setting is a positive integer N
, Mel would pull and run N
due tasks each poll.
If it is a negative integer -N
(other than -1
), the number of due tasks pulled and ran each poll would vary such that the total number of running tasks would not be greater than N
.
-1
sets no limits. Mel would pull as many tasks as are due each poll, and run all of them.
Integrations
Carbon mailer
Link: https://github.com/luckyframework/carbon
-
Require
mel/carbon
, after your emails:# ->>> src/app.cr # ... require "emails/base_email" require "emails/**" require "mel/carbon" # ...
-
Set up base email:
# ->>> src/emails/base_email.cr abstract class BaseEmail < Carbon::Email # ... include JSON::Serializable # ... end
-
Configure deliver later strategy:
# ->>> config/email.cr BaseEmail.configure do |settings| # ... settings.deliver_later_strategy = Mel::Carbon::DeliverLaterStrategy.new # ... end
Development
Create a .env.sh
file:
#!/bin/bash export REDIS_URL='redis://localhost:6379/0'
Update the file with your own details. Then run tests with source .env.sh && crystal spec -Dpreview_mt
.
Contributing
- Fork it
- Switch to the
master
branch:git checkout master
- Create your feature branch:
git checkout -b my-new-feature
- Make your changes, updating changelog and documentation as appropriate.
- Commit your changes:
git commit
- Push to the branch:
git push origin my-new-feature
- Submit a new Pull Request against the
GrottoPress:master
branch.
Introduction
This tutorial is an introduction into one of the scripting languages in Maya: MEL — Maya Embedded Language. This tutorial covers:
Contents
- 1 Introduction
- 2 The Script Editor
- 3 MEL Commands
- 4 Command reference
- 5 Syntax
- 5.1 Errors
- 6 Variables
- 6.1 Types
- 6.2 Declaring variables
- 6.3 Using variables in calculations
- 6.4 printing variable values
- 7 Control structures
- 7.1 Comparisons (tests)
- 7.2 IF
- 7.3 WHILE loop
- 7.3.1 iteration
- 7.3.2 infinite loops
- 7.4 FOR loop
- 7.5 Loop in loop
- 8 Exercise
This tutorial uses a pavilion as an example to exercise the topics covered in this tutorial. At the end of the tutorial you can finish the pavilion to check the knowledge you obtained.
The Script Editor
You can enter commands in Maya in the Script editor. You can open the Script Editor through Window > General Editors > Script Editor. This interface is used to enter commands and scripts, but the history panel also provides feedback.
If the Script Editor is opened, you’ll see a window similar to the image above. The windows is divided into two parts: The top part is the history, the bottom part is where you can type. The history probably already contains some output. You’ll notice that most actions in Maya result in output in the history panel. It shows the commands that are being executed and the results of those commands.
If make a polygon cube for example, we’ll see something like this:
polyCube -w 1 -h 1 -d 1 -sx 1 -sy 1 -sz 1 -ax 0 1 0 -tx 1 -ch 1; // Result: pCube1 polyCube1 //
The first line is the command being executed. It shows the name of the command and several options, called flags for that command. You can recognize a flag by the dash (-) prefix, followed by one or more values.
The second line is the result of the commands. In this case it shows the name of the cube that has been created.
However, the functionality of the built-in Script Editor in Maya is limited. As soon as your scripts gets a little more complex, it’s recommended to use a different editor. (The script editor in Maya 8.5 is improved dramatically, but still an external editor can be nice). One of the possibilities is Crimson Editor. If you save your file with the .mel extension, Crimson will understand it’s a MEL-script and will ‘color code’ your script to improve readability, for instance: commands and variables get their own color. The extension .mel isn’t in the standard list of file types when you save a file in Crimson. You need to suffix (type) .mel yourself.
You can use Copy-Paste to get your script from Crimson to the Script Editor van Maya to execute it, or you can save it in Crimson first (always a good idea) and the use File > Open Script in the Script Editor.
MEL Commands
You can enter MEL commands in the Script Editor. If you want to execute your command (or commands), you need to press Ctrl + enter (or the enter of your numpad). When you just press enter without ctrl, the cursor will jump to a new line.
Exercise:
Let’s take a look at some commands: Open the Script Editor. To clear the History in the top panel, press the button (or select Edit > Clear History), so we have some overview.
Now make a polygon cube the normal way: Create > Polygon Primitives > Cube (Make sure you disable Interactive creation in the Polygon Primitives menu first). You should see the corresponding command that is executed appear in the history.
The history shows you the command that was executed — polyCube — and directly beneath the result of that command — in this case the name of the object that was created pCube1. Note that the result line starts with //. Later on we will see that this denotes a comment.
A script really is just a list of commands which are executed one by one (from top to bottom) once you execute the script (Ctrl + enter). In a bit you’ll see various ways to influence which commands in your script are executed (which should be skipped or repeated).
Command reference
The list of commands may seem endless at first and the number of options for each command is even longer. It’s madness to know each and every one by heart and it’s completely useless to try. The Maya help contains a complete Command Reference, which describes every command and the flags for that command. You can open the Command Reference directly through the Help menu: Help > MEL Command Reference.
Exercise:
Let’s go back to our polyCube example. Directly after the command you see some more text:
polyCube -w 1 -h 1 -d 1 -sx 1 -sy 1 -sz 1 -ax 0 1 0 -tx 1 -ch 1;
These are the options for the command. They determine the behavior of the command, such as the size of the cube that is created in this example. A command can have zero or more options, each specified by a name prefixed by a dash. The first option in this example is the -w option. The Command Reference learns us that this specifies the width of the cube.
Now try some other actions in Maya, such as the creation of other geometry, moving and scaling objects, selecting objects, the create polygon tool, etc and see which commands are executed. Find the commands in the Command Reference and try to determine the purpose of some of the options.
Most of the commands have pretty sensible default values for most of the options. The default values are documented in the Command Reference. If the default value is ok, you don’t need to specify it. This is useful when we start using the commands in our own script; it saves you a lot of typing.
Exercise:
Let’s try to create a cube by typing the command ourselves and move it around. You may start with a new, empty scene, at this point.
Position your cursor in the bottom part of the Script Editor, the Input field, and enter the following command:
polyCube;
Note that MEL is case sensitive, so you need to enter the command with a capital C in polyCube. When you press Ctrl + enter the command is executed. A default cube should be created and the command disappears from the input field. Note that this cube gets automatically selected.
Now try to move the selected object:
move -r 0 4 -2.6;
Your cube should be moved. The option -r denotes a relative translation (see Command Reference). The object is in this case translated by: 4 units in the y and -2,6 in z. There is no translation in x.
An absolute translation would move the pivot point of the object to the specified coordinates.
Try to move the cube relatively and absolutely yourself and try to scale and rotate it…
Syntax
Computers aren’t too smart. They need very strict rules in order to understand the commands in your script. Therefore some rules should be applied when writing a script. Those rules are called syntax. You can compare it to the grammar of a language.
- everything is case sensitive. The command polycube won’t work, polyCube will
- Each command is terminated by a ; at the end of the line (after the flags, if applicable). Only if you enter just a single command, you may omit the semicolon , but that is not recommended.
- Comments (which shouldn’t be executed) are preceded by // and end at the end of the line.
- Large blocks of comments (multiple lines) are placed between /* [comment] */
Especially when your script is getting more complex or very long, it’s of vital importance to use comments. It helps you (and others) to keep an overview and it makes it easier to understand your script if you use it at any later date. It helps you to understand the structure and find out how the script works.
Errors
By default, Maya doesn’t tell you in which line number the error occurs. You can enable this by History > Line numbers in errors. Very useful.
Common causes for (syntax) errors:
- Cannot find procedure «name«
- Maya doesn’t know this command. Probably you made a typo; note that everything is case sensitive.
- Syntax Error
- There is a serious problem with the syntax. Probably you forgot a semicolon (;), or bracket somewhere. The cause of the error doesn’t need to be in the line number Maya reports (it reports the line where it detects a problem). Often the problem is in the line before the line where the error occurs.
- Invalid flag
- —name: The option (flag) you specified is not recognized.
- Invalid argument
- The value(s) you specified for one or more of the options of the command are invalid. This can happen when you forget to specify a value of when you specify the wrong type of value (text instead of a number, for example).
You can find more information on MEL errors in MEL Troubleshooting.
Variables
If you enter all commands with all values (numbers, sizes, etc) manually, a script isn’t really too useful. It’s very likely that the ‘normal’ interface will prove to be faster or at least more convenient. Scripting gets powerful when you use variables.
You may know variables (x, y, t) from math; variables are labels that refer to a value that may not be known at a given time. They correspond to a value (at the time of execution). A variable has three key properties:
- a variable has a name
- a variable has a value
- a variable has a type
Variable names start with a $ to denote a variable. Variable names may not contain spaces, points or any special character. They can’t begin with a number either.
example: $height (variable with the name height)
A variable can be assigned a value:
$height = 6.2;
Once that command has been executed, the variable $height has a value of 6.2. The computer will keep this value for height in memory until you assign a new value (or you shutdown Maya).
This variable can now be used in any command, for example:
polyCube -h $height;
This will create a cube. As soon as the command is executed, Maya will read the value of $height from memory and use that in this command. If this example is executed directly after the previous example, the value will be 6.2.
Exercise:
Take a look at the example below and analyze what happens when each line is executed in order:
$height = 6.2; polyCube -h $height; $height = 4; polyCube -h $height;
All in all not too exciting yet, but we’re getting there. This is the first step in writing more complex (and useful) scripts.
Types
There are a few types of variables. The type of a variable determines what kind of values can be stored. The most important types are:
- float
- numbers with a decimal precision
- integer (int)
- numbers without decimals
- string
- text
- vector
- vectors (we won’t use them for now)
As soon as a variable is created and is set to a specific type, it can only store values of that type. If you try to assign a value of a different type, you may get an error or warning or you can get strange results. Only assigning a integer number to a float variable will work without errors; if you assign a decimal number to a variable with type int, all decimals will be ignored.
You cannot change the type of a variable. You need to restart Maya to start with a clean slate.
Declaring variables
To prevent any confusion or errors from occurring related to the type of a variable, it is recommended to declare a variable on first use. This will look something like this:
int $integer_number;
float $name_of_my_var;
Declaring a variable should be done once for each variable in each script. To keep track of the variables you’ve used (declared) so far, it’s recommended to place them all at the top of your script. Use comments to describe the use of variables.
You can declare a variable and assign it a value in one go:
int $number = 4;
float $length = 25.3;
string $text = «a piece of text»;
Using variables in calculations
You can do calculations with your variables where you apply them. The calculation will be done and the result is used in the command.
Exercise:
Create a new, empty scene. Enter the following script in the script editor
float $gridsize = 0.3; polyCube -w (4*$gridsize); polyCube -w (6*$gridsize);
If you’ve got this working, adapt the script to make two cubes: one with a width of 2 times the gridsize, a height of half a gridsize and en depth of 6 times the gridsize. The other cube should have a width of 2 times the gridsize + 2, a height of 5 and a depth of gridsize to the power of two. The variable gridsize should have a value of 2.2. If that works (make sure you copy your script before executing), change the variable gridsize to 0.5 to see if it still works.
Notice the round brackets around the calculation (4 * $gridsize). The are compulsory to ensure that Maya will first determine the result of the calculation ‘value of $gridsize times four’ and then use the result of that calculation in the command (polyCube).
printing variable values
…
Control structures
Before was mentioned that a script, a list of commands, was executed line by line (sequentially) from start to end. But other than the default of executing a script sequentially, you can influence the ‘flow’ of the execution of your script. You might want to execute a part of your script only in special cases (determined by some condition you set). It is also possible to repeat the execution of one or more lines several times, without putting the command in your script multiple times (copy paste).
This is achieved by the so called control structures. These structures determine the execution of your script. The next section covers the IF-structure and two repetitive structures: the WHILE-loop and the FOR-loop.
Comparisons (tests)
Most control structures use some form of comparison, called tests. Two values are compared and tested. The test will yield true or false. You can compare the value of a variable to a fixed value, but you can also test (the values of) two variables or some form of calculation. Some examples:
- $a == 4
- equal to; see ! below
- $a < 5
- less than
- $a > $b
- greater than
- 5 >= $b
- greater than or equal to
- $a <= $b
- less than or equal to
- $a != (2*$b)
- not equal to
! Notice: In comparisons, there is a fundamental difference between == and =
= denotes assigning a value to a variable and will always result in true.
== denotes the comparison of two values. It will only result in true when both values are equal.
IF
if ( $a < $b ) {
polyCube -h $a;
}
In this example the value of variable $a is compared to the value of variable $b. If (and only if) the value of $a is less than the value of $b (the result of the test is true), the command (or commands) between the curly brackets { … } is executed.
This structure can be expanded to an If-else structure:
if ( $a < $b ) {
polyCube -h $a;
} else {
polyCube -h 5;
move -r 0 4 0;
}
The if-part is equal to the example before, but here we’ve added a part: the keyword else and a second block enclosed by curly brackets. This second block is executed when the test is not true, false.
WHILE loop
float $distance = 0;
while ( $distance < 50 ) {
polyCube;
move -r $distance 0 0;
$distance = $distance + 7.2;
}
In a While-loop, the command(s) between the curly brackets is executed repeatedly for as long as the test results true.
iteration
The While structure will do the test and only if it results true, the commands between curly brackets are executed. This is called an iteration. After a single iteration, the structure will repeat the events. It will test the expression and if it results true (again), it will execute the commands. If the expression results false it will terminate the entire structure. A loop has zero or more iterations, but in most cases one or more.
infinite loops
If none of the parameters of the expression (comparison) change, the loop will be repeated forever as the test will return true each and every time. If an infnite loop is executed, you need to terminate Maya through the task manager. Not saving any of the files that are open.
Warning: Make sure you don’t create an infinite loop and save your script before executing it!
So you need to incorporate a command in a While-loop that influences the test that determines if a next iteration is to be executed.
FOR loop
for ( $i = 1; $i < 10; $i = $i + 1 ) {
polyCube;
move -r ($i * 3) 0 0;
}
The For-loop is a variation to the While-loop. It is often used when ‘something’ needs to be repeated ‘a (counted) number of times’. This type of loop uses a counter that keeps track of the number of iterations.
In the For-loop there are three components between the round brackets separated by semicolons: initialization, condition and iteration.
- Initialization
- $i = 1
- A variable $i that is used as a counter is set to its starting value
- Condition
- $i < 10
- The test that determines whether the loop is continued to be executed. In the example $i must be less than 10.
- Iteration
- $i = $i + 1
- In each iteration this command is executed after the command between curly brackets have been executed. In the example the value of $i is increased by 1.
You can do the exact same thing using a While-loop. It would look something like this:
int $i = 1;
while ( $i < 10 ) {
polyCube;
move -r ($i * 3) 0 0;
$i = $i + 1;
}
Both options are correct, only the For-loop will be a more clear in most cases. Especially when the commands between curly brackets is very long.
Loop in loop
Loop in Loop
you can create a new loop within the curly brackets of a loop. An example of the application of a loop-in-a-loop is when you want to create a grid of objects:
for ( $i=0; $i < 10; $i++ ) {
for ( $j=0; $j < 5; $j++ ) {
polyCube;
move ($i * 3) 0 ($j * 2);
}
}
$i++: $i++ is the same as $i = $i + 1. It’s a shorthand notation.