Skip to content

Wire -- mesh of points, or suite of points

wire

Wire(points=None, indices=None, tracks=None, groups=None, options=None)

Bases: NMesh

This class defines a mesh of points, used for two purposes:

  • curves, wires, paths, storing the ordered suite of point indices
  • cloud points, points extracted from other mesh, storing the unordered points indices

Most of the methods of Wire are intended for the first use case.

conventions:

  • A curve is considered closed (or to be a loop) when its final index is the same as the first.
  • tracks are matching indices, giving a group each index. But for curves, track[i] gives a group for edge (indices[i], indices[i+1])

Attributes:

Name Type Description
points

points buffer

indices

indices of the line's points in the buffer

tracks

group index for each point in indices it can be used to associate groups to points or to edges (if to edges, then take care to still have as many track as indices)

groups

data associated to each point (or edge)

options

custom informations for the entire wire

Source code in madcad/mesh/wire.py
36
37
38
39
40
41
def __init__(self, points=None, indices=None, tracks=None, groups=None, options=None):
	self.points = ensure_typedlist(points, vec3)
	self.indices = ensure_typedlist(indices if indices is not None else range(len(self.points)), 'I')
	self.tracks = tracks if tracks is not None else None
	self.groups = groups if groups is not None else [None]
	self.options = options or {}
Special methods

__add__(other)

append the indices and points of the other wire

Source code in madcad/mesh/wire.py
54
55
56
57
58
59
60
61
62
63
64
65
66
def __add__(self, other):
	''' append the indices and points of the other wire '''
	if isinstance(other, Wire):
		r = Wire(
			self.points if self.points is other.points else self.points[:],
			self.indices[:],
			self.tracks[:] if self.tracks else None,
			self.groups if self.groups is other.groups else self.groups[:],
			)
		r.__iadd__(other)
		return r
	else:
		return NotImplemented

__iadd__(other)

append the indices and points of the other wire

Source code in madcad/mesh/wire.py
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
def __iadd__(self, other):
	''' append the indices and points of the other wire '''
	if isinstance(other, Wire):
		li = len(self.indices)

		if self.points is other.points:
			self.indices.extend(other.indices)
		else:
			lp = len(self.points)
			self.points.extend(other.points)
			self.indices.extend(i+lp  for i in other.indices)

		if self.groups is other.groups:
			if self.tracks or other.tracks:
				if not self.tracks:
					self.tracks = typedlist.full(0, li, 'I')
				self.tracks.extend(other.tracks or typedlist.full(0, len(other.indices), 'I'))
		else:
			lg = len(self.groups)
			self.groups.extend(other.groups)
			if not self.tracks:
				self.tracks = typedlist.full(0, li, 'I')
			if other.tracks:
				self.tracks.extend(track+lg	for track in other.tracks)
			else:
				self.tracks.extend(typedlist.full(lg, len(other.indices), 'I'))
		return self
	else:
		return NotImplemented

__len__()

Source code in madcad/mesh/wire.py
43
def __len__(self):	return len(self.indices)

__iter__()

Source code in madcad/mesh/wire.py
44
def __iter__(self):	return (self.points[i] for i in self.indices)

__getitem__(i)

return the ith point of the wire, useful to use the wire in a same way as list of points

equivalent to self.points[self.indices[i]]

Source code in madcad/mesh/wire.py
45
46
47
48
49
50
51
52
def __getitem__(self, i):
	''' return the ith point of the wire, useful to use the wire in a same way as list of points

		equivalent to `self.points[self.indices[i]]`
	'''
	if isinstance(i, Integral):		return self.points[self.indices[i]]
	elif isinstance(i, slice):	return typedlist((self.points[j] for j in self.indices[i]), dtype=vec3)
	else:						raise TypeError('item index must be int or slice')
Data management

own(**kwargs)

Return a copy of the current mesh, which attributes are referencing the original data or duplicates if demanded

Examples:

