Skip to content

Web -- mesh of edges

web

Web(points=None, edges=None, tracks=None, groups=None, options=None)

Bases: NMesh

set of bipoint edges, used to represent wires this definition is very close to the definition of Mesh, but with edges instead of triangles

Note

a Wire instance can contain non-connex geometries (like many separated outlines, called islands), or even non-linear meshing (like intersecting curves). In the purpose of part design, many functions may need more regular characteristics, so checking methods exists and it is up to the user to ensure the mesh do provide them when calling the demanding functions

Attributes:

Name Type Description
points

typedlist of vec3 for points

edges

typedlist of couples for edges, the couple is oriented (meanings of this depends on the usage)

tracks

typedlist of integers giving the group each line belong to

groups

custom information for each group

options

custom informations for the entire web

Source code in madcad/mesh/web.py
32
33
34
35
36
37
def __init__(self, points=None, edges=None, tracks=None, groups=None, options=None):
	self.points = ensure_typedlist(points, vec3)
	self.edges = ensure_typedlist(edges, uvec2)
	self.tracks = ensure_typedlist(tracks or typedlist.full(0, len(self.edges), 'I'), 'I')
	self.groups = groups if groups is not None else [None] * (max(self.tracks, default=-1)+1)
	self.options = options or {}
Special methods

__add__(other)

return a new mesh concatenating the faces and points of both meshes

Source code in madcad/mesh/web.py
39
40
41
42
43
44
45
46
47
48
49
50
51
def __add__(self, other):
	''' return a new mesh concatenating the faces and points of both meshes '''
	if isinstance(other, Web):
		r = Web(
			self.points if self.points is other.points else self.points[:],
			self.edges[:],
			self.tracks[:],
			self.groups if self.groups is other.groups else self.groups[:],
			)
		r.__iadd__(other)
		return r
	else:
		return NotImplemented

__iadd__(other)

append the faces and points of the other mesh

Source code in madcad/mesh/web.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def __iadd__(self, other):
	''' append the faces and points of the other mesh '''
	if isinstance(other, Web):
		if self.points is other.points:
			self.edges.extend(other.edges)
		else:
			lp = len(self.points)
			self.points.extend(other.points)
			self.edges.extend(e+lp  for e in other.edges)
		if self.groups is other.groups:
			self.tracks.extend(other.tracks)
		else:
			lt = len(self.groups)
			self.groups.extend(other.groups)
			self.tracks.extend(t+lt   for t in other.tracks)
		return self
	else:
		raise NotImplementedError()
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 points below the specified distance, or below the precision return a dictionary of points remapping {src index: dst index}

O(n) implementation thanks to hashing

