Skip to content

primitives -- 3D Primitives for Wire generation

Definition of 3D primitive objects

Primitives are parameterized objects, that can be baked into a mesh/web/wire object. A primitive object must have the following signature:

class SomePrimitive:
        # method baking the primitive in some general-purpose 3D object
        def mesh(self) -> Mesh/Web/Wire:
                ...

        # for the solver
        # primitive attributes the solver has to consider as variables or variable container
        slvvars = 'fields', 'for', 'solver', 'variables'
        # optional method constraining the primitive parameters (to keep points on a circle for instance)
        def fit(self) -> err**2 as float:
                ...

Curve resolution

Some primitive types are curves, the discretisation is important for visual as well as for result quality (remember that even if something looks like a perfect curves, it's still polygons). The resolution (subdivision) of curve is done following the following cirterions present in the 'settings' module

Specification priority order:

  1. Optional argument resolution passed to primitive.mesh() or to web() or wire()
  2. Optional attribute resolution of the primitive object
  3. Value of settings.resolution at bake time.

Specification format:

    ('fixed', 16)   # fixed amount of 16 subdivisions
    ('rad', 0.6)    # max polygon angle is 0.6 rad
    ('radm', 0.6)
    ('radm2', 0.6)

Primitives are parametrized objects, that can be baked into a mesh/web/wire object. A primitive object must have the following signature:

class SomePrimitive:
    # method baking the primitive in some general-purpose 3D object
    # resolution is optional and defaults to the primitive settings
    # that defaults to the current settings at call time
    def mesh(self, resolution=None) -> Mesh/Web/Wire:
        ...

    # for the solver
    # primitive attributes the solver has to consider as variables or variable container
    slvvars = 'fields', 'for', 'solver', 'variables'
    # optional method constraining the primitive parameters (to keep points on a circle for instance)
    def fit(self) -> err**2 as float:
        ...

isprimitive(obj)

Return True if obj match the signature for primitives

Source code in madcad/primitives.py
60
61
62
def isprimitive(obj):
	''' Return True if obj match the signature for primitives '''
	return hasattr(obj, 'mesh') and hasattr(obj, 'slvvars')

Curve resolution

Some primitive types are curves, the discretization is important for visual as well as for result quality (remember that even if something looks like a perfect curves, it's still polygons). The resolution (subdivision) of curve is done following the following criterion present in the settings module

Specification priority order:

  1. Optional argument resolution passed to primitive.mesh() or to web() or wire()
  2. Optional attribute resolution of the primitive object
  3. Value of settings.resolution at bake time.

Specification format:

('fixed', 16)   # fixed amount of 16 subdivisions
('rad', 0.6)    # max polygon angle is 0.6 rad
('radm', 0.6)
('radm2', 0.6)

Primitives types

Segment(a, b)

Bases: object

Segment from a to b

segment

Source code in madcad/primitives.py
75
76
def __init__(self, a, b):
	self.a, self.b = a,b

__slots__ = ('a', 'b') class-attribute instance-attribute

__eq__ = _trivial_eq class-attribute instance-attribute

direction property

origin property

slvvars = ('a', 'b') class-attribute instance-attribute

__call__(t)

Source code in madcad/primitives.py
80
81
def __call__(self, t):
	return mix(self.a, self.b, t)

slv_tangent(pt)

Source code in madcad/primitives.py
92
93
def slv_tangent(self, pt):
	return self.direction

mesh(resolution=None)

Source code in madcad/primitives.py
95
96
def mesh(self, resolution=None):
	return mesh.Wire([self.a, self.b], groups=[None])

__repr__()

Source code in madcad/primitives.py
98
99
def __repr__(self):
	return 'Segment({}, {})'.format(self.a, self.b)

display(scene)

Source code in madcad/primitives.py
101
102
def display(self, scene):
	return self.mesh().display(scene)

Circle(axis, radius, alignment=vec3(1, 0, 0), resolution=None)

Bases: object

Circle centered around the axis origin, with the given radius, in an orthogonal plane to the axis direction

circle

Source code in madcad/primitives.py
322
323
324
325
def __init__(self, axis: Axis, radius: float, alignment=vec3(1,0,0), resolution=None):
	self.axis, self.radius = axis, radius
	self.alignment = alignment
	self.resolution = resolution

__slots__ = ('axis', 'radius', 'alignment', 'resolution') class-attribute instance-attribute

alignment = alignment instance-attribute

resolution = resolution instance-attribute

__eq__ = _trivial_eq class-attribute instance-attribute

center property

slvvars = ('axis', 'radius') class-attribute instance-attribute

slv_tangent = tangent class-attribute instance-attribute

fit()

Source code in madcad/primitives.py
333
334
def fit(self):
	return (length(self.axis[1])-1) **2,

tangent(pt)

Tangent to the closest point of the curve to pt

Source code in madcad/primitives.py
336
337
338
def tangent(self, pt):
	''' Tangent to the closest point of the curve to pt '''
	return normalize(cross(pt-self.axis[0], self.axis[1]))

mesh(resolution=None)

Source code in madcad/primitives.py
343
344
345
346
347
348
349
350
def mesh(self, resolution=None):
	x,y,z = dirbase(self.axis[1], self.alignment)
	angle = 2*pi
	div = settings.curve_resolution(angle*self.radius, angle, self.resolution or resolution)
	return mesh.Wire(typedlist(
		self.center + self.radius * (x*cos(t) + y*sin(t))
		for t in linrange(0, angle, div=div, end=False)
		)).close()

__repr__()

Source code in madcad/primitives.py
352
353
def __repr__(self):
	return 'Circle({}, {})'.format(self.axis, self.radius)

display(scene)

Source code in madcad/primitives.py
355
356
def display(self, scene):
	return self.mesh().display(scene)

ArcCentered(axis, a, b, resolution=None)

Bases: object

Arc from a to b, centered around the origin of the axis.

arccentered

An axis is requested instead of a point (that would be more intuitive), to solve the problem when a,b, center are aligned

Source code in madcad/primitives.py
161
162
163
164
def __init__(self, axis, a, b, resolution=None):
	self.axis = axis
	self.a, self.b = a, b
	self.resolution = resolution

__slots__ = ('axis', 'a', 'b', 'resolution') class-attribute instance-attribute

axis = axis instance-attribute

resolution = resolution instance-attribute

__eq__ = _trivial_eq class-attribute instance-attribute

center property

radius property

slvvars = ('axis', 'a', 'b') class-attribute instance-attribute

slv_tangent = tangent class-attribute instance-attribute

tangent(pt)

Tangent to the closest point of the curve to pt

Source code in madcad/primitives.py
176
177
178
def tangent(self, pt):
	''' Tangent to the closest point of the curve to pt '''
	return cross(normalize(pt - self.axis[0]), self.axis[1])

fit()

Source code in madcad/primitives.py
182
183
184
185
186
187
def fit(self):
	return (dot(self.a-self.axis[0], self.axis[1]) **2,
			dot(self.b-self.axis[0], self.axis[1]) **2,
			(distance(self.a,self.axis[0]) - distance(self.b,self.axis[0])) **2,
			(length(self.axis[1])-1) **2,
			)

mesh(resolution=None)

Source code in madcad/primitives.py
189
190
def mesh(self, resolution=None):
	return mkarc(self.axis, self.a, self.b, resolution or self.resolution)

__repr__()

Source code in madcad/primitives.py
192
193
def __repr__(self):
	return 'ArcCentered({}, {}, {})'.format(self.axis, self.a, self.b)

display(scene)

Source code in madcad/primitives.py
195
196
def display(self, scene):
	return self.mesh().display(scene)

ArcThrough(a, b, c, resolution=None)

Bases: object

Arc from a to c, passing through b

arcthrough

Source code in madcad/primitives.py
111
112
113
def __init__(self, a,b,c, resolution=None):
	self.a, self.b, self.c = a,b,c
	self.resolution = resolution

__slots__ = ('a', 'b', 'c', 'resolution') class-attribute instance-attribute

resolution = resolution instance-attribute

__eq__ = _trivial_eq class-attribute instance-attribute

center property

radius property

axis property

slvvars = ('a', 'b', 'c') class-attribute instance-attribute

slv_tangent = tangent class-attribute instance-attribute

tangent(pt)

Tangent to the closest point of the curve to pt

Source code in madcad/primitives.py
134
135
136
137
def tangent(self, pt):
	''' Tangent to the closest point of the curve to pt '''
	c = self.center
	return normalize(cross(pt-c, cross(self.a-c, self.c-c)))

mesh(resolution=None)

Source code in madcad/primitives.py
142
143
144
145
def mesh(self, resolution=None):
	center = self.center
	z = normalize(cross(self.c-self.b, self.a-self.b))
	return mkarc((center, z), self.a, self.c, resolution or self.resolution)

__repr__()

Source code in madcad/primitives.py
147
148
def __repr__(self):
	return 'Axis({}, {}, {})'.format(self.a, self.b, self.c)

display(scene)

Source code in madcad/primitives.py
150
151
def display(self, scene):
	return self.mesh().display(scene)

ArcTangent(a, b, c, resolution=None)

Bases: object

An arc always tangent to Segment(a,b) and Segment(c,b). The solution is unique.

arctangent

Source code in madcad/primitives.py
204
205
206
def __init__(self, a, b, c, resolution=None):
	self.a, self.b, self.c = a,b,c
	self.resolution = resolution

__slots = ('a', 'b', 'c') class-attribute instance-attribute

resolution = resolution instance-attribute

__eq__ = _trivial_eq class-attribute instance-attribute

center property

radius property

axis property

slvvars = ('a', 'b', 'c') class-attribute instance-attribute

slv_tangent = tangent class-attribute instance-attribute

tangent(pt)

Tangent to the closest point of the curve to pt

Source code in madcad/primitives.py
226
227
228
229
def tangent(self, pt):
	''' Tangent to the closest point of the curve to pt '''
	z = cross(self.a-self.b, self.c-self.b)
	return normalize(cross(pt-self.center, z))

fit()

Source code in madcad/primitives.py
234
235
def fit(self):
	return (distance(self.a, self.b) - distance(self.c, self.b)) **2,

mesh(resolution=None)

Source code in madcad/primitives.py
237
238
def mesh(self, resolution=None):
	return mkarc(self.axis, self.a, self.c, resolution or self.resolution)

__repr__()

Source code in madcad/primitives.py
240
241
def __repr__(self):
	return 'ArcTangent({}, {}, {})'.format(self.a, self.b, self.c)

display(scene)

Source code in madcad/primitives.py
243
244
def display(self, scene):
	return self.mesh().display(scene)

Ellipsis(center, minor, major, resolution=None)

Bases: object

Ellipsis centered around the given point, with the given major and minor semi axis

Source code in madcad/primitives.py
361
362
363
364
365
def __init__(self, center: vec3, minor: vec3, major: vec3, resolution=None):
	self.center = center
	self.minor = minor
	self.major = major
	self.resolution = resolution

__slots__ = ('center', 'minor', 'major', 'resolution') class-attribute instance-attribute

center = center instance-attribute

minor = minor instance-attribute

major = major instance-attribute

resolution = resolution instance-attribute

__eq__ = _trivial_eq class-attribute instance-attribute

axis property

the ellipsis axis, deduces from its major and minor semi axis

slvvars = ('center', 'minor', 'major') class-attribute instance-attribute

mesh(resolution=None)

Source code in madcad/primitives.py
376
377
378
379
380
381
382
383
def mesh(self, resolution=None):
	angle = 2*pi
	radius = sqrt(max(length2(self.minor), length2(self.major)))
	div = settings.curve_resolution(angle*radius, angle, self.resolution or resolution)
	return mesh.Wire(typedlist(
		self.center + self.minor * cos(t) + self.major * sin(t)
		for t in linrange(0, angle, div=div, end=False)
		)).close()

__repr__()

Source code in madcad/primitives.py
385
386
def __repr__(self):
	return 'Ellipsis({}, {}, {})'.format(self.center, self.minor, self.major)

display(scene)

Source code in madcad/primitives.py
388
389
def display(self, scene):
	return self.mesh().display(scene)

TangentEllipsis(a, b, c, resolution=None)

Bases: object

An quater of ellipsis always tangent to Segment(a,b) and Segment(c,b). The solution is unique.

tangentellipsis

Source code in madcad/primitives.py
268
269
270
def __init__(self, a,b,c, resolution=None):
	self.a, self.b, self.c = a,b,c
	self.resolution = resolution

__slots__ = ('a', 'b', 'c', 'resolution') class-attribute instance-attribute

resolution = resolution instance-attribute

__eq__ = _trivial_eq class-attribute instance-attribute

axis property

center property

slvvars = ('a', 'b', 'c') class-attribute instance-attribute

slv_tangent = tangent class-attribute instance-attribute

__call__(t)

Source code in madcad/primitives.py
274
275
276
277
278
279
def __call__(self, t):
	x = 0.5*pi*t
	return (  (self.b - self.a)*sin(x)
	        + (self.b - self.c)*cos(x)
	        + self.a + self.c - 2*self.b
	        )

tangent(pt)

Tangent to the closest point of the curve to pt

Source code in madcad/primitives.py
289
290
291
292
def tangent(self, pt):
	''' Tangent to the closest point of the curve to pt '''
	c,z = self.axis
	return normalize(cross(z, pt - c))

mesh(resolution=None)

Axis directions doesn't need to be normalized nor oriented

Source code in madcad/primitives.py
297
298
299
300
301
302
303
304
305
306
307
308
def mesh(self, resolution=None):
	''' Axis directions doesn't need to be normalized nor oriented '''
	origin = self.b
	x = origin - self.a
	y = origin - self.c
	div = settings.curve_resolution(distance(self.a,self.c), anglebt(x,-y), self.resolution or resolution)
	pts = [self.a]
	for i in range(div+1):
		t = pi/2 * i/(div+1)
		pts.append(x*sin(t) + y*cos(t) + origin-x-y)
	pts.append(self.c)
	return mesh.Wire(pts, groups=[None])

__repr__()

Source code in madcad/primitives.py
310
311
def __repr__(self):
	return 'TangentEllipsis({}, {}, {})'.format(self.c, self.b, self.c)

display(scene)

Source code in madcad/primitives.py
313
314
def display(self, scene):
	return self.mesh().display(scene)

Interpolated(points, weights=None, resolution=None)

Bases: object

Interpolated curve passing through the given points (3rd degree bezier spline)

interpolated

The tangent in each point is determined by the direction between adjacent points The point weights is how flattened is the curve close to the point tangents

Source code in madcad/primitives.py
408
409
410
411
def __init__(self, points, weights=None, resolution=None):
	self.points = points
	self.weights = weights or [1] * len(self.points)
	self.resolution = resolution

__slots__ = ('points', 'weights', 'resolution') class-attribute instance-attribute

points = points instance-attribute

weights = weights or [1] * len(self.points) instance-attribute

resolution = resolution instance-attribute

__eq__ = _trivial_eq class-attribute instance-attribute

mesh(resolution=None)

Source code in madcad/primitives.py
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
def mesh(self, resolution=None):
	pts = self.points
	if not pts:		return Wire()

	# get tangent to each point
	tas = [self.weights[i-1] * length(pts[i]-pts[i-1]) * normalize(pts[i]-pts[i-2])
				for i in range(2,len(pts))]
	tbs = [self.weights[i-1] * length(pts[i-2]-pts[i-1]) * normalize(pts[i-2]-pts[i])
				for i in range(2,len(pts))]
	tas.insert(0, tbs[0] - 2*project(tbs[0], pts[1]-pts[0]))
	tbs.append(tas[-1] - 2*project(tas[-1], pts[-2]-pts[-1]))

	# stack points to curve
	curve = []
	for i in range(len(pts)-1):
		a,b = pts[i], pts[i+1]
		ta,tb = tas[i], tbs[i]
		# tangent to the curve in its inflexion point
		mid = 1.25*(b-a) + 0.25*(tb-ta)
		# get resolution
		div = 1 + settings.curve_resolution(
						length(ta) + length(tb),
						anglebt(-tb, mid) + anglebt(ta, mid),
						self.resolution or resolution)

		# append the points for this segment
		for i in range(div+1):
			curve.append(interpol2((a,ta), (b,tb), i/(div+1)))

	curve.append(b)
	return mesh.Wire(curve, groups=[None])

box()

Source code in madcad/primitives.py
447
448
def box(self):
	return boundingbox(self.points)

__repr__()

Source code in madcad/primitives.py
450
451
def __repr__(self):
	return 'Interpolated({}, {})'.format(self.points, self.weights)

display(scene)

Source code in madcad/primitives.py
453
454
455
def display(self, scene):
	from .rendering.d3.marker import SplineDisplay
	return SplineDisplay(scene, typedlist_to_numpy(self.points, np.float32), typedlist_to_numpy(self.mesh().points, np.float32))

Softened(points, weights=None, resolution=None)

Bases: object

Interpolated curve tangent to each segment midpoint (3rd degree bezier curve)

softened

The points weights is the weight in the determination of each midpoint

Source code in madcad/primitives.py
465
466
467
468
def __init__(self, points, weights=None, resolution=None):
	self.points = points
	self.weights = weights or [1] * len(self.points)
	self.resolution = resolution

__slots__ = ('points', 'weights', 'resolution') class-attribute instance-attribute

points = points instance-attribute

weights = weights or [1] * len(self.points) instance-attribute

resolution = resolution instance-attribute

__eq__ = _trivial_eq class-attribute instance-attribute

mesh(resolution=None)

Source code in madcad/primitives.py
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
def mesh(self, resolution=None):
	pts = self.points
	if not pts:		return Wire()

	# find midpoints
	mid = [	(self.weights[i-1]*pts[i-1] + self.weights[i]*pts[i])
				/ (self.weights[i-1]+self.weights[i])
			for i in range(1,len(pts)) ]
	mid[0] = pts[0]
	mid[-1] = pts[-1]

	# stack points to curve
	curve = []
	for i in range(len(pts)-2):
		a,b = mid[i],  mid[i+1]
		ta,tb = 2*(pts[i+1]-a),  2*(pts[i+1]-b)
		# get resolution
		div = 1 + settings.curve_resolution(
					length(ta) + length(tb),
					anglebt(ta, -tb),
					self.resolution or resolution)

		# append the points for this segment
		for i in range(div+1):
			curve.append(interpol2((a,ta), (b,tb), i/(div+1)))

	curve.append(b)
	return mesh.Wire(curve, groups=[None])

box()

Source code in madcad/primitives.py
501
502
def box(self):
	return boundingbox(self.points)

__repr__()

Source code in madcad/primitives.py
504
505
def __repr__(self):
	return 'Softened({}, {})'.format(self.points, self.weights)

display(scene)

Source code in madcad/primitives.py
507
508
509
def display(self, scene):
	from .rendering.d3.marker import SplineDisplay
	return SplineDisplay(scene, typedlist_to_numpy(self.points, np.float32), typedlist_to_numpy(self.mesh().points, np.float32))