File: morse.in

package info (click to toggle)
morse-simulator 1.4-2
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 186,952 kB
  • ctags: 3,992
  • sloc: ansic: 108,311; python: 25,692; cpp: 786; makefile: 120; xml: 34; sh: 7
file content (840 lines) | stat: -rwxr-xr-x 31,940 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
#! @PYTHON_EXECUTABLE@
import sys
import os
import platform
import logging
## Configuring default logging
try:
    from morse.core.ansistrm import ColorizingStreamHandler
    log_handler = ColorizingStreamHandler()
except ImportError:
    log_handler = logging.StreamHandler()

logger = logging.getLogger('morse')

formatter = logging.Formatter("* %(message)s\n")
log_handler.setFormatter(formatter)

logger.addHandler(log_handler)
logger.setLevel(logging.INFO)
##

try:
    sys.path.append("@PYTHON_INSTDIR@")
    from morse.version import VERSION
    from morse.environments import Environment
    from morse.core.exceptions import MorseEnvironmentError
except ImportError as detail:
    logger.error("Unable to continue: '%s'\nVerify that your PYTHONPATH variable points to the MORSE installed libraries" % detail)
    sys.exit()

import subprocess
import shutil
import glob
import re
import tempfile

try:
    import configparser
    import argparse
except ImportError as exn:
    logger.error("Cannot import %s" % exn)
    sys.exit()

#Blender version must be egal or bigger than...
MIN_BLENDER_VERSION = "2.65"
#Blender version must be smaller than...
LAST_TESTED_BLENDER_VERSION = "2.77"

#Unix-style path to the MORSE default scene and templates, within the prefix
DEFAULT_SCENE_PATH = "share/morse/data/morse_default.blend"
DEFAULT_SCENE_AUTORUN_PATH = "share/morse/data/morse_default_autorun.blend"

DEFAULT_SCENE_NAME = "default.py"

#MORSE prefix (automatically detected)
morse_prefix = ""
#Path to Blender executable (automatically detected)
blender_exec = ""
#Path to MORSE default scene (automatically detected)
default_scene_abspath = ""

class MorseError(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)

def retrieve_blender_from_path():
    try:
        blenders_in_path = subprocess.Popen(
                                ['which', '-a', 'blender'],
                                stdout=subprocess.PIPE).communicate()[0]
        res = blenders_in_path.decode().splitlines()
    except OSError:
        return []

    return res

def check_blender_version(blender_path):
    version = None
    try:
        version_str = subprocess.Popen(
                                [blender_path, '--version'],
                                stdout=subprocess.PIPE).communicate()[0]
        for line in version_str.splitlines():
            line = line.decode().strip()
            if line.startswith("Blender"):
                version = line.split()[1] + '.' + line.split()[3][:-1]
                break
    except OSError:
        return None

    if not version:
        logger.error("Could not recognize Blender version!" \
                     "Please copy the output of " +  blender_path + \
                     " --version and send it to morse-dev@laas.fr.")
        return None

    logger.info("Checking version of " + blender_path + "... Found v." + version)

    if version.split('.') < MIN_BLENDER_VERSION.split('.'):
        return False
    if version.split('.') > LAST_TESTED_BLENDER_VERSION.split('.'):
        logger.warning("Version %s of Blender is untested but should work" % version)
    return version

def check_blender_python_version(blender_path):
    """ Creates a small Python script to execute within Blender and get the
    current Python version bundled with Blender
    """
    tmpF = tempfile.NamedTemporaryFile(delete = False)
    try:
        tmpF.write(b"import sys\n")
        tmpF.write(b"print('>>>' + '.'.join((str(x) for x in sys.version_info[:3])))\n")
        tmpF.write(b"print('###%s' % sys.path)\n")
        tmpF.flush()
        tmpF.close()
        output = subprocess.Popen(
            [blender_path, '-b', '-P', tmpF.name],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        ).communicate() # get only stdout
        stdout = output[0].decode()
        stderr = output[1].decode()
        version = stdout.split('>>>')[1][0:5]
        logger.info("Checking version of Python within Blender " + blender_path + \
                        "... Found v." + version)

        if stderr:
            logger.warning("Blender emitted errors during launch:\n" + stderr)

        return version
    except (OSError, IndexError):
            return None
    finally:
        tmpF.close()
        os.unlink (tmpF.name)

