analitics

Pages

Showing posts with label tool. Show all posts
Showing posts with label tool. Show all posts

Saturday, August 3, 2024

Blender 3D and python scripting - part 029.

This is a simple Blender 3D script that render images from myimage_000 to myimage_035 around object named Cube.
The script can be changed with any object and any steps for 0 to 360 degree.
import bpy
import math
# Set the object that the camera will orbit around
#target_object = bpy.data.objects["MyObject"]

# Create a new empty object
empty = bpy.data.objects.new("Empty", None)

# Set the empty object's location to the origin point
empty.location = (0, 0, 0)

# Set the starting position for the camera
camera = bpy.data.objects["Camera"]

# Set the number of degrees to rotate the camera around the object
degrees = 360

# Set the distance that the camera should be from the object
distance = 7.6

# Set the speed at which the camera should orbit
speed = 10

# Set the direction in which the camera should orbit (1 for clockwise, -1 for counter-clockwise)
direction = 1

# Set the camera to track the object
bpy.ops.object.select_all(action="DESELECT")
camera.select_set(True)

# Set the distance to origin point
camera.location = (-distance, 0, 0)
bpy.context.view_layer.objects.active = camera

# Remove all constraints from the object "Cube"
bpy.data.objects['Cube'].select_get()
bpy.context.view_layer.objects.active = bpy.data.objects['Cube']
bpy.ops.object.constraints_clear()

# Add a track to constraint to the object and set it
bpy.ops.object.constraint_add(type="TRACK_TO")
bpy.ops.object.track_set(type="TRACKTO")

# Set the target object as the tracking target
bpy.data.objects['Cube'].select_get()
bpy.context.view_layer.objects.active = bpy.data.objects['Cube']

# Select the file image format
bpy.context.scene.render.image_settings.file_format = 'PNG'

# Animate the camera orbiting around the object
for frame in range(0, 36):
    # Set the current frame
    bpy.context.scene.frame_set(frame)

    # Calculate the new position for the camera based on its distance from the object
    x = distance * math.sin(math.radians(frame*speed*direction))
    y = distance * math.cos(math.radians(frame*speed*direction))
    camera.location = (x,y,0)
    # Set the output path for the rendered image
    bpy.context.scene.render.filepath = "C:\\tmp\\myimage_" + str(frame).zfill(3) + ".png"
    # Render the frame and save it to the output file
    bpy.ops.render.render(write_still=True)

Tuesday, June 25, 2024

Blender 3D and python scripting - part 028.

A simple script for Blender 4.0.0 version with Bright Pencil material, see the output image:
This is the source code:
import bpy
import random

# fix an active object before changing mode
if bpy.context.view_layer.objects.active is None:
    # If there is no active object, set the first object in the scene as active
    if len(bpy.context.scene.objects) > 0:
        bpy.context.view_layer.objects.active = bpy.context.scene.objects[0]
    else:
        print("There are no objects in the scene to set as active.")

# set the mode
bpy.ops.object.mode_set(mode='OBJECT')

# set camera from the scene
cam_ob = bpy.context.scene.camera

# if is a camera in the scene and set it as active object
if cam_ob is None:
    print("There is no camera in the scene.")
elif cam_ob.type == 'CAMERA':
    # Set the camera as active object
    bpy.context.view_layer.objects.active = cam_ob
    print("The camera has been set as active object.")
else:
    print(f"The object {cam_ob.type} is set as a camera, but it is not of type 'CAMERA'.")

# set data for the ink brush
gpencil = bpy.data.grease_pencils.new("Bright Pencil")

# make material for the ink brush
if "Bright Material" in bpy.data.materials.keys():
    gp_mat = bpy.data.materials["Bright Material"]
else:
    gp_mat = bpy.data.materials.new("Bright Material")

if not gp_mat.is_grease_pencil:
    bpy.data.materials.create_gpencil_data(gp_mat)
    bpy.context.object.active_material.grease_pencil.color = (0, 0, 1, 1)


# set the object for the ink brush 
if "Bright Pencil" not in bpy.data.objects:
    gp_data = bpy.data.objects.new("Bright Pencil", gpencil)
    bpy.context.scene.collection.objects.link(gp_data)
# if it does not already exist in the scene
else:
    gp_data = bpy.data.objects["Bright Pencil"]

# assign material for drawing 
if gp_mat.name not in gp_data.data.materials:
    gp_data.data.materials.append(gp_mat)

# define a function to create random lines
def create_random_strokes(num_strokes, max_width):
    # Set the active object and mode
    bpy.context.view_layer.objects.active = gp_data
    bpy.ops.object.mode_set(mode='PAINT_GPENCIL')

    # get or create layer and set it as active
    if gpencil.layers and gpencil.layers.active:
        layer = gpencil.layers.active
    else:
        layer = gpencil.layers.new('my_test_layer', set_active=True)
    # set layer as active
    gpencil.layers.active = layer

    # get or create frame and set it as active using change_frame() method
    if layer.frames and layer.active_frame:
        frame = layer.active_frame
    else:
        frame = layer.frames.new(1)

    for _ in range(num_strokes):
        stroke = frame.strokes.new()
        stroke.line_width = int(random.uniform(1.0, max_width))
        stroke.points.add(count=3)
        for point in stroke.points:
            point.co = (random.uniform(-1.0, 1.0), random.uniform(-1.0, 1.0), 0.0)

# this function with desired parameters
create_random_strokes(num_strokes=10, max_width=16.0)

# return to original mode
bpy.ops.object.mode_set(mode='OBJECT')

Wednesday, June 5, 2024

Blender 3D and python scripting - part 027.

Is more easy to create your player character based on the armature.
The next step is to add more animation and export to your game engine ...
Based on this issue - blender.stackexchange website, I tested to today and works very well:
Let's see the source code:
import bpy
import mathutils 
from mathutils import Vector 
from math import *

class ArmatureMenu(bpy.types.Menu):
    bl_label = "Mesh 2 Armature Menu"
    bl_idname = "OBJECT_MT_Mesh_From_Armature"

    def draw(self, context):
        layout = self.layout
        layout.operator("wm.mesh_from_armature", text="Pyramid").mesh_type = 'Pyramid' # from here
        layout.operator("wm.mesh_from_armature", text="Tapered").mesh_type = 'Tapered' # from here
        layout.operator("wm.mesh_from_armature", text="Box").mesh_type = 'Box' # from here

def CreateMesh(self, meshType):

    obj = bpy.context.active_object

    if obj == None:
        self.report({"ERROR"}, "No selection" )
    elif obj.type != 'ARMATURE':
        self.report({"ERROR"}, "Armature expected" )
    else:
        processArmature( bpy.context, obj, meshType = meshType )

#Create the base object from the armature
def meshFromArmature( arm ):
    name = arm.name + "_mesh"
    meshData = bpy.data.meshes.new( name + "Data" )
    meshObj = bpy.data.objects.new( name, meshData )
    meshObj.matrix_world = arm.matrix_world.copy()
    return meshObj

