Making an Add-on for Precision Drawing Tools (CAD)

Thank you!

All the functions are in the new version now, here I am extruding all selected vertices to the point where the active vertex is normal (perpendicular if you prefer) to the imaginary line between the other two:

Before:

After:

I am tempted to add a Duplicate operation as well, I will think on this before I do it…

I need to do some bug testing, then I can upload this for you all to pull to pieces… :joy:

Cheers, Clock. :cocktail:

EDIT:

I couldn’t agree more… but we need more support to get this into Blender.

3 Likes

Or you can use the shear operator, with the 3D cursor as the pivot point and a magnitude of 1. If you want to stick with the scale operator, enable advanced numeric input and use sqrt(2) as your scale factor instead of doing the math externally.

1 Like

Yes, I am aware of this, but you cant shear say 8 degrees, like you would want to do if you were making a casting with 8 degree relief angles…

Again, yes I know this, but my quoting the value of root 2 was just a little joke…

Cheers, Clock.

Hi there, I’m a bit late on this thread and sadly haven’t got much to offer to the discussion, what it really got my attention is what you mentioned on you first post when you say you used AN (animation nodes?) and that you don’t need AN to execute the node. I have also built a nodegraph with AN composed of several nodetrees and would like to transform it into a addon but not sure how to do that, do you? I know AN can writhe the execution code from a nodetree but when you have a bunch of those it doesn’t work and it still need the AN addon to work.

Please if you have any info on this it would be very much appreciated. Thank you

1 Like

Welcome to Devtalk!

I am a bit busy just now so I will get back to you later today. Basically I used all the functions from my node in the Add-on, but made other code to make it an Add-on. I had to learn how to do this first as I had never done one before…

Cheers, Clock.

Edit:

I will be uploading this code to my website soon, once all the debugging is done.

1 Like

Here is the current state of play - I have added the “Taper” Tool - this allows you to effectively “Shear” some vertices, but instead of the Blender system where you just move along an axis for a set amount, you can put in the angle of the shear. Here is before:

The Active Vertex is the rotation centre, I am working in “RotX-MovY” mode, which will effectively rotate the face about the Z axis and move the vertices along the Y axis, here is after:

I know you can do 45 degrees by normal Blender shearing techniques, but what about the other end that was done to 19.5639 degrees?

I have also added the “Origin To Cursor” Tool, this works in Edit mode and makes the origin of the object the location of the cursor, without moving the vertices from their position in world space. The Cursor can be positioned using any of the PDT tools, or standard Bender tools. You do NOT have to exit Edit mode to accomplish this task now.

Cheers, Clock. :cocktail::crazy_face:

1 Like

OK I am stuck again, using code from the 2.8 API docs, I wrote this:

        elif data == 'DG':
            ret = bmesh.ops.duplicate(bm, geom=[v for v in bm.verts if v.select]+
                        [e for e in bm.edges if e.select]+[f for f in bm.faces if f.select])
            geom_dupe = ret["geom"]
            verts_dupe = [v for v in geom_dupe if isinstance(v, bmesh.types.BMVert)]
            del ret
            bmesh.ops.translate(
                bm,
                verts=verts_dupe,
                vec=(x_loc, y_loc, z_loc))
            bmesh.update_edit_mesh(obj.data)
        elif data == 'EG':
            ret = bmesh.ops.extrude_face_region(bm, geom=[v for v in bm.verts if v.select]+
                        [e for e in bm.edges if e.select]+[f for f in bm.faces if f.select])
            geom_dupe = ret["geom"]
            verts_dupe = [v for v in geom_dupe if isinstance(v, bmesh.types.BMVert)]
            del ret
            bmesh.ops.translate(
                bm,
                verts=verts_dupe,
                vec=(x_loc, y_loc, z_loc))
            bmesh.update_edit_mesh(obj.data)

I already have bm established as the Bmesh from the object. Can anyone see what is going wrong, when run this, it just clears the selection and does nothing in the way of new geometry.

Thanks!

Shearing by 1/tan(radians(90 + θ)) hasn’t let me down yet, but I’d be lying if I said I your implementation isn’t more convenient.