>>> b = a.own(points=True, faces=False)
>>> b.points is a.points
False
>>> b.faces is a.faces
True
Source code in madcad/mesh/container.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def own(self, **kwargs) -> NMesh:
	''' Return a copy of the current mesh, which attributes are referencing the original data or duplicates if demanded

	Examples:
		>>> b = a.own(points=True, faces=False)
		>>> b.points is a.points
		False
		>>> b.faces is a.faces
		True
	'''
	new = copy(self)
	for name, required in kwargs.items():
		if required:
			setattr(new, name, deepcopy(getattr(self, name)))
	return new

option(**kwargs)

Update the internal options with the given dictionary and the keywords arguments. This is only a shortcut to set options in a method style.

Source code in madcad/mesh/container.py
43
44
45
46
47
48
def option(self, **kwargs) -> NMesh:
	''' Update the internal options with the given dictionary and the keywords arguments.
		This is only a shortcut to set options in a method style.
	'''
	self.options.update(kwargs)
	return self

transform(trans)

Apply the transform to the points of the mesh, returning the new transformed mesh

Source code in madcad/mesh/container.py
50
51
52
53
54
55
def transform(self, trans) -> NMesh:
	''' Apply the transform to the points of the mesh, returning the new transformed mesh'''
	trans = transformer(trans)
	transformed = copy(self)
	transformed.points = typedlist((trans(p) for p in self.points), dtype=vec3)
	return transformed

mergeclose(limit=None)

merge close points ONLY WHEN they are already linked by an edge. the meaning of this method is different than Web.mergeclose()

Source code in madcad/mesh/wire.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def mergeclose(self, limit=None):
	''' merge close points ONLY WHEN they are already linked by an edge.
		the meaning of this method is different than `Web.mergeclose()`
	'''
	if limit is None:	limit = self.precision()
	limit *= limit
	merges = {}
	for i in reversed(range(1, len(self.indices))):
		if distance2(self[i-1], self[i]) <= limit:
			merges[self.indices[i]] = self.indices[i-1]
			self.indices.pop(i)
			if self.tracks:
				self.tracks.pop(i-1)
	if distance2(self[0], self[-1]) < limit:
		merges[self.indices[-1]] = self.indices[0]
		self.indices[-1] = self.indices[0]
		if self.tracks:
			self.tracks[-1] = self.tracks[0]
	return merges

mergepoints(merges)

merge points with the merge dictionnary {src index: dst index} merged points are not removed from the buffer.

Source code in madcad/mesh/wire.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def mergepoints(self, merges) -> Wire:
	''' merge points with the merge dictionnary {src index: dst index}
		merged points are not removed from the buffer.
	'''
	j = 0
	for i,f in enumerate(self.edges):
		f = merges.get(f, f)
		if not f == self.edges[i-1]:
			self.indices[i] = f
			if self.tracks:
				self.tracks[j] = self.tracks[i]
			j += 1
	del self.indices[j:]
	return self

mergegroups(defs=None, merges=None)

Merge the groups according to the merge dictionary The new groups associated can be specified with defs The former unused groups are not removed from the buffer and the new ones are appended

If merges is not provided, all groups are merged, and defs is the data associated to the only group after the merge

Source code in madcad/mesh/container.py
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def mergegroups(self, defs=None, merges=None) -> NMesh:
	''' Merge the groups according to the merge dictionary
		The new groups associated can be specified with defs
		The former unused groups are not removed from the buffer and the new ones are appended

		If merges is not provided, all groups are merged, and defs is the data associated to the only group after the merge
	'''
	if merges is None:	
		self.groups = [defs]
		self.tracks = typedlist.full(0, len(self.tracks), 'I')
	else:
		if defs:
			l = len(self.groups)
			self.groups.extend(defs)
		else:
			l = 0
		for i,t in enumerate(self.tracks):
			if t in merges:
				self.tracks[i] = merges[t]+l
	return self

strippoints()

remove points that are used by no edge if used is provided, these points will be removed without usage verification

no reindex table is returned as its generation costs more than the stripping operation

Source code in madcad/mesh/wire.py
112
113
114
115
116
117
118
119
120
121
122
def strippoints(self):
	''' remove points that are used by no edge
		if used is provided, these points will be removed without usage verification

		no reindex table is returned as its generation costs more than the stripping operation
	'''
	self.points = typedlist((self.points[i]	for i in self.indices), dtype=vec3)
	self.indices = typedlist(range(len(self.points)), dtype='I')
	if self.points[-1] == self.points[0]:
		self.points.pop()
		self.indices[-1] = 0

