[SOLVED] Proper custom gizmo scaling

I didn’t get any traction over at BA and I didn’t spot anything here either but how do I properly account for the screen-space size of my Gizmo?

Basically I want the Gizmo to be the same size regardless of how close the user is zoomed into the object. The built in gizmos seem to handle this properly but I can’t seem to find the right set of properties and matrices to set here.

Zoomed In – The spacing between the boxes here is what I’m after:

Zoomed Out – Incorrect; I want to maintain the offset between the boxes:

As you can see, the boxes correctly maintain their size (which is what I want), but they do NOT maintain their effective distance offset from each other (which I can’t figure out how to maintain).

And eh, why not, interesting code drop here. It’s a pretty standard setup except my GizmoGroup contains 6 Gizmo’s whereas the template examples typically deal with just 1:

class DCONFIG_GT_symmetry_gizmo(bpy.types.Gizmo):
    # snip
    # snip
    # snip

    def draw(self, context):

    def draw_select(self, context, select_id):
        self.draw_custom_shape(self.custom_shape, select_id=select_id)

    def setup(self):
        if not hasattr(self, "custom_shape"):
            self.custom_shape = self.new_custom_shape('TRIS', self.cube_tri_verts)

    def update(self, mat_target):
        mat_t = Matrix.Translation(self.draw_offset * 0.25)
        self.matrix_basis = mat_t @ mat_target

    def invoke(self, context, event):
        return {'RUNNING_MODAL'}

    def modal(self, context, event, tweak):
        if event.value == 'PRESS':
            self.op.direction = self.direction
            return {'FINISHED'}

        return {'RUNNING_MODAL'}

class DCONFIG_GGT_symmetry_gizmo(bpy.types.GizmoGroup):
    bl_label = "Symmetry Gizmo Group"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'WINDOW'
    bl_options = {'3D'}

    def my_target_operator(context):
        wm = context.window_manager
        op = wm.operators[-1] if wm.operators else None
        return op if getattr(op, "dc_uses_symmetry_gizmo", False) else None

    def poll(cls, context):
        op = cls.my_target_operator(context)
        if op is None:
            wm = context.window_manager
            return False
        return True

    def setup(self, context):
        def setup_widget(direction, draw_offset, color):
            mpr = self.gizmos.new("DCONFIG_GT_symmetry_gizmo")
            mpr.op = DCONFIG_GGT_symmetry_gizmo.my_target_operator(context)
            mpr.direction = direction
            mpr.draw_offset = draw_offset

            mpr.color = color
            mpr.alpha = 0.3
            mpr.color_highlight = color
            mpr.alpha_highlight = 0.5

            mpr.select_bias = 0
            mpr.scale_basis = 0.2

            mpr.use_select_background = True
            mpr.use_draw_scale = True
            mpr.use_select_background = True
            mpr.use_event_handle_all = False

        ui_theme_prefs = context.preferences.themes[0].user_interface
        setup_widget("POSITIVE_X", Vector((-1, 0, 0)), ui_theme_prefs.axis_x)
        setup_widget("NEGATIVE_X", Vector((1, 0, 0)), ui_theme_prefs.axis_x)
        setup_widget("POSITIVE_Y", Vector((0, -1, 0)), ui_theme_prefs.axis_y)
        setup_widget("NEGATIVE_Y", Vector((0, 1, 0)), ui_theme_prefs.axis_y)
        setup_widget("POSITIVE_Z", Vector((0, 0, -1)), ui_theme_prefs.axis_z)
        setup_widget("NEGATIVE_Z", Vector((0, 0, 1)), ui_theme_prefs.axis_z)

    def refresh(self, context):
        target = context.active_object

        mat_target = target.matrix_world.normalized()
        for mpr in self.gizmos:

Ok, so with a few folks help in blender.chat, I was able to resolve this.

You need to set the use_draw_offset_scale to True on your Gizmo object AND use the matrix_offset field instead of matrix_basis

However, there’s a weird gotcha if you also set use_draw_scale to True. If you set this to True after you’ve already set use_draw_offset_scale things are broken again. I will follow up with some devs later to see if that’s by design or not.