Last active
December 17, 2017 08:01
-
-
Save ShikouYamaue/7bed38e3f8cc48c10b9b1b83e75621be to your computer and use it in GitHub Desktop.
Dijkstras Sphere
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
from maya import cmds | |
from maya import OpenMayaUI | |
from collections import OrderedDict | |
import maya.api.OpenMaya as om | |
import math | |
import copy | |
import time | |
import numpy as np | |
#PySide2、PySide両対応 | |
import imp | |
try: | |
imp.find_module('PySide2') | |
from PySide2.QtWidgets import * | |
from PySide2.QtGui import * | |
from PySide2.QtCore import * | |
except ImportError: | |
from PySide.QtGui import * | |
from PySide.QtCore import * | |
class Option(): | |
def __init__(self): | |
self.window = MainWindow(getMayaWindow()) | |
self.window.resize(250, 65) | |
self.window.show() | |
global start_vtx | |
global distance_dict | |
global script_job | |
global distance | |
if 'script_job' in globals(): | |
pass | |
else: | |
script_job = None | |
class MainWindow(QMainWindow): | |
def __init__(self, parent = None): | |
global start_vtx | |
global distance_dict | |
global script_job | |
super(MainWindow, self).__init__(parent) | |
try: | |
cmds.delete('pSphere*') | |
except: | |
pass | |
self.mesh = cmds.polySphere(cuv=1, sy=40, ch=0, sx=60, r=10, ax=(0, 1, 0))[0] | |
cmds.delete(self.mesh+'.f[1189:1199]') | |
cmds.selectMode(co=True) | |
wrapper = QWidget() | |
self.setCentralWidget(wrapper) | |
self.main_layout = QVBoxLayout() | |
wrapper.setLayout(self.main_layout) | |
#----------------------------------------------------------------------- | |
label = QLabel('Dijkstras', self) | |
self.main_layout.addWidget(label) | |
self.main_layout.addWidget(make_h_line()) | |
self.scale_layout = QHBoxLayout() | |
self.main_layout.addLayout(self.scale_layout) | |
#タイトルラベル | |
label = QLabel('Max Distance',self) | |
self.scale_layout.addWidget(label) | |
#スケールボタン追加 | |
button = QPushButton('Compute Dijkstras') | |
button.clicked.connect(Callback(self.compute_dijkstras)) | |
self.main_layout.addWidget(button) | |
self.distance = QDoubleSpinBox(self)#スピンボックス | |
self.distance.setRange(0, 100) | |
self.distance.setValue(10.0)#値を設定 | |
self.distance.setDecimals(2)#小数点桁数設定 | |
self.scale_layout.addWidget(self.distance) | |
#スライダバーを設定 | |
self.distance_sld = QSlider(Qt.Horizontal,self) | |
self.distance_sld.setRange(0, 10000) | |
self.distance_sld.setValue(self.distance.value()) | |
self.scale_layout.addWidget(self.distance_sld) | |
#スライダーとボックスの値をコネクト。連動するように設定。 | |
self.distance_sld.valueChanged.connect(lambda:self.distance.setValue(self.distance_sld.value()/100.0)) | |
self.distance.editingFinished.connect(lambda:self.distance_sld.setValue(float(self.distance.value())*100)) | |
self.distance.valueChanged.connect(self.set_global_param) | |
global distance | |
global pre_distance | |
distance = self.distance.value() | |
pre_distance = self.distance.value() | |
def compute_dijkstras(self): | |
global start_vtx | |
global distance_dict | |
global pre_distance | |
global vtx_pos_dict | |
sel_vtx = cmds.polyListComponentConversion(cmds.ls(sl=True), tv=True) | |
sel_vtx = cmds.filterExpand(sel_vtx, sm=31)[0] | |
self.remove_job() | |
targetMesh = self.mesh | |
start_vtx = targetMesh+'.'+sel_vtx.split('.')[-1] | |
cmds.select(start_vtx, r=True) | |
#メッシュの全頂点を取得しておく | |
self.all_vtx = cmds.polyListComponentConversion(targetMesh, tv=True) | |
self.all_vtx = cmds.filterExpand(self.all_vtx, sm=31) | |
vtx_pos_dict = {vtx:np.array(cmds.xform(vtx, q=True, t=True, ws=True)) for vtx in self.all_vtx} | |
goal_vtx = self.all_vtx[:] | |
goal_vtx.remove(start_vtx) | |
goal_vtx = goal_vtx[-1] | |
#全頂点のつながり辞書を作っておく、頂点をエッジ→頂点変換すると周辺の頂点が取れる現象を応用。 | |
temp_Dict = {} | |
for vtx in self.all_vtx: | |
around = cmds.polyListComponentConversion(vtx, tf=True) | |
around = cmds.polyListComponentConversion(around, tv=True) | |
around = cmds.filterExpand(around, sm=31) | |
around.remove(vtx) | |
roundList = [] | |
a = om.MPoint(vtx_pos_dict[vtx]) | |
for rVtx in around: | |
b = om.MPoint(vtx_pos_dict[rVtx]) | |
dist = (a-b).length() | |
roundList.append((rVtx, dist)) | |
temp_Dict[vtx] = roundList | |
#スタートとゴールの経路を保持するためオーダードディクトを利用 | |
routeDict = OrderedDict() | |
#スタートとゴールを最初と最後のキーと要素に設定する。あとは適当。 | |
routeDict[start_vtx] = temp_Dict[start_vtx] | |
for k in temp_Dict.keys(): | |
if k != start_vtx and k != goal_vtx: | |
routeDict[k] = temp_Dict[k] | |
routeDict[goal_vtx] = temp_Dict[goal_vtx] | |
#探索用変数を色々初期化 | |
nodeNum = len(routeDict)#ノードの総数 | |
distance_dict = {vtx:float('inf') for vtx in self.all_vtx}#ノードごとの距離のリスト、初期値無限大を与える | |
previous_nodes_dict = {vtx:'' for vtx in self.all_vtx}#最短経路でそのノードのひとつ前に到達するノードの対応辞書 | |
distance_dict[start_vtx] = 0#初期のノードの距離は0とする | |
unsearched_nodes = copy.copy(distance_dict)#未探索ノード | |
i = 0 | |
while(len(unsearched_nodes) != 0): #未探索ノードがなくなるまで繰り返す | |
#未探索ノードのうちで最小のindex番号を取得 | |
min_dist = min(unsearched_nodes.values()) | |
min_id = unsearched_nodes.values().index(min_dist) | |
target_min_key = unsearched_nodes.keys()[min_id] | |
#未探索ノードから除去しておく | |
unsearched_nodes.pop(target_min_key) | |
#ターゲットノードの周辺ノード取得 | |
round_vertex = routeDict[target_min_key] | |
#周辺ノードへのスタートからの到達距離を比較、以前設定した距離より小さい場合は更新する。 | |
for rVtx in round_vertex: | |
i += 1 | |
real_dist = distance_dict[target_min_key] + rVtx[1]#実際の距離 | |
if distance_dict[rVtx[0]] > real_dist: | |
distance_dict[rVtx[0]] = real_dist | |
unsearched_nodes[rVtx[0]] = real_dist | |
#そのノードに最短で到達するための一つまえのノード辞書を更新しておく | |
previous_nodes_dict[rVtx[0]] = target_min_key | |
self.create_job() | |
def set_global_param(self): | |
global distance | |
distance = self.distance.value() | |
def create_job(self): | |
global start_vtx | |
global script_job | |
global base_pos | |
global pre_pos | |
global pre_distance | |
global distance | |
distance = self.distance.value() | |
pre_distance = self.distance.value() | |
if script_job is None: | |
base_pos = np.array(cmds.xform(start_vtx, q=True, t=True, ws=True)) | |
pre_pos = base_pos | |
script_job = cmds.scriptJob(e=("idle", "move_with_cost_ratio()"), kws=False) | |
def closeEvent(self, e): | |
self.remove_job() | |
def remove_job(self): | |
global script_job | |
if script_job is not None: | |
print 'kill job' | |
cmds.scriptJob(k=script_job) | |
script_job = None | |
def move_with_cost_ratio(): | |
global start_vtx | |
global distance_dict | |
global pre_pos | |
global distance | |
global pre_distance | |
global vtx_pos_dict | |
if distance == 0.0: | |
[cmds.xform(vtx, t=vtx_pos_dict[vtx], ws=True) for vtx in distance_dict.keys()] | |
return | |
pos = np.array(cmds.xform(start_vtx, q=True, t=True, ws=True)) | |
if str(pos) != str(pre_pos) or distance!= pre_distance: | |
moved = pos - base_pos | |
for vtx, cost in distance_dict.items(): | |
if vtx == start_vtx: | |
continue | |
if cost > distance: | |
cmds.xform(vtx, t=vtx_pos_dict[vtx], ws=True) | |
continue | |
ratio = (distance - cost)/distance | |
new_pos = vtx_pos_dict[vtx]+moved*ratio | |
cmds.xform(vtx, t=new_pos, ws=True) | |
pre_pos = pos | |
pre_distance = distance | |
class Callback(object): | |
def __init__(self, func, *args, **kwargs): | |
self.__func = func | |
self.__args = args | |
self.__kwargs = kwargs | |
def __call__(self, *args, **kwargs): | |
cmds.undoInfo(openChunk=True) | |
try: | |
return self.__func(*self.__args, **self.__kwargs) | |
except: | |
raise | |
finally: | |
cmds.undoInfo(closeChunk=True) | |
def getMayaWindow(): | |
try: | |
imp.find_module("shiboken2") | |
import shiboken2 | |
return shiboken2.wrapInstance(long(OpenMayaUI.MQtUtil.mainWindow()), QWidget) | |
except ImportError: | |
import shiboken | |
return shiboken.wrapInstance(long(OpenMayaUI.MQtUtil.mainWindow()), QWidget) | |
def make_h_line(): | |
hline = QFrame() | |
hline.setFrameShape(QFrame.HLine) | |
hline.setFrameShadow(QFrame.Sunken) | |
return hline | |
Option() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment