Skip to content

Commit

Permalink
Initial proof of concept
Browse files Browse the repository at this point in the history
  • Loading branch information
fieldOfView committed Aug 24, 2018
0 parents commit 1a23228
Show file tree
Hide file tree
Showing 7 changed files with 922 additions and 0 deletions.
63 changes: 63 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
*.qmlc

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx documentation
docs/_build/

# PyBuilder
target/

#Ipython Notebook
.ipynb_checkpoints
661 changes: 661 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

131 changes: 131 additions & 0 deletions MeshTools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Copyright (c) 2018 fieldOfView
# MeshTools is released under the terms of the AGPLv3 or higher.

from PyQt5.QtCore import QObject

import numpy
import trimesh

from UM.Extension import Extension
from UM.Application import Application
from UM.Message import Message

from UM.Scene.Selection import Selection
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
from UM.Mesh.MeshData import MeshData, calculateNormalsFromIndexedVertices
from UM.Mesh.MeshBuilder import MeshBuilder

from cura.Scene.CuraSceneNode import CuraSceneNode

from .SetTransformMatrixOperation import SetTransformMatrixOperation

from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")

class MeshTools(Extension, QObject,):
def __init__(self, parent = None):
QObject.__init__(self, parent)
Extension.__init__(self)

self._controller = Application.getInstance().getController()

self.addMenuItem(catalog.i18nc("@item:inmenu", "Check models"), self.checkSelectedMeshes)
self.addMenuItem(catalog.i18nc("@item:inmenu", "Fix simple holes"), self.fixSimpleHolesForSelectedMeshes)
self.addMenuItem(catalog.i18nc("@item:inmenu", "Fix model normals"), self.fixNormalsForSelectedMeshes)
self.addMenuItem(catalog.i18nc("@item:inmenu", "Split model into parts"), self.splitSelectedMeshes)

self._message = Message(title=catalog.i18nc("@info:title", "Mesh Tools"))

def checkSelectedMeshes(self):
message_body = catalog.i18nc("@info:status", "Model summary:")
for node in Selection.getAllSelectedObjects():
tri_node = self._toTriMesh(node.getMeshData())
message_body = message_body + "\n - %s:" % node.getName()
if tri_node.is_watertight:
message_body = message_body + " " + catalog.i18nc("@info:status", "is watertight")
else:
message_body = message_body + " " + catalog.i18nc("@info:status", "is NOT watertight")
if tri_node.body_count > 1:
message_body = message_body + " " + catalog.i18nc("@info:status", "and consists of {body_count} submeshes").format(body_count = tri_node.body_count)

self._message.setText(message_body)
self._message.show()

def fixSimpleHolesForSelectedMeshes(self):
for node in Selection.getAllSelectedObjects():
tri_node = self._toTriMesh(node.getMeshData())
success = tri_node.fill_holes()
self._replaceSceneNode(node, [tri_node])
if not success:
self._message.setText(catalog.i18nc("@info:status", "The mesh needs more extensive repair to become watertight"))
self._message.show()

def fixNormalsForSelectedMeshes(self):
for node in Selection.getAllSelectedObjects():
tri_node = self._toTriMesh(node.getMeshData())
tri_node.fix_normals()
self._replaceSceneNode(node, [tri_node])

def splitSelectedMeshes(self):
for node in Selection.getAllSelectedObjects():
tri_node = self._toTriMesh(node.getMeshData())
if tri_node.body_count > 1:
self._replaceSceneNode(node, tri_node.split())

def _replaceSceneNode(self, existing_node, trimeshes):
name = existing_node.getName()
file_name = existing_node.getMeshData().getFileName()
transformation = existing_node.getWorldTransformation()
parent = existing_node.getParent()
extruder_id = existing_node.callDecoration("getActiveExtruder")
build_plate = existing_node.callDecoration("getBuildPlateNumber")

op = GroupedOperation()
op.addOperation(RemoveSceneNodeOperation(existing_node))

for i, tri_node in enumerate(trimeshes):
mesh_data = self._toMeshData(tri_node)

new_node = CuraSceneNode()
new_node.setSelectable(True)
new_node.setMeshData(mesh_data)
new_node.callDecoration("setActiveExtruder", extruder_id)
new_node.addDecorator(BuildPlateDecorator(build_plate))
new_node.addDecorator(SliceableObjectDecorator())

op.addOperation(AddSceneNodeOperation(new_node, parent))
op.addOperation(SetTransformMatrixOperation(new_node, transformation))

op.push()

def _toTriMesh(self, mesh_data: MeshData) -> trimesh.base.Trimesh:
return trimesh.base.Trimesh(vertices=mesh_data.getVertices(), faces=mesh_data.getIndices(), vertex_normals=mesh_data.getNormals())

def _toMeshData(self, tri_node: trimesh.base.Trimesh) -> MeshData:
tri_faces = tri_node.faces
tri_vertices = tri_node.vertices

indices = []
vertices = []

index_count = 0
face_count = 0
for triface in tri_faces:
face = []
for triindex in triface:
vertices.append(tri_vertices[triindex])
face.append(index_count)
index_count = index_count + 1
indices.append(face)
face_count = face_count + 1

vertices = numpy.asarray(vertices, dtype=numpy.float32)
indices = numpy.asarray(indices, dtype=numpy.int32)
normals = calculateNormalsFromIndexedVertices(vertices, indices, face_count)

mesh_data = MeshData(vertices=vertices, indices=indices, normals=normals)
return mesh_data
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# MeshTools

This plugin adds several tools for analysis and manipulation of meshes
46 changes: 46 additions & 0 deletions SetTransformMatrixOperation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright (c) 2018 Aldo Hoeben / fieldOfView.
# MeshTools is released under the terms of the AGPLv3 or higher.

from UM.Operations.SetTransformOperation import SetTransformOperation
from UM.Math.Matrix import Matrix
from UM.Math.Vector import Vector

## Operation that translates, rotates and scales a node all at once.
class SetTransformMatrixOperation(SetTransformOperation):
## Creates the transform operation.
#
# \param node The scene node to transform.
# \param transform A fully formed transformation matrix to transform the node with.
def __init__(self, node, transform = None):
super().__init__(node)

self._old_transformation = node.getWorldTransformation()
self._new_transformation = transform

## Merges this operation with another SetTransformMatrixOperation.
#
# This prevents the user from having to undo multiple operations if they
# were not his operations.
#
# You should ONLY merge this operation with an older operation. It is NOT
# symmetric.
#
# \param other The older operation with which to merge this operation.
# \return A combination of the two operations, or False if the merge
# failed.
def mergeWith(self, other):
if type(other) is not SetTransformMatrixOperation:
return False
if other._node != self._node: # Must be on the same node.
return False

op = SetTransformMatrixOperation(self._node)
op._old_transformation = other._old_transformation
op._new_transformation = self._new_transformation
return op

## Returns a programmer-readable representation of this operation.
#
# A programmer-readable representation of this operation.
def __repr__(self):
return "SetTransformMatrixOperation(node = {0})".format(self._node)
10 changes: 10 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright (c) 2019 Aldo Hoeben / fieldOfView
# MeshTools is released under the terms of the AGPLv3 or higher.

from . import MeshTools

def getMetaData():
return {}

def register(app):
return {"extension": MeshTools.MeshTools()}
8 changes: 8 additions & 0 deletions plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "Mesh Tools",
"author": "fieldOfView",
"version": "3.5.0",
"description": "Adds several mesh analysis and manipulation tools",
"api": 4,
"i18n-catalog": "cura"
}

0 comments on commit 1a23228

Please sign in to comment.