def check_default_scene(prefix):

    global default_scene_abspath
    #Check morse_default.blend is found
    default_scene_abspath = os.path.join(os.path.normpath(prefix), os.path.normpath(DEFAULT_SCENE_PATH))

    #logger.info("Looking for the MORSE default scene here: " + default_scene_abspath)

    if not os.path.exists(default_scene_abspath):
        raise MorseError(default_scene_abspath)
    else:
        return default_scene_abspath

def set_prefixes(silent = False):
    """
    Checks that the environment is correctly setup to run MORSE.
    Raises exceptions when an error is detected.
    """

    global morse_prefix

    loglevel = logger.getEffectiveLevel()
    if silent:
        logger.setLevel(logging.WARNING)

    ###########################################################################
    #Check platform
    if not 'linux' in sys.platform:
        logger.warning("MORSE has only been tested on Linux. It may work\n" + \
        "on other operating systems as well, but without any guarantee")
    else:
        logger.info("Running on Linux. Alright.")

    ###########################################################################
    #Check PYTHONPATH variable

    found = False
    for dir in sys.path:
        if os.path.exists(os.path.join(dir, "morse/blender/main.py")):
            logger.info("Found MORSE libraries in '" + dir + "/morse/blender'. Alright.")
            found = True
            break

    if not found:
        logger.error(  "We could not find the MORSE Python libraries in your\n" +\
                        "system. If MORSE was installed to some strange location,\n" + \
                        "you may want to add it to your PYTHONPATH.\n" + \
                        "Check INSTALL for more details.")
        raise MorseError("PYTHONPATH not set up.")
    ###########################################################################
    #Detect MORSE prefix
    #-> Check for $MORSE_ROOT, then script current prefix
    try:
        prefix = os.environ['MORSE_ROOT']
        logger.info("$MORSE_ROOT environment variable is set. Checking for default scene...")

        check_default_scene(prefix)
        logger.info("Default scene found. The prefix seems ok. Using it.")
        morse_prefix = prefix

    except MorseError:
        logger.warning("Couldn't find the default scene from $MORSE_ROOT prefix!\n" + \
        "Did you move your installation? You should fix that!\n" + \
        "Trying to look for alternative places...")
    except KeyError:
        pass

    if morse_prefix == "":
        #Trying to use the script location as prefix (removing the trailing '/bin'
        # if present)
        logger.info("Trying to figure out a prefix from the script location...")
        prefix = os.path.abspath(os.path.dirname(sys.argv[0]))
        if prefix.endswith('bin'):
            prefix = prefix[:-3]

        try:
            check_default_scene(prefix)

            logger.info("Default scene found. The prefix seems ok. Using it.")
            morse_prefix = prefix
            os.environ['MORSE_ROOT'] = prefix
            logger.info("Setting $MORSE_ROOT environment variable to default prefix [" + prefix + "]")

        except MorseError as me:
            logger.error("Could not find the MORSE default scene (I was expecting it\n" + \
                    "there: " + me.value + ").\n" + \
                    "If you've installed MORSE files in an exotic location, check that \n" + \
                    "the $MORSE_ROOT environment variable points to MORSE root directory.\n" + \
                    "Else, try to reinstall MORSE.")
            raise

        if silent:
            #restoring initial logger level
            logger.setLevel(loglevel)