#Create the bone geometry (vertices and faces)
def boneGeometry( l1, l2, x, z, baseSize, l1Size, l2Size, base, meshType ):
    
    if meshType == 'Tapered':
        print(meshType)
        x1 = x * baseSize * l1Size 
        z1 = z * baseSize * l1Size

        x2 = x * baseSize * l2Size 
        z2 = z * baseSize * l2Size
    elif meshType == 'Box':
        print(meshType)
        lSize = (l1Size + l2Size) / 2
        x1 = x * baseSize * lSize 
        z1 = z * baseSize * lSize

        x2 = x * baseSize * lSize 
        z2 = z * baseSize * lSize

    else: # default to Pyramid
        print(meshType)
        x1 = x * baseSize * l1Size 
        z1 = z * baseSize * l1Size

        x2 = Vector( (0, 0, 0) )
        z2 = Vector( (0, 0, 0) )

    verts = [
        l1 - x1 + z1,
        l1 + x1 + z1,
        l1 - x1 - z1,
        l1 + x1 - z1,
        l2 - x2 + z2,
        l2 + x2 + z2,
        l2 - x2 - z2,
        l2 + x2 - z2
        ] 

    faces = [
        (base+3, base+1, base+0, base+2),
        (base+6, base+4, base+5, base+7),
        (base+4, base+0, base+1, base+5),
        (base+7, base+3, base+2, base+6),
        (base+5, base+1, base+3, base+7),
        (base+6, base+2, base+0, base+4)
        ]

    return verts, faces

#Process the armature, goes through its bones and creates the mesh
def processArmature(context, arm, genVertexGroups = True, meshType = 'Pyramid'):
    print("processing armature {0} {1}".format(arm.name, meshType) )

    #Creates the mesh object
    meshObj = meshFromArmature( arm )
    context.collection.objects.link( meshObj )

    verts = []
    edges = []
    faces = []
    vertexGroups = {}

    bpy.ops.object.mode_set(mode='EDIT')

    try:
        #Goes through each bone
        for editBone in [b for b in arm.data.edit_bones if b.use_deform]:
            boneName = editBone.name
            # print( boneName )
            poseBone = arm.pose.bones[boneName]

            #Gets edit bone informations
            editBoneHead = editBone.head
            editBoneTail = editBone.tail
            editBoneVector = editBoneTail - editBoneHead
            editBoneSize = editBoneVector.dot( editBoneVector )
            editBoneRoll = editBone.roll
            editBoneX = editBone.x_axis
            editBoneZ = editBone.z_axis
            editBoneHeadRadius = editBone.head_radius
            editBoneTailRadius = editBone.tail_radius

            #Creates the mesh data for the bone
            baseIndex = len(verts)
            baseSize = sqrt( editBoneSize )
            newVerts, newFaces = boneGeometry( editBoneHead, editBoneTail, editBoneX, editBoneZ, baseSize, editBoneHeadRadius, editBoneTailRadius, baseIndex, meshType )

            verts.extend( newVerts )
            faces.extend( newFaces )

            #Creates the weights for the vertex groups
            vertexGroups[boneName] = [(x, 1.0) for x in range(baseIndex, len(verts))]

        #Assigns the geometry to the mesh
        meshObj.data.from_pydata(verts, edges, faces)

    except:
        bpy.ops.object.mode_set(mode='OBJECT')
    else:
        bpy.ops.object.mode_set(mode='OBJECT')

    #Assigns the vertex groups
    if genVertexGroups:
        for name, vertexGroup in vertexGroups.items():
            groupObject = meshObj.vertex_groups.new(name=name)
            for (index, weight) in vertexGroup:
                groupObject.add([index], weight, 'REPLACE')

    #Creates the armature modifier
    modifier = meshObj.modifiers.new('ArmatureMod', 'ARMATURE')
    modifier.object = arm
    modifier.use_bone_envelopes = False
    modifier.use_vertex_groups = True

    meshObj.data.update()

    return meshObj

class MeshFromArmatureOperator(bpy.types.Operator):
    bl_idname = "wm.mesh_from_armature"
    bl_label  = "MeshFromArmatureOperator"

    mesh_type : bpy.props.StringProperty(name="mesh_type")

    def execute(self, context):
        print('The mesh type is', self.mesh_type)
        CreateMesh(self, self.mesh_type)
        return {'FINISHED'}

def register():
    bpy.utils.register_class( ArmatureMenu )
    bpy.utils.register_class( MeshFromArmatureOperator )


def unregister():
    bpy.utils.unregister_class( ArmatureMenu )
    bpy.utils.unregister_class( MeshFromArmatureOperator )


if __name__ == "__main__":
    register()

    # The menu can also be called from scripts
    bpy.ops.wm.call_menu(name='OBJECT_MT_Mesh_From_Armature')
The result of this running script using a mixamo animation is this:

Thursday, April 4, 2024

Python 3.12.2 : Python and the Fedora Messaging Infrastructure - part 001.

I tried using the Fedora Messaging online tool with the python package of the same name on Python version 3.12.2.
You can find the documentation on the official page./div>
I created a working folder called FedoraMessaging:
[mythcat@fedora PythonProjects]$ mkdir FedoraMessaging
[mythcat@fedora PythonProjects]$ cd FedoraMessaging
You need to install the fedora-messaging and rabbitmq-server packages.
[root@fedora FedoraMessaging]# dnf5 install fedora-messaging
Updating and loading repositories:
Repositories loaded.
Package                             Arch    Version                       Repository         Size
Installing:                                                                                      
 fedora-messaging                   noarch  3.5.0-1.fc41                  rawhide        38.6 KiB
...
[root@fedora FedoraMessaging]# dnf install rabbitmq-server
At some point it will ask for a reboot.
You need to install the python package named fedora-messaging.
[root@fedora FedoraMessaging]# pip install --user fedora-messaging
Collecting fedora-messaging
...
Installing collected packages: pytz, incremental, wrapt, tomli, rpds-py, pyasn1, pika, hyperlink, constantly, attrs, 
referencing, pyasn1-modules, automat, twisted, jsonschema-specifications, service-identity, jsonschema, crochet, 
fedora-messaging
Successfully installed attrs-23.2.0 automat-22.10.0 constantly-23.10.4 crochet-2.1.1 fedora-messaging-3.5.0 
hyperlink-21.0.0 incremental-22.10.0 jsonschema-4.21.1 jsonschema-specifications-2023.12.1 pika-1.3.2 pyasn1-0.6.0 
pyasn1-modules-0.4.0 pytz-2024.1 referencing-0.34.0 rpds-py-0.18.0 service-identity-24.1.0 tomli-2.0.1 twisted-24.3.0 
wrapt-1.16.0
You need to start the broker:
[mythcat@fedora FedoraMessaging]$ sudo systemctl start rabbitmq-server
I used the source code from the documentation to test its functionality with a python script named hello_test.py.
from fedora_messaging import api, config

