i have a math Problem here with my 'Edge Angle' Python Function. The 'Global' setting works fine (in 2D), but the 'Local' Function is beyond my math capabilities. The goal is an mesh with perpendicular edges (right mesh), but i have no idea how to do the axis transfomation
import bpy
import bmesh
import math
from math import sqrt, pi, degrees
import mathutils
from mathutils import Vector
def setupVertEdit():
set3DViewModeEdit()
initMeshEdit()
return
def initMeshEdit():
mod.gObj = bpy.context.active_object
mod.gObjData = mod.gObj.data
mod.gBMesh=bmesh.from_edit_mesh(mod.gObjData)
mod.gMat_world = mod.gObj.matrix_world
mod.ctx = bpy.context.scene
return
def get3DViewMode():
return bpy.ops.mesh.select_mode()
def set3DViewModeEdit():
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type="VERT")
return
def getBMeshSelectionMode(vMode=None):
sM = bpy.context.scene.tool_settings.mesh_select_mode
if vMode == 'vert':
return sM[0]
if vMode == 'edge':
return sM[1]
if vMode == 'face':
return sM[2]
# print(bpy.context.scene.tool_settings.mesh_selectmodode.__len__())
return bpy.context.scene.tool_settings.mesh_select_mode
def updateData():
bmesh.update_edit_mesh(mod.gObjData)
return
def getSelectedVerts():
return [v for v in mod.gBMesh.verts if v.select]
def countSelectedVerts():
return (len(getSelectedVerts()))
def getActiveVert(optSel=0):
elem = mod.gBMesh.select_history.active
if elem is not None and isinstance(elem, bmesh.types.BMVert):
return elem
elif optSel == 'sel' and countSelectedVerts() > 0:
elem = mod.gBMesh.verts[0]
gBMesh.select_history.add(elem)
return elem
return None
def CreateEdge(vOrientation,
vDir_xy,
vAxisAngleXY,
vAxisAngleZ,
vLenght):
# get Verts count and location
verts_sel = getSelectedVerts()
verts_sel_count = countSelectedVerts()
if verts_sel_count == 0 or mod.ActiveVert == None:
return
fnDebugPrint('vAxisAngleXY ', vAxisAngleXY)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if vOrientation=='global':
if verts_sel_count < 1:
self.report({'WARNING'}, ': no selection')
return
if vDir_xy == 'cw':
vAxisAngleXY = 450 - vAxisAngleXY
newVertCo = fnGetAeqatorialCoordinates(vLenght, vAxisAngleXY, vAxisAngleZ)
# newVertCo = fnGetAeqatorialCoordinates(vLenght, vAxisAngleXY, vAxisAngleZ)
else:
vAxisAngleXY = 90 - (-1 * vAxisAngleXY)
newVertCo = fnGetAeqatorialCoordinates(vLenght,vAxisAngleXY, vAxisAngleZ)
vActiveVertLocalCoor = mod.ActiveVert.co
vActiveVertGlobalCoor = mod.gMat_world * vActiveVertLocalCoor
vertNew = mod.gBMesh.verts.new(mod.ActiveVert.co + newVertCo)
vertsPair = {mod.ActiveVert,vertNew}
mod.gBMesh.edges.new(vertsPair)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if vOrientation=='local':
if verts_sel_count < 2:
return False
if verts_sel[0].index == mod.ActiveVert.index:
vDestinationVertLocal = verts_sel[0]
vReferenceVertLocal = verts_sel[1]
else:
vDestinationVertLocal = verts_sel[1]
vReferenceVertLocal = verts_sel[0]
vDestinationVertGlobalCoor = vDestinationVertLocal.co
vReferenceVertGlobalCoor = vReferenceVertLocal.co
diffPolarAngle = fnGetAngleAlpha(vReferenceVertGlobalCoor,vDestinationVertGlobalCoor)
vAxisAngleZ += 90 - diffPolarAngle
vAxisAngleXY += 90
if vDir_xy == 'cw':
newVertCo = fnGetAeqatorialCoordinates(vLenght, 180 - vAxisAngleXY, vAxisAngleZ)
else:
newVertCo = fnGetAeqatorialCoordinates(vLenght, vAxisAngleXY, vAxisAngleZ)
vertNewCoor = vDestinationVertLocal.co + newVertCo
vertNew = mod.gBMesh.verts.new(vertNewCoor)
vertsPair = {vDestinationVertLocal,vertNew}
mod.gBMesh.edges.new(vertsPair)
updateData()
# Print vVariableName and vValue
def fnDebugPrint(vVariableName, vValue):
print(vVariableName + ' = ' + str(vValue))
def fnGetAngleAlpha(vertRef, vertDest):
v1 = vertRef - vertDest
v2 = Vector((0, 0, 1))
a1 = v1.angle(v2)
if a1 > pi * 0.5:
a1 = pi - a1
return degrees(a1)
# spherical coordinates to Cartesian coordinates
def fnGetAeqatorialCoordinates(vRadius, vAzimuth_xy, vPolarAngle_z):
x = vRadius * cos(vPolarAngle_z) * cos(vAzimuth_xy)
y = vRadius * cos(vPolarAngle_z) * sin(vAzimuth_xy)
z = vRadius * sin(vPolarAngle_z)
return Vector([x, y, z])
def fnGetCartesianCoordinates(vec):
# result Values: radius, polarangle, azimutangle
x = vec.x
y = vec.y
z = vec.z
radius = sqrt(x ** 2 + y ** 2 + z ** 2)
if radius == 0:
return CartCoorReturn(0, 0, 0)
if x == 0 and y == 0:
if z > 0:
polarangle = 90
else:
polarangle = -90
else:
polarangle = asin(z / radius)
if x == 0 and y == 0:
return CartCoorReturn(radius, polarangle, 0)
if x > 0:
if y >= 0:
return CartCoorReturn(radius, polarangle, atan(y / x))
if y < 0:
return CartCoorReturn(radius, polarangle, 360 - abs(atan(y / x)))
if x == 0:
if y > 0:
return CartCoorReturn(radius, polarangle, y * 180 / 2)
if y <= 0:
return CartCoorReturn(radius, polarangle, y * 180 / 2 + 360)
if x < 0:
if y >= 0:
return CartCoorReturn(radius, polarangle, atan(y / x) + 180)
if y < 0:
return CartCoorReturn(radius, polarangle, 180 + atan(y / x))
def sin(v):
return math.sin(math.radians(v))
def cos(v):
return math.cos(math.radians(v))
def tan(v):
return math.tan(math.radians(v))
def asin(v):
return math.degrees(math.asin(v))
def acos(v):
return math.degrees(math.acos(v))
def atan(v):
return math.degrees(math.atan(v))
# Registration
# All panels and operators must be registered with Blender; otherwise
# they do not show up. The simplest way to register everything in the
# file is with a call to bpy.utils.registermododule(__name__).
#
def register():
bpy.utils.register_module(__name__)
if __name__ != "__main__":
bpy.types.VIEW3D_MT_edit_mesh_vertices.append(_menu_func)
print('register done')
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.VIEW3D_MT_edit_mesh_vertices.remove(menu_func)
print('unregister done')
#
# Menu in tools region
#
class ToolsPanel(bpy.types.Panel):
bl_label = 'Adjust Mesh'
bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS"
bl_category = 'Tools'
bl_context = "mesh_edit"
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
slayout = self.layout
row = slayout.row(align=True)
row.operator("mesh.angle_edge")
#
# The button classes
#
# Button to open Angle edge -Dialog
class OBJECT_edge_Button(bpy.types.Operator):
bl_idname = "mesh.angle_edge"
bl_label = "Edge Angle"
def execute(self, context):
bpy.ops.SetAngleEdgeDialogOperator('INVOKE_DEFAULT')
return {'FINISHED'}
class SetAngleEdgeDialogOperator(bpy.types.Operator):
bl_idname = "mesh.angle_edge"
bl_label = 'Edge Angle Dialog'
bl_space_type = 'VIEW_3D'
bl_region_type = 'WINDOW'
bl_context = "data"
bl_options = {'REGISTER', 'UNDO'} #Wichtig! ohne diese Einträge kein F6-Menü
# Local / Global Toggle Button
enum_items_Ori = (('local',
'Local',
'create edge, local angle'),
('global',
'Global',
'create edge, global angle'))
bpy.types.Scene.Toggle_Local_Global = bpy.props.EnumProperty(items=enum_items_Ori, default="local")
# cw / ccw Toggle Button
enum_items_Dir = (('ccw',
'ccw',
'create edge, counterclockwise'),
('cw',
'cw',
'create edge, clockwise'))
bpy.types.Scene.Toggle_cw_ccw = bpy.props.EnumProperty(items=enum_items_Dir, default='cw')
floatAngleXY = bpy.props.FloatProperty(name="cw/ccw Angle",
description="Angle xy",
default=0,
min=-360,
max=360,
precision=2)
floatAngleZ = bpy.props.FloatProperty(name="up/down Angle",
description="Angle z",
default=0,
min=-360,
max=360,
precision=2)
floatLenght = bpy.props.FloatProperty(name="Lenght",
description="Lenght of new edge",
default=1,
min=-9999,
max=9999,
soft_min=-100,
soft_max=100,
precision=3)
# Execution
def execute(self, context):
initMeshEdit()
if not CreateEdge(mod.ctx.Toggle_Local_Global,
mod.ctx.Toggle_cw_ccw,
self.floatAngleXY,
self.floatAngleZ,
self.floatLenght):
self.report({'WARNING'}, ": " + mod.err_msg)
else:
self.report({'INFO'}, ": created Angle edge")
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
slayout = self.layout
initMeshEdit()
if getBMeshSelectionMode('vert'):
mod.ActiveVert = getActiveVert()
verts_sel_count = countSelectedVerts()
# if mod.ActiveVert is not None:
if verts_sel_count == 0 or mod.ActiveVert == None:
# err_msg = 'Base [Local]: Select 2 verts, 1 vert as active (shift MB) :: Base [Global]: Select 1 vert, 1 vert as active'
slayout.label('Base [Local]: Select 2 verts, 1 vert as active (shift MB)')
slayout.label('Base [Global]: Select 1 vert, 1 vert as active')
self.report({'WARNING'}, ': no selection: '+mod.err_msg)
else:
# Check Edges Data Enviroment
check_msg = 'Mesh: '+mod.gObj.name
slayout.label(check_msg)
# new row in Dialog
row = slayout.row()
# Local / Global Toggle Button
row.prop(context.scene, 'Toggle_Local_Global', expand=True)
# cw / ccw Toggle Button
row.prop(context.scene, 'Toggle_cw_ccw', expand=True)
# new row in Dialog
row = slayout.row(align=True)
# cw / ccw Angle
row.prop(self,
'floatAngleXY',
toggle=True,
icon='BLENDER')
# z Angle
row.prop(self,
'floatAngleZ',
toggle=True,
icon='BLENDER')
# new row in Dialog
row = slayout.row()
row.prop(self,
'floatLenght',
toggle=True,
icon='BLENDER')
class clsmododul:
pass
# Global Variable
mod = clsmododul()
mod.gObj = None
mod.gObjData = None
mod.gBMesh = None
mod.gMat_world = None
mod.ctx = None
mod.ActiveVert = None
mod.ActiveEdge = None
mod.addon_Label = 'Set Verts to Axis'
mod.fDiv = 1
mod.fAbs = 0
mod.err_msg = 'Base [Local]: Select 2 verts, 1 vert as active (shift MB) :: Base [Global]: Select 1 vert, 1 vert as active'
if __name__ == "__main__":
register()
