OSArch Community

Gizmos for parametric objects in Bonsai

  1. G

    I have been experimenting with drawing gizmos in the viewport for parametric objects, I'll share a standalone script you can just run in the script editor to test out. Feel free to give any constructive feedback.

    Things to keep in mind / to improve :

    • Use custom meshes for arrow gizmos or communicate better what does what. Unfortunately ressources on how to use gizmos is ... pretty scarce so it's a lot of trial and error.

    • No snapping

    • Support all parameters

    • No idea about performance but I didn't notice anything drastic

    Cheers

    
    import bpy
    
    
    from bpy.types import Gizmo
    
    from mathutils import Vector, Matrix
    
    from math import pi
    
    
    
    
    class BIM_OT_toggle_gizmo(bpy.types.Operator):
    
        bl_idname = "serendipicad.toggle_gizmo"
    
        bl_label = "Toggle Gizmo"
    
        obj: bpy.props.StringProperty()
    
        prop_path: bpy.props.StringProperty()
    
    
        def execute(self, context):
    
            obj = bpy.data.objects[self.obj]
    
            parts = self.prop_path.split(".")
    
            for part in parts[:-1]:
    
                obj = getattr(obj, part)
    
            setattr(obj, parts[-1], not getattr(obj, parts[-1]))
    
            return {"FINISHED"}
    
    
    
    
    class GizmoLock(Gizmo):
    
        bl_idname = "VIEW3D_GT_lock"
    
    
        __slots__ = (
    
            "custom_shape_closed",
    
            "custom_shape_open",
    
        )
    
    
        tris_closed = [
    
            (-0.3519617021083832, 0.7437955141067505, 0.0),
    
            (-0.3048076927661896, 0.9197763204574585, 0.0),
    
            (-0.4225003123283386, 0.9877263307571411, 0.0),
    
            (-0.4225003123283386, 0.9877263307571411, 0.0),
    
            (-0.3048076927661896, 0.9197763204574585, 0.0),
    
            (-0.1759808510541916, 1.0486031770706177, 0.0),
    
            (-0.24393069744110107, 1.1662957668304443, 0.0),
    
            (-0.1759808510541916, 1.0486031770706177, 0.0),
    
            (2.9078805141580233e-08, 1.0957571268081665, 0.0),
    
            (2.9078805141580233e-08, 1.2316569089889526, 0.0),
    
            (2.9078805141580233e-08, 1.0957571268081665, 0.0),
    
            (0.1759808510541916, 1.0486031770706177, 0.0),
    
            (0.243930846452713, 1.1662957668304443, 0.0),
    
            (0.1759808510541916, 1.0486031770706177, 0.0),
    
            (0.30480796098709106, 0.9197763204574585, 0.0),
    
            (0.4225005805492401, 0.9877263307571411, 0.0),
    
            (0.30480796098709106, 0.9197763204574585, 0.0),
    
            (0.35196200013160706, 0.7437955141067505, 0.0),
    
            (-0.48786139488220215, 0.7437955141067505, 0.0),
    
            (0.48786139488220215, 4.5077928945147505e-08, 0.0),
    
            (0.48786139488220215, 0.7437955141067505, 0.0),
    
            (-0.3519617021083832, 0.7437955141067505, 0.0),
    
            (-0.4225003123283386, 0.9877263307571411, 0.0),
    
            (-0.48786139488220215, 0.7437955141067505, 0.0),
    
            (-0.4225003123283386, 0.9877263307571411, 0.0),
    
            (-0.1759808510541916, 1.0486031770706177, 0.0),
    
            (-0.24393069744110107, 1.1662957668304443, 0.0),
    
            (-0.24393069744110107, 1.1662957668304443, 0.0),
    
            (2.9078805141580233e-08, 1.0957571268081665, 0.0),
    
            (2.9078805141580233e-08, 1.2316569089889526, 0.0),
    
            (2.9078805141580233e-08, 1.2316569089889526, 0.0),
    
            (0.1759808510541916, 1.0486031770706177, 0.0),
    
            (0.243930846452713, 1.1662957668304443, 0.0),
    
            (0.243930846452713, 1.1662957668304443, 0.0),
    
            (0.30480796098709106, 0.9197763204574585, 0.0),
    
            (0.4225005805492401, 0.9877263307571411, 0.0),
    
            (0.4225005805492401, 0.9877263307571411, 0.0),
    
            (0.35196200013160706, 0.7437955141067505, 0.0),
    
            (0.487861692905426, 0.74379563331604, 0.0),
    
            (-0.48786139488220215, 0.7437955141067505, 0.0),
    
            (-0.48786139488220215, 4.5077928945147505e-08, 0.0),
    
            (0.48786139488220215, 4.5077928945147505e-08, 0.0),
    
        ]
    
    
        tris_open = [
    
            (-0.12838619947433472, 1.3143587112426758, 0.0),
    
            (0.025773197412490845, 1.411454677581787, 0.0),
    
            (-0.0144234299659729, 1.541273593902588, 0.0),
    
            (-0.0144234299659729, 1.541273593902588, 0.0),
    
            (0.025773197412490845, 1.411454677581787, 0.0),
    
            (0.20782703161239624, 1.4184625148773193, 0.0),
    
            (0.23792517185211182, 1.5509872436523438, 0.0),
    
            (0.20782703161239624, 1.4184625148773193, 0.0),
    
            (0.3689943850040436, 1.3335046768188477, 0.0),
    
            (0.4613226056098938, 1.433225393295288, 0.0),
    
            (0.3689943850040436, 1.3335046768188477, 0.0),
    
            (0.4660903215408325, 1.1793451309204102, 0.0),
    
            (0.5959094166755676, 1.2195416688919067, 0.0),
    
            (0.4660903215408325, 1.1793451309204102, 0.0),
    
            (0.47309836745262146, 0.997291088104248, 0.0),
    
            (0.6056233048439026, 0.9671931266784668, 0.0),
    
            (0.47309836745262146, 0.997291088104248, 0.0),
    
            (0.3881405293941498, 0.8361238241195679, 0.0),
    
            (-0.48786139488220215, 0.7437955141067505, 0.0),
    
            (0.48786139488220215, 4.5077928945147505e-08, 0.0),
    
            (0.48786139488220215, 0.7437955141067505, 0.0),
    
            (-0.12838619947433472, 1.3143587112426758, 0.0),
    
            (-0.0144234299659729, 1.541273593902588, 0.0),
    
            (-0.22810709476470947, 1.406686782836914, 0.0),
    
            (-0.0144234299659729, 1.541273593902588, 0.0),
    
            (0.20782703161239624, 1.4184625148773193, 0.0),
    
            (0.23792517185211182, 1.5509872436523438, 0.0),
    
            (0.23792517185211182, 1.5509872436523438, 0.0),
    
            (0.3689943850040436, 1.3335046768188477, 0.0),
    
            (0.4613226056098938, 1.433225393295288, 0.0),
    
            (0.4613226056098938, 1.433225393295288, 0.0),
    
            (0.4660903215408325, 1.1793451309204102, 0.0),
    
            (0.5959094166755676, 1.2195416688919067, 0.0),
    
            (0.5959094166755676, 1.2195416688919067, 0.0),
    
            (0.47309836745262146, 0.997291088104248, 0.0),
    
            (0.6056233048439026, 0.9671931266784668, 0.0),
    
            (0.6056233048439026, 0.9671931266784668, 0.0),
    
            (0.3881405293941498, 0.8361238241195679, 0.0),
    
            (0.48786142468452454, 0.74379563331604, 0.0),
    
            (-0.48786139488220215, 0.7437955141067505, 0.0),
    
            (-0.48786139488220215, 4.5077928945147505e-08, 0.0),
    
            (0.48786139488220215, 4.5077928945147505e-08, 0.0),
    
        ]
    
    
        def draw(self, context):
    
            self.draw_custom_shape(self.get_custom_shape(context))
    
    
        def get_custom_shape(self, context):
    
            return (
    
                self.custom_shape_closed
    
                if context.active_object.BIMStairProperties.total_length_lock
    
                else self.custom_shape_open
    
            )
    
    
        def setup(self):
    
            if not hasattr(self, "custom_shape_closed"):
    
                self.custom_shape_closed = self.new_custom_shape("TRIS", self.tris_closed)
    
            if not hasattr(self, "custom_shape_open"):
    
                self.custom_shape_open = self.new_custom_shape("TRIS", self.tris_open)
    
    
        def draw_select(self, context, select_id):
    
            self.draw_custom_shape(self.get_custom_shape(context), select_id=select_id)
    
    
    
    
    class MY_GIZMOGROUP_GT(bpy.types.GizmoGroup):
    
        bl_idname = "MY_GIZMOGROUP_GT"
    
        bl_label = "My Gizmo Group"
    
        bl_space_type = "VIEW_3D"
    
        bl_region_type = "WINDOW"
    
        bl_options = {"3D", "PERSISTENT"}
    
    
        attr_names = (
    
            "width",
    
            "height",
    
            "total_length_target",
    
            "nosing_length",
    
            "nosing_depth",
    
            "tread_run",
    
            "tread_depth",
    
            "tread_rise",
    
        )
    
        red = (1, 0.2, 0.2)
    
        green = (0.2, 0.8, 0.2)
    
        blue = (0.2, 0.2, 1)
    
        gizmo_scale_basis = 0.75
    
        colors = {
    
            "width": green,
    
            "height": blue,
    
            "total_length_target": red,
    
            "nosing_length": red,
    
            "nosing_depth": blue,
    
            "tread_run": red,
    
            "tread_depth": blue,
    
            "tread_rise": blue,
    
        }
    
    
        **@classmethod**
    
        def poll(cls, context):
    
            return (
    
                context.active_object
    
                and hasattr(context.active_object, "BIMStairProperties")
    
                and context.active_object.BIMStairProperties.is_editing
    
            )
    
    
        def get_gizmo_matrix_width(self, props):
    
            return Matrix.Rotation(-pi / 2, 4, Vector((1, 0, 0)))
    
    
        def get_gizmo_matrix_height(self, props):
    
            return Matrix.Translation(((props.number_of_treads + 1) * props.tread_run, 0, 0))
    
    
        def get_gizmo_matrix_total_length_target(self, props):
    
            return Matrix.Translation((0, 0, props.height)) @ Matrix.Rotation(pi / 2, 4, (0, 1, 0))
    
    
        def get_gizmo_matrix_nosing_length(self, props):
    
            return Matrix.Translation((0, 0, props.height / (props.number_of_treads + 1))) @ Matrix.Rotation(
    
                -pi / 2, 4, (0, 1, 0)
    
            )
    
    
        def get_gizmo_matrix_nosing_depth(self, props):
    
            return Matrix.Translation(
    
                (-props.nosing_length, 0, props.height / (props.number_of_treads + 1))
    
            ) @ Matrix.Rotation(-pi, 4, (0, 1, 0))
    
    
        def get_gizmo_matrix_tread_run(self, props):
    
            return Matrix.Translation((0, 0, props.height / (props.number_of_treads + 1))) @ Matrix.Rotation(
    
                pi / 2, 4, (0, 1, 0)
    
            )
    
    
        def get_gizmo_matrix_tread_depth(self, props):
    
            return Matrix.Translation(((props.number_of_treads + 1) * props.tread_run, 0, props.height)) @ Matrix.Rotation(
    
                pi, 4, (0, 1, 0)
    
            )
    
    
        def get_gizmo_matrix_tread_rise(self, props):
    
            return Matrix.Translation((props.tread_run, 0, 0))
    
    
        def setup(self, context):
    
            def add_gizmo_prop(prop, move_get_cb_override=None, move_set_cb_override=None):
    
                gizmo = self.gizmos.new("GIZMO_GT_arrow_3d")
    
    
                def move_get_cb(p):
    
                    props = bpy.context.active_object.BIMStairProperties
    
                    return getattr(props, p)
    
    
                def move_set_cb(p, value):
    
                    props = bpy.context.active_object.BIMStairProperties
    
                    setattr(props, p, value)
    
    
                gizmo.target_set_handler(
    
                    "offset",
    
                    get=lambda: move_get_cb_override(prop) if move_get_cb_override else move_get_cb(prop),
    
                    set=lambda value: (
    
                        move_set_cb_override(prop, value) if move_set_cb_override else move_set_cb(prop, value)
    
                    ),
    
                )
    
    
                gizmo.color = self.colors.get(prop, (1, 1, 1))
    
                setattr(self, f"gizmo_{attr_name}", gizmo)
    
                gizmo.scale_basis = self.gizmo_scale_basis
    
                gizmo.alpha = 1
    
                gizmo.alpha_highlight = 1
    
    
            for attr_name in self.attr_names:
    
                if attr_name == "tread_rise":
    
    
                    def move_get_cb(p):
    
                        props = bpy.context.active_object.BIMStairProperties
    
                        return props.height / (props.number_of_treads + 1)
    
    
                    def move_set_cb(p, value):
    
                        props = bpy.context.active_object.BIMStairProperties
    
                        props.height = value * (props.number_of_treads + 1)
    
    
                    add_gizmo_prop(attr_name, move_get_cb_override=move_get_cb, move_set_cb_override=move_set_cb)
    
                else:
    
                    add_gizmo_prop(attr_name)
    
    
            self.gizmo_lock_length = self.gizmos.new("VIEW3D_GT_lock")
    
    
            props = self.gizmo_lock_length.target_set_operator("serendipicad.toggle_gizmo")
    
            props.obj = context.active_object.name
    
            props.prop_path = "BIMStairProperties.total_length_lock"
    
            self.gizmo_lock_length.scale_basis = 0.5
    
            self.gizmo_lock_length.use_draw_modal = True
    
            self.gizmo_lock_length.alpha = 1
    
            self.gizmo_lock_length.alpha_highlight = 1
    
    
        def refresh(self, context):
    
            obj = context.active_object
    
            props = obj.BIMStairProperties
    
            for attr_name in self.attr_names:
    
                gizmo = getattr(self, f"gizmo_{attr_name}", None)
    
                if gizmo is None:
    
                    continue
    
                gizmo.matrix_basis = obj.matrix_world @ getattr(self, f"get_gizmo_matrix_{attr_name}")(props)
    
    
                if attr_name == "total_length_target":
    
                    gizmo.scale_basis = 0 if props.total_length_lock else self.gizmo_scale_basis
    
                if attr_name == "tread_depth":
    
                    gizmo.scale_basis = 0 if props.stair_type == "GENERIC" else self.gizmo_scale_basis
    
                if attr_name == "nosing_depth":
    
                    gizmo.scale_basis = 0 if props.nosing_length == 0 else self.gizmo_scale_basis
    
    
            matrix = obj.matrix_world @ Matrix.Rotation(-pi / 2, 4, Vector((0, 0, 1)))
    
            matrix @= Matrix.Translation((-props.width / 2, props.total_length_target + 0.5, props.height))
    
            self.gizmo_lock_length.matrix_basis = matrix
    
            self.gizmo_lock_length.color = self.red if props.total_length_lock else self.green
    
            self.gizmo_lock_length.color_highlight = [c + 0.5 for c in self.gizmo_lock_length.color]
    
    
    
    
    bpy.utils.register_class(BIM_OT_toggle_gizmo)
    
    bpy.utils.register_class(GizmoLock)
    
    bpy.utils.register_class(MY_GIZMOGROUP_GT)
    
  2. S

    @Gorgious

    cool stuff!

    From my personal wishlist: a Gizmo, or some shortcut, to vertically extend the height of walls against a beam, similar to what available with walls/slabs, I currently have to manually take measurements for that.

    ..or is there already and I missed it? ;)

    thanks

  3. D

    Wow, this absolutely amazing, great work.

    While I generally dislike working with gizmos (as opposed to hotkeys), this is a great for usability, and a big step to make Bonsai more user friendly for less technical users.

    This is an excellent step in answer > @Moult question:

    What's stopping us from unhesitatingly recommending Bonsai (...)?

    To make it truly usable I also think supporting snapping is a must though, since in this field we tend to dislike eyeballing stuff. ;)

    It does not sound trivial though, especially with multiple selections

  4. T
  5. G

    Another experiment on walls :

    I do think that snapping must be supported before further work on the matter to make it really useful.

    @theoryshaw I believe code could be extended to work on all selected objects rather than active one. Might be very hacky though. I think a constraint based parametric system would be more suitable for your need. eg assign specific properties from several objects to a group and when modifying the group the values are updated and geometry, too.

    Edit : Well it already kinda does

  6. S

    @Gorgious

    I do think that snapping must be supported before further work on the matter to make it really useful.

    1000+

    @duarteframos

    I too prefer hotkey, for smoother workflow, but maybe it's a matter of taste. For vertically extend walls against beams I won't complain if a gizmo were available right now though :D

  7. T

    bring in the snap doctor! ;) @bruno_perdigao

  8. Z

    I love Gizmos everywhere for everything !

    Reliable and easy locking translations in 3D to one or more axes I helpful.

  9. M

    Yes I think this is definitely planned, ill have a chat with Bruno :)

  10. M

Login or Register to reply.