def check_setup():

    global blender_exec
    global morse_prefix

    set_prefixes()

    ###########################################################################
    #Check Blender version
    #First, look for the $MORSE_BLENDER env variable
    try:
        blender_exec = os.environ['MORSE_BLENDER']
        version = check_blender_version(blender_exec)
        if version:
            logger.info("Blender found from $MORSE_BLENDER. Using it (Blender v." + \
            version + ")")
        elif version == False:
            blender_exec = ""
            logger.warning("The $MORSE_BLENDER environment variable points to an " + \
            "incorrect version of\nBlender! You should fix that! Trying to look " + \
            "for Blender in alternative places...")
        elif version == None:
            blender_exec = ""
            logger.warning("The $MORSE_BLENDER environment variable doesn't point " + \
            "to a Blender executable! You should fix that! Trying to look " + \
            "for Blender in alternative places...")
    except KeyError:
        pass

    if blender_exec == "":
        #Then, check the version of the Blender executable in the path
        for blender_path in retrieve_blender_from_path():
            blender_version_path = check_blender_version(blender_path)

            if blender_version_path:
                blender_exec = blender_path
                logger.info("Found Blender in your PATH\n(" + blender_path + \
                ", v." + blender_version_path + ").\nAlright, using it.")
                break

        #Eventually, look for another Blender in the MORSE prefix
        if blender_exec == "":
            blender_prefix = os.path.join(os.path.normpath(morse_prefix), os.path.normpath("bin/blender"))
            blender_version_prefix = check_blender_version(blender_prefix)

            if blender_version_prefix:
                blender_exec = blender_prefix
                logger.info("Found Blender in your prefix/bin\n(" + blender_prefix + \
                ", v." + blender_version_prefix + ").\nAlright, using it.")

            else:
                logger.error("Could not find a correct Blender executable, neither in the " + \
                "path or in MORSE\nprefix. Blender >= " + MIN_BLENDER_VERSION + \
                " is required to run MORSE.\n" + \
                "You can alternatively set the $MORSE_BLENDER environment variable " + \
                "to point to\na specific Blender executable")
                raise MorseError("Could not find Blender executable")

    ###########################################################################
    #Check Python version within Blender
    python_version = check_blender_python_version(blender_exec)
    if python_version == None:
        logger.warn("Blender's Python version could not be determined. "
                    "Crossing fingers.")
    else:
        our_python_version = '.'.join((str(x) for x in sys.version_info[:3]))
        if (python_version != our_python_version):
            logger.error("Blender is compiled for Python " + python_version + \
                   " but MORSE has been compiled for Python " + \
                   our_python_version + "! Check your MORSE build configuration"
                   " or the selected Blender version.")
            if 'MORSE_SILENT_PYTHON_CHECK' not in os.environ:
                raise MorseError("Bad Python version")
        else:
            logger.info("Blender and Morse are using Python " + \
            python_version + ". Alright.")

def get_config_file():
    config_path = os.path.expanduser("~/.morse")
    if not os.path.exists(config_path):
        os.mkdir(config_path)

    return os.path.join(config_path, "config")

def add_site(name, path):
    config = configparser.SafeConfigParser()
    config.read(get_config_file())

    if not config.has_section("sites"):
        config.add_section("sites")

    config.set('sites', name, path)

    with open(get_config_file(), 'w') as configfile:
        config.write(configfile)

def get_site(name):
    """ Returns the absolute path to a simulation environment specified
    in ~/.morse/config, if it exists, None otherwise.
    """
    config = configparser.SafeConfigParser()
    config.read(get_config_file())

    if not config.has_option("sites", name):
        return None

    return config.get('sites', name)

def delete_site(name):
    config = configparser.SafeConfigParser()
    config.read(get_config_file())

    if config.has_option("sites", name):
        config.remove_option("sites", name)

    with open(get_config_file(), 'w') as configfile:
        config.write(configfile)


def set_morse_environ(site):

        logger.info('Adding <%s> to PYTHONPATH and MORSE_RESOURCE_PATH.' % site)

        sys.path.insert(0, os.path.join(site, 'src'))

        if 'MORSE_RESOURCE_PATH' in os.environ:
                # pre-pend the site path to be sure we shadow existing resources
            os.environ['MORSE_RESOURCE_PATH'] = "%s" % os.path.join(site, 'data') + os.pathsep + os.environ['MORSE_RESOURCE_PATH']
        else:
            os.environ['MORSE_RESOURCE_PATH'] = "%s" % os.path.join(site, 'data')



