Tutorial as a Python Script

I am not an experienced python programmer so I wanted to post here:

I want this thread to explore the possibility of creating tutorial scripts or tools for making them. That is: scripts which act as in-program tutorials. For example: What code would you use to overlay a text message on the 3D viewport (such as the screencast_keys_addon text currently does)* How would you write a script that did the following:
 1) Lock or hide most of the panels
 2) Put text on screen (such as "Add a cube to your scene")
 3) Highlight the button or menu items by changing that specific button or menu items color
 4) Unlock or un-hide other menus/panels once specific actions were completed (like adding a cube).

Could we develop the code tools to do some of this tedious stuff more easily?

  • I looked at the screencast addon py file and it seems that all the text on screen functionality I’m talking about is there… but I don’t understand it and there is a lot of extra stuff in there for drawing the mouse shape etc. What about just creating text on screen? Or how do I make my own shapes like arrows. It looks like the mouse shapes were plotted out point for point which would be arduous for a given non-trivial shape.

indeed interesting, I have no Idea how to do it either, but it would help a lot for tutorials making :slight_smile:

You can do the font drawing with Blender’s blf module (http://www.blender.org/documentation/blender_python_api_2_68_5/blf.html). This manual side has a very basic “Hello World” Example. You can draw other shapes with OpenGL (bgl module) but that is very tedious.

Maybe you can use the Grease Pencil? I’m not sure how to access grease pencil strokes from python and how to manipulate them. But at least you can draw them and don’t have to mess around with OpenGL code.

  1. Lock or hide most of the panels

There’s no feature to lock or hide panels. You could indirectly lock panels by setting .enabled = False on every UI layout element, but that would be an ugly hack. Hiding would be a hack as well, you would basically save a reference to a panel and then unregister it.

  1. Put text on screen (such as “Add a cube to your scene”)

Draw callbacks are your friend, you can use both, bgl (opengl) and blf (font drawing) for this. With little programming experience, I wouldn’t recommend to try this, as it’s not as trivial as you might wish.

  1. Highlight the button or menu items by changing that specific button or menu items color

We can’t? API doesn’t support this by purpose - avoid scripters messing up the UI and violate the design rules.

  1. Unlock or un-hide other menus/panels once specific actions were completed (like adding a cube).

Add a poll() method to your menu or operator, if the condition is negative, operator button will be grayed out in panels and menus not show at all.

Maybe you can use the Grease Pencil? I’m not sure how to access grease pencil strokes from python and how to manipulate them.

Dump grease pencil data code snippet:

for gp in bpy.data.grease_pencil:
    print(gp.name)
    for layer in gp.layers:
        print("  " + layer.info)
        for frame in layer.frames:
            print("    Frame " + str(frame.frame_number))
            for i, stroke in enumerate(frame.strokes):
                print("      Stroke " + str(i))
                for j, point in enumerate(stroke.points):
                    print("        Point %i: %r" % (j, point.co))
    print("-" * 20)

you can manipulate the points like vertices, e.g. point.co.x += 1.5

if you are looking for example of displaying text on screen this one http://pastebin.com/JY2xx85u has some simple code than screen cast addon.

Cool idea!
I actually had a similar idea, and thought about using unit-testing to ensure the users perform each step correctly (eventually I thought it’s probably not going to be that useful because you’d need a more visual, customized output for the user, but some manner of testing framework will be useful).

It would really be awesome to allow people to perform interactive tutorials from within blender, or even demonstrate tools and applications interactively instead of a boring ol’ manual.

There are some really nice suggestions there with using the grease pencil. It’s also worth considering using downloadable packed blend files that will include code or image files / animations for each tut.

Anyway, if you need any help with this, I’d be happy to join the efforts.

@CoDEmanX
Thanks for the reply. I have been messing around with bgl and blf to no avail (you were right!). I am really interested in this topic/project though, I hope I can give it enough momentum to attract more capable coders and learn something myself. Is is possible to create a new screen that only has the 3D view or whatever area you want in it? Here is my code that creates a new screen called “Tutorial” the humblest of beginnings. How can I set with python code what areas are present? I’m guessing the bpy.ops.screen.new() creates a copy of whatever screen you are on.

>>>>

import bpy

orig_screen_names = []
for screen in bpy.data.screens:
orig_screen_names.append(screen.name)

bpy.ops.screen.new()

print(orig_screen_names)

for screen in bpy.data.screens:
if screen.name not in orig_screen_names:
screen.name = “Tutorial”

you can create new screens, but not start with a single area opened. It’s possible to use bpy.ops.screen.area_join() to close areas, but you need to provide the pixel coordinates of the area widgets, and it only works like in viewport - areas must share edges or they won’t close. So better create a screen by hand, maybe load it with python from .blend if necessary.

The types of editors can be changed easily, e.g.

bpy.context.screen.areas[2].type = ‘CONSOLE’

Can you attach a screenshot of what your intended result would be?
You can make this in photoshop or Qt Designer(free designer for designing QT apps)

The code is very scattered and undocumented at the moment, but here is the main tutorialWindow.py
http://www.pasteall.org/45856/python

I can probably whip up a UI for you, but Qt is currently not built into blender, and to include it would take blenders filesize up 70-140mb, which developers seem against.
Though including Qt in blender would be a great boost in users as the inability to easily construct interfaces in blender is a significant drawback for larger studios adopting it as a subtool.
-Alex

After some experimenting, it turns out to be somewhat impossible - it either crashes blender or doesn’t work as intended:

import bpy


class ModalTimerOperator(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_timer_operator"
    bl_label = "Modal Timer Operator"

    _timer = None

    def modal(self, context, event):
        if event.type == 'ESC':
            return self.cancel(context)

        if event.type == 'TIMER':
            s = bpy.context.screen
            bpy.context.window.screen = bpy.data.screens[0]
            bpy.context.window.screen = s
            if self.area_count > 1:
                print('TIMER')
                for i, area1 in enumerate(bpy.context.screen.areas):
                    for j, area2 in enumerate(bpy.context.screen.areas):
                        if area1 == area2: continue
                        print(area1.type, i, j, area2.type)
                        if (area1.x == area2.x and area1.width == area2.width) or \
                           (area1.y == area2.y and area1.height == area2.height):
                            print(area1.x, area2.x, area1.y, area2.y)
                            ret = bpy.ops.screen.area_join(min_x=area1.x, 

min_y=area1.y, max_x=area2.x, max_y=area2.y)
                            print(ret)

                            #bpy.ops.screen.screen_full_area()
                            #bpy.ops.screen.back_to_previous()
                            if ret == {'FINISHED'}:
                                self.area_count -= 1
                            print(self.area_count)
                            break
            else:
                context.area.type == 'VIEW_3D'
                print(context.area.type)
                return self.cancel(context)
            
        return {'RUNNING_MODAL'}

    def execute(self, context):
        self.area_count = len(context.screen.areas)
        self._timer = context.window_manager.event_timer_add(0.1, context.window)
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        context.window_manager.event_timer_remove(self._timer)
        return {'CANCELLED'}


def register():
    bpy.utils.register_class(ModalTimerOperator)


def unregister():
    bpy.utils.unregister_class(ModalTimerOperator)


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.wm.modal_timer_operator()


It joins all areas of current screen, but it fails to change area.type to a certain one (no clue why). If you change the timer to e.g. 0.01, blender crashes instantly. There seems to be a problem with the drawing code, I change screen for a moment to force a redraw 'cause other methods failed (tag_redraw on areas, wm.redraw_timer)

//edit: you can actually solve the problem of blender not changing area type at the end by settings all areas to the desired area in the execute() method. The instability problem persists however.