NP Station

Very cool and well thought…

There had been efforts to build features like this into Blender (for example, the Blueprint tool that can create and remove geometry), but I’m not sure what the status is on them. I think development on these features may have been put on hold to focus more on bug fixing. Those updates were the original reason I held off on porting more of NP Station to 2.8 as they were going to include changes to the parts of Blender’s add-on interface that NP Station used.

Thanks, been a while since that post. Hope the numbers are more in our favor these days :wink:

Anybody happen to know a user-friendly means of rotating an object to align to another rotated object’s face? Alike the NP station’s “select 1st, 2nd vertex, rotate and snap to 3rd vertex”.

Been beating my head against a wall with this

copy object to selected has no Interface.

Select Vertex, Edge or Face on your Source Object, then selected Vertex, Edge or Face on your target Object.
Search function (F3) – copy object to selected.
The Script makes a Copy from your Source Object and place it on the Target Object.

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

bl_info = {
    "name": "Copy Object To Selected",
    "author": "Rolando Pera",
    "description": "",
    "blender": (2, 80, 0),
    "location": "",
    "warning": "",
    "category": "Generic",
}

import bpy
from mathutils import Vector, Matrix, Quaternion
from itertools import chain


def copy_object_at_face(target_obj, target_obj_mesh, source_obj, source_obj_mesh, to_mat_world, this_collection):

    # copy the object at the center of each face with the same orientation as its normal
    for poly in target_obj_mesh.polygons:
        if poly.select is True:
            copy = source_obj.copy()
            this_collection.objects.link(copy)
            copy_mat_world = copy.matrix_world
            bpy.ops.mesh.primitive_circle_add(vertices=3, radius=0.2, fill_type="NGON")
            parent_obj = bpy.context.active_object

            for face in source_obj_mesh.polygons:
                if face.select is True:
                    copy_loc = Matrix.Translation(face.center)
                    copy_quat = face.normal.to_track_quat("-Z", "Y")
                    copy_mat = (copy_mat_world @ copy_loc @ copy_quat.to_matrix().to_4x4())
                    parent_obj.matrix_world = copy_mat
                    copy.select_set(True)
                    bpy.ops.object.parent_set(type="OBJECT")
                    loc = Matrix.Translation(poly.center)
                    quat = poly.normal.to_track_quat("Z", "Y")
                    mat = to_mat_world @ loc @ quat.to_matrix().to_4x4()
                    parent_obj.matrix_world = mat
                    bpy.ops.object.parent_clear(type="CLEAR_KEEP_TRANSFORM")
                    copy.select_set(False)
                    bpy.ops.object.delete()
    return


def copy_object_at_vertex(target_obj, target_obj_mesh, source_obj, source_obj_mesh, to_mat_world, this_collection):

    # copy the object at selected vertices with the same orientation as their normals
    for vertex in target_obj_mesh.vertices:
        if vertex.select is True:
            copy = source_obj.copy()
            this_collection.objects.link(copy)
            copy_mat_world = copy.matrix_world

            bpy.ops.mesh.primitive_circle_add(vertices=3, radius=0.2,
                                              fill_type="NGON")
            parent_obj = bpy.context.active_object

            for face in source_obj_mesh.polygons:
                if face.select is True:
                    copy_loc = Matrix.Translation(face.center)
                    copy_quat = face.normal.to_track_quat("-Z", "Y")
                    copy_mat = (copy_mat_world @ copy_loc @ copy_quat.to_matrix().to_4x4())
                    parent_obj.matrix_world = copy_mat
                    copy.select_set(True)
                    bpy.ops.object.parent_set(type="OBJECT")
                    loc = Matrix.Translation(vertex.co)
                    quat = vertex.normal.to_track_quat("Z", "Y")
                    mat = to_mat_world @ loc @ quat.to_matrix().to_4x4()
                    parent_obj.matrix_world = mat
                    bpy.ops.object.parent_clear(type="CLEAR_KEEP_TRANSFORM")
                    copy.select_set(False)
                    bpy.ops.object.delete()
    return


def copy_item_at_face(target_obj, target_obj_mesh, source_obj, to_mat_world, this_collection):

    # copy the object at the center of each face with the same orientation as its normal
    for poly in target_obj_mesh.polygons:
        if poly.select is True:
            copy = source_obj.copy()
            this_collection.objects.link(copy)
            loc = Matrix.Translation(poly.center)
            quat = poly.normal.to_track_quat("-Z", "Y")
            mat = to_mat_world @ loc @ quat.to_matrix().to_4x4()
            copy.matrix_world = mat
    return


