Tutorial 18: Handles

To access the tutorials in ArcGIS CityEngine, click Help > Download Tutorials and Examples. After choosing a tutorial or example, the project is automatically downloaded and added to your workspace.

Handles with lighthouse

This tutorial is intended for writers of CGA code who want to add handles to their models. Handles provide a way to edit CGA attributes from within the 3D Viewport window. This simplifies editing models and provides visual hints as to what an attribute will control.

Part 1: Basic linear handles

Handles are generated by an attribute annotation used in CGA to provide a set of dynamic editing tools. They allow you to manipulate the dimensions and position of your models directly in the Viewport window with precision. In this section, you will begin by creating linear handles.

  1. Expand the Tutorial_18_Handles tutorial folder in the Navigator window.
  2. Double-click the part 1.cej file in the scenes folder to open the scene in the Viewport window.
  3. Click Yes when asked "Would you like to regenerate these models?"
  4. Select the cube model, and experiment with the height, width, depth, and offset attributes in the Inspector window to understand the behavior.
  5. Double-click the rules/cube.cga rule file to open it in the CGA Editor window.

    It has the handle annotations commented out.

    #@Handle(shape=Cube)
    attr height = 10
    
    #@Handle(shape=Cube, axis=x, skin=sphere, color="#6666ff")
    attr width = 10
    
    #@Handle(shape=Cube, axis=z, skin=diameterArrow)
    attr depth = 10
    
    #@Handle(shape=ShapeForHandleOnly
    axis=x
    reference=origin
    slip=inside
    translate={0,0.5,0})
    attr offsetX = 5
    
    
    Lot --> 
    	[ s(offsetX,height,1) ShapeForHandleOnly ] t(offsetX,0,0) 
    	t(0,0,-depth/2) s(width,0,depth)
    	extrude(height)
    	Cube
    
    Cube --> 
    	color("#E5E6E7")
    
    ShapeForHandleOnly --> NIL

Create a height handle

Next, you will create a handle for the height of the cube.

  1. Add an annotation to the height attribute and click File > Save to save the rule:

    @Handle(shape=Cube)
    attr height = 10

    This adds a handle, which controls the height of the cube model. A handle gets attached to the scope of a CGA rule. In this first example, the Cube rule is chosen because its scope coincides with the resulting cube models geometry.

  2. Select the cube model, and click the Generate Models tool Generate (Ctrl+G) to regenerate and show the handle.

    You can now edit the value of the attribute by clicking and dragging the handle’s blue arrow. As you drag the handle, the value of the attribute is shown.

    Model with height handle

    Note:
    The handle is positioned outside of the model; as you rotate the view, the handle moves so that it does not occlude the model

  3. Zoom in.

    The handle is no longer able to hold its position outside the model and is placed on the model.

    Height handled zoomed in

Make a width handle

Next, you will create a second handle for the width of the cube.

  1. Add an annotation to the width attribute and press Ctrl+S to save the rule:

    @Handle(shape=Cube, axis=x, skin=sphere, color="#6666ff")
    attr width = 10

    This attaches another handle to the same rule named Cube, and uses additional parameters to customize its position and appearance:

    • axis=x — Specifies that the direction of the handle should be in the x direction relative to the Cube scope.
    • skin=sphere — Changes the handle tip from arrow to a sphere.
    • color=”#6666ff” — Changes the handle color from the default blue to purple.
  2. Press Ctrl+G to generate the model again and observe the new handle.

    Model with multiple handles

  3. Hover the mouse pointer over a handle.

    CityEngine highlights both the associated scope in the Viewport window and the attribute the Inspector window.

    Hover over handle and Inspector display

Specify a depth handle

Next, create a third handle that controls the depth of the cube.

  1. Add this annotation to the depth attribute, then save and regenerate:

    @Handle(shape=Cube, axis=z, skin=diameterArrow)
    attr depth = 10

    Again, set the axis parameter to specify the scope axis on which the handle should lie. The depth attribute of the model behaves differently than the width or height attributes. It scales the depth of the cube around a central point. To accommodate this, use the parameter skin=diameterArrow. This creates a handle with different drag behavior, and two blue arrows that can both be used to change the depth of the cube model.

    Model with multiple handles

  2. Rotate the view.

    As the view rotates, the handles avoid rendering conflicts with both the model and each other.

Create an offset handle