def get_scene(name, subname = ""):
    """ Tries to figure out which Builder script the user is refering to
    and return the absolute path to it.

    If only one parameter *name* is given:

    - if *name* is a configured simulation environment with prefix $ENVROOT:
        - if '$ENVROOT/default.py' exists, launch it.
        - else, if any file with an extension {.py|.blend} exists, launch the first
            one (in alphanumerical order)
        - else if a file called *name* exists in the current directory, launch it.
        - else, fail
    - else check if a file called *name* exists, and launch it (note that in that
    case, *name* can contain an absolute path or a path relative to the current
    directory).

    If two parameters *name* and *subname* are given:

    - if *name* is a configured simulation environment with prefix $ENVROOT:
        - if *$ENVROOT/subname* exists, launch it
        - else, add $ENVROOT to MORSE environment, and if *subname* exists, launch it (note
          that *subname* can contain an absolute path or a path relative to the current
          directory)
    - else fail
    """

    if not subname:

        site = get_site(name)
        if site:
            default = os.path.join(site, DEFAULT_SCENE_NAME)
            set_morse_environ(site)

            if os.path.exists(default):
                logger.info('Using default scene in environment <%s>.' % name)
                return default

            logger.info('Using environment <%s>.' % name)

            candidates = [os.path.join(site, f) for f in os.listdir(site) if re.match(r'.*\.(py|blend)', f)]
            candidates = sorted([c for c in candidates if os.path.isfile(c)]) # filters out directories
            if candidates:
                logger.warning('No scene specified. Launching MORSE with first found '
                               'in environment: <%s>.' % candidates[0])
                logger.info('You can specify another scene by passing its name as'
                            ' parameter after the simulation environment.')
                return sortedcandidates[0]

        if os.path.exists(name) and os.path.isfile(name):
            logger.info('Loading simulation <%s>.' % os.path.abspath(name))
            return os.path.abspath(name)

        logger.error('Cannot find a MORSE environment or simulation '
                     'scene matching <%s>!' % name)

    else: #both name and subname are defined

        site = get_site(name)
        if site:
            logger.info('Using environment <%s>.' % name)
            set_morse_environ(site)

            candidate = os.path.join(site, subname)
            if os.path.exists(candidate) and os.path.isfile(candidate):
                logger.info('Loading simulation <%s>.' % candidate)
                return candidate
            elif os.path.exists(subname) and os.path.isfile(subname):
                logger.info('Loading simulation <%s>.' % os.path.abspath(subname))
                return os.path.abspath(subname)


            logger.error('Simulation <%s> not found! (I was looking for <%s> or <%s>)' % (subname, candidate, os.path.abspath(subname)))
        else:
            logger.error('MORSE environment <%s> does not exist!' % name)

def create_environment(args):

    name = args.env
    force = args.force

    set_prefixes(silent = True)

    site = get_site(name)
    if not force and site:
        logger.error("You already have a simulation environment "
                     "called \"%s\" (pointing to <%s>)!" % (name, site))
        logger.error("Use 'morse create -f <env>' to overwrite it.")
        sys.exit()


    add_site(name, os.path.abspath(name))

    env = Environment(morse_prefix, name)

    env.create(force)

    logger.info("A new simulation environment has been successfully "
                "created in <%s>." % os.path.abspath(name))
    logger.info("You can run it directly with \"morse run %s\" or you "
                "can start editing it." % name)

def import_environment(args):


    path = os.path.abspath(args.path)
    name = args.name
    if not name:
        name = os.path.basename(path)

    force = args.force

    site = get_site(name)
    if not force and site:
        logger.error("You already have a simulation environment "
                     "called \"%s\" (pointing to <%s>)!" % (name, site))
        logger.error("Use 'morse import -f <path>' to overwrite it.")
        sys.exit()


    add_site(name, path)

    logger.info("The simulation environment <%s> has been successfully "
                "imported." % name)
    logger.info("You can run it directly with \"morse run %s\" or you "
                "can start editing it." % name)