stripgroups()

Remove groups that are used by no faces. return the reindex list.

Source code in madcad/mesh/container.py
74
75
76
77
def stripgroups(self) -> list:
	''' Remove groups that are used by no faces. return the reindex list. '''
	self.groups, self.tracks, reindex = striplist(self.groups, self.tracks)
	return reindex

finish()

Finish and clean the mesh note that this operation can cost as much as other transformation operation job done

  • mergeclose
  • strippoints
  • stripgroups
  • check
Source code in madcad/mesh/container.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
	def finish(self) -> NMesh:
		''' Finish and clean the mesh 
			note that this operation can cost as much as other transformation operation
			job done

            - mergeclose
            - strippoints
            - stripgroups
            - check
		'''
		self.mergeclose()
		self.strippoints()
		self.stripgroups()
		self.check()
		return self
Mesh checks

check()

raise if the internal data are not consistent

Source code in madcad/mesh/wire.py
165
166
167
168
169
170
171
172
173
174
175
def check(self):
	''' raise if the internal data are not consistent '''
	if not (isinstance(self.points, typedlist) and self.points.dtype == vec3):	raise MeshError("points must be a typedlist(dtype=vec3)")
	if not (isinstance(self.indices, typedlist) and self.indices.dtype == 'I'): 	raise MeshError("indices must be a typedlist(dtype='I')")
	if self.tracks and not (isinstance(self.tracks, typedlist) and self.tracks.dtype == 'I'): 	raise MeshError("tracks must be a typedlist(dtype='I')")
	l = len(self.points)
	for i in self.indices:
		if i >= l:	raise MeshError("some indices are greater than the number of points", i, l)
	if self.tracks:
		if len(self.indices) != len(self.tracks):	raise MeshError("tracks list doesn't match indices list length")
		if max(self.tracks) >= len(self.groups):	raise MeshError("some tracks are greater than the number of groups", max(self.tracks), len(self.groups))

isvalid()

Return true if the internal data is consistent (all indices referes to actual points and groups)

Source code in madcad/mesh/container.py
118
119
120
121
122
def isvalid(self):
	''' Return true if the internal data is consistent (all indices referes to actual points and groups) '''
	try:				self.check()
	except MeshError:	return False
	else:				return True
Selection methods

pointnear(point)

Return the nearest point the the given location

Source code in madcad/mesh/container.py
131
132
133
134
def pointnear(self, point: vec3) -> int:
	''' Return the nearest point the the given location '''
	return min(	range(len(self.points)), 
				lambda i: distance(self.points[i], point))

pointat(point, neigh=NUMPREC)

Return the index of the first point at the given location, or None

Source code in madcad/mesh/container.py
126
127
128
129
def pointat(self, point: vec3, neigh=NUMPREC) -> int:
	''' Return the index of the first point at the given location, or None '''
	for i,p in enumerate(self.points):
		if distance(p,point) <= neigh:	return i

groupnear(point)

return group id if the edge the closest to the given point

Source code in madcad/mesh/wire.py
180
181
182
def groupnear(self, point: vec3) -> int:
	''' return group id if the edge the closest to the given point '''
	return self.tracks[self.edgenear(point)]

edgenear(point)

return the index of the closest edge to the given point

Source code in madcad/mesh/wire.py
184
185
186
187
def edgenear(self, point: vec3) -> int:
	''' return the index of the closest edge to the given point '''
	return min( range(len(self.indices)-1),
				key=lambda i: distance_pe(point, self.edgepoints(i)) )

group(groups)

extract a part of the mesh corresponding to the designated groups.

    Groups can be be given in either the following ways:
            - a set of group indices

                    This can be useful to combine with other functions. However it can be difficult for a user script to keep track of which index correspond to which created group

            - an iterable of group qualifiers

                    This is the best way to designate groups, and is meant to be used in combination with `self.qual()`.
                    This mode selects every group having all the input qualifiers