config.conf.setup_logging()
api.consume(lambda message: print(message))

from fedora_messaging import api, config

config.conf.setup_logging()
api.publish(api.Message(topic="hello by mythcat", body={"Hello": "world!"}))
I ran it and got this response:
[mythcat@fedora FedoraMessaging]$ python hello_test.py
[fedora_messaging.message INFO] Registering the 'base.message' key as the '<class 'fedora_messaging.message.Message'>' 
class in the Message class registry
[fedora_messaging.twisted.protocol INFO] Waiting for 0 consumer(s) to finish processing before halting
[fedora_messaging.twisted.protocol INFO] Finished canceling 0 consumers
[fedora_messaging.twisted.protocol INFO] Disconnect requested, but AMQP connection already gone
I created another python script named my_consumer.py, to check if this works:
from fedora_messaging import api, config
# Setup logging
config.conf.setup_logging()
# Define the callback function to process messages
def process_message(message):
    # Check if the message topic matches "hello by mythcat"
    if message.topic == "hello by mythcat":
        print(f"Received message: {message.body}")
    else:
        print(f"Ignoring message with topic: {message.topic}")
# Consume messages
api.consume(process_message)
I ran it and got this response:
[mythcat@fedora FedoraMessaging]$ python my_consumer.py
[fedora_messaging.twisted.protocol INFO] Successfully registered AMQP consumer Consumer(queue=amq.gen-9lKk7sGeYY5I40bdc5VrzQ,
callback=<function process_message at 0x7fdb0f5da160>)
[fedora_messaging.message INFO] Registering the 'base.message' key as the '<class 'fedora_messaging.message.Message'>'
class in the Message class registry
[fedora_messaging.twisted.consumer INFO] Consuming message from topic hello by mythcat 
(message id 800a1540-1e91-4b4a-a125-15e33eebb699)
Received message: {'Hello': 'world!'}
[fedora_messaging.twisted.consumer INFO] Successfully consumed message from topic hello by mythcat 
(message id 800a1540-1e91-4b4a-a125-15e33eebb699)
It can be seen that the answer is received and displayed correctly.

Thursday, December 7, 2023

Python 3.13.0a1 : Testing with scapy - part 001.

Scapy is a powerful interactive packet manipulation library written in Python. Scapy is able to forge or decode packets of a wide number of protocols, send them on the wire, capture them, match requests and replies, and much more. see the official website.
You need to install NPCap.
Beacon frames are transmitted periodically, they serve to announce the presence of a wireless LAN and to synchronise the members of the service set.
In IBSS network beacon generation is distributed among the stations.
Beacon frames are transmitted by the access point (AP) in an infrastructure basic service set (BSS).
Beacon frames include information about the access point and supported data rates and what encryption is being used.
These are received by your device’s wireless network interface and interpreted by your operating system to build the list of available networks.
The beacon variable indicates the capabilities of our access point.
Let's see the source code:
C:\PythonProjects\scapy_001>pip install scapy
Collecting scapy
  Downloading scapy-2.5.0.tar.gz (1.3 MB)
     ---------------------------------------- 1.3/1.3 MB 3.5 MB/s eta 0:00:00
  Installing build dependencies ... done
...
Successfully built scapy
Installing collected packages: scapy
Successfully installed scapy-2.5.0
The source code is simple:
from scapy.all import Dot11,Dot11Beacon,Dot11Elt,RadioTap,sendp,hexdump

netSSID = 'testSSID'       #Network name here
iface = 'Realtek PCIe GbE Family Controller'         #Interface name here

dot11 = Dot11(type=0, subtype=8, addr1='ff:ff:ff:ff:ff:ff',
addr2='22:22:22:22:22:22', addr3='33:33:33:33:33:33')
beacon = Dot11Beacon(cap='ESS+privacy')
essid = Dot11Elt(ID='SSID',info=netSSID, len=len(netSSID))
rsn = Dot11Elt(ID='RSNinfo', info=(
'\x01\x00'                 #RSN Version 1
'\x00\x0f\xac\x02'         #Group Cipher Suite : 00-0f-ac TKIP
'\x02\x00'                 #2 Pairwise Cipher Suites (next two lines)
'\x00\x0f\xac\x04'         #AES Cipher
'\x00\x0f\xac\x02'         #TKIP Cipher
'\x01\x00'                 #1 Authentication Key Managment Suite (line below)
'\x00\x0f\xac\x02'         #Pre-Shared Key
'\x00\x00'))               #RSN Capabilities (no extra capabilities)

frame = RadioTap()/dot11/beacon/essid/rsn

frame.show()
print("\nHexdump of frame:")
hexdump(frame)
input("\nPress enter to start\n")

sendp(frame, iface=iface, inter=0.100, loop=1)
Let's run this source code:
python scapy_network_001.py
###[ RadioTap ]###
  version   = 0
  pad       = 0
  len       = None
  present   = None
  notdecoded= ''
###[ 802.11 ]###
     subtype   = Beacon
     type      = Management
     proto     = 0
     FCfield   =
     ID        = 0
     addr1     = ff:ff:ff:ff:ff:ff (RA=DA)
     addr2     = 22:22:22:22:22:22 (TA=SA)
     addr3     = 33:33:33:33:33:33 (BSSID/STA)
     SC        = 0
###[ 802.11 Beacon ]###
        timestamp = 0
        beacon_interval= 100
        cap       = ESS+privpython scapy_network_001.py
###[ RadioTap ]### tion Element ]###
  version   = 0      = SSID
  pad       = 0      = 8
  len       = None   = 'testSSID'
  present   = Noneation Element ]###
  notdecoded= ''     = RSN
###[ 802.11 ]###     = None
     subtype   = Beacon'\x01\x00\x00\x0f¬\x02\x02\x00\x00\x0f¬\x04\x00\x0f¬\x02\x01\x00\x00\x
     type      = Management
     proto     = 0
     FCfield   =
     ID        = 0
     addr1     = ff:ff:ff:ff:ff:ff (RA=DA)FF FF FF FF  ................
     addr2     = 22:22:22:22:22:22 (TA=SA)33 33 00 00  ..""""""333333..
     addr3     = 33:33:33:33:33:33 (BSSID/STA)8 74 65  ........d.....te
     SC        = 049 44 30 1C 01 00 00 0F C2 AC 02 02  stSSID0.........
###[ 802.11 Beacon ]### 00 0F C2 AC 02 01 00 00 0F C2  ................
        timestamp = 0                                  ....
        beacon_interval= 100
        cap       = ESS+privacy
###[ 802.11 Information Element ]###
           ID        = SSID..................................................................
           len       = 8.....................................................................
           info      = 'testSSID'
###[ 802.11 Information Element ]###
           ID        = RSN
           len       = None>
           info      = '\x01\x00\x00\x0f¬\x02\x02\x00\x00\x0f¬\x04\x00\x0f¬\x02\x01\x00\x00\x0f¬\x02\x00\x00'