def copy_item_at_vertex(target_obj, target_obj_mesh, source_obj, to_mat_world, this_collection):

    # copy the object at selected vertices with the same orientation as their normals
    for vertex in target_obj_mesh.vertices:
        if vertex.select is True:
            copy = source_obj.copy()
            this_collection.objects.link(copy)
            loc = Matrix.Translation(vertex.co)
            quat = vertex.normal.to_track_quat("-Z", "Y")
            mat = to_mat_world @ loc @ quat.to_matrix().to_4x4()
            copy.matrix_world = mat
    return


def create_collection(collection_name):
    if collection_name in bpy.data.collections:
        return bpy.data.collections[collection_name]
    else:
        new_collection = bpy.data.collections.new(collection_name)
        scene.collection.children.link(new_collection)
        return new_collection


class OBJECT_OT_copy_to_selected(bpy.types.Operator):
    """Copy Object to Selected"""

    bl_idname = "object.copy_to_selected"
    bl_label = "Copy Object to Selected"
    bl_options = {"REGISTER", "UNDO"}

    def execute(self, context):

        # some initial values
        global scene
        scene = bpy.context.scene
        source_obj_sel = "mesh_obj"

        #  get the number of objects selected
        sel_objs = bpy.context.selected_objects
        num_sel_objs = len(sel_objs)
        if num_sel_objs != 2:
            self.report({"ERROR"}, "Two objects must be selected (source and target).")
            return {"FINISHED"}

        # get the currently selected mesh selection mode
        select_mode = context.tool_settings.mesh_select_mode
        sel_mode_face = select_mode[2]

        # store the location of 3d cursor
        cursor_location = scene.cursor.location.copy()

        # get current active object, apply scale, set mode to object and deselect it
        target_obj = bpy.context.active_object
        if target_obj.type != "MESH":
            self.report({"ERROR"}, "The target object is not a Mesh.")
            return {"FINISHED"}
        bpy.ops.object.mode_set(mode="OBJECT")
        bpy.ops.object.make_single_user(type="SELECTED_OBJECTS", obdata=True)
        bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
        target_obj_mesh = target_obj.data
        to_mat_world = target_obj.matrix_world
        bpy.context.active_object.select_set(False)

        sel_obj = bpy.context.selected_objects

        # if it is a mesh or a curve, center its origin on selected edit mode element
        for obj in sel_obj:
            context.view_layer.objects.active = obj
            source_obj_mesh = obj.data
            if obj.type == "MESH":
                sel_poly_source = [f for f in source_obj_mesh.polygons if f.select]
                # verify that only one face is selected on source object
                if len(sel_poly_source) != 1:
                    bpy.ops.object.mode_set(mode="EDIT")
                    self.report({"ERROR"}, "Select one face (none or more than one selected).")
                    return {"FINISHED"}
                obj_mat_world = obj.matrix_world
                for face in source_obj_mesh.polygons:
                    if face.select:
                        center_loc = obj_mat_world @ Vector(face.center)
                        scene.cursor.location = center_loc
                bpy.ops.object.origin_set(type="ORIGIN_CURSOR")

            elif obj.type == "CURVE":
                sel_points = [
                    p
                    for p in chain(*[s.bezier_points for s in source_obj_mesh.splines])
                    if p.select_control_point
                ]
                bpy.ops.object.mode_set(mode="EDIT")
                if len(sel_points) != 1:
                    self.report({"ERROR"}, "One control point must be selected (none or more selected).")
                    return {"FINISHED"}
                bpy.ops.view3d.snap_cursor_to_selected()
                bpy.ops.object.mode_set(mode="OBJECT")
                bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
                source_obj_sel = "none_mesh_obj"

            else:
                source_obj_sel = "none_mesh_obj"

        # set 3d cursor at origin of world
        scene.cursor.location = (0.0, 0.0, 0.0)

        # get curently active object to copy
        source_obj = bpy.context.object

        # get name of object to create collection name if it doesn't exists
        collection_name = source_obj.name + "_Copies"
        """ self.report({"ERROR"}, collection_name)
        return {"FINISHED"} """

        this_collection = create_collection(collection_name)

        # get what elements are selected on target object and the right function to call
        sel_poly_target = [p for p in target_obj_mesh.polygons if p.select]
        sel_vert_target = [v for v in target_obj_mesh.vertices if v.select]

        if sel_poly_target != [] and source_obj_sel == "mesh_obj":
            if sel_mode_face is True:
                copy_object_at_face(
                    target_obj,
                    target_obj_mesh,
                    source_obj,
                    source_obj_mesh,
                    to_mat_world,
                    this_collection,
                )
            else:
                copy_object_at_vertex(
                    target_obj,
                    target_obj_mesh,
                    source_obj,
                    source_obj_mesh,
                    to_mat_world,
                    this_collection,
                )
        elif sel_poly_target != [] and source_obj_sel == "none_mesh_obj":
            if sel_mode_face is True:
                copy_item_at_face(target_obj, target_obj_mesh, source_obj, to_mat_world, this_collection)
            else:
                copy_item_at_vertex(target_obj, target_obj_mesh, source_obj, to_mat_world, this_collection)
        elif sel_vert_target != [] and source_obj_sel == "mesh_obj":
            copy_object_at_vertex(target_obj, target_obj_mesh, source_obj, source_obj_mesh, to_mat_world, this_collection)
        elif sel_vert_target != [] and source_obj_sel == "none_mesh_obj":
            copy_item_at_vertex(target_obj, target_obj_mesh, source_obj, to_mat_world, this_collection)
        else:
            bpy.context.view_layer.objects.active = target_obj
            # target_obj.select = True
            target_obj.select_set(True)
            bpy.ops.object.mode_set(mode="EDIT")
            self.report({"ERROR"}, "At least one face or vertex should be selected on the target.")
            return {"FINISHED"}

        # set 3d cursor back to original location
        scene.cursor.location = cursor_location

        # set source object to active
        context.view_layer.objects.active = source_obj

        # set source object into edit mode and selection to faces. Select all the faces
        if source_obj_sel == "mesh_obj":
            bpy.ops.object.mode_set(mode="EDIT")
            bpy.context.tool_settings.mesh_select_mode = (False, False, True)
            bpy.ops.mesh.select_all(action="SELECT")

            # change transform to normal and pivot to active element
            context.scene.transform_orientation_slots[0].type = "NORMAL"
            bpy.data.scenes[0].tool_settings.transform_pivot_point = "ACTIVE_ELEMENT"

        elif source_obj_sel == "none_mesh_obj":
            # self.report({"ERROR"}, "None mesh object")
            # change transform to global and pivot to individual origins
            context.scene.transform_orientation_slots[0].type = "GLOBAL"
            bpy.data.scenes[0].tool_settings.transform_pivot_point = "INDIVIDUAL_ORIGINS"
        # end of def execute()
        return {"FINISHED"}