Examples:

>>> # create a mesh with only the given groups
>>> mesh.group({1, 3, 8, 9})
<Mesh ...>
>>> # create a mesh with all the groups having the following qualifiers
>>> mesh.group(['extrusion', 'arc'])
<Mesh ...>
Source code in madcad/mesh/wire.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
def group(self, groups):
	''' extract a part of the mesh corresponding to the designated groups.

		Groups can be be given in either the following ways:
			- a set of group indices

				This can be useful to combine with other functions. However it can be difficult for a user script to keep track of which index correspond to which created group

			- an iterable of group qualifiers

				This is the best way to designate groups, and is meant to be used in combination with `self.qual()`.
				This mode selects every group having all the input qualifiers

	Examples:
		>>> # create a mesh with only the given groups
		>>> mesh.group({1, 3, 8, 9})
		<Mesh ...>

		>>> # create a mesh with all the groups having the following qualifiers
		>>> mesh.group(['extrusion', 'arc'])
		<Mesh ...>
	'''
	if not self.tracks:
		return self
	if isinstance(groups, set):			pass
	elif hasattr(groups, '__iter__'):	groups = set(groups)
	else:								groups = (groups,)
	indices = typedlist(dtype=self.indices.dtype)
	tracks = typedlist(dtype=self.tracks.dtype)
	for i,t in zip(self.indices, self.tracks):
		if t in groups:
			indices.append(i)
			tracks.append(t)
	return Wire(self.points, indices, tracks, self.groups, self.options)
Extraction methods

length()

curviform length of the wire (sum of all edges length)

Source code in madcad/mesh/wire.py
247
248
249
250
251
252
def length(self) -> float:
	''' curviform length of the wire (sum of all edges length) '''
	s = 0
	for i in range(1,len(self.indices)):
		s += distance(self[i-1], self[i])
	return s

surface()

return the surface enclosed by the web if planar and is a loop (else it has no meaning)

Source code in madcad/mesh/wire.py
254
255
256
257
258
259
260
def surface(self) -> float:
	''' return the surface enclosed by the web if planar and is a loop (else it has no meaning) '''
	o = self.barycenter()
	s = vec3(0)
	for i in range(1, len(self)):
		s += cross(self[i-1]-o, self[i]-o)
	return length(s)

barycenter()

curve barycenter

Source code in madcad/mesh/wire.py
262
263
264
265
266
267
268
269
270
271
272
273
def barycenter(self) -> vec3:
	''' curve barycenter '''
	if not self.indices:	return vec3(0)
	if len(self.indices) == 1:	return self.points[self.indices[0]]
	acc = vec3(0)
	tot = 0
	for i in range(1,len(self)):
		a,b = self[i-1], self[i]
		weight = distance(a,b)
		tot += weight
		acc += weight*(a+b)
	return acc / (2*tot)

barycenter_points()

Barycenter of points used

Source code in madcad/mesh/container.py
249
250
251
def barycenter_points(self) -> vec3:
	''' Barycenter of points used '''
	return sum(self.points[i]	for i in self.indices) / len(self.indices)

box()

Return the extreme coordinates of the mesh (vec3, vec3)

Source code in madcad/mesh/container.py
237
238
239
240
241
242
243
244
245
246
247
def box(self) -> Box:
	''' Return the extreme coordinates of the mesh (vec3, vec3) '''
	if not self.points:		return Box()
	p = next(filter(isfinite, self.points))
	max = deepcopy(p)
	min = deepcopy(p)
	for pt in self.points:
		for i in range(3):
			if   pt[i] < min[i]:	min[i] = pt[i]
			elif pt[i] > max[i]:	max[i] = pt[i]
	return Box(min, max)

normal()

return an approximated normal to the curve as if it was the outline of a flat surface. if this is not a loop the result is undefined.

Source code in madcad/mesh/wire.py
275
276
277
278
279
280
281
282
283
def normal(self) -> vec3:
	''' return an approximated normal to the curve as if it was the outline of a flat surface.
		if this is not a loop the result is undefined.
	'''
	area = vec3(0)
	c = self.barycenter()
	for i in range(1, len(self)):
		area += cross(self[i-1]-c, self[i]-c)
	return normalize(area)