1 Like

Yes I can you d***wad! I looked at it :microscope: and you left the brackets of the set of geom=( blah, blah)! This works better:

                ret = bmesh.ops.duplicate(bm, geom = (
                    [f for f in bm.faces if f.select]+
                    [e for e in bm.edges if e.select]+
                    [v for v in bm.verts if v.select]),
                    use_select_history = True)

Now it does this:

Extruded Geometry in “Direction” mode (Distance @ Angle) - works for “Duplicate Geometry” as well and updates the selection so you can just keep going… I also made the face on its own by this duplication method. It will work on Vertices, Edges and Faces all at once.

Cheers, Clock. :upside_down_face::joy:

3 Likes

Well, this is a significant moment for me, no, I have not just lost my virginity, I have just done this in a very short time (40 mins):

“So what!” I hear you cry, well a number of things:

  1. The beam and brackets are perfectly accurate, within the confines of double precision floating numbers.
  2. The holes are not done with Booleans so the polygon (isn’t a “polygon” an empty parrot cage) topology is not wrecked, same goes for the bevels.
  3. The flanges on the beam are exactly at 5 degrees and are not drawn by zooming in and putting the vertices as close as you can by eye to get an intersection. The brackets slope at exactly 2.5 degrees BTW.
  4. It has dimensions and can be fully dimensioned.
  5. It can now be produced as an engineering drawing.
  6. It was modelled using conventional CAD techniques.

I can now see a route to producing a proper 4 View (Front, Right & Top Elevations with Isometric) drawing, with a border, having now found out how to dimension and make them viewable in different orientations. I will of course add all significant dimensions before making the drawing. I am also playing with producing line drawings using Freestyle, but am not very good with that yet… :poop:

So why significant? Well it is all done with Vanilla Blender, MeasureIt and my PDT add-on, not a CAD package in sight. Well, I am pleased anyway. :grin:

Cheers, Clock. :cocktail:

PS. If you are wondering what it is, it forms part of the chassis of the latest Ford Pickup Truck. :rofl::rofl:

13 Likes

nice to see how your project (and skills) grows :wink:

1 Like

You can do it this way, but if you have a large number of properties to register, using bpy.types.PropertyGroup and bpy.props.PointerProperty can sometimes make for cleaner code.

For a quick and ugly example:

import datetime
import bpy

class TestPropG(bpy.types.PropertyGroup):
    time_test_min: bpy.props.IntProperty()
    time_test_sec: bpy.props.IntProperty() 
    time_set: bpy.props.BoolProperty(default=False)
    
class NAMESPACE_OT_lowcase(bpy.types.Operator):
    bl_idname = "namespace.lowcase"
    bl_label = "My Basic Operator"

    def invoke(self, context, event):
        print("\nRunning: My Basic Operator")  # debug
        foo = bpy.context.scene.test_the_pg
        if foo.time_set:
            print("prev min: %2d" % foo.time_test_min)
            print("prev sec: %2d" % foo.time_test_sec)
        else:
            print("no prev time")
            foo.time_set = True
        tmp_time = datetime.datetime.now()
        foo.time_test_min = tmp_time.minute
        foo.time_test_sec = tmp_time.second
        return {'FINISHED'}


def register():
    bpy.utils.register_class(TestPropG)
    bpy.utils.register_class(NAMESPACE_OT_lowcase)
    bpy.types.Scene.test_the_pg = bpy.props.PointerProperty(type=TestPropG)

def unregister():
    del bpy.types.Scene.test_the_pg
    bpy.utils.unregister_class(NAMESPACE_OT_lowcase)
    bpy.utils.unregister_class(TestPropG)

if __name__ == "__main__":
    register()

This adds several properties into TestPropG class when register runs. It can then be stored as a sort-of “super property” through the test_the_pg PointerProperty which gets stored in the scene.

Each time you call “My Basic Operator” from Blender’s search menu, it will print out the previous time “My Basic Operator” was run.

I think there’s a better way to implement this with the CollectionProperty, but I can’t remember it’s implementation details right now.

4 Likes

I’m impressed, and very curious, to try out your current state of development.

Have you considered to publish your code on Github.com ?

1 Like

I am not at home till later today, I have a github, but it is not on there yet, I will either upload it to my website, or github later today, it is not finished yet and there was a no help file either!

Cheers, Clock

Thank you! I was a bit busy yesterday at my flying club, so please excuse short response.

I have uploaded the Add-on to my website, I have not had time to do any help for this yet, but I hope it is reasonably intuitive and if you have any question, please ask and I will reply.

Both versions are there, but the UI version has more options and is better written. I have not had time to completely test it yet so there may still be some errors…

My GitHub is currently only concerned with my Animation Nodes additions, so I really need to make another project for this work, again I can do that in time.

@_nBurn Thank you very much for that code, I will look at how I can implement this, it seems a better idea than having lots of individual variables. Currently there are 13 variables including the enumerators for the options selectors, so it is definitely worth doing this.

Cheers Clock.

1 Like

OK, it’s not perfect yet, I am having problems getting the dimension lines to work properly with MeasureIT, but it shows some promise:

It is, however, 100% Blender + MeasureIt + my PDT, I can now render this, but not this time!

Cheers, Cock.

4 Likes

OK, I would like some help please.

I have a list of object in an outside blend file that I got with this code:

path = str(bpy.utils.user_resource('SCRIPTS', "addons"))+'/clockworxpdt/parts_library.blend'
with bpy.data.libraries.load(path) as (data_from, data_to):
    object_names = [ob for ob in data_from.objects]

I would like to display this as a list in the UI Panel so I can select one of the objects, then I will append this object to the current blend file - this I can do, but I cannot seem to make the list selector in the UI panel, can someone help please? I think I have to use a template_list but I am not sure… Does someone have a worked example for this?

The variable object_names is a list of the object names from the external blend file, so this code will append the second one in the list:

bpy.ops.wm.append(filepath=path,directory=path+'/Object',filename=object_names[1])
obj = bpy.context.view_layer.objects[object_names[1]]
obj.location = Vector((3,0,0))

And move it to location 3,0,0.

Thanks, Clock.

OK, normal service is resumed, I ask for help, I work it out for myself… :rofl::rofl::rofl::rofl:

Here is the new stuff:

The Parts Library is now at Beta 1 stage. I can either link or append objects, collections and materials from my library to any blender project file. I made the objects without materials in the library, so I could then add them to a project and link a material from the library to the appended objects. That way you don’t get a shed load of copy materials. It all seems to be working well at this stage of testing, but I need to try to break it. I have also done some cleaning of the code for the PDT Drawing Tools bit, but not uploaded this to my website yet.

Appended objects are positioned at the cursor location, rather than at their original origin point, this makes sense to me when assembling something from library parts, just placed the cursor where you want the part and append it, it is already then in the right place.

I solved the problem of creating choices for whatever is in the library by trickery, lateral thinking and black magic - my favourite kind of development tools. :mechanical_arm: :pray:

Cheers, Clock. :cocktail: :grin:

4 Likes

Hi @clockmender,

It has been a pleasure reading through this thread every now and then. I didn’t manage to read each and every post, so maybe I’ve missed the bit. I was wondering if you have considered custom construction planes?

Thanks for the kind comment @jesterKing

This is on my ToDo list, I want to be able to use the view rotation and to set that with precise values (like rotate the view 17.5 degrees in Z, then 11 in X), but I don’t know yet how to translate Global values in Local for the view, so if I have a delta of 1,3,1 in Global it is easy, but I have to work out how to translate that into Local in the view orientation of 1,3,1… I think I have to use Matrix Maths, but I am not sure yet. I would then add “Custom” to the working plane selection, which would mean “Current View Orientation”.

I hope to be able to do this soon, so if anyone wants to point me in the right direction, I should be most grateful!

Cheers, Clock. :cocktail:

EDIT:

I have now added Search Strings to the Parts Library options so you can just search for objects with “gear” in them for example:

12