Navigating through a dictionary in Python - python

I have a dictionary called "locations". A function I'm writing for the dictionary is set up in the format (d, description, current) where 'd' references the dictionary, 'description' references a string describing the location we are looking for, and 'current' references the spot we currently are in the dictionary as a pair of coordinates (x,y).
Basically, each location has multiple positions in the dictionary each with its own pair of coordinates and my goal is to find the closest position to where we currently are in the dictionary (current). The strategy is to use the distance formula to calculate this.
For example if we were looking for the nearest gas station and we were currently at (2,2), the function should return (3,1) for the nearest station if the two stations were at (3,1) and (1,4) as (3,1) is closer to (2,2) Any advice on my current code would be appreciated.
Code:
def closest(d, description, current):
current_location = (x, y)
d = {(3,1):'gas', (1,4):'gas', (2,1):'food', (5,5):'food'}
distancesFromCurrent = [distanceFormula(z, current_location) for z in places]
for z in d:
if z < minimum float:
return z
My current code has no errors but is definitely not working correctly. It is just returning 0,0 and I'm not sure how I can fix it to return the coordinates of the closest location to our current position.

After considering the comments, here is my solution.
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Sun Nov 6 21:42:22 2016
#author: michaelcurrin
"""
import math
def findDistance(A, B):
"""
In 2D space find the distance between two co-orinates is
known as Eucliciean distance.
Args
A: tuple or list of x and y co-ordinates
e.g. (1,2) e.g. [1,2]
B: as A.
Retuns
distance: float. Decimal value for shortest between A and B
"""
x = (A[0] - B[0])
y = (A[1] - B[1])
distance = math.sqrt(x**2 + y**2) # square root
# remove comment if you want to see this outputted
# print distance
return distance
def GetClosestPlace(places, loc, feature):
"""find shortest distance between current location and each locations
but only ones which have the desired feature"""
# add distance from current location to each location
for index in range(len(places)):
# only continue if feature exists at place
if feature in places[index]['features']:
# calculate
distance = findDistance(loc,
places[index]['location'])
else:
# this is to represent n/a for now as every location needs a distance
# for this version, so that it will not be chosen
distance = 1000
# add calculated distance to existing dictionary for location
places[index]['distance'] = distance
# find shortest distance and return details for that place
allDistances = [x['distance'] for x in places]
shortestDistance = min(allDistances)
for place in places:
if place['distance'] == shortestDistance:
return place
placesList = [dict(name='foo',location=(0,3), features=['gas', 'food']),
dict(name='bar',location=(4,6), features=['food', 'hospital']),
dict(name='abc',location=(0,9), features=['gas','barber']),
dict(name='xyz',location=(2,2), features=['food','barber'])
]
currentLocation = (5,9)
desiredFeature='food'
closestPlace = GetClosestPlace(placesList, currentLocation, desiredFeature)
print 'Current location: %s' % str(currentLocation)
print 'Desired feature: %s ' % desiredFeature
print
print 'The closest place is...'
print 'Name: %s' % closestPlace['name']
print 'Location %s' % str(closestPlace['location'])
print 'Distance %f' % closestPlace['distance']
# join multiple features in the list with commas
print 'Features: %s' % ', '.join(closestPlace['features'])
"""
OUTPUT
Current location: (5, 9)
Desired feature: food
The closest place is...
Name: bar
Location (4, 6)
Distance 3.162278
Features: food, hospital
"""

I think you need to use the dictionary input to calculate results for place names against their distance from the current location as a single float (or decimal).
Something like
current_location = (x, y)
distancesFromCurrent = [distanceFormula(z, current_location) for z in places]
Where distanceFormula would be using you distance calc in function.
Once you have that for all places inputted, then you can do another loop find the minimum float value in the dictionary and return its corresponding place name and its co-ordinate location (from the original input).
I think you could change from dictionary to list input to be in this format below. (if you have anything data like this already to show us that would help too)
placesList = [dict(name='abc',location=(0,3), features=['gas station','mall', 'police dept', 'fire dept']),
dict(name='xyz',location=(4,5), features=['police dept', 'hospital']),
#etc.
]
Then your function would have find the closest the location but first filter out the locations which have the feature which matches your description.
Hope that helps.

Related

find the least euclidean distance from the given points

I have a list of dictionaries. The key is item_cd and value is location_coordinates.
I would like to find the euclidian distance of these locations and create a separate similar list based on the proximity of their locations.
Steps
The initial distance will be calculated from the origin i.e. (0,0).
The closest item will be added to a separate list suppose it is {5036885955: [90.0, 61.73]} in this case.
Now I would like to find the closest order from the centroid of the previous orders. In this case, the centroid will be [90.0, 61.73] of order {5036885955: [90.0, 61.73]} as there is only one coordinate at the moment. The next closest order from the centroid of the previous order will be {5036885984: [86.0, 73.03]}
I would like to repeat the above steps until the list is empty
Input :
[{5036885850: [92.0, 88.73]}, {5036885955: [90.0, 61.73]}, {5036885984: [86.0, 73.03]}, {5036885998: [102.0, 77.54]}]
What I have tried :
def calculate_centroid(self, locations: list) -> list:
"""
get the centroid of the order
param: Location object
return: centroid of an order
"""
x, y = [p[0] for p in locations], [p[1] for p in locations]
centroid = [round(sum(x) / len(locations), 2), round(sum(y) / len(locations), 2)]
return centroid
def get_closest_order_to_origin(self, order_centroid_list: list) -> list:
"""
get the next closest order
"""
initial_order = [[0, 0]]
next_order = []
centroids = []
for order in order_centroid_list:
for shipping_req_key, centroid in order.items():
distance = math.sqrt(((initial_order[0][0] - centroid[0]) ** 2) + ((initial_order[0][1] - centroid[1]) ** 2))
I am not sure how should I proceed further. I would appreciate your feedback
You can use numpy.linalg.norm() to calculate the Euclidean distance between two numpy array. So you can declare those position arrays as a numpy array and apply this function to calculate distance.
Let's see this step by step.
Declared initial current_pos as origin (0,0). Extracted the points sequentially in the list named pos_list. Declared result_list as empty list to fetch results.
From the pos_list each positions can be taken and Euclidean distance with current_pos is calculated and stored in the dist_list.
The min(dist_list) gives the minimum distance min_dist. The corresponding index of min_dist can be fetched from dist_list and the relevant position and entry of the input data can be identified and processed (i.e. removing from data and appending to result_list)
The whole process continues until the data becomes empty.
The whole implementation:
import numpy as np
data = [{5036885850: [92.0, 88.73]}, {5036885955: [90.0, 61.73]}, {5036885984: [86.0, 73.03]}, {5036885998: [102.0, 77.54]}]
pos_list = [list(item.values())[0] for item in data]
current_pos = np.array([0,0])
result_list = []
while (len(data) != 0):
dist_list = [np.linalg.norm(current_pos - np.array(pos)) for pos in pos_list]
min_dist = min(dist_list)
item_index = dist_list.index(min_dist)
to_be_moved = data[item_index]
result_list.append(to_be_moved)
# current_pos = pos_list[item_index]
pos_list.remove(pos_list[item_index])
data.remove(to_be_moved)
print(result_list)
You're requirement was not clear in the question, so I'm handling two cases.
First case: Arrange the points in such an order such that the distances of each points from the origin is increasing. The above code works for this scenario and this matches your expected output.
Second case: Arrange the points a,b,c,d,... in such a way so that a is nearest to origin, b is nearest to a, c is nearest to d and so on. To see this case in action just revert the commented line.
NOTE: If any type of sort or positional modification is done, this solution will break.
UPDATE
According to your last added condition, you need to calculate the centroid of all the points in the resulting order, then find the order that is closest to the centroid.
You can pass the list of points that has been already added to the result find the centroid with the following function:
def get_centroid(lst):
arr = np.array(lst)
length = arr.shape[0]
if length == 0:
return 0,0
else:
sum_x = np.sum(arr[:, 0])
sum_y = np.sum(arr[:, 1])
return sum_x/float(length), sum_y/float(length)
to do so, keep a track of the point that is added to the final result and removed from the pos_list in another list called pos_removed_list:
pos_removed_list.append(pos_list[item_index])
and replace the commented line:
# current_pos = pos_list[item_index]
with
current_pos = np.array(get_centroid(pos_removed_list))
and you're ready to go!
**Full Code: **
import numpy as np
def get_centroid(lst):
arr = np.array(lst)
length = arr.shape[0]
if length == 0:
return 0,0
else:
sum_x = np.sum(arr[:, 0])
sum_y = np.sum(arr[:, 1])
return sum_x/float(length), sum_y/float(length)
data = [{5036885850: [92.0, 88.73]}, {5036885955: [90.0, 61.73]}, {5036885984: [86.0, 73.03]}, {5036885998: [102.0, 77.54]}]
pos_list = [list(item.values())[0] for item in data]
pos_removed_list = []
current_pos = np.array((0,0))
result_list = []
print('initial centroid: ' + str(current_pos))
while (len(data) != 0):
dist_list = [np.linalg.norm(current_pos - np.array(pos)) for pos in pos_list]
min_dist = min(dist_list)
item_index = dist_list.index(min_dist)
to_be_moved = data[item_index]
print('moved: ' + str(to_be_moved))
result_list.append(to_be_moved)
pos_removed_list.append(pos_list[item_index])
pos_list.remove(pos_list[item_index])
current_pos = np.array(get_centroid(pos_removed_list))
print('current centroid: ' + str(current_pos))
data.remove(to_be_moved)
print('\nfinal result: ' + str(result_list))
Your step by step output will be:
initial centroid: [0 0]
moved: {5036885955: [90.0, 61.73]}
current centroid: [90. 61.73]
moved: {5036885984: [86.0, 73.03]}
current centroid: [88. 67.38]
moved: {5036885998: [102.0, 77.54]}
current centroid: [92.66666667 70.76666667]
moved: {5036885850: [92.0, 88.73]}
current centroid: [92.5 75.2575]
final result: [{5036885955: [90.0, 61.73]}, {5036885984: [86.0, 73.03]}, {5036885998: [102.0, 77.54]}, {5036885850: [92.0, 88.73]}]
Let me know if this is what you were looking for
Using the key argument to the min function can help keep our code efficient and clean. In addition we can help keep track of everything with a few functions that use type aliases to annotate their inputs and outputs. We can calculate the new centroid in constant time from the old centroid as long as we keep track of how many points are involved in calculating the old centroid. Finally we can minimize distance squared to the centroid instead of distance and save ourselves calculating square roots.
from typing import Callable, cast, Dict, List, Tuple
Point = Tuple[float, float]
Order = Dict[int, List[float]]
orders: List[Order] = [
{5036885850: [92.0, 88.73]},
{5036885955: [90.0, 61.73]},
{5036885984: [86.0, 73.03]},
{5036885998: [102.0, 77.54]}
]
def new_centroid(new_point: Point, old_centroid: Point, old_centroid_weight: float) -> Point:
new_x, new_y = new_point
old_x, old_y = old_centroid
new_point_weight = 1 / (old_centroid_weight + 1)
return (
new_point_weight * new_x + (1 - new_point_weight) * old_x,
new_point_weight * new_y + (1 - new_point_weight) * old_y,
)
def distance_squared_to(point: Point) -> Callable[[Order], float]:
def distance_squared_to_point(order: dict) -> float:
order_point = get_point(order)
from_x, from_y = point
order_x, order_y = order_point
return (from_x - order_x) ** 2 + (from_y - order_y) ** 2
return distance_squared_to_point
def get_point(order: Order) -> Point:
return cast(Point, list(order.values()).pop())
sorted_orders = []
centroid = (0.0, 0.0)
centroid_weight = 0
while orders:
next_order = min(orders, key=distance_squared_to(centroid))
orders.remove(next_order)
sorted_orders.append(next_order)
next_point = get_point(next_order)
centroid = new_centroid(next_point, centroid, centroid_weight)
centroid_weight += 1
print(sorted_orders)
A couple notes here. Unfortunately, Python doesn't have a really nice idiom for removing the minimum element from a collection. We have to find the min (order n) and then call the remove on that element (order n again). We could use something like numpy.argmin but that's probably overkill and a pretty heavy dependency for this.
The second note is that our Orders are kinda hard to work with. To get the location, we have to cast the values as a list and pop an element (or just take the first). Given an order we can't index into it without introspecting to find the key. It might be easier if the orders were just a tuple where the first element was the order number and the second was the location. Any number of other forms could also be helpful (named tuple, data class, dictionary with keys item_cd and location_coordinates, ...). Location coordinates could also be just a tuple instead of a list, but that's more of a type hinting issue than anything else.

Distance map returned from shortest_distance function misses entries of certain vertices

I have a network present in a postgres database, where I can route with the pgrouting extension. I've read this into mem, and now want to calculate the distance of all nodes within 0.1 hours from a specific starting node:
dm = G.new_vp("double", np.inf)
gt.shortest_distance(G, source=nd[102481678], weights=wgts, dist_map = dm, max_dist=0.1)
where wgts is an EdgePropertyMap containing the weights per edge, and nd is a reverse mapping to get vertex index from the outside id.
In pgRouting this delivers 349 reachable nodes, using graph-tool only 328. The results are more or less the same (e.g. the furthest node is the same with the exact same cost, nodes present in both lists have same distance), but the graph-tool distance map just seems to miss certain nodes. The weird thing is that I found a cul-de-sac node labeled with a distance (second one from below), but the node connecting the cul-de-sac with the outside world is missing. Seems weird, because if the connecting node would not be reachable, the cul-de-sac would be unreachable as well.
I've compiled a MWE: https://gofile.io/d/YpgjSw
Below is the python code:
import graph_tool.all as gt
import numpy as np
import time
# construct list of source, target, edge-id (edge-id not really used in this example)
l = []
with open('nw.txt') as f:
rows = f.readlines()
for row in rows:
id = int(row.split('\t')[0])
source = int(row.split('\t')[1])
target = int(row.split('\t')[2])
l.append([source, target, id])
l.append([target, source, id])
print len(l)
# construct graph
G = gt.Graph(directed=True)
G.ep["edge_id"] = G.new_edge_property("int")
n = G.add_edge_list(l, hashed=True, eprops=G.ep["edge_id"])
# construct a dict for mapping outside node-id's to internal id's (node indexes)
nd = {}
i = 0
for x in n:
nd[x] = i
i = i + 1
# construct a dict for mapping (source, target) combis to a cost and reverse cost
db_wgts = {}
with open('costs.txt') as f:
rows = f.readlines()
for row in rows:
source = int(row.split('\t')[0])
target = int(row.split('\t')[1])
cost = float(row.split('\t')[2])
reverse_cost = float(row.split('\t')[3])
db_wgts[(source, target)] = cost
db_wgts[(target, source)] = reverse_cost
# construct an edge property and fill it according to previous dict
wgts = G.new_edge_property("double")
i = 0
for e in G.edges():
i = i + 1
print i
print e
s = n[int(e.source())]
t = n[int(e.target())]
try:
wgts[e] = db_wgts[(s, t)]
except KeyError:
# this was necessary
wgts[e] = 1000000
# calculate shortest distance to all nodes within 0.1 total cost from source-node with outside-id of 102481678
dm = G.new_vp("double", np.inf)
gt.shortest_distance(G, source=nd[102481678], weights=wgts, dist_map = dm, max_dist=0.1)
# some mumbo-jumbo for getting the result in a nice node-id: cost format
ar = dm.get_array()
idxs = np.where(dm.get_array() < 0.1)
vals = ar[ar < 0.1]
final_res = [(i, v) for (i,v) in zip(list(idxs[0]), list(vals))]
final_res.sort(key=lambda tup: tup[1])
for x in final_res:
print n[x[0]], x[1]
# output saved in result_missing_nodes.txt
# 328 records, should be 349
To illustrate (one of the) missing nodes:
>>> dm[nd[63447311]]
0.0696234786274957
>>> dm[nd[106448775]]
0.06165528930577409
>>> dm[nd[127601733]]
inf
>>> dm[nd[100428293]]
0.0819900275163846
>>>
This doesn't seem possible because this is the local layout of the network, labels are the id's referenced above:
This is a numerical precision problem. You have very low edge weights (1e-6) combined with very large values (1000000), which cause differences to be lost to finite precision. If you replace all values 1000000 (which I assume mean infinite weight) by numpy.inf, you actually get a more stable calculation, and no missing nodes in your example.
An even better alternative is to actually remove the "infinite weight"
edges using an edge filter:
u = GraphView(G, efilt=wgts.fa < 1000000)
and compute the distances on that.

TypeError: object of type 'map' has no len() Python3

I'm trying to implement KMeans algorithm using Pyspark it gives me the above error in the last line of the while loop. it works fine outside the loop but after I created the loop it gave me this error
How do I fix this ?
# Find K Means of Loudacre device status locations
#
# Input data: file(s) with device status data (delimited by '|')
# including latitude (13th field) and longitude (14th field) of device locations
# (lat,lon of 0,0 indicates unknown location)
# NOTE: Copy to pyspark using %paste
# for a point p and an array of points, return the index in the array of the point closest to p
def closestPoint(p, points):
bestIndex = 0
closest = float("+inf")
# for each point in the array, calculate the distance to the test point, then return
# the index of the array point with the smallest distance
for i in range(len(points)):
dist = distanceSquared(p,points[i])
if dist < closest:
closest = dist
bestIndex = i
return bestIndex
# The squared distances between two points
def distanceSquared(p1,p2):
return (p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2
# The sum of two points
def addPoints(p1,p2):
return [p1[0] + p2[0], p1[1] + p2[1]]
# The files with device status data
filename = "/loudacre/devicestatus_etl/*"
# K is the number of means (center points of clusters) to find
K = 5
# ConvergeDist -- the threshold "distance" between iterations at which we decide we are done
convergeDist=.1
# Parse device status records into [latitude,longitude]
rdd2=rdd1.map(lambda line:(float((line.split(",")[3])),float((line.split(",")[4]))))
# Filter out records where lat/long is unavailable -- ie: 0/0 points
# TODO
filterd=rdd2.filter(lambda x:x!=(0,0))
# start with K randomly selected points from the dataset
# TODO
sample=filterd.takeSample(False,K,42)
# loop until the total distance between one iteration's points and the next is less than the convergence distance specified
tempDist =float("+inf")
while tempDist > convergeDist:
# for each point, find the index of the closest kpoint. map to (index, (point,1))
# TODO
indexed =filterd.map(lambda (x1,x2):(closestPoint((x1,x2),sample),((x1,x2),1)))
# For each key (k-point index), reduce by adding the coordinates and number of points
reduced=indexed.reduceByKey(lambda x,y: ((x[0][0]+y[0][0],x[0][1]+y[0][1]),x[1]+y[1]))
# For each key (k-point index), find a new point by calculating the average of each closest point
# TODO
newCenters=reduced.mapValues(lambda x1: [x1[0][0]/x1[1], x1[0][1]/x1[1]]).sortByKey()
# calculate the total of the distance between the current points and new points
newSample=newCenters.collect() #new centers as a list
samples=zip(newSample,sample) #sample=> old centers
samples1=sc.parallelize(samples)
totalDistance=samples1.map(lambda x:distanceSquared(x[0][1],x[1]))
# Copy the new points to the kPoints array for the next iteration
tempDist=totalDistance.sum()
sample=map(lambda x:x[1],samples) #new sample for next iteration as list
sample
You are getting this error because you are trying to get len of map object (of generator type) which do not supports len. For example:
>>> x = [[1, 'a'], [2, 'b'], [3, 'c']]
# `map` returns object of map type
>>> map(lambda a: a[0], x)
<map object at 0x101b75ba8>
# on doing `len`, raises error
>>> len(map(lambda a: a[0], x))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object of type 'map' has no len()
In order to find the length, you will have to type-cast the map to list (or tuple) and then you may call len over it. For example:
>>> len(list(map(lambda a: a[0], x)))
3
Or it is even better to simply create a list using the list comprehension (without using map) as:
>>> my_list = [a[0] for a in x]
# since it is a `list`, you can take it's length
>>> len(my_list)
3

python - divide world into bins

I am working on trying to put moving balls into appropriate bins. I like to think I'm on the right track but I've been stuck for awhile now.
I left code out that didn't seem relevant to my question but if those who answer need further details I can provide them. Basically, I have a world of 200 moving balls. They have an X and Y coordinate. I want to divide the world into square bins of width 256 and place the balls in the appropriate bin.
My approach to this was to put them into a dictionary. It looked like this:
dict_of_balls = {}
for i in range(len(balls)):
xb = int(balls[i].x/256)
yb = int(balls[i].y/256)
and I wanted to make the keys a tuple of the (xb, yb) pairs and then place the appropriate balls in that bin but I don't think you can use tuples as keys...
The code is below:
import math
import random
import time
import sys
ball_min_radius = 16.0 #world coordinates
ball_max_radius = 128.0 #world coordniates
number_balls = 200
class Ball:
"""
Implements a point/ball
"""
def __init__(self):
self.x = random.uniform(world_min_x,world_max_x)
self.y = random.uniform(world_min_y,world_max_y)
self.radius = int(random.uniform(ball_min_radius,ball_max_radius))
def __lt__(self, other):
return self.id < other.id
def main():
world_min_x = -200.0*number_balls**.5 # minimum x in world coordinates
world_max_x = +200.0*number_balls**.5 # maximum x in world coordinates
world_min_y = -200.0*number_balls**.5 # minimum y in world coordinates
world_max_y = +200.0*number_balls**.5 # maximum y in world coordinates
balls = [Ball() for i in range(number_balls)]
so does anyone have any ideas for how to divide the world into bins based on the given world coordinates? I am unsure of which data structure to use since I can't use tuples for keys. Thanks in advance for any feedback.
Why do you want a dictionary? Here's how you would do this, but keep in mind you will only get one ball per bin because you are specifically casting their key to be (int, int) and keys are unique.
If you use a collection, you can also sort (in my example I sort by the region identifiers):
I am not sure what you are doing that for, but you can do it:
import math
import random
import time
import sys
ball_min_radius = 16.0 #world coordinates
ball_max_radius = 128.0 #world coordniates
number_balls = 200
world_min_x = -200.0*number_balls**.5 # minimum x in world coordinates
world_max_x = +200.0*number_balls**.5 # maximum x in world coordinates
world_min_y = -200.0*number_balls**.5 # minimum y in world coordinates
world_max_y = +200.0*number_balls**.5 # maximum y in world coordinates
class Ball:
"""
Implements a point/ball
"""
def __init__(self):
self.x = random.uniform(world_min_x,world_max_x)
self.y = random.uniform(world_min_y,world_max_y)
self.radius = int(random.uniform(ball_min_radius,ball_max_radius))
def __lt__(self, other):
return self.id < other.id
def __str__(self):
return 'x={x} y={y} r={r}'.format(x=self.x, y=self.y, r=self.radius)
def main():
balls = [Ball() for i in range(number_balls)]
dict_of_balls = {}
ball_collection = []
for b in balls:
xb = int(b.x/256)
yb = int(b.y/256)
key = (xb, yb)
dict_of_balls[key] = b
ball_collection.append((key, b))
print 'length of dictionary:{}'.format(len(dict_of_balls.keys()))
print 'length of collection:{}'.format(len(ball_collection))
Notice that the dictionary has fewer items than the collection.
You can also print each item this way pretty trivially:
for b in ball_collection:
print 'ball region: {r} with coords: {c}'.format(r=b[0], c=b[1])
Or, sort them if you want:
print 'Collections also let you sort the collection by region(s)...'
sorted_list = sorted(ball_collection, key= lambda x: (x[0][0], x[0][1]))
for b in sorted_list:
print 'ball region: {r} with coords: {c}'.format(r=b[0], c=b[1])
You can also pretty simply get balls in a specific region too:
print '... or get only ones in a specific region'
subset = [b for b in ball_collection if b[0][0] == 1]
for b in subset:
print 'ball region: {r} with coords: {c}'.format(r=b[0], c=b[1])
main()
A collection seems to do what you are actually wanting.
You can use tuple for keys in a dictionary, since tuple is immutable. The only data type you can't use for a dictionary key is a list [] or set {}
**a = {(1,2):'example1', (2,3):'example2'}
>>> a[(1,2)]
'example1'**
So I believe this should make it much easier to solve your problem.

How to find matching vertices in multiple maya meshes

I'm trying to compare the locations of vertices on one mesh to another and generate a list of paired vertices, (the ultimate purpose is to pair up vertices on a neck geo with the top verts of a body geo.)
The way I'm 'pairing' them is to just compare the distances between all vertices in both meshes and then match up the closest ones to eachother by ordering them in separate lists, (neck_geo_verts[0] is paired with body_geo_verts[0].)
I want to use OpenMaya as I've heard it considerably faster than cmds.xform.
Here's my code so far getting the verts, although it's using cmds and not the Maya API. I am having a really tough time finding what I need from the Maya documentation.
# The user selects an edge on both the bottom of the neck and top of the body, then this code gets all the vertices in an edge border on both of those geos and populates two lists with the vertices
import maya.cmds as mc
import maya.api.OpenMaya as om
import re
mc.unloadPlugin('testingPlugin.py')
mc.loadPlugin('testingPlugin.py')
def main():
geoOneVerts = []
geoTwoVerts = []
edges = cmds.ls(selection=True, sn=True)
geoOneEdgeNum = re.search(r"\[([0-9_]+)\]", edges[0])
geoTwoEdgeNum = re.search(r"\[([0-9_]+)\]", edges[1])
cmds.polySelect(add=True, edgeBorder=int(geoOneEdgeNum.group(1)))
geoOneEdgeBorder = cmds.ls(selection=True, sn=True)
geoOneEdgeVerts = cmds.polyInfo(edgeToVertex=True)
for vertex in geoOneEdgeVerts:
vertexPairNums = re.search(r":\s*([0-9_]+)\s*([0-9_]+)", vertex)
geoOneVerts.append(vertexPairNums.group(1))
geoOneVerts.append(vertexPairNums.group(2))
cmds.polySelect(replace=True, edgeBorder=int(geoTwoEdgeNum.group(1)))
geoTwoEdgeBorder = cmds.ls(selection=True, sn=True)
geoTwoEdgeVerts = cmds.polyInfo(edgeToVertex=True)
for vertex in geoTwoEdgeVerts:
vertexPairNums = re.search(r":\s*([0-9_]+)\s*([0-9_]+)", vertex)
geoTwoVerts.append(vertexPairNums.group(1))
geoTwoVerts.append(vertexPairNums.group(2))
geoOneVerts = list(set(geoOneVerts))
geoTwoVerts = list(set(geoTwoVerts))
# How do I use OpenMaya to compare the distance from the verts in both lists?
main()
EDIT: This code gives me two lists filled with the DAG names of vertices on two meshes. I'm unsure how to get the positions of those vertices to compare the distance between the vertices in both lists and I'm also unsure if I should be using maya.cmds for this as opposed to maya.api.OpenMaya considering the amount of vertices I'm going to be operating on.
EDIT2: Thanks to Theodox and hundreds of searches for the help. I ended up making a version that worked using boundary vertices and one that assumed paired vertices on both meshes would be in identical global space. Both of which I chose to use the Maya API and forewent Maya Commands completely for performance reasons.
Vesion1 (Using Boundary Verts):
import maya.OpenMaya as om
def main():
geo1Verts = om.MFloatPointArray()
geo2Verts = om.MFloatPointArray()
selectionList = om.MSelectionList()
om.MGlobal.getActiveSelectionList(selectionList)
geo1SeamVerts = getSeamVertsOn(selectionList, 1)
geo2SeamVerts = getSeamVertsOn(selectionList, 2)
pairedVertsDict = pairSeamVerts(geo1SeamVerts, geo2SeamVerts)
def getSeamVertsOn(objectList, objectNumber):
count = 0
indexPointDict = {}
selectedObject = om.MObject()
iter = om.MItSelectionList(objectList, om.MFn.kGeometric)
while not iter.isDone():
count += 1
connectedVerts = om.MIntArray()
if (count != objectNumber):
iter.next()
else:
iter.getDependNode(selectedObject)
vertexIter = om.MItMeshVertex(selectedObject)
while not vertexIter.isDone():
if (vertexIter.onBoundary()):
vertex = om.MPoint()
vertex = vertexIter.position()
indexPointDict[int(vertexIter.index())] = vertex
vertexIter.next()
return indexPointDict
def pairSeamVerts (dictSeamVerts1, dictSeamVerts2):
pairedVerts = {}
if (len(dictSeamVerts1) >= len(dictSeamVerts2)):
for vert1 in dictSeamVerts1:
distance = 0
closestDistance = 1000000
vertPair = 0
for vert2 in dictSeamVerts2:
distance = dictSeamVerts1[vert1].distanceTo(dictSeamVerts2[vert2])
if (distance < closestDistance):
closestDistance = distance
vertPair = vert2
pairedVerts[vert1] = vertPair
return (pairedVerts)
else:
for vert1 in dictSeamVerts2:
distance = 0
closestDistance = 1000000
vertPair = 0
for vert2 in dictSeamVerts1:
distance = dictSeamVerts2[vert1].distanceTo(dictSeamVerts1[vert2])
if (distance < closestDistance):
closestDistance = distance
vertPair = vert2
pairedVerts[vert1] = vertPair
return (pairedVerts)
main()
Version2 (Assuming Paired Vertices Would Share a Global Space):
import maya.OpenMaya as om
def main():
selectionList = om.MSelectionList()
om.MGlobal.getActiveSelectionList(selectionList)
meshOneVerts = getVertPositions(selectionList, 1)
meshTwoVerts = getVertPositions(selectionList, 2)
meshOneHashedPoints = hashPoints(meshOneVerts)
meshTwoHashedPoints = hashPoints(meshTwoVerts)
matchingVertList = set(meshOneHashedPoints).intersection(meshTwoHashedPoints)
pairedVertList = getPairIndices(meshOneHashedPoints, meshTwoHashedPoints, matchingVertList)
def getVertPositions(objectList, objectNumber):
count = 0
pointList = []
iter = om.MItSelectionList(objectList, om.MFn.kGeometric)
while not iter.isDone():
count = count + 1
if (count != objectNumber):
iter.next()
dagPath = om.MDagPath()
iter.getDagPath(dagPath)
mesh = om.MFnMesh(dagPath)
meshPoints = om.MPointArray()
mesh.getPoints(meshPoints, om.MSpace.kWorld)
for point in range(meshPoints.length()):
pointList.append([meshPoints[point][0], meshPoints[point][1], meshPoints[point][2]])
return pointList
def hashPoints(pointList):
_clamp = lambda p: hash(int(p * 10000) / 10000.00)
hashedPointList = []
for point in pointList:
hashedPointList.append(hash(tuple(map(_clamp, point))))
return (hashedPointList)
def getPairIndices(hashListOne, hashListTwo, matchingHashList):
pairedVertIndices = []
vertOneIndexList = []
vertTwoIndexList = []
for hash in matchingHashList:
vertListOne = []
vertListTwo = []
for hashOne in range(len(hashListOne)):
if (hashListOne[hashOne] == hash):
vertListOne.append(hashOne)
for hashTwo in range(len(hashListTwo)):
if (hashListTwo[hashTwo] == hash):
vertListTwo.append(hashTwo)
pairedVertIndices.append([vertListOne, vertListTwo])
return pairedVertIndices
main()
API is significantly faster for the distance comparison method, but in this case I think the real killer is likely to be the algorithm. Comparing every vert to ever other is a lot of math.
Probably the easiest thing to do is to come up with a way to hash the vertices instead: turn each xyz point into a single value that can be compared with others without doing the distances: two verts with the same hash would necessarily be in the same position. You can tweak the hash algorithm to quantize the vert positions a bit to account for floating point error at the same time.
Here's a way to hash a point (down to 4 significant digits, which you can tweak by changing the constant in _clamp) :
def point_hash(point):
'''
hash a tuple, probably a cmds vertex pos
'''
_clamp = lambda p: hash(int(p * 10000) / 10000.00)
return hash(tuple(map(_clamp, point)))
As long as both sets of verts are hashed in the same space (presumably world space) identical hashes will mean matched verts. All you'd have to do is to loop through each mesh, creating a dictionary which keyed the vertex hash to the vertex index. Here's a way to do it in cmds:
def vert_dict(obj):
'''
returns a dictionary of hash: index pairs representing the hashed verts of <obj>
'''
results = dict()
verts = cmds.xform(obj + ".vtx[*]", q=True, t=True, ws=True)
total = len(verts)/ 3
for v in range(total):
idx = v * 3
hsh = point_hash (verts[idx: idx + 3])
results[hsh] = v
return results
You can find the intersecting verts - the ones present in both meshes -- by intersecting the keys from both dictionaries. Then convert the matching verts in both meshes back to vertex indices by looking up in the two dictionaries.
Unless the meshes are really heavy, this should be doable without the API since all the work is in the hash function which has no API analog.
The only likely issue would be making sure that the verts were in the same space. You would have to fall back on a distance based strategy if you can't get the verts into the same space for some reason.
If you want to get a more useable result from the op's version 2 script (instead of returning nested and combined lists), you could do something like the following:
indices = lambda itr, val: (i for i, v in enumerate(itr) if v==val) #Get the index of each element of a list matching the given value.
matching = set(hashA).intersection(hashB)
return [i for h in matching
for i in zip(indices(hashA, h), indices(hashB, h))]
which will return a list of two element tuples representing the matched vertex pairs:
[(119, 69), (106, 56), (82, 32), (92, 42), ...
Also, you can use om.MSpace.kObject to compare the two mesh objects in local space depending on your specific needs.

Categories

Resources