def delete_environment(args):

    name = args.env
    force = args.force

    site = get_site(name)
    if not site:
        logger.error("Found no simulation environment called \"%s\"" % name)
        sys.exit()

    if force:
        ok = 'y'
    else:
        ok = input("Are you sure you want to delete the environment \"%s\" [y/N]" % name)

    if ok.lower() == 'y':
        if os.path.exists(site):
            shutil.rmtree(site)
        delete_site(name)
        logger.info("Environment \"%s\" successfully deleted." % name)

def add_component(args):

    type = args.type
    name = args.name
    env = args.env
    force = args.force

    set_prefixes(silent = True)


    site = get_site(env)
    if not site:
        logger.error("No simulation environment called \"%s\"!" % env)
        logger.error("Use 'morse create %s' to create it first." % env)
        sys.exit()

    env = Environment(morse_prefix, env, site)

    try:
        env.add_component(type, name, force = force)
    except MorseEnvironmentError as e:
        logger.error("%s" % e)
        sys.exit(1)

    logger.debug("A new %s called <%s> has been added to <%s>." % (type, name, args.env))


def prelaunch():
    logger.info(version())
    try:
        logger.setLevel(logging.WARNING)
        logger.info("Checking up your environment...\n")
        check_setup()
    except MorseError as e:
        logger.error("Your environment is not yet correctly setup to run MORSE!\n" +\
        "Please fix it with above information.\n" +\
        "You can also run 'morse check' for more details.")
        sys.exit()

def launch_simulator(scene=None, script=None, node_name=None, geometry=None,
        noaudio = False, script_options = []):
    """Starts Blender on an empty new scene or with a given scene.

    :param tuple geometry: if specified, a tuple (width, height, dx, dy) that
            specify the Blender window geometry. dx and dy are the distance in
            pixels from the lower left corner. They can be None (in this case, they
            default to 0).
    :param list script_options: if specified, elements of this list are passed
            to the Python script (and are accessible with MORSE scripts via
            sys.argv)
    """

    logger.info("*** Launching MORSE ***\n")
    logger.info("PREFIX= " + morse_prefix)

    if not os.path.exists(scene):
        logger.error(scene + " does not exist!\nIf you want to create a new scene " + \
        "called " + scene + ",\nplease create a Python script using the Builder API, run 'morse edit [script_filename]' and save as a .blend file.")
        sys.exit(1)

    logger.info("Executing: " + blender_exec + " " + scene + "\n\n")

    #Flush all outputs before launching Blender process
    sys.stdout.flush()

    # Redefine the PYTHONPATH to include both the user-set path plus
    # default system paths (like '/usr/lib/python*')
    env = os.environ
    env["PYTHONPATH"] = os.pathsep.join(sys.path)

    # Add the MORSE node name to env.
    if node_name:
        env["MORSE_NODE"] = node_name
        logger.info("Setting MORSE_NODE to " + env["MORSE_NODE"])

    # Prepare the geometry
    if geometry:
        w,h,dx,dy = geometry
        # -w for a window with borders, -p for the window geometry
        other_params = ["-w", "-p", dx if dx else '0', dy if dy else '0', w, h]
    else:
        other_params = ["-w"]

    if script_options:
        other_params.append('--')
        other_params += script_options

    other_params.append(env) # must be the last option
    blender_exec_encoded = os.fsencode(blender_exec)
    exec_args = [blender_exec_encoded, blender_exec]

    if noaudio:
        exec_args += ["-setaudio", "NULL"]

    exec_args += [scene, "-y"]
    exec_args += ["--no-native-pixels"]

    #Replace the current process by Blender
    if script is not None:
        if os.name == 'nt':
            # need a temporary file because PYTHONPATH is disrespected
            tmpF = tempfile.NamedTemporaryFile(delete = False)
            try:
                script_path = os.path.abspath(script)
                script_path = script_path.replace ('\\', '/')
                tmpF.write(("import sys\n").encode())
                for element in sys.path:
                    element = element.replace('\\', '/')
                    tmpF.write(("sys.path.append('" + element + "')\n").encode())
                tmpF.write(("exec(open('" + script_path + "').read())\n").encode())
                tmpF.write(("import os\n").encode())
                tmpF.flush()
                tmpF.close()
                script = tmpF.name
            finally:
                # if the temporary file is still open then an error
                # occured and we need to delete it here, else we should
                # not touch it
                if not tmpF.closed:
                    tmpF.close()
        logger.info("Executing Blender script: " + script)
        exec_args += ["-P", script]

    exec_args += other_params
    os.execle(*exec_args)

def do_check(args):
    try:
        logger.info("Checking up your environment...\n")
        check_setup()
    except MorseError as e:
        logger.error(e.value)
        logger.error("Your environment is not correctly setup to run MORSE!")
        sys.exit()
    logger.info("Your environment is correctly setup to run MORSE.")


def process_run_edit(args):

    script_options = args.pyoptions
    if args.color:
        script_options.append("with-colors")
    if args.reverse_color:
        script_options.append("with-colors")
        script_options.append("with-reverse-colors")

    # xmas mode!
    import datetime
    today = datetime.date.today()
    if today > datetime.date(today.year, 12, 25):
        script_options.append("with-xmas-colors")

    if args.mode == "run":
        if not args.env:
            logger.error("'run' mode requires a simulation environment and/or a simulation scene (.py or .blend)")
            sys.exit()

        scene = get_scene(args.env, args.file) or sys.exit(1)

        prelaunch()
        if scene.endswith(".blend"):
            launch_simulator(
                   scene, \
                   geometry=args.geom, \
                   node_name=args.name, \
                   noaudio=args.noaudio, \
                   script_options = script_options)
        else:
            launch_simulator(
                   os.path.join(morse_prefix, DEFAULT_SCENE_AUTORUN_PATH), \
                   scene, \
                   geometry=args.geom, \
                   node_name=args.name, \
                   noaudio=args.noaudio, \
                   script_options = script_options)

    else:  # args.mode == "edit":
        if not args.env:
            logger.error("'edit' mode requires a simulation environment and/or a simulation scene to edit (.blend or .py)")
            sys.exit()

        scene = get_scene(args.env, args.file) or sys.exit(1)

        prelaunch()

        if scene.endswith(".blend"):
            launch_simulator(
                   scene, \
                   geometry=args.geom, \
                   node_name=args.name, \
                   noaudio=args.noaudio, \
                   script_options = script_options)
        else:
            base_scene = args.base if 'base' in vars(args) else default_scene_abspath
            launch_simulator(
                   scene = base_scene, \
                   script = scene, \
                   geometry=args.geom, \
                   node_name=args.name, \
                   noaudio=args.noaudio, \
                   script_options = script_options)

def version():
    return("morse " + VERSION)