Source code in madcad/mesh/container.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def mergeclose(self, limit=None) -> dict:
	''' Merge points below the specified distance, or below the precision 
		return a dictionary of points remapping  {src index: dst index}

		O(n) implementation thanks to hashing
	'''
	if limit is None:	limit = self.precision()

	merges = {}
	points = PointSet(limit)
	for i in range(len(self.points)):
		used = points.add(self.points[i])
		if used != i:	merges[i] = used
	self.points = points.points
	self.mergepoints(merges)
	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/web.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def mergepoints(self, merges) -> Web:
	''' 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 = uvec2(
			merges.get(f[0], f[0]),
			merges.get(f[1], f[1]),
			)
		if not f[0] == f[1]:
			self.edges[j] = f
			self.tracks[j] = self.tracks[i]
			j += 1
	del self.edges[j:]
	del self.tracks[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 faces, return the reindex list. if used is provided, these points will be removed without usage verification

return a table of the reindex made

Source code in madcad/mesh/web.py
86
87
88
89
90
91
92
93
def strippoints(self) -> list:
	''' remove points that are used by no faces, return the reindex list.
		if used is provided, these points will be removed without usage verification

		return a table of the reindex made
	'''
	self.points, self.edges, reindex = striplist(self.points, self.edges)
	return reindex

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()

check that the internal data references are good (indices and list lengths)

Source code in madcad/mesh/web.py
129
130
131
132
133
134
135
136
137
138
139
140
141
def check(self):
	''' check that the internal data references are good (indices and list lengths) '''
	if not (isinstance(self.points, typedlist) and self.points.dtype == vec3):	raise MeshError("points must be a typedlist(dtype=vec3)")
	if not (isinstance(self.edges, typedlist) and self.edges.dtype == uvec2): 	raise MeshError("edges must be in a typedlist(dtype=uvec2)")
	if not (isinstance(self.tracks, typedlist) and self.tracks.dtype == 'I'): 	raise MeshError("tracks must be in a typedlist(dtype='I')")
	l = len(self.points)
	for line in self.edges:
		for p in line:
			if p >= l:	raise MeshError("some indices are greater than the number of points", line, l)
			if p < 0:	raise MeshError("point indices must be positive", line)
		if line[0] == line[1]:	raise MeshError("some edges use the same point multiple times", line)
	if len(self.edges) != len(self.tracks):	raise MeshError("tracks list doesn't match edge list length")
	if max(self.tracks, default=-1) >= len(self.groups): raise MeshError("some line group indices are greater than the number of groups", max(self.tracks, default=-1), 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

isline()

true if each point is used at most 2 times by edges

Source code in madcad/mesh/web.py
116
117
118
119
120
121
122
123
def isline(self):
	''' true if each point is used at most 2 times by edges '''
	reached = typedlist.full(0, len(self.points), 'I')
	for line in self.edges:
		for p in line:	reached[p] += 1
	for r in reached:
		if r > 2:	return False
	return True

isloop()

true if the wire form a loop

Source code in madcad/mesh/web.py
125
126
127
def isloop(self):
	''' true if the wire form a loop '''
	return len(self.extremities_oriented()) == 0
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/web.py
147
148
149
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/web.py
151
152
153
154
def edgenear(self, point: vec3) -> int:
	''' return the index of the closest edge to the given point '''
	return min( range(len(self.edges)),
				key=lambda i: distance_pe(point, self.edgepoints(i)) )

group(quals)

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/web.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def group(self, quals) -> Web:
	''' 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 ...>
	'''
	edges = typedlist(dtype=uvec2)
	tracks = typedlist(dtype='I')
	for i in self.qualified_indices(quals):
		edges.append(self.edges[i])
		tracks.append(self.tracks[i])
	return Web(self.points, edges, tracks, self.groups)

replace(mesh, groups=None)

replace the given groups by the given mesh. If groups is not specified, it will take the matching groups (with same index) in the current mesh

Source code in madcad/mesh/web.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
def replace(self, mesh, groups=None) -> Web:
	''' replace the given groups by the given mesh.
		If groups is not specified, it will take the matching groups (with same index) in the current mesh
	'''
	if groups:
		groups = set(self.qualified_groups(groups))
	else:
		groups = set(mesh.tracks)
	j = 0
	for i,t in enumerate(self.tracks):
		if t not in groups:
			self.edges[j] = self.edges[i]
			self.tracks[j] = t
			j += 1
	del self.edges[j:]
	del self.tracks[j:]
	self += mesh
	return self

qualify(*quals, select=None, replace=False)

Set a new qualifier for the given groups

Parameters:

Name Type Description Default
quals

the qualifiers to enable for the selected mesh groups

()
select iterable

if specified, only the groups having all those qualifiers will be added the new qualifiers

None
replace bool

if True, the qualifiers in select will be removed

False

Examples:

>>> pool = meshb.qualify('part-a') + meshb.qualify('part-b')
>>> set(meshb.faces) == set(pool.group('part-b').faces)
True
>>> chamfer(mesh, ...).qualify('my-group', select='chamfer', replace=True)
>>> mesh.group('my-group')
<Mesh ...>
Source code in madcad/mesh/container.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def qualify(self, *quals, select=None, replace=False) -> NMesh:
	''' Set a new qualifier for the given groups 

	Parameters:
		quals:				the qualifiers to enable for the selected mesh groups
		select (iterable):	if specified, only the groups having all those qualifiers will be added the new qualifiers
		replace (bool):		if True, the qualifiers in select will be removed

	Examples:
		>>> pool = meshb.qualify('part-a') + meshb.qualify('part-b')
		>>> set(meshb.faces) == set(pool.group('part-b').faces)
		True

		>>> chamfer(mesh, ...).qualify('my-group', select='chamfer', replace=True)
		>>> mesh.group('my-group')
		<Mesh ...>
	'''
	if select is None:	it = range(len(self.groups))
	else:				it = self.qualified_groups(select)
	for i in it:
		group = self.groups[i]
		if group is None:
			self.groups[i] = group = {}
		if not hasattr(group, '__setitem__'):
			raise TypeError('cannot qualify a group that is not a dictionary')
		if replace:
			for key in select:
				if key in group:
					del group[key]
		for key in quals:
			group[key] = None

	return self

qualified_indices(quals)

Yield the faces indices when their associated group are matching the requirements

Source code in madcad/mesh/container.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def qualified_indices(self, quals):
	''' Yield the faces indices when their associated group are matching the requirements '''
	if isinstance(quals, int):
		yield from self.qualified_indices({quals})
	elif isinstance(quals, str):
		yield from self.qualified_indices([quals])

	elif not quals:
		yield from range(len(self.tracks))
	elif isinstance(next(iter(quals)), int):
		if not isinstance(quals, set):	quals = set(quals)
		for i,t in enumerate(self.tracks):
			if t in quals:
				yield i
	else:
		inclusive = None in quals
		seen = {}
		for i,t in enumerate(self.tracks):
			if t in seen:
				ok = seen[t]
			elif self.groups[t]:
				ok = seen[t] = all(k in self.groups[t]   for k in quals)
			else:
				ok = inclusive
			if ok:
				yield i

qualified_groups(quals)

Yield the groups indices when they are matching the requirements

Source code in madcad/mesh/container.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
def qualified_groups(self, quals):
	''' Yield the groups indices when they are matching the requirements '''
	if isinstance(quals, int):
		yield quals
	elif isinstance(quals, str):
		yield from self.qualified_groups([quals])

	elif not quals:
		yield from range(len(self.groups))
	elif isinstance(next(iter(quals)), int):
		yield from quals
	else:
		inclusive = None in quals
		for i, group in enumerate(self.groups):
			if group and all(key in group   for key in quals) or inclusive:
				yield i
Extraction methods

maxnum()

Maximum numeric value of the mesh, use this to get an hint on its size or to evaluate the numeric precision

Source code in madcad/mesh/container.py
216
217
218
219
220
221
222
223
def maxnum(self) -> float:
	''' Maximum numeric value of the mesh, use this to get an hint on its size or to evaluate the numeric precision '''
	m = 0
	for p in self.points:
		for v in p:
			a = abs(v)
			if a > m:	m = a
	return m

precision(propag=3)

Numeric coordinate precision of operations on this mesh, allowed by the floating point precision

Source code in madcad/mesh/container.py
225
226
227
def precision(self, propag=3) -> float:
	''' Numeric coordinate precision of operations on this mesh, allowed by the floating point precision '''
	return self.maxnum() * NUMPREC * (2**propag)

length()

total length of edges

Source code in madcad/mesh/web.py
375
376
377
378
379
380
def length(self) -> float:
	''' total length of edges '''
	s = 0
	for e in self.edges:
		s += distance(self.points[e[1]], self.points[e[0]])
	return s

surface()

return the surface enclosed by the web if planar and is composed of loops (else it has no meaning)

Source code in madcad/mesh/web.py
382
383
384
385
386
387
388
def surface(self) -> float:
	''' return the surface enclosed by the web if planar and is composed of loops (else it has no meaning) '''
	o = self.barycenter()
	s = vec3(0)
	for e in self.edges:
		s += cross(self.points[e[1]] - o, self.points[e[0]] - o)
	return length(s)

barycenter()

curve barycenter of the mesh

Source code in madcad/mesh/web.py
390
391
392
393
394
395
396
397
398
399
400
def barycenter(self) -> vec3:
	''' curve barycenter of the mesh '''
	if not self.edges:	return vec3(0)
	acc = vec3(0)
	tot = 0
	for e in self.edges:
		a,b = self.edgepoints(e)
		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)

usepointat(point, neigh=NUMPREC)

Return the index of the first point in the mesh at the location. If none is found, insert it and return the index

Source code in madcad/mesh/container.py
229
230
231
232
233
234
235
def usepointat(self, point, neigh=NUMPREC) -> int:
	''' Return the index of the first point in the mesh at the location. If none is found, insert it and return the index '''
	i = self.pointat(point, neigh=neigh)
	if i is None:
		i = len(self.points)
		self.points.append(point)
	return i

edgepoints(e)

tuple of the points for edge e

the edge can be given using its index in self.edges or using a tuple of point idinces

Source code in madcad/mesh/web.py
206
207
208
209
210
211
212
def edgepoints(self, e) -> tuple:
	''' tuple of the points for edge `e`

		the edge can be given using its index in `self.edges` or using a tuple of point idinces
	'''
	if isinstance(e, Integral):	e = self.edges[e]
	return self.points[e[0]], self.points[e[1]]

edgedirection(e)

direction of edge e

the edge can be given using its index in self.edges or using a tuple of point idinces

Source code in madcad/mesh/web.py
214
215
216
217
218
219
220
def edgedirection(self, e) -> vec3:
	''' direction of edge e

		the edge can be given using its index in `self.edges` or using a tuple of point idinces
	'''
	if isinstance(e, Integral):	e = self.edges[e]
	return normalize(self.points[e[1]] - self.points[e[0]])

extremities()

return the points that are ending arcs 1D equivalent of Mesh.outlines()

Source code in madcad/mesh/web.py
266
267
268
269
270
def extremities(self) -> Wire:
	''' return the points that are ending arcs
		1D equivalent of Mesh.outlines()
	'''
	return Wire(self.points, typedlist(self.extremities_unoriented, dtype='I'))

groupextremities()

return the extremities of each group. 1D equivalent of Mesh.groupoutlines()

On a frontier between multiple groups, there is as many points as groups, each associated to a group.

Source code in madcad/mesh/web.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
def groupextremities(self) -> 'Wire':
	''' return the extremities of each group.
		1D equivalent of Mesh.groupoutlines()

		On a frontier between multiple groups, there is as many points as groups, each associated to a group.
	'''
	indices = typedlist(dtype='I')
	tracks = []
	tmp = {}
	# insert points belonging to different groups
	for i,edge in enumerate(self.edges):
		track = self.tracks[i]
		for p in edge:
			if p in tmp:
				if tmp[p] != track:
					indices.append(p)
					tracks.append(track)
				del tmp[p]
			else:
				tmp[p] = track
	indices.extend(tmp.keys())
	tracks.extend(tmp.values())
	return Wire(self.points, indices, tracks, self.groups)

frontiers(*args)

return a Wire of points that split the given groups appart.

    The arguments are groups indices or lists of group qualifiers (as set in `qualify()`). If there is one only argument it is considered as as list of arguments.

    - if no argument is given, then return the frontiers between every groups
    - to include the groups extremities that are on the group border but not at the frontier with an other group, add `None` to the group set

Examples:

>>> w = Web([...], [uvec2(0,1), uvec2(1,2)], [0, 1], [...])
>>> w.frontiers(0,1).indices
[1]
>>> # equivalent to
>>> w.frontiers({0,1}).indices
[1]
>>> w.frontiers(0,None).indices
[0]
Source code in madcad/mesh/web.py
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
def frontiers(self, *args) -> 'Wire':
	''' return a Wire of points that split the given groups appart.

		The arguments are groups indices or lists of group qualifiers (as set in `qualify()`). If there is one only argument it is considered as as list of arguments.

		- if no argument is given, then return the frontiers between every groups
		- to include the groups extremities that are on the group border but not at the frontier with an other group, add `None` to the group set

	Examples:
		>>> w = Web([...], [uvec2(0,1), uvec2(1,2)], [0, 1], [...])
		>>> w.frontiers(0,1).indices
		[1]

		>>> # equivalent to
		>>> w.frontiers({0,1}).indices
		[1]

		>>> w.frontiers(0,None).indices
		[0]
	'''
	if args:
		if len(args) == 1 and hasattr(args[0], '__iter__'):
			args = args[0]
		groups = set()
		for arg in args:
			if arg is None:		groups.add(None)
			else:				groups.update(self.qualified_groups(arg))
	else:
		groups = None

	indices = typedlist(dtype='I')
	tracks = typedlist(dtype='I')
	couples = OrderedDict()
	belong = {}
	for i,edge in enumerate(self.edges):
		track = self.tracks[i]
		if groups is None or None in groups or track in groups:
			for p in edge:
				if p in belong:
					if belong[p] != track and (groups is None or track in groups and belong[p] in groups):
						g = edgekey(belong[p],track)
						indices.append(p)
						tracks.append(couples.setdefault(g, len(couples)))
					del belong[p]
				else:
					belong[p] = track
	if groups and None in groups:
		for p, i in belong.items():
			if i in groups:
				g = (i,None)
				indices.append(p)
				tracks.append(couples.setdefault(g, len(couples)))
	return Wire(self.points, indices, tracks, list(couples))

arcs()

return the contiguous portions of this web

Source code in madcad/mesh/web.py
449
450
451
452
def arcs(self) -> '[Wire]':
	''' return the contiguous portions of this web '''
	return [	Wire(self.points, typedlist(loop, dtype=uvec2))
				for loop in suites(self.edges, oriented=False)]

islands()

return the unconnected parts of the mesh as several meshes

Source code in madcad/mesh/web.py
435
436
437
438
439
440
441
442
443
444
445
446
447
def islands(self) -> '[Web]':
	''' return the unconnected parts of the mesh as several meshes '''
	islands = []
	island = Web(points=self.points, groups=self.groups)
	for edge, group in self.assignislands():
		if group > len(islands):
			islands.append(island)
			island = Web(points=self.points, groups=self.groups)
		else:
			island.edges.append(self.edges[edge])
			island.tracks.append(self.tracks[edge])
	islands.append(island)
	return islands

groupislands()

return the same web but with a new group each island

Source code in madcad/mesh/web.py
427
428
429
430
431
432
433
def groupislands(self) -> 'Web':
	''' return the same web but with a new group each island '''
	tracks = typedlist.full(0, len(self.edges), dtype='I')
	track = 0
	for edge, track in self.assignislands():
		tracks[edge] = track
	return Web(self.points, self.edges, tracks, [None]*(track+1))

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/web.py
229
230
231
232
233
234
235
236
237
238
def segmented(self, group=None) -> 'Web':
	''' 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 Web(self.points, self.edges,
				typedlist(range(len(self.edges)), dtype='I'),
				[group]*len(self.edges),
				self.options,
				)

flip()

reverse direction of all edges

Source code in madcad/mesh/web.py
222
223
224
225
226
227
def flip(self) -> 'Web':
	''' reverse direction of all edges '''
	return Web(	self.points,
				typedlist((uvec2(b,a)  for a,b in self.edges), dtype=uvec2),
				self.tracks,
				self.groups)