Hexdump of frame:
0000  00 00 08 00 00 00 00 00 80 00 00 00 FF FF FF FF  ................
0010  FF FF 22 22 22 22 22 22 33 33 33 33 33 33 00 00  ..""""""333333..
0020  00 00 00 00 00 00 00 00 64 00 11 00 00 08 74 65  ........d.....te
0030  73 74 53 53 49 44 30 1C 01 00 00 0F C2 AC 02 02  stSSID0.........
0040  00 00 0F C2 AC 04 00 0F C2 AC 02 01 00 00 0F C2  ................
0050  AC 02 00 00                                      ....

Press enter to start

.................................................................
Sent 130 packets.

Friday, October 13, 2023

Blender 3D and python scripting - part 026.

Today I tested the bpy python module from Blender 3D software version 3.5 and I made this lite addon that showed me a modal dialog and checked and installed the Pillow python module.
The script don't install Pillow because is not fixed.
The main reason was to add my Python tools and features to Blender 3D and share with you.
bl_info = {
    "name": "Tools by catafest",
    "blender": (3, 0, 0),
    "category": "3D View",
}

import bpy
from bpy.types import Operator, Panel
from bpy.props import StringProperty

try:
    import importlib
    importlib.import_module("Pillow")
    PIL_installed = True
except ImportError:
    PIL_installed = False

def install_pillow():
    import subprocess
    try:
        subprocess.run([bpy.app.binary_path, '--python-exit-code', '1', '-m', 'ensurepip'])
        subprocess.check_call([bpy.app.binary_path, '-m', 'pip', 'install', 'Pillow'])
    except subprocess.CalledProcessError as e:
        print("Eroare la instalarea Pillow:", e)

# Operator pentru a afișa fereastra modală cu informații despre instalarea Pillow
class CATAFEST_IMAGES_OT_show_pillow_message(Operator):
    bl_idname = "catafest.show_pillow_message"
    bl_label = "Show Pillow Message"

    def execute(self, context):
        global PIL_installed
        message = "Pillow este instalat." if PIL_installed else "Pillow nu este instalat."

        # Dacă Pillow nu este instalat, încercați să-l instalați
        if not PIL_installed:
            install_pillow()
            try:
                import importlib
                importlib.import_module("Pillow")
                PIL_installed = True
                message = "Pillow a fost instalat cu succes!" if PIL_installed else "Eroare la instalarea Pillow."
            except ImportError:
                PIL_installed = False

        # Afișați fereastra modală în centrul ecranului
        bpy.ops.catafest.show_modal_message('INVOKE_DEFAULT', title="Starea Pillow", message=message)
        return {'FINISHED'}

    def invoke(self, context, event):
        return self.execute(context)

# Operator pentru a afișa fereastra modală personalizată
class CATAFEST_IMAGES_OT_show_modal_message(Operator):
    bl_idname = "catafest.show_modal_message"
    bl_label = "Show Modal Message"

    title: bpy.props.StringProperty(default="Message")
    message: bpy.props.StringProperty(default="")

    def execute(self, context):
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self, width=400)

    def draw(self, context):
        layout = self.layout
        layout.label(text=self.message)

# Panel pentru bara laterală din 3D View
class VIEW3D_PT_tools_image(Panel):
    bl_label = "Images"
    bl_idname = "VIEW3D_PT_tools_image"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Tools by catafest'

    def draw(self, context):
        layout = self.layout
        layout.operator(CATAFEST_IMAGES_OT_show_modal_message.bl_idname)
        layout.operator(CATAFEST_IMAGES_OT_show_pillow_message.bl_idname)

def register():
    bpy.utils.register_class(CATAFEST_IMAGES_OT_show_modal_message)
    bpy.utils.register_class(CATAFEST_IMAGES_OT_show_pillow_message)
    bpy.utils.register_class(VIEW3D_PT_tools_image)

def unregister():
    bpy.utils.unregister_class(CATAFEST_IMAGES_OT_show_modal_message)
    bpy.utils.unregister_class(CATAFEST_IMAGES_OT_show_pillow_message)
    bpy.utils.unregister_class(VIEW3D_PT_tools_image)

if __name__ == "__main__":
    register()

Monday, January 23, 2023

Python 3.11.0 : About py launcher tool for Windows.

The py launcher tool for Windows is included in the Python for Windows installer starting from version 3.3.
This command-line tool allows you to easily configure and switch between multiple versions of Python installed on a Windows system.
This tool allows you to easily switch between different versions of Python without modifying the system's PATH environment variable, and it also allows you to set default versions of Python for different file extensions.
Let's see some examples
This will argument will give you the current version of your python command:
py --version
Python 3.11.0
This will enumerate all your python versions from Windows O.S.:
py -0
 -V:3.11 *        Python 3.11 (64-bit)
 -V:3.10          Python 3.10 (64-bit)
 -V:3.9           Python 3.9 (64-bit)
 -V:3.7           Python 3.7 (64-bit)