if __name__ == '__main__':

    def parsegeom(string):
        dx = dy = None
        try:
             w, remain = string.split('x')
             if '+' in remain:
                h, remain = remain.split('+')
                dx, dy = remain.split(',')
             else:
                h = remain
        except ValueError:
             raise argparse.ArgumentTypeError("The geometry must be formatted as WxH or WxH+dx,dy")
        return (w,h,dx,dy)

    # Check whether MORSE_NODE is defined
    default_morse_node = platform.uname()[1]
    try:
        default_morse_node = os.environ["MORSE_NODE"]
    except:
        pass

    parser = argparse.ArgumentParser(description='The Modular OpenRobots Simulation Engine')

    # First, general MORSE options
    parser.add_argument('-n', '--noaudio', action='store_true',
                       help='dont look for sound card')
    parser.add_argument('-c', '--color', action='store_true',
                       help='uses colors for MORSE output.')
    parser.add_argument('--reverse-color', action='store_true',
                       help='uses darker colors for MORSE output.')
    parser.add_argument('-v', '--version', action='version',
                       version=version(), help='returns the current MORSE version')



    ## Then, the sub-commands

    subparsers = parser.add_subparsers(title="modes", description="type 'morse <mode> --help' for details", dest = "mode")

    # create
    create_parser = subparsers.add_parser('create', help="creates a new simulation environment in the current directory.")
    create_parser.add_argument('env', default="", help="the name of the environment to create.")
    create_parser.add_argument('-f', '--force', action='store_true',
                       help='forces the creation (possibly overwriting files).')
    create_parser.set_defaults(func=create_environment)

    # import
    import_parser = subparsers.add_parser('import', help="imports a pre-existing simulation environment.")
    import_parser.add_argument('path', default="", help="the relative or absolute path to the environment. For instance './lab_sim_checkout'.")
    import_parser.add_argument('name', default="", nargs='?', help="the name of the environment (extracted from path if not specified).")
    import_parser.add_argument('-f', '--force', action='store_true',
                       help='forces the import (possibly overwriting existing environment).')
    import_parser.set_defaults(func=import_environment)


    # rm
    rm_parser = subparsers.add_parser('rm', help="deletes an existing simulation environment.")
    rm_parser.add_argument('env', default="", help="the environment to delete.")
    rm_parser.add_argument('-f', '--force', action='store_true',
                       help='silently delete.')
    rm_parser.set_defaults(func=delete_environment)


    # add
    add_parser = subparsers.add_parser('add', help="adds a template for a component to an environment.")
    add_parser.add_argument('type', choices=['robot', 'sensor', 'actuator'],
            help="the type of component you want to add.")
    add_parser.add_argument('name', default="", help="the name of the new component.")
    add_parser.add_argument('env', default="", help="the environment where to add.")
    add_parser.add_argument('-f', '--force', action='store_true',
                       help='overwrite existing component with the same name.')
    add_parser.set_defaults(func=add_component)



    # check
    check_parser = subparsers.add_parser('check', help="checks your environment is correctly configured.")
    check_parser.set_defaults(func=do_check)

    # run
    run_parser = subparsers.add_parser('run', help="starts a simulation.")
    run_parser.add_argument('env', default="", nargs='?',
            help="the simulation environment to run.")
    run_parser.add_argument('file', default="", nargs='?',
            help="the exact scene (.py or .blend) to run (if 'env' is given, within this environment). Refer to manual for details.")
    run_parser.add_argument('pyoptions', nargs=argparse.REMAINDER,
                       help="optional parameters, passed to the Blender python engine in sys.argv")
    run_parser.add_argument('--name', default=default_morse_node,
                       help="when running in multi-node mode, sets the name of this node (defaults "
                            "to either MORSE_NODE if defined or current hostname).")
    run_parser.add_argument('-g', '--geometry', dest='geom', type=parsegeom, action='store',
                       help="sets the simulator window geometry. Expected format: WxH or WxH+dx,dy "
                            "to set an initial x,y delta (from lower left corner).")
    run_parser.set_defaults(func=process_run_edit)

    # edit
    edit_parser = subparsers.add_parser('edit', help="opens an existing scene for edition.")
    edit_parser.add_argument('env', default="", nargs='?',
            help="the simulation environment to edit.")
    edit_parser.add_argument('file', default="", nargs='?',
            help="the exact scene (.py or .blend) to edit (if 'env' is given, within this environment). Refer to manual for details.")
    edit_parser.add_argument('pyoptions', nargs=argparse.REMAINDER,
                       help="optional parameters, passed to the Blender python engine in sys.argv")
    edit_parser.add_argument('-b', '--base', dest='BASE',
                       help='specify a Blender scene used as base to apply the Python script.')
    edit_parser.add_argument('--name', default=default_morse_node,
                       help="when running in multi-node mode, sets the name of this node (defaults "
                            "to either MORSE_NODE if defined or current hostname).")
    edit_parser.add_argument('-g', '--geometry', dest='geom', type=parsegeom, action='store',
                       help="sets the simulator window geometry. Expected format: WxH or WxH+dx,dy "
                            "to set an initial x,y delta (from lower left corner).")
    edit_parser.set_defaults(func=process_run_edit)



    args = parser.parse_args()
    try:
        args.func(args)
    except AttributeError:
        parser.print_usage()