edgepoints(e)

shorthand to the tuple of points forming edge e

Source code in madcad/mesh/wire.py
226
227
228
229
230
def edgepoints(self, e) -> tuple:
	''' shorthand to the tuple of points forming edge e '''
	if isinstance(e, Integral):
		e = self.edge(e)
	return self.points[e[0]], self.points[e[1]]

edgedirection(e)

direction of edge e

Source code in madcad/mesh/wire.py
232
233
234
235
236
def edgedirection(self, e) -> vec3:
	''' direction of edge e '''
	if isinstance(e, Integral):
		e = self.edge(e)
	return normalize(self.points[e[1]] - self.points[e[0]])

edge(i)

ith edge of the wire

Source code in madcad/mesh/wire.py
239
240
241
def edge(self, i) -> uvec2:
	''' ith edge of the wire '''
	return uvec2(self.indices[i], self.indices[i+1])

edges()

list of successive edges of the wire

Source code in madcad/mesh/wire.py
243
244
245
def edges(self) -> typedlist:
	''' list of successive edges of the wire '''
	return typedlist((self.edge(i)  for i in range(len(self.indices)-1)), dtype=uvec2)

vertexnormals(loop=False)

return the opposed direction to the curvature in each point this is called normal because it would be the normal to a surface whose section would be that wire

Source code in madcad/mesh/wire.py
285
286
287
288
289
290
291
292
293
294
def vertexnormals(self, loop=False):
	''' return the opposed direction to the curvature in each point
		this is called normal because it would be the normal to a surface whose section would be that wire
	'''
	normals = typedlist.full(vec3(0), len(self.indices))
	for i in range(len(self.indices)):
		a,b,c = self.indices[i-2], self.indices[i-1], self.indices[i]
		normals[i-1] = normalize(normalize(self.points[b]-self.points[a]) + normalize(self.points[b]-self.points[c]))
	self._make_loop_consistency(normals, loop)
	return normals

tangents(loop=False)

return approximated tangents to the curve as if it was a surface section. if this is not a loop the result is undefined.

Source code in madcad/mesh/wire.py
296
297
298
299
300
301
302
303
304
305
def tangents(self, loop=False):
	''' return approximated tangents to the curve as if it was a surface section.
		if this is not a loop the result is undefined.
	'''
	tangents = typedlist.full(vec3(0), len(self.indices))
	for i in range(len(self.indices)):
		a,b,c = self.indices[i-2], self.indices[i-1], self.indices[i]
		tangents[i-1] = normalize(cross(self.points[b]-self.points[a], self.points[b]-self.points[c]))
	self._make_loop_consistency(tangents, loop)
	return tangents

flip()

reverse direction of all edges

Source code in madcad/mesh/wire.py
332
333
334
335
336
337
338
339
340
341
342
def flip(self) -> 'Wire':
	''' reverse direction of all edges '''
	indices = deepcopy(self.indices)
	indices.reverse()
	if self.tracks:
		tracks = deepcopy(self.tracks[:-1])
		tracks.reverse()
		tracks.append(self.tracks[-1])
	else:
		tracks = None
	return Wire(self.points, indices, tracks, self.groups, self.options)

close()

make a loop of the wire by appending its first point to its end

Source code in madcad/mesh/wire.py
344
345
346
347
348
349
350
def close(self) -> Wire:
	''' make a loop of the wire by appending its first point to its end '''
	if not self.isclosed():
		self.indices.append(self.indices[0])
		if self.tracks:
			self.tracks.append(self.tracks[0])
	return self

segmented(group=None)

return a copy of the mesh with a group each edge

if group is specified, it will be the new definition put in each groups

Source code in madcad/mesh/wire.py
360
361
362
363
364
365
366
367
368
369
370
def segmented(self, group=None) -> 'Wire':
	''' return a copy of the mesh with a group each edge

		if group is specified, it will be the new definition put in each groups
	'''
	return Wire(self.points,
				self.indices,
				typedlist(range(len(self.indices)), dtype='I'),
				[group]*len(self.indices),
				self.options,
				)