For the final parameter, offsetX, you will create a handle that does not move when the view changes.

  1. Add the following handle annotation to offsetX and a dedicated placeholder scope:

    @Handle(shape=ShapeForHandleOnly, axis=x, reference=origin, slip=inside)
    attr offsetX = 5
    
    Lot --> 
    	[ s(offsetX,height,1) ShapeForHandleOnly ] t(offsetX,0,0) 
    	t(0,0,-depth/2) s(width,0,depth)
    	extrude(height)
    	Cube
    
    Cube --> 
    	color("#E5E6E7")
    
    ShapeForHandleOnly --> NIL

    This handle is attached to an empty placeholder scope ShapeForHandleOnly that was created just for this handle and produces no geometry.

  2. To visualize the scope of the shape, open the Model Hierarchy window and select the ShapeForHandleOnly.

    Translate scope

    The slip=inside handle parameter requests that the handle lies inside the model silhouette and does not move with the viewpoint. Normally, a handle may appear on any four of the scope’s edges in the given axis direction; however, reference=origin causes the handle to only appear on the edge adjacent to the scope’s origin.

  3. Edit this new handle so that it touches the center of the cube:

    @Handle(shape=ShapeForHandleOnly, 
            axis=x, reference=origin, slip=inside, translate={0,0.5,0})
    attr offsetX = 5

The translate=parameter moves the handle relative to the current scope. The argument {0,0.5,0} moves the handle 0.5 times the scope-y scale (height) in the y direction (up).

Translate floor to center

Without the translate= parameter, the offsetX handle lies on the floor (left), and with translate=, the handle lies in the center of the cube (right). While the first three handles lie outside the silhouette, slip=inside allows this handle to lie inside the invisible cube’s silhouette at all times.

When you hover over the handle, the scope of the rule named shape is highlighted in blue.

Part 2: Advanced linear handles

Welcome to the next phase of your journey. You will now engage with a more intricate model of a windmill.

Create a basic windmill handle

In Part 1 of this tutorial, you added handles to a cuboid shape. As you moved the viewpoint, the handles snapped to align with the same planes as the faces of the cubes. You can do the same with the cylindrical windmill object.

  1. Open the part 2.cej scene.

    Explore the different attributes in the scene.

  2. Open the rules/windmill.cga rule file.

    Notice how these attributes govern a spectrum of features, encompassing cylindrical forms and repetitive elements. This is where you will delve into the methodology of integrating handles into these distinctive geometries.

  3. Add an annotation to create a basic handle and define a range for the attribute value.

    @Handle(shape=UnderRoof)
    @Range(min=3, max=100)
    attr height = 7

    Complex window handles with roof scope

    As you rotate the view, you'll notice that this handle isn’t very effective. As the view rotates, it jumps between positions at the corners of the UnderRoof scope. Because the scope is larger and a different shape than the building, it is difficult to follow the association between the model and the handle.

  4. Modify the basic handle and disable the range restriction.

    @Handle(shape=UnderRoof, reference=center, slip=screen)
    @Range(min=3, max=100, restricted=false)
    attr height = 7

    Here, the reference=center parameter attaches the handle to the center of the scope (see the CGA reference for details of the other reference positions available). The slip= parameter defines the direction in which the handle is moved outside the model. In this case, slip=screen specifies that it moves parallel to the camera 2D screen. The resulting handle movement is much smoother and clearer.

Add radius handles

You can continue to add two handles controlling the radius of the windmill at the top and bottom of the screen.

  1. Add an annotation to create radius handles:

    @Handle(shape=UnderRoof, reference=radial, align=bottomLeft)
    @Range(min=1.5, max=9)
    attr bottomRadius = 2
    
    @Handle(shape=UnderRoof, reference=radial, align=topLeft)
    @Range(min=1.0, max=5.1)
    attr topRadius = 1

  2. Rotate the view around.

    Windmill with handles

    As you rotate the view around the model, you'll notice that the two new handles move appropriately for the model’s geometry. The reference=radial parameter positions the handles to measure the radius of a cylinder; align= gives preferred locations for the handles in the Viewport window. Using align ensures a consistent screen viewport location for each handle. Otherwise, the bottomRadius handle may be shown at the top of the model.

Create chain handles

In many models, a single attribute affects more than one part. In this windmill example, the bladeLength attribute changes the length of each of the four blades.

CityEngine chains such handles together and shows a single chain for each attribute.

  1. Add a handle annotation to the bladeLength attribute:

    @Handle(shape=Blade, axis=x, reference=origin)
    attr bladeLength = height * 0.6

    Depending on the view direction, different chains of handles may be shown. In this example, CityEngine will show one chain of two handles for the windmill blades:

    Windmill with chain handles

  2. Turn the chain behavior off using the repeat=none parameter.

    @Handle(shape=Blade, axis=x, reference=origin, repeat=none)
    attr bladeLength = height * 0.6

    Windmill with non-chain handles

Add handles to the door and windows

The windowScale attribute is a little more involved and manipulates the size of the windows and the door.

Note:
The single Windoor rule creates both the windows and the door.

  1. Create a handle that controls the windowScale attribute.

    @Handle(shape=Windoor, axis=y)
    @Range(min=0.1, max=1.5)
    attr windowScale = 1

    Windmill with window handles

    Tip:

    Editing the height attribute will show different numbers of windows.

    Another observation about the windowScale attribute is that the value of the scaling factor is not the same as the size of the windows. Furthermore, each window or door has a different-sized scope associated with it. CityEngine places a chain of handles over the repeating elements and scales the length of each handle to match each scope. Dragging such a linear handle (with a different value than the length of the handle) will show the real-world length in parentheses.

    Window handles with windowScale

  2. Hover over a handle until it highlights the scope to which it is attached.

    This behavior occurs for all handle types. Here, the Windoor scope displays blue in color.

    Windmill door handle

  3. Explore the handle placement from different view directions.

    Windmill with all handles