This will set the python command to a specific version in this case 3.10 :
>py -3.10
Python 3.10.7 (tags/v3.10.7:6cc6b13, Sep  5 2022, 14:08:36) [MSC v.1933 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()
This will run a python script named scripting001.py with a specific version of python:
py -3.10 scripting001.py

Monday, August 15, 2022

Blender 3D and python scripting - part 025.

In this tutorial I will show a simple python script for add a String interface to the Group Output.
You can see in the next image the result of this script.
You need to have an modifier Geometry Nodes or add new one.
This is the source script with comments for each step I used:
import bpy

#get active object - default 
obj = bpy.context.active_object

# set the default start for working with Geometry Nodes modifier
# you need to have a Geometry Nodes modifier
node_group = obj.modifiers['GeometryNodes'].node_group
nodes = node_group.nodes

#get the node named 'Group Output'
geom_out = nodes.get('Group Output')

#create a string node 
string_node = nodes.new('FunctionNodeInputString')
# set the name to 'String'
string_out = string_node.outputs['String']
# set the value to "This is a string"
string_node.string = "This is a string"

# link to the Group Output
node_group.links.new(string_out, geom_out.inputs[-1])

Sunday, August 14, 2022

Python : Install python with winget.

The winget command line tool enables users to discover, install, upgrade, remove and configure applications on Windows 10 and Windows 11 computers. This tool is the client interface to the Windows Package Manager service.
The winget can be installed on Windows 11 from the Windows Store.
More about winget command can be read on the Microsoft website.
After installation you can use the PowerShell command shell to install the python with this command:
winget install --id Python.Python.3 -e --source winget
Found Python 3 [Python.Python.3] Version 3.10.6
This application is licensed to you by its owner.
Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
Downloading https://www.python.org/ftp/python/3.10.6/python-3.10.6-amd64.exe
  ██████████████████████████████  27.5 MB / 27.5 MB
Successfully verified installer hash
Starting package install...
Successfully installed

Wednesday, August 10, 2022

Blender 3D and python scripting - part 024.

In this tutorial I will show you how to use GeometryNodes with python script and Blender A.P.I.
You can see the result in the next image.
The Object Info node gets information from objects. This can be useful to control parameters in the geometry node tree with an external object, either directly by using its geometry, or via its transformation properties. An Object Info node can be added quickly by dragging an object into the node editor.
Another information can be found on the manual link.
In the next script you can see I created a simple BezierCurve object.
The definition named new_GeometryNodes_group is used to create two nodes GroupInit and GroupOutput.
I commente the source code to see some steps.
# import python packages
import bpy
from mathutils import Vector

# create a simpple BezierCurve and rename it with 'BezierCurveGeormetryNode'
bpy.ops.curve.primitive_bezier_curve_add()
bpy.ops.object.modifier_add(type='NODES')  

curve = bpy.context.active_object
curve.name = 'BezierCurveGeormetryNode'

# define a function for GroupInit and GroupOutput
def new_GeometryNodes_group():
    ''' Create a new empty node group that can be used
        in a GeometryNodes modifier.
    '''
    node_group = bpy.data.node_groups.new('GeometryNodes', 'GeometryNodeTree')
    inNode = node_group.nodes.new('NodeGroupInput')
    inNode.outputs.new('NodeSocketGeometry', 'Geometry')
    outNode = node_group.nodes.new('NodeGroupOutput')
    outNode.inputs.new('NodeSocketGeometry', 'Geometry')
    node_group.links.new(inNode.outputs['Geometry'], outNode.inputs['Geometry'])
    # the -3.5 is value for how far will be set the GroupInit and GroupOutput in the area of GeormetryNodes
    inNode.location = Vector((-3.5*inNode.width, 0))
    outNode.location = Vector((3.5*outNode.width, 0))
    return node_group

# the default curve modifier has no node group set, you need to set :
if curve.modifiers[-1].node_group:
    node_group = curve.modifiers[-1].node_group    
else:
    node_group = new_GeometryNodes_group()
    curve.modifiers[-1].node_group = node_group

# set default grup node as nodes
nodes = node_group.nodes

# get both nodes for each one 
group_in = nodes.get('Group Input')
group_out = nodes.get('Group Output')

# add the GeometryNodeObjectInfo to the GeometryNode area 
new_node_obj = nodes.new('GeometryNodeObjectInfo')
new_node_obj.inputs[0].default_value = bpy.data.objects["BezierCurveGeormetryNode"]

Saturday, July 23, 2022

Blender 3D and python scripting - part 023.

This script will add a submenu to the main Help menu with an icon with a folder that will open the explorer from the Windows operating system.
I have not solved the tooltip of this button, it is set to show a message about opening a URL.
This is the source code:
import bpy

def menu_func(self, context):
    '''Open explorer in windows systems'''
    self.layout.operator(
            "wm.url_open", text="Open explorer", icon='FILE_FOLDER').url = "C:/"
def register():
    bpy.types.TOPBAR_MT_help.append(menu_func)

def unregister():
    bpy.types.TOPBAR_MT_help.remove(menu_func)

if __name__ == "__main__":
    register()
If you want to change the tooltip then need to create a class OpenOperator and wrapper for this operator function and set the bl_label and all you need to have it.
The menu_func will get the layout operator with all defined in the class OpenOperator and will set the tooltip with the text: Open explorer in windows systems.
See this new source code:
import bpy

def menu_func(self, context):
    self.layout.operator(
            OpenOperator.bl_idname, text="Open explorer", icon='FILE_FOLDER')

class OpenOperator(bpy.types.Operator):
    """Open explorer in windows systems"""
    bl_idname = "wm.open_explorer"
    bl_label = "Open explorer"

    def execute(self, context):
        bpy.ops.wm.url_open(url="C:/")
        return {'FINISHED'}


def register():
    bpy.utils.register_class(OpenOperator)
    bpy.types.TOPBAR_MT_help.append(menu_func)


def unregister():
    bpy.utils.unregister_class(OpenOperator)
    bpy.types.TOPBAR_MT_help.remove(menu_func)


if __name__ == "__main__":
    register()

Thursday, July 7, 2022

Blender 3D and python scripting - part 022.

In the last tutorial, we exemplified with the default template from Blender 3D how to create a panel in the Object area.
Today I will show you how to modify this panel with some useful elements for developing an addon.
The purpose of the old tutorial on this is the differences and changes that must be made to the template source code to introduce the following functions:
    StringProperty(
    BoolProperty( 
    IntProperty(
    IntVectorProperty(
    FloatProperty(
    FloatVectorProperty(
    BoolVectorProperty(
Some arguments need to be modified to have different input data, see the selection of colors in the attached image:
I commented on the source code areas in the template and added my changes:
The class also called SceneSettingItem and CollectionProperty is currently being tested and is not finalized to be implemented, it can be seen in panel: 0 items.
It can be seen that any defined class must be registered and unregistered
Here is the source code used to get the new screenshot changes:
import bpy

# Assign a collection
class SceneSettingItem(bpy.types.PropertyGroup):
    name = bpy.props.StringProperty(name="Cube")
    mesh = bpy.props.PointerProperty(type=bpy.types.Mesh)
    


PROPS = [
            ('myString', bpy.props.StringProperty(name='myString', default='this is my string!')),
            ('myBoolean', bpy.props.BoolProperty(name='myBoolean', default=False)),
            ('myInt', bpy.props.IntProperty(name='myInt', default=1)),
            ('myIntVectorXYZ', bpy.props.IntVectorProperty(subtype='XYZ')),
            ('myFloat', bpy.props.FloatProperty(name='myFloat', default=1)),
            ('myFloatVectorXYZ', bpy.props.FloatVectorProperty(subtype='XYZ')),
            ('myBooleanVector', bpy.props.BoolVectorProperty(size=3)),
            ('myBooleanVectorXYZ', bpy.props.BoolVectorProperty(size=3,subtype='XYZ')),
            ('myBooleanVectorColor', bpy.props.FloatVectorProperty(name="Edit Mode Color", subtype='COLOR',  default=(0.76, 0.0, 0.0), size=3, min=0, max=1)),
            ('myCollectionProperty', bpy.props.CollectionProperty(type=SceneSettingItem)),
        ]    

class HelloWorldPanelVariables(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel Variables"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

#    def draw(self, context):
#        layout = self.layout
#        obj = context.object
#        row = layout.row()
#        row.label(text="Hello world!", icon='WORLD_DATA')
#        row = layout.row()
#        row.label(text="Active object is: " + obj.name)
#        row = layout.row()
#        row.prop(obj, "name")
#        row = layout.row()
#        row.operator("mesh.primitive_cube_add")

    def draw(self, context):
        col = self.layout.column()
        for (prop_name, _) in PROPS:
            row = col.row()
            row.prop(context.scene, prop_name)

        
def register():
    bpy.utils.register_class(SceneSettingItem)
    bpy.utils.register_class(HelloWorldPanelVariables)
    for (prop_name, prop_value) in PROPS:
        setattr(bpy.types.Scene, prop_name, prop_value)

def unregister():
    bpy.utils.unregister_class(SceneSettingItem)
    bpy.utils.unregister_class(HelloWorldPanelVariables)    
    for (prop_name, _) in PROPS:
        delattr(bpy.types.Scene, prop_name)

if __name__ == "__main__":
    register()

Wednesday, July 6, 2022

Blender 3D and python scripting - part 021.

I will continue the series of tutorials with python and the Blender 3D software interface.
From the main menu we can get to the scripting part and here we choose Templates - Python - Ui Panel Simple.
The source code will be added to the python editor.
Save this source code with a name and load it as an addon.
After loading this source code it can be found at Properties at Object, see screenshot.
You can see the source code from the Ui Panel Simple template that I used.
import bpy

class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    def draw(self, context):
        layout = self.layout

        obj = context.object

        row = layout.row()
        row.label(text="Hello world!", icon='WORLD_DATA')

        row = layout.row()
        row.label(text="Active object is: " + obj.name)
        row = layout.row()
        row.prop(obj, "name")

        row = layout.row()
        row.operator("mesh.primitive_cube_add")

def register():
    bpy.utils.register_class(HelloWorldPanel)


def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)

if __name__ == "__main__":
    register()

Sunday, June 26, 2022

Blender 3D and python scripting - part 020.

So far I have added or branched used python scripts in the blender and created a UV texture for this use UV Smart projection.
The resulting script is quite large in content.
I thought I should create an addon to use and show you how to do it.
Let's follow the basic steps when working with python scripts in Blender 3D.
  1. go to the tag named: Scripting;
  2. use main menu to create a new script from Text - New;
  3. use main menu - Template - Python - Addon Add Object;
  4. save the script with a good name, I used: addon_catafest_add_branch.py;
  5. save the blend file with a good name: I used: addon_catafest_add_branch.blend;
See this screenshot:
After these changes you can modify in the template file the parts related to the names, descriptions and other elements that will be viewed in Blender 3D
Restart the Blender 3D software, open the python script with all changes and run it.
In the 3D Viewport area press the shortkeys: Shift + A and use Mesh menu to see this addon feature, see screenshot:
This is source code I used:
bl_info = {
    "name": "New branch",
    "author": "Your Name Here",
    "version": (1, 0),
    "blender": (3, 3, 0),
    "location": "View3D > Add > Mesh > New Object",
    "description": "Adds a new branch Mesh Object",
    "warning": "",
    "doc_url": "",
    "category": "Add Mesh",
}


import bpy
from bpy.types import Operator
from bpy.props import FloatVectorProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector


def add_object(self, context):
    scale_x = self.scale.x
    scale_y = self.scale.y

    verts = [
        Vector((-1 * scale_x, 1 * scale_y, 0)),
        Vector((1 * scale_x, 1 * scale_y, 0)),
        Vector((1 * scale_x, -1 * scale_y, 0)),
        Vector((-1 * scale_x, -1 * scale_y, 0)),
    ]

    edges = []
    faces = [[0, 1, 2, 3]]

    mesh = bpy.data.meshes.new(name="New Object Mesh")
    mesh.from_pydata(verts, edges, faces)
    # useful for development when the mesh may be invalid.
    # mesh.validate(verbose=True)
    object_data_add(context, mesh, operator=self)


class OBJECT_OT_add_object(Operator, AddObjectHelper):
    """Create a new branch Mesh Object"""
    bl_idname = "mesh.add_object"
    bl_label = "Add Mesh Object"
    bl_options = {'REGISTER', 'UNDO'}

    scale: FloatVectorProperty(
        name="scale",
        default=(1.0, 1.0, 1.0),
        subtype='TRANSLATION',
        description="scaling",
    )

    def execute(self, context):

        add_object(self, context)

        return {'FINISHED'}


# Registration

def add_object_button(self, context):
    self.layout.operator(
        OBJECT_OT_add_object.bl_idname,
        text="catafest - add branch",
        icon='PLUGIN')


# This allows you to right click on a button and link to documentation
def add_object_manual_map():
    url_manual_prefix = "https://docs.blender.org/manual/en/latest/"
    url_manual_mapping = (
        ("bpy.ops.mesh.add_object", "scene_layout/object/types.html"),
    )
    return url_manual_prefix, url_manual_mapping


def register():
    bpy.utils.register_class(OBJECT_OT_add_object)
    bpy.utils.register_manual_map(add_object_manual_map)
    bpy.types.VIEW3D_MT_mesh_add.append(add_object_button)


def unregister():
    bpy.utils.unregister_class(OBJECT_OT_add_object)
    bpy.utils.unregister_manual_map(add_object_manual_map)
    bpy.types.VIEW3D_MT_mesh_add.remove(add_object_button)


if __name__ == "__main__":
    register()

Saturday, June 18, 2022

Blender 3D and python scripting - part 019.

Today I'm going to show you how to have a UV map for the entire mesh of the object created with the smart projection mode.
It's the same when you create a UV map using UV Mapping - Smart UV Project in the UV Editing view.
In principle, everything created in the Blender 3D interface has an equivalent in the A.P.I. of the 3D Blender.
You can see a screenshot with this script:
Here you see the source code that includes this option.
import bpy
import random

# import bmesh 
import bmesh

MinNubmer = -10
MaxNumber = 10

# Clean up the area , uncoment the next two row to keep
# branch after running the script
#bpy.ops.object.select_all(action="SELECT")
#bpy.ops.object.delete()

# Number of branches
branch = 4
# Create the verts array
verts = [(0,0,0)]
# Create the edges array
edges = [(0,0)]
# Create the faces array
faces = []

# define random number for X and Y axis 
def RN():
    return  random.randint(MinNubmer, MaxNumber) / 20 

# define random number for positive Z axis
def RNZ():
    return  random.randint(10, 50) / 10  

# create a list of branch thicknesses
rand_list = []

name_branch = "TreeMesh"
# define createBranch 

def createBranch(branch, name_branch):
    # Create the mesh for branch 
    mesh = bpy.data.meshes.new(name_branch) 
    for i in range(1,branch):
        rand_list.append(RNZ()/30)
        # sort all reverse by thicknesses
        rand_list.sort(reverse=True)

    # generate vertices list for drawing the branch
    for i in range(1,branch):
        verts.append((rand_list[i-1] +0.1,rand_list[i-1]+0.1,RNZ()))
        edges.append((i-1,i))
    
    # sort the list of vertices by last number witch is Z axis 
    verts.sort(key=lambda x: x[2])
    # create branch update and validate, see documentation
    mesh.from_pydata(verts, edges, faces) 
    mesh.update()
    mesh.validate()
    # Create object to hold the mesh branch with the new name for object
    obj = bpy.data.objects.new(name_branch+'_Obj', mesh)
    return obj

# create a new branch     
def createNewBranch(obj_branch, n):
    bpy.ops.object.mode_set(mode="EDIT", toggle=False)
    me = obj_branch.data
    bm = bmesh.from_edit_mesh(me)
    bm.select_mode = {'VERT'}

    for i,v in enumerate(bm.verts):
        # select only by the index of list 
        if i == n:
            v.select = ( v.co.x > 0.0 )
            v2 = v    
        else: 
            v.select = False
    # flush and update view 
    v1 = bm.verts.new( (RN()+(v.co.x) + 1.0 , RN()+(v.co.y) + 1.0 , (v.co.z) - (v.co.z)/3) )
    #v1 = bm.verts.new(1, 1, 3)
    bm.edges.new((v1, v2))
    rand_list.append(0.01)
    rand_list.sort(reverse=True)
    # update 
    bm.select_flush_mode()   
    me.update()
    #mesh.validate()
    #bmesh.update_edit_mesh(obj_branch.data)
    

# use the createBranch
obj_branch = createBranch(branch, name_branch)


## now set up shape key in Blender
#mesh=obj_branch.data
#sk_basis = obj_branch.shape_key_add(name='Basis',from_mix=False)
#sk_basis.interpolation = 'KEY_LINEAR'
## must set relative to false here
#obj_branch.data.shape_keys.use_relative = False

## create new shape key
#sk = obj_branch.shape_key_add(name='Deform',from_mix=False)
#sk.interpolation = 'KEY_LINEAR'
#sk.slider_min = 0
#sk.slider_max = 2

# ... and add it to the scene
scene = bpy.context.scene
scene.collection.objects.link(obj_branch)

# this will fix the error ...  mode_set_poll()
bpy.context.view_layer.objects.active = obj_branch  

createNewBranch(obj_branch, 1)

# print tool for developing area 
def print_python_console(data):
    for window in bpy.context.window_manager.windows:
        screen = window.screen
        for area in screen.areas:
            if area.type == 'CONSOLE':
                override = {'window': window, 'screen': screen, 'area': area}
                bpy.ops.console.scrollback_append(override, text=str(data), type="OUTPUT")

# used to see the size of radius skin for each vertices
print_python_console(rand_list)

# fix error :  skin modifier is locked when using edit mode.
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
# add the skin modifier - NOT FIXED FOR THE LAST BRANC ADDED
obj_branch.modifiers.new(name="SK", type="SKIN")
bpy.context.view_layer.objects.active = obj_branch  
# get the skin vertices layers
skin_vertices = obj_branch.data.skin_vertices
# get the layer
skin_layer = skin_vertices[0]
for i in range(1,branch+1):
    # assigns radius for each vertice to sized the branch 
    skin_layer.data[i-1].radius = (rand_list[i-1], rand_list[i-1]) 
    #Indices 0 and 1 are the vertex indices
    skin_layer.data[i].radius = (rand_list[i-1],rand_list[i-1])

# this will apply the modifier named 'SK'
bpy.ops.object.modifier_apply( modifier = 'SK' )
#
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
bpy.ops.object.skin_root_mark()

bpy.ops.object.mode_set(mode="OBJECT", toggle=True)
# set modes for user 

mesh = bpy.data.meshes.new(name_branch+'_Obj') 
mesh.update()
mesh.validate()

import math 

def get_dimension(normal):
    x_abs = math.fabs(normal[0])
    y_abs = math.fabs(normal[1])
    z_abs = math.fabs(normal[2])
    if z_abs >= x_abs and z_abs >= y_abs:
        return 2
    elif x_abs >= y_abs:
        return 0
    else:
        return 1

texture_scale = 1.0
bpy.ops.object.mode_set(mode="EDIT", toggle=True)  
bpy.ops.mesh.select_all(action='SELECT')  
me = obj_branch.data
bm = bmesh.from_edit_mesh(me)

bpy.ops.uv.sphere_project()

uv_layer = bm.loops.layers.uv.verify()

# adjust uv coordinates
for face in bm.faces:
    for l in face.loops:
        luv = l[uv_layer]
        # select UV vertex if these are in certain range
        if 0 <= luv.uv.x <= 1 and 0 <= luv.uv.y <= 1:
            luv.select = True
            luv = l[uv_layer]
            luv.uv = l.vert.co.yz * texture_scale
            luv.uv = l.vert.co.xz * texture_scale
            luv.uv = l.vert.co.xy * texture_scale
                
bmesh.update_edit_mesh(me) 
me.update()  

bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.uv.smart_project(angle_limit=1.15192, island_margin=0, area_weight=0, correct_aspect=True, scale_to_bounds=False)
# use this for cylinder project for UV map 
#bpy.ops.uv.cylinder_project(direction='ALIGN_TO_OBJECT',
#align='POLAR_ZX',
#radius=1.0,
#correct_aspect=True,
#clip_to_bounds=False,
#scale_to_bounds=True)


bmesh.update_edit_mesh(me)
bpy.ops.object.mode_set(mode="OBJECT", toggle=True)

Thursday, June 16, 2022

Blender 3D and python scripting - part 018.

In this tutorial I will show you how to create a UV map.
I searched the web and the documentation briefly and I didn't find anything very concrete, but here I created a functional example.
The example contains a function that calculates dimensions, which are then processed and added to the UV Editing workspace.
This is what the source code added to the initial script tested looks like:
mesh.validate()

import math 

def get_dimension(normal):
    x_abs = math.fabs(normal[0])
    y_abs = math.fabs(normal[1])
    z_abs = math.fabs(normal[2])
    if z_abs >= x_abs and z_abs >= y_abs:
        return 2
    elif x_abs >= y_abs:
        return 0
    else:
        return 1

texture_scale = 1.0
bpy.ops.object.mode_set(mode="EDIT", toggle=True)    
me = obj_branch.data
bm = bmesh.from_edit_mesh(me)

uv_layer = bm.loops.layers.uv.verify()


for f in bm.faces:
    largest_index = get_dimension(f.normal)
    for l in f.loops:
        luv = l[uv_layer]
        luv.uv = l.vert.co.yz * texture_scale
        luv.uv = l.vert.co.xz * texture_scale
        luv.uv = l.vert.co.xy * texture_scale


me.update()
Here is a screenshot of the result, you can see that it only shows the node, you can select for all nodes to see a final result.

Tuesday, June 14, 2022

Blender 3D and python scripting - part 017.

In this tutorial I will show you how you can apply an modifier in Blender 3D using the python script.
First, you need to comment these source code rows in order to allow to apply the modifier.
## now set up shape key in Blender
#mesh=obj_branch.data
#sk_basis = obj_branch.shape_key_add(name='Basis',from_mix=False)
#sk_basis.interpolation = 'KEY_LINEAR'
## must set relative to false here
#obj_branch.data.shape_keys.use_relative = False

## create new shape key
#sk = obj_branch.shape_key_add(name='Deform',from_mix=False)
#sk.interpolation = 'KEY_LINEAR'
#sk.slider_min = 0
#sk.slider_max = 2
You cannot aply an modifier if you have skape keys.
To apply an modifyer you can use this line of source code:
# this will apply the modifier named 'SK'
bpy.ops.object.modifier_apply( modifier = 'SK' )
If you go to the UV Editing area you will see the modifier is apply and you can create an UV map.

Monday, June 13, 2022

Blender 3D and python scripting - part 016.

On this day, I will digress from the series of tutorials started and presented and show you how to install other python packages in Blender 3D.
Go to the bin folder where the python is install, see my path of Blender 3D.
C:\blender-3.3.0-alpha+master.add1da52ad78-windows.amd64-release\3.3\python\bin
Use these commands in to window command shell to install OpenCv python module.
python.exe -m ensurepip
python.exe -m pip install --upgrade pip
python.exe -m pip install opencv-python 
python.exe -m pip install opencv-contrib-python
Run in the blender script area these commands, in order to see if this python package working.
import cv2
cv2.version
You can create a simple script and test it, see the next example:
import numpy as np
import cv2
  
# Creating a black image with 3 channels
# RGB and unsigned int datatype
img = np.zeros((400, 400, 3), dtype = "uint8")
  
# Creating line
cv2.line(img, (21, 167), (100, 99), (0, 0, 255), 8)
  
cv2.imshow('dark', img)
  
# Allows us to see image
# until closed forcefully
cv2.waitKey(0)
cv2.destroyAllWindows()
You can see a screenshot with this python script:

Sunday, June 12, 2022

Blender 3D and python scripting - part 015.

The tutorial for today is about adding armature to the skin, and has a single line of source code:
bpy.ops.object.skin_armature_create(modifier="SK")
This will add an armature with bones for each edge based on the skin modifier.
See the result of this ...
The full source code is this:
import bpy
import random

# import bmesh 
import bmesh

MinNubmer = -10
MaxNumber = 10

# Clean up the area , uncoment the next two row to keep
# branch after running the script
#bpy.ops.object.select_all(action="SELECT")
#bpy.ops.object.delete()

# Number of branches
branch = 4
# Create the verts array
verts = [(0,0,0)]
# Create the edges array
edges = [(0,0)]
# Create the faces array
faces = []

# define random number for X and Y axis 
def RN():
    return  random.randint(MinNubmer, MaxNumber) / 20 

# define random number for positive Z axis
def RNZ():
    return  random.randint(10, 50) / 10  

# create a list of branch thicknesses
rand_list = []

name_branch = "TreeMesh"
# define createBranch 

def createBranch(branch, name_branch):
    # Create the mesh for branch 
    mesh = bpy.data.meshes.new(name_branch) 
    for i in range(1,branch):
        rand_list.append(RNZ()/30)
        # sort all reverse by thicknesses
        rand_list.sort(reverse=True)

    # generate vertices list for drawing the branch
    for i in range(1,branch):
        verts.append((rand_list[i-1] +0.1,rand_list[i-1]+0.1,RNZ()))
        edges.append((i-1,i))

    # sort the list of vertices by last number witch is Z axis 
    verts.sort(key=lambda x: x[2])
    # create branch update and validate, see documentation
    mesh.from_pydata(verts, edges, faces) 
    mesh.update()
    mesh.validate()
    # Create object to hold the mesh branch with the new name for object
    obj = bpy.data.objects.new(name_branch+'_Obj', mesh)
    return obj

# create a new branch     
def createNewBranch(obj_branch, n):
    bpy.ops.object.mode_set(mode="EDIT", toggle=False)
    me = obj_branch.data
    bm = bmesh.from_edit_mesh(me)
    bm.select_mode = {'VERT'}

    for i,v in enumerate(bm.verts):
        # select only by the index of list 
        if i == n:
            v.select = ( v.co.x > 0.0 )
            v2 = v    
        else: 
            v.select = False
    # flush and update view 
    v1 = bm.verts.new( (RN()+(v.co.x) + 1.0 , RN()+(v.co.y) + 1.0 , (v.co.z) - (v.co.z)/3) )
    #v1 = bm.verts.new(1, 1, 3)
    bm.edges.new((v1, v2))
    rand_list.append(0.01)
    rand_list.sort(reverse=True)
    # update 
    bm.select_flush_mode()   
    me.update()
    #mesh.validate()
    #bmesh.update_edit_mesh(obj_branch.data)

# use the createBranch
obj_branch = createBranch(branch, name_branch)


# now set up shape key in Blender
mesh=obj_branch.data
sk_basis = obj_branch.shape_key_add(name='Basis',from_mix=False)
sk_basis.interpolation = 'KEY_LINEAR'
# must set relative to false here
obj_branch.data.shape_keys.use_relative = False

# create new shape key
sk = obj_branch.shape_key_add(name='Deform',from_mix=False)
sk.interpolation = 'KEY_LINEAR'
sk.slider_min = 0
sk.slider_max = 2

# ... and add it to the scene
scene = bpy.context.scene
scene.collection.objects.link(obj_branch)

# this will fix the error ...  mode_set_poll()
bpy.context.view_layer.objects.active = obj_branch  

createNewBranch(obj_branch, 1)

# print tool for developing area 
def print_python_console(data):
    for window in bpy.context.window_manager.windows:
        screen = window.screen
        for area in screen.areas:
            if area.type == 'CONSOLE':
                override = {'window': window, 'screen': screen, 'area': area}
                bpy.ops.console.scrollback_append(override, text=str(data), type="OUTPUT")

# used to see the size of radius skin for each vertices
print_python_console(rand_list)

# fix error :  skin modifier is locked when using edit mode.
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
# add the skin modifier
obj_branch.modifiers.new(name="SK", type="SKIN")
bpy.context.view_layer.objects.active = obj_branch  
# get the skin vertices layers
skin_vertices = obj_branch.data.skin_vertices
# get the layer
skin_layer = skin_vertices[0]
for i in range(1,branch+1):
    # assigns radius for each vertice to sized the branch 
    skin_layer.data[i-1].radius = (rand_list[i-1], rand_list[i-1]) 
    #Indices 0 and 1 are the vertex indices
    skin_layer.data[i].radius = (rand_list[i-1],rand_list[i-1])

bpy.ops.object.skin_armature_create(modifier="SK")

# set modes for user 
bpy.ops.object.mode_set(mode="EDIT", toggle=False)
bpy.ops.object.skin_root_mark()
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)

Saturday, June 11, 2022

Blender 3D and python scripting - part 014.

In this tutorial I will show you how can add a shape key to the branch using this source code:
# now set up shape key in Blender
mesh=obj_branch.data
sk_basis = obj_branch.shape_key_add(name='Basis',from_mix=False)
sk_basis.interpolation = 'KEY_LINEAR'
# must set relative to false here
obj_branch.data.shape_keys.use_relative = False

# create new shape key
sk = obj_branch.shape_key_add(name='Deform',from_mix=False)
sk.interpolation = 'KEY_LINEAR'
sk.slider_min = 0
sk.slider_max = 2

# ... and add it to the scene