Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
"""
The printableObject module contains a printableObject class,
which is used to represent a single object that can be printed.
A single object can have 1 or more meshes which represent different sections for multi-material extrusion.
"""
__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
import time
import math
import os
import numpy
numpy.seterr(all='ignore')
from Cura.util import polygon
class printableObject(object):
"""
A printable object is an object that can be printed and is on the build platform.
It contains 1 or more Meshes. Where more meshes are used for multi-extrusion.
Each object has a 3x3 transformation matrix to rotate/scale the object.
This object also keeps track of the 2D boundary polygon used for object collision in the objectScene class.
"""
def __init__(self, originFilename):
self._originFilename = originFilename
if originFilename is None:
self._name = 'None'
else:
self._name = os.path.basename(originFilename)
if '.' in self._name:
self._name = os.path.splitext(self._name)[0]
self._meshList = []
self._position = numpy.array([0.0, 0.0])
self._matrix = numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)
self._transformedMin = None
self._transformedMax = None
self._transformedSize = None
self._boundaryCircleSize = None
self._drawOffset = None
self._boundaryHull = None
self._printAreaExtend = numpy.array([[-1,-1],[ 1,-1],[ 1, 1],[-1, 1]], numpy.float32)
self._headAreaExtend = numpy.array([[-1,-1],[ 1,-1],[ 1, 1],[-1, 1]], numpy.float32)
self._headMinSize = numpy.array([1, 1], numpy.float32)
self._printAreaHull = None
self._headAreaHull = None
self._headAreaMinHull = None
self._loadAnim = None
def copy(self):
ret = printableObject(self._originFilename)
ret._matrix = self._matrix.copy()
ret._transformedMin = self._transformedMin.copy()
ret._transformedMax = self._transformedMax.copy()
ret._transformedSize = self._transformedSize.copy()
ret._boundaryCircleSize = self._boundaryCircleSize
ret._boundaryHull = self._boundaryHull.copy()
ret._printAreaExtend = self._printAreaExtend.copy()
ret._printAreaHull = self._printAreaHull.copy()
ret._drawOffset = self._drawOffset.copy()
for m in self._meshList[:]:
m2 = ret._addMesh()
m2.vertexes = m.vertexes
m2.vertexCount = m.vertexCount
m2.vbo = m.vbo
m2.vbo.incRef()
return ret
def _addMesh(self):
m = mesh(self)
self._meshList.append(m)
return m
def _postProcessAfterLoad(self):
for m in self._meshList:
m._calculateNormals()
self.processMatrix()
if numpy.max(self.getSize()) > 10000.0:
for m in self._meshList:
m.vertexes /= 1000.0
self.processMatrix()
if numpy.max(self.getSize()) < 1.0:
for m in self._meshList:
m.vertexes *= 1000.0
self.processMatrix()
def applyMatrix(self, m):
self._matrix *= m
self.processMatrix()
def processMatrix(self):
self._transformedMin = numpy.array([999999999999,999999999999,999999999999], numpy.float64)
self._transformedMax = numpy.array([-999999999999,-999999999999,-999999999999], numpy.float64)
self._boundaryCircleSize = 0
hull = numpy.zeros((0, 2), numpy.int)
for m in self._meshList:
transformedVertexes = m.getTransformedVertexes()
hull = polygon.convexHull(numpy.concatenate((numpy.rint(transformedVertexes[:,0:2]).astype(int), hull), 0))
transformedMin = transformedVertexes.min(0)
transformedMax = transformedVertexes.max(0)
for n in xrange(0, 3):
self._transformedMin[n] = min(transformedMin[n], self._transformedMin[n])
self._transformedMax[n] = max(transformedMax[n], self._transformedMax[n])
#Calculate the boundary circle
transformedSize = transformedMax - transformedMin
center = transformedMin + transformedSize / 2.0
boundaryCircleSize = round(math.sqrt(numpy.max(((transformedVertexes[::,0] - center[0]) * (transformedVertexes[::,0] - center[0])) + ((transformedVertexes[::,1] - center[1]) * (transformedVertexes[::,1] - center[1])) + ((transformedVertexes[::,2] - center[2]) * (transformedVertexes[::,2] - center[2])))), 3)
self._boundaryCircleSize = max(self._boundaryCircleSize, boundaryCircleSize)
self._transformedSize = self._transformedMax - self._transformedMin
self._drawOffset = (self._transformedMax + self._transformedMin) / 2
self._drawOffset[2] = self._transformedMin[2]
self._transformedMax -= self._drawOffset
self._transformedMin -= self._drawOffset
self._boundaryHull = polygon.minkowskiHull((hull.astype(numpy.float32) - self._drawOffset[0:2]), numpy.array([[-1,-1],[-1,1],[1,1],[1,-1]],numpy.float32))
self._printAreaHull = polygon.minkowskiHull(self._boundaryHull, self._printAreaExtend)
self.setHeadArea(self._headAreaExtend, self._headMinSize)
def getName(self):
return self._name
def getOriginFilename(self):
return self._originFilename
def getPosition(self):
return self._position
def setPosition(self, newPos):
self._position = newPos
def getMatrix(self):
return self._matrix
def getMaximum(self):
return self._transformedMax
def getMinimum(self):
return self._transformedMin
def getSize(self):
return self._transformedSize
def getDrawOffset(self):
return self._drawOffset
def getBoundaryCircle(self):
return self._boundaryCircleSize
def setPrintAreaExtends(self, poly):
self._printAreaExtend = poly
self._printAreaHull = polygon.minkowskiHull(self._boundaryHull, self._printAreaExtend)
self.setHeadArea(self._headAreaExtend, self._headMinSize)
def setHeadArea(self, poly, minSize):
self._headAreaExtend = poly
self._headMinSize = minSize
self._headAreaHull = polygon.minkowskiHull(self._printAreaHull, self._headAreaExtend)
pMin = numpy.min(self._printAreaHull, 0) - self._headMinSize
pMax = numpy.max(self._printAreaHull, 0) + self._headMinSize
square = numpy.array([pMin, [pMin[0], pMax[1]], pMax, [pMax[0], pMin[1]]], numpy.float32)
self._headAreaMinHull = polygon.clipConvex(self._headAreaHull, square)
def mirror(self, axis):
matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
matrix[axis][axis] = -1
self.applyMatrix(numpy.matrix(matrix, numpy.float64))
def getScale(self):
return numpy.array([
numpy.linalg.norm(self._matrix[::,0].getA().flatten()),
numpy.linalg.norm(self._matrix[::,1].getA().flatten()),
numpy.linalg.norm(self._matrix[::,2].getA().flatten())], numpy.float64);
def setScale(self, scale, axis, uniform):
currentScale = numpy.linalg.norm(self._matrix[::,axis].getA().flatten())
scale /= currentScale
if scale == 0:
return
if uniform:
matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
else:
matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
matrix[axis][axis] = scale
self.applyMatrix(numpy.matrix(matrix, numpy.float64))
def setSize(self, size, axis, uniform):
scale = self.getSize()[axis]
scale = size / scale
if scale == 0:
return
if uniform:
matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
else:
matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
matrix[axis][axis] = scale
self.applyMatrix(numpy.matrix(matrix, numpy.float64))
def resetScale(self):
x = 1/numpy.linalg.norm(self._matrix[::,0].getA().flatten())
y = 1/numpy.linalg.norm(self._matrix[::,1].getA().flatten())
z = 1/numpy.linalg.norm(self._matrix[::,2].getA().flatten())
self.applyMatrix(numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64))
def resetRotation(self):
x = numpy.linalg.norm(self._matrix[::,0].getA().flatten())
y = numpy.linalg.norm(self._matrix[::,1].getA().flatten())
z = numpy.linalg.norm(self._matrix[::,2].getA().flatten())
self._matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
self.processMatrix()
def layFlat(self):
transformedVertexes = self._meshList[0].getTransformedVertexes()
minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
dotMin = 1.0
dotV = None
for v in transformedVertexes:
diff = v - minZvertex
len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
if len < 5:
continue
dot = (diff[2] / len)
if dotMin > dot:
dotMin = dot
dotV = diff
if dotV is None:
return
rad = -math.atan2(dotV[1], dotV[0])
self._matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
rad = -math.asin(dotMin)
self._matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
transformedVertexes = self._meshList[0].getTransformedVertexes()
minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
dotMin = 1.0
dotV = None
for v in transformedVertexes:
diff = v - minZvertex
len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
if len < 5:
continue
dot = (diff[2] / len)
if dotMin > dot:
dotMin = dot
dotV = diff
if dotV is None:
return
if dotV[1] < 0:
rad = math.asin(dotMin)
else:
rad = -math.asin(dotMin)
self.applyMatrix(numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64))
def scaleUpTo(self, size):
vMin = self._transformedMin
vMax = self._transformedMax
scaleX1 = (size[0] / 2 - self._position[0]) / ((vMax[0] - vMin[0]) / 2)
scaleY1 = (size[1] / 2 - self._position[1]) / ((vMax[1] - vMin[1]) / 2)
scaleX2 = (self._position[0] + size[0] / 2) / ((vMax[0] - vMin[0]) / 2)
scaleY2 = (self._position[1] + size[1] / 2) / ((vMax[1] - vMin[1]) / 2)
scaleZ = size[2] / (vMax[2] - vMin[2])
scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
if scale > 0:
self.applyMatrix(numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64))
#Split splits an object with multiple meshes into different objects, where each object is a part of the original mesh that has
# connected faces. This is useful to split up plate STL files.
def split(self, callback):
ret = []
for oriMesh in self._meshList:
ret += oriMesh.split(callback)
return ret
def canStoreAsSTL(self):
return len(self._meshList) < 2
#getVertexIndexList returns an array of vertexes, and an integer array for each mesh in this object.
# the integer arrays are indexes into the vertex array for each triangle in the model.
def getVertexIndexList(self):
vertexMap = {}
vertexList = []
meshList = []
for m in self._meshList:
verts = m.getTransformedVertexes(True)
meshIdxList = []
for idx in xrange(0, len(verts)):
v = verts[idx]
hashNr = int(v[0] * 100) | int(v[1] * 100) << 10 | int(v[2] * 100) << 20
vIdx = None
if hashNr in vertexMap:
for idx2 in vertexMap[hashNr]:
if numpy.linalg.norm(v - vertexList[idx2]) < 0.001:
vIdx = idx2
if vIdx is None:
vIdx = len(vertexList)
vertexMap[hashNr] = [vIdx]
vertexList.append(v)
meshIdxList.append(vIdx)
meshList.append(numpy.array(meshIdxList, numpy.int32))
return numpy.array(vertexList, numpy.float32), meshList
class mesh(object):
"""
A mesh is a list of 3D triangles build from vertexes. Each triangle has 3 vertexes.
A "VBO" can be associated with this object, which is used for rendering this object.
"""
def __init__(self, obj):
self.vertexes = None
self.vertexCount = 0
self.vbo = None
self._obj = obj
def _addFace(self, x0, y0, z0, x1, y1, z1, x2, y2, z2):
n = self.vertexCount
self.vertexes[n][0] = x0
self.vertexes[n][1] = y0
self.vertexes[n][2] = z0
n += 1
self.vertexes[n][0] = x1
self.vertexes[n][1] = y1
self.vertexes[n][2] = z1
n += 1
self.vertexes[n][0] = x2
self.vertexes[n][1] = y2
self.vertexes[n][2] = z2
self.vertexCount += 3
def _prepareFaceCount(self, faceNumber):
#Set the amount of faces before loading data in them. This way we can create the numpy arrays before we fill them.
self.vertexes = numpy.zeros((faceNumber*3, 3), numpy.float32)
self.normal = numpy.zeros((faceNumber*3, 3), numpy.float32)
self.vertexCount = 0
def _calculateNormals(self):
#Calculate the normals
tris = self.vertexes.reshape(self.vertexCount / 3, 3, 3)
normals = numpy.cross( tris[::,1 ] - tris[::,0] , tris[::,2 ] - tris[::,0] )
lens = numpy.sqrt( normals[:,0]**2 + normals[:,1]**2 + normals[:,2]**2 )
normals[:,0] /= lens
normals[:,1] /= lens
normals[:,2] /= lens
n = numpy.zeros((self.vertexCount / 3, 9), numpy.float32)
n[:,0:3] = normals
n[:,3:6] = normals
n[:,6:9] = normals
self.normal = n.reshape(self.vertexCount, 3)
self.invNormal = -self.normal
def _vertexHash(self, idx):
v = self.vertexes[idx]
return int(v[0] * 100) | int(v[1] * 100) << 10 | int(v[2] * 100) << 20
def _idxFromHash(self, map, idx):
vHash = self._vertexHash(idx)
for i in map[vHash]:
if numpy.linalg.norm(self.vertexes[i] - self.vertexes[idx]) < 0.001:
return i
def getTransformedVertexes(self, applyOffsets = False):
if applyOffsets:
pos = self._obj._position.copy()
pos.resize((3))
pos[2] = self._obj.getSize()[2] / 2
offset = self._obj._drawOffset.copy()
offset[2] += self._obj.getSize()[2] / 2
return (numpy.matrix(self.vertexes, copy = False) * numpy.matrix(self._obj._matrix, numpy.float32)).getA() - offset + pos
return (numpy.matrix(self.vertexes, copy = False) * numpy.matrix(self._obj._matrix, numpy.float32)).getA()
def split(self, callback):
vertexMap = {}
vertexToFace = []
for idx in xrange(0, self.vertexCount):
if (idx % 100) == 0:
callback(idx * 100 / self.vertexCount)
vHash = self._vertexHash(idx)
if vHash not in vertexMap:
vertexMap[vHash] = []
vertexMap[vHash].append(idx)
vertexToFace.append([])
faceList = []
for idx in xrange(0, self.vertexCount, 3):
if (idx % 100) == 0:
callback(idx * 100 / self.vertexCount)
f = [self._idxFromHash(vertexMap, idx), self._idxFromHash(vertexMap, idx+1), self._idxFromHash(vertexMap, idx+2)]
vertexToFace[f[0]].append(idx / 3)
vertexToFace[f[1]].append(idx / 3)
vertexToFace[f[2]].append(idx / 3)
faceList.append(f)
ret = []
doneSet = set()
for idx in xrange(0, len(faceList)):
if idx in doneSet:
continue
doneSet.add(idx)
todoList = [idx]
meshFaceList = []
while len(todoList) > 0:
idx = todoList.pop()
meshFaceList.append(idx)
for n in xrange(0, 3):
for i in vertexToFace[faceList[idx][n]]:
if not i in doneSet:
doneSet.add(i)
todoList.append(i)
obj = printableObject(self._obj.getOriginFilename())
obj._matrix = self._obj._matrix.copy()
m = obj._addMesh()
m._prepareFaceCount(len(meshFaceList))
for idx in meshFaceList:
m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][0]]
m.vertexCount += 1
m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][1]]
m.vertexCount += 1
m.vertexes[m.vertexCount] = self.vertexes[faceList[idx][2]]
m.vertexCount += 1
obj._postProcessAfterLoad()
ret.append(obj)
return ret
d6cd84b4fThis file is pinned to the selected release. Download the original to open it in your design tool.