Part 3: Other types of handles

In the final workflow of this tutorial, you'll look at color, toggle, and rotational handles, as well as hiding handles by removing scopes.

Add a scope to the model

When working with unfamiliar CGA code, it is often useful to use the Model Hierarchy window to explore the available scopes.

  1. Open part 3.cej scene.
  2. Generate the model in the scene.
  3. Open the rules/biped.cga rule file.

    import leftArm  : "limb.cga" 
    import rightArm : "limb.cga" 
    import leftLeg  : "limb.cga" 
    import rightLeg : "limb.cga" 
    
    @Handle(type=linear, axis=x, reference=origin, translate={0, 0.5,0.5},shape=HeadHandle^1, skin=diameterArrow)
    attr boneSize = 0.16
    
    @Handle(type=linear, shape=Mass, align=left)
    attr height = 1.85
    
    @Color
    attr color = "#888888"
    
    attr showLimbHandles = false
    
    Lot --> 
    	t (-boneSize*2.15,0,0)
    	s (boneSize*4.3, 0, boneSize )
    	extrude(height)
    	Mass
    	
    Mass -->
    	split (y)
    	{
    		~4 : Body |
    		boneSize*2 : Head 
    	}
    	
    Head -->
    	t (scope.sx/2-boneSize , 0, scope.sz/2-boneSize )
    	s (boneSize*2, boneSize*2, boneSize*2)
    	HeadHandle("sphere.obj")
    	
    HeadHandle(mesh) --> 
    	i (mesh) X
    
    # (…)

  4. In Part 1 and Part 2 of this tutorial, you used linear handles. In addition to these types, handles can control other types of attributes. Next, you'll add a color handle that sits above the head of your model.
  5. Click Window > to open the Model Hierarchy window.
  6. Click the Inspect model tool to open the shape tree.
  7. Click the head of the model to show the scopes in the Model Hierarchy.
  8. Navigate the hierarchy to locate an appropriate scope with a unique name.

    Here, HeadHandle seems to be a good scope for your handle. X would not be ideal to attach a handle since it is used several places throughout this CGA rule.

  9. Hover over HeadHandle in the Model Hierarchy window.

    It shows that this rule has one input parameter with sphere.obj as the current value.

    Model with inspect model

Create a color handle

Now, you will add a color handle to the HeadHandle scope you identified in the last section.

  1. Add the following annotation to the attribute named color:

    @Handle(type=color, shape=HeadHandle^1, axis=y, reference=center)
    @Color
    attr color = "#888888"

    The scope is identified with its rule name and the number of parameters that the rule takes, in this case, shape=HeadHandle^1. In previous examples, you have been able to exclude this number-of-parameters identifier, as it has been zero. The color attribute is a string, and by using the type=color parameter, you can edit it in the Viewport window. Color, toggle, and selector handles behave differently to linear handles: axis=y specifies that the handle may move in the y (up) direction, while reference=center attaches the handle to a single point at the center of the scope.

  2. Generate the model.

    It now has a color handle that appears as a triangle. If you hover over this new handle a color wheel appears which can now be used to edit the color attribute.

    Model with color handles

    A click and drag inside the triangle changes the color's saturation and brightness, while holding the same click and dragging around the large circle changes the hue. Previously selected colors appear on the outer edge in smaller circles.

Control handles' visibility

It is possible to hide handles by controlling the generation of their associated scopes in the rule. Each arm and leg of this model is created by a single imported rule instance. Each imported instance has three parameters to control the position of the limb. However, these twelve handles add a lot of visual clutter to the model. Therefore, you'll attach these handles to optional scopes, which are only created if a Boolean attribute (showLimbHandles) is true.

  1. Attach the handles to optional scopes.

    @Handle(type=toggle, shape=HeadHandle^1, slip=screen, align=right)
    attr showLimbHandles = false

    The handle type toggle is provided for Boolean attributes and displays as a square shaped switch. This time, you'll use slip=screen and align=right to position the handle in a consistent location to the right of the spherical head.

  2. After adding the handle annotations, press Ctrl+S to save and Ctrl+G to regenerate the model.

    Model with switch handles

  3. Control the toggle handle by either clicking it or clicking and dragging up and down.

    Using the handle, set showLimbHandles to true. When active, the rotate handles are displayed for each of the limbs. These can be used to pose the model of the person as desired.

By default, rotate handles do not move as the view direction changes. They are, however, invisible when viewed from the side. To use a certain handle, rotating the camera around the model often provides better control over it. You can see all the handles positioned on the model, and the attributes impacted by editing the model using this method. So have fun exploring all the possibilities of handles and how you can apply them to your models.