classes = (OBJECT_OT_copy_to_selected,)


def register():
    from bpy.utils import register_class

    for cls in classes:
        register_class(cls)


def unregister():
    from bpy.utils import unregister_class

    for cls in reversed(classes):
        unregister_class(cls)

Thank you so much nBurn sir.
the WIP is working in 2.81 what is just amazing.

What a great addon.

This np_station_29.09.19.zip doesn’t seem to be working with Blender 2.82a. Is there a version of np_station that does work with Blender 2.82a?

1 Like

Maybe, I don’t know.
I am not a maintainer)
We are actually waiting for default base snap abilities in 2.8x

Awsome. And thats just what we need: noun-verb action.
And pivot setting within, not as second action to any transformation.
Already invented by Sketchup-Google and many others…

1 Like

Noun-verb was invented way before Google existed)
In software like AutoCAD

True but thats not the point.
The point is pivot operation /and UCS also/.
In SKP by Google all is about EASE for quick operation and creation.

This software was built around that paradigm.

Judging by the number of various addons on Origin&Pivot manipulation
sth really unhealthy with it. And this ilness is stubborn and protruding one…;
Recently ,introduced with a bang, dissolving, destructive Extrude, well known as Push/Pull )) - also by SKP…So hard to make it earlier?! I can rememeber discussion on this forum in sth about 2009

1 Like

Sorry guys, the addon works only with Blender 2.7x.

I’d buy that tool for 2.8+ and I think many architecture related artists and (ex-)sketchup users would. Any chance You’d consider updating it?

3 Likes

I want to be the first one to buy this addon

1 Like

Thanks guys. I wish it was that easy.

NP_Station is huge and really badly written by a frustrated architect, basically as a proof of concept. If i were to update it i would have to write it from zero and have resources to maintain and upgrade it afterwards. That would require a really good python coder with full time devotion. I am just an architect and it takes one and a half day per day already.

Furthermore i made a decision early on that, as Blender is, NP_Station would remain free forever. The hope was that the Blender community and developers would pick up the importance of having such tools and take it from there. The devs are payed for this and are programmers by vocation. The problem is - the tools never gained the needed momentum and public support and i never gained superhuman powers…

4 Likes

But you might coordinate us for lobbying for that :wink: ??

1 Like

Well, we can’t say i didn’t try before :slight_smile:

Perhaps some sort of organizing the CAD (precision modelling) user base could help enhancing the visibility…

Let’s face it, there was only about 320 downloads of NP_Station in the 2.7x era (out of a quarter of a million of registered users of this site). And the addon is free. It seems there is simply not enough users interested in this stuff.

Well, hard to argue the facts indeed…
Do you-however-consider upgrade to 2.9x?
OR what wed just could do to make it implemented?