summaryrefslogtreecommitdiff
path: root/core/interface.py
blob: 1a746ddb933a485c28916102c900cf85219fa382 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/python
from yamlcrap import FennObject
from package import package_file
import math

#Interface.volume should be some positive float if it is the male
#or some negative number if it is the female.
#thus summing the volume of two interfaces in a Connection should give 0 for a perfect fit

try: 
  import igraph; use_igraph=True
  #TODO this should go in its own file i guess
  class FakeIGraph:
    '''provides more pythonic interface to igraph'''
    def __init__(self):
        self.graph = igraph.Graph(0)
        self.g = self.graph
        
    def __repr__(self):
        return self.graph.summary()
        
    def add_connection(self, connection, technique=None):
        i1, i2 = connection.interface1, connection.interface2
        v1, v2 = self.g.vs(interface_eq=i1), self.g.vs(interface_eq=i2)
        assert len(v1)==1 and len(v2)==1, 'There Can Only Be One!!!'
        v1, v2 = v1[0], v2[0] #we want the vertex, not the VertexSeq
        e = self.new_edge(v1, v2)
        e['technique'] = technique
        
    def add_part(self, part):
        vp = self.new_vertex()
        vp['part'] = part
        vp['label'] = str(part.name)
        vp['fontcolor'] = 'saddlebrown'
        if hasattr(part, 'ldraw'): #FIXME should be 'icon'
            vp['image'] = package_file(part.package.name, str(part.ldraw)+'.png').name #abs path
            vp['shape'] = 'none' #otherwise it will draw a circle around the icon
        else:
            vp['image'] = ''
            vp['shape'] = 'box'
        for i in part.interfaces:
            vi = self.new_vertex()
            vi['interface'] = i
            vi['label'] = str(part.interfaces.index(i))
            #vi['label'] = i.name
            vi['fontcolor'] = 'black'
            vi['shape'] = 'none'
            vi['image'] = 'none'
            self.g.add_edges((vp.index, vi.index))
            
    def del_part(self, part):
        vp = self.g.vs(part_eq=part)
        assert len(vp) == 1, "there should be exactly one of each part in the graph"
        for i in vp[0]['part'].interfaces:
            vi = self.g.vs(interface_eq=i)
            assert len(vi) == 1, "there should be exactly one of each interface in the graph"
            self.g.delete_vertices(vi)
        self.g.delete_vertices(vp)
        
    def new_edge(self, v1, v2):
        self.g.add_edges((v1.index, v2.index))
        return self.g.es[self.g.ecount()-1]
        
    def new_vertex(self):
        self.g.add_vertices(1)
        return self.g.vs[self.g.vcount()-1]
        
    def dict(self):
        #this ought to be a generator i guess
        rval = {}
        for i in self.g.vs():
            part = i.attributes()['part']
            if part is not None:
                rval.update({i.index : part}) #um, should i return the vertex instead of the part?
            interface = i.attributes()['interface']
            if interface is not None:
                rval.update({i.index : interface})
        return rval
except ImportError: 
    use_igraph=False
    class FakeIGraph:
        '''igraph is not installed'''
        def add_connection(self, conn): pass
        def add_part(self, part): pass
        def del_part(self, part): pass
        def dict(self): return {}

class Interface(FennObject):
    '''"units" should be what is being transmitted through the interface, not about the structure.
    a screw's head transmits a force (N), but not a pressure (N/m**2) because the m**2 is actually interface geometry. theinterface geometry is located at "point" (in mm for now, sorry) and rotated about its Z vector clockwise "rotation" degrees looking away from the origin. (TODO: verify) The geometry is then rotated such that its Z vector points along "orientation".
    
    An alternative way to specify geometry is with "point" and two vectors: "x_vec" and "y_vec".
    in both cases the mating trajectory is along the Z vector.'''
    yaml_tag = '!interface'
    converted = False
    def __init__(self, name=None, units=None, geometry=None, point=[0,0,0], orientation=[0,0,1], rotation=0, part=None, max_connections=1):
        self.name, self.units, self.geometry, self.part = name, units, geometry, part
        #self.point, self.orientation, self.rotation = point, orientation, rotation
        self.max_connections = max_connections
        self.connected = []
        self.identifier = None
        self.complement = None #should be overwritten for specific problem domains
    def is_busy(self):
        if len(self.connected) >= self.max_connections: return True
        else: return False
    def compatible(self, other):
        '''returns True if other is complementary. this method should probably get overwritten for specific problem domains.'''
        for t in self.setify(self.complement):
            if isinstance(other, t): return True
        return False
    def options(self, parts):
        '''what other interfaces can this interface connect to?'''
        parts = self.setify(parts) #yay sets!
        if self.part in parts: parts.remove(self.part) #unless it's really flexible
        rval = set()
        for part in parts:
            for i in part.interfaces:
                if i.compatible(self) and self.compatible(i):
                    if not i.is_busy() and not self.is_busy():
                        rval.add(Connection(self,i))
        return list(rval)     
    def __repr__(self):
        if self.part:
            part_name = self.part.name
        else: part_name = None
        return 'Interface("%s", part="%s")' % (self.name, part_name)

class Connection:
    '''a temporary scenario to see if we should connect these two interfaces'''
    def __init__(self, interface1, interface2):
        assert hasattr(interface1, 'connected')
        assert hasattr(interface2, 'connected')
        self.interface1 = interface1
        self.interface2 = interface2
        
    def connect(self, technique='connect', cgraph=None):
        '''make interface1 and interface2 aware of this connection; update interface2.part's transformation attribute.'''
        #add edge to connection graph
        if cgraph is not None:
            assert self.interface1.part in cgraph.dict().values()
            assert self.interface2.part in cgraph.dict().values()
            cgraph.add_connection(self, technique=technique)
        
        i1 = self.interface1.part.interfaces.index(self.interface1)
        i2 = self.interface2.part.interfaces.index(self.interface2)
        print "connecting %s's %s to %s's %s (brick1[%s] to brick2[%s])" %(self.interface1.part.name,  self.interface1.name, self.interface2.part.name, self.interface2.name,  i1, i2)
        self.interface1.connected.append(self) #.append(self.interface2)?
        self.interface2.connected.append(self)
        return
        
    def __repr__(self):
        return "%s(%s, %s)" % (self.__class__.__name__, self.interface1, self.interface2)
        
    def makes_sense(self): #should we include is_busy()?
        return self.interface1.compatible(self.interface2) and self.interface2.compatible(self.interface1)