mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-06 01:06:02 +08:00
Added model transformation options (scale/flip/rotate)
This commit is contained in:
parent
b09a4c14cd
commit
ed61e889dc
@ -85,6 +85,11 @@ def getSkeinPyPyProfileInformation():
|
||||
'Correct_Mesh': DEFSET,
|
||||
'Unproven_Mesh': DEFSET,
|
||||
'SVG_Viewer': DEFSET,
|
||||
'FlipX': storedSetting("flip_x"),
|
||||
'FlipY': storedSetting("flip_y"),
|
||||
'FlipZ': storedSetting("flip_z"),
|
||||
'Scale': storedSetting("model_scale"),
|
||||
'Rotate': storedSetting("model_rotate_base"),
|
||||
},'scale': {
|
||||
'Activate_Scale': "False",
|
||||
'XY_Plane_Scale_ratio': DEFSET,
|
||||
|
@ -26,14 +26,14 @@ class advancedConfigWindow(configBase.configWindowBase):
|
||||
validators.validFloat(c, 0.0)
|
||||
configBase.TitleRow(left, "Sequence")
|
||||
c = configBase.SettingRow(left, "Print order sequence", 'sequence', ['Loops > Perimeter > Infill', 'Loops > Infill > Perimeter', 'Infill > Loops > Perimeter', 'Infill > Perimeter > Loops', 'Perimeter > Infill > Loops', 'Perimeter > Loops > Infill'], 'Sequence of printing. The perimeter is the outer print edge, the loops are the insides of the walls, and the infill is the insides.');
|
||||
c = configBase.SettingRow(left, "Force first layer sequence", 'force_first_layer_sequence', ['True', 'False'], 'This setting forces the order of the first layer to be \'Perimeter > Loops > Infill\'')
|
||||
c = configBase.SettingRow(left, "Force first layer sequence", 'force_first_layer_sequence', True, 'This setting forces the order of the first layer to be \'Perimeter > Loops > Infill\'')
|
||||
|
||||
configBase.TitleRow(left, "Infill")
|
||||
c = configBase.SettingRow(left, "Infill pattern", 'infill_type', ['Line', 'Grid Circular', 'Grid Hexagonal', 'Grid Rectangular'], 'Pattern of the none-solid infill. Line is default, but grids can provide a strong print.')
|
||||
c = configBase.SettingRow(left, "Solid infill top", 'solid_top', ['True', 'False'], 'Create a solid top surface, if set to false the top is filled with the fill percentage. Useful for cups/vases.')
|
||||
c = configBase.SettingRow(left, "Solid infill top", 'solid_top', True, 'Create a solid top surface, if set to false the top is filled with the fill percentage. Useful for cups/vases.')
|
||||
|
||||
configBase.TitleRow(left, "Joris")
|
||||
c = configBase.SettingRow(left, "Joris the outer edge", 'joris', ['False', 'True'], '[Joris] is a code name for smoothing out the Z move of the outer edge. This will create a steady Z increase over the whole print. It is intended to be used with a single walled wall thickness to make cups/vases.')
|
||||
c = configBase.SettingRow(left, "Joris the outer edge", 'joris', False, '[Joris] is a code name for smoothing out the Z move of the outer edge. This will create a steady Z increase over the whole print. It is intended to be used with a single walled wall thickness to make cups/vases.')
|
||||
|
||||
main.Fit()
|
||||
self.Fit()
|
||||
|
@ -107,20 +107,22 @@ class SettingRow():
|
||||
self.type = type
|
||||
|
||||
self.label = wx.StaticText(panel, -1, label)
|
||||
getSettingFunc = settings.getPreference
|
||||
if self.type == 'profile':
|
||||
if isinstance(defaultValue, types.StringTypes):
|
||||
self.ctrl = wx.TextCtrl(panel, -1, settings.getProfileSetting(configName, defaultValue))
|
||||
else:
|
||||
self.ctrl = wx.ComboBox(panel, -1, settings.getProfileSetting(configName, defaultValue[0]), choices=defaultValue, style=wx.CB_DROPDOWN|wx.CB_READONLY)
|
||||
getSettingFunc = settings.getProfileSetting
|
||||
if isinstance(defaultValue, types.StringTypes):
|
||||
self.ctrl = wx.TextCtrl(panel, -1, getSettingFunc(configName, defaultValue))
|
||||
self.ctrl.Bind(wx.EVT_TEXT, self.OnSettingChange)
|
||||
elif isinstance(defaultValue, types.BooleanType):
|
||||
self.ctrl = wx.CheckBox(panel, -1, style=wx.ALIGN_RIGHT)
|
||||
self.ctrl.SetValue(getSettingFunc(configName, defaultValue) == "True")
|
||||
self.ctrl.Bind(wx.EVT_CHECKBOX, self.OnSettingChange)
|
||||
else:
|
||||
if isinstance(defaultValue, types.StringTypes):
|
||||
self.ctrl = wx.TextCtrl(panel, -1, settings.getPreference(configName, defaultValue))
|
||||
else:
|
||||
self.ctrl = wx.ComboBox(panel, -1, settings.getPreference(configName, defaultValue[0]), choices=defaultValue, style=wx.CB_DROPDOWN|wx.CB_READONLY)
|
||||
self.ctrl = wx.ComboBox(panel, -1, getSettingFunc(configName, defaultValue[0]), choices=defaultValue, style=wx.CB_DROPDOWN|wx.CB_READONLY)
|
||||
self.ctrl.Bind(wx.EVT_TEXT, self.OnSettingChange)
|
||||
#self.helpButton = wx.Button(panel, -1, "?", style=wx.BU_EXACTFIT)
|
||||
#self.helpButton.SetToolTip(wx.ToolTip(help))
|
||||
|
||||
self.ctrl.Bind(wx.EVT_TEXT, self.OnSettingTextChange)
|
||||
self.ctrl.Bind(wx.EVT_ENTER_WINDOW, lambda e: panel.main.OnPopupDisplay(self))
|
||||
self.ctrl.Bind(wx.EVT_LEAVE_WINDOW, panel.main.OnPopupHide)
|
||||
|
||||
@ -131,7 +133,7 @@ class SettingRow():
|
||||
#sizer.Add(helpButton, (x,y+2))
|
||||
sizer.SetRows(x+1)
|
||||
|
||||
def OnSettingTextChange(self, e):
|
||||
def OnSettingChange(self, e):
|
||||
if self.type == 'profile':
|
||||
settings.putProfileSetting(self.configName, self.GetValue())
|
||||
else:
|
||||
@ -158,10 +160,13 @@ class SettingRow():
|
||||
self.panel.main.UpdatePopup(self)
|
||||
|
||||
def GetValue(self):
|
||||
return self.ctrl.GetValue()
|
||||
return str(self.ctrl.GetValue())
|
||||
|
||||
def SetValue(self, value):
|
||||
self.ctrl.SetValue(value)
|
||||
if isinstance(self.ctrl, wx.CheckBox):
|
||||
self.ctrl.SetValue(str(value) == "True")
|
||||
else:
|
||||
self.ctrl.SetValue(value)
|
||||
|
||||
#Settings notify works as a validator, but instead of validating anything, it calls another function, which can use the value.
|
||||
class settingNotify():
|
||||
@ -176,4 +181,6 @@ class settingNotify():
|
||||
self.func(f)
|
||||
return validators.SUCCESS, ''
|
||||
except ValueError:
|
||||
self.func()
|
||||
return validators.SUCCESS, ''
|
||||
|
||||
|
@ -181,7 +181,7 @@ class UltimakerCheckupPage(InfoPage):
|
||||
|
||||
wx.MessageBox('Please move the printer head to the center of the machine\nalso move the platform so it is not at the highest or lowest position,\nand make sure the machine is powered on.', 'Machine check', wx.OK | wx.ICON_INFORMATION)
|
||||
wx.CallAfter(self.AddProgressText, "Checking endstops")
|
||||
if self.DoCommCommandWithTimeout('M119') != "ok x_min:l x_max:l y_min:l y_max:l z_min:l z_max:l"
|
||||
if self.DoCommCommandWithTimeout('M119') != "ok x_min:l x_max:l y_min:l y_max:l z_min:l z_max:l":
|
||||
wx.CallAfter(self.AddProgressText, "Error: There is a problem in your endstops!")
|
||||
wx.CallAfter(self.AddProgressText, "Error: One of them seems to be pressed while it shouldn't")
|
||||
return
|
||||
|
@ -147,6 +147,23 @@ class mainWindow(configBase.configWindowBase):
|
||||
|
||||
nb.AddPage(alterationPanel.alterationPanel(nb), "Start/End-GCode")
|
||||
|
||||
(left, right) = self.CreateConfigTab(nb, '3D Model')
|
||||
configBase.TitleRow(left, "Scale")
|
||||
c = configBase.SettingRow(left, "Scale", 'model_scale', '1.0', '')
|
||||
validators.validFloat(c, 0.01)
|
||||
configBase.settingNotify(c, self.preview3d.updateModelTransform)
|
||||
configBase.TitleRow(left, "Flip")
|
||||
c = configBase.SettingRow(left, "Flip X", 'flip_x', False, '')
|
||||
configBase.settingNotify(c, self.preview3d.updateModelTransform)
|
||||
c = configBase.SettingRow(left, "Flip Y", 'flip_y', False, '')
|
||||
configBase.settingNotify(c, self.preview3d.updateModelTransform)
|
||||
c = configBase.SettingRow(left, "Flip Z", 'flip_z', False, '')
|
||||
configBase.settingNotify(c, self.preview3d.updateModelTransform)
|
||||
configBase.TitleRow(right, "Rotate")
|
||||
c = configBase.SettingRow(right, "Rotate (deg)", 'model_rotate_base', '0', '')
|
||||
validators.validFloat(c)
|
||||
configBase.settingNotify(c, self.preview3d.updateModelTransform)
|
||||
|
||||
# load and slice buttons.
|
||||
loadButton = wx.Button(self, -1, 'Load STL')
|
||||
sliceButton = wx.Button(self, -1, 'Slice to GCode')
|
||||
|
@ -110,9 +110,13 @@ class previewPanel(wx.Panel):
|
||||
|
||||
def DoModelLoad(self):
|
||||
self.modelDirty = False
|
||||
self.triangleMesh = fabmetheus_interpret.getCarving(self.modelFilename)
|
||||
triangleMesh = fabmetheus_interpret.getCarving(self.modelFilename)
|
||||
triangleMesh.origonalVertexes = list(triangleMesh.vertexes)
|
||||
for i in xrange(0, len(triangleMesh.origonalVertexes)):
|
||||
triangleMesh.origonalVertexes[i] = triangleMesh.origonalVertexes[i].copy()
|
||||
self.triangleMesh = triangleMesh
|
||||
self.gcode = None
|
||||
self.moveModel()
|
||||
self.updateModelTransform()
|
||||
wx.CallAfter(self.updateToolbar)
|
||||
wx.CallAfter(self.glCanvas.Refresh)
|
||||
|
||||
@ -145,6 +149,38 @@ class previewPanel(wx.Panel):
|
||||
self.glCanvas.renderTransparent = False
|
||||
self.glCanvas.Refresh()
|
||||
|
||||
def updateModelTransform(self, f=0):
|
||||
if self.triangleMesh == None:
|
||||
return
|
||||
for face in self.triangleMesh.faces:
|
||||
face.normal = None
|
||||
scale = 1.0
|
||||
rotate = 0.0
|
||||
try:
|
||||
scale = float(settings.getProfileSetting('model_scale', '1.0'))
|
||||
rotate = float(settings.getProfileSetting('model_rotate_base', '0.0')) / 180 * math.pi
|
||||
except:
|
||||
pass
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
scaleZ = scale
|
||||
if settings.getProfileSetting('flip_x') == 'True':
|
||||
scaleX = -scaleX
|
||||
if settings.getProfileSetting('flip_y') == 'True':
|
||||
scaleY = -scaleY
|
||||
if settings.getProfileSetting('flip_z') == 'True':
|
||||
scaleZ = -scaleZ
|
||||
mat00 = math.cos(rotate) * scaleX
|
||||
mat01 =-math.sin(rotate) * scaleY
|
||||
mat10 = math.sin(rotate) * scaleX
|
||||
mat11 = math.cos(rotate) * scaleY
|
||||
|
||||
for i in xrange(0, len(self.triangleMesh.origonalVertexes)):
|
||||
self.triangleMesh.vertexes[i].x = self.triangleMesh.origonalVertexes[i].x * mat00 + self.triangleMesh.origonalVertexes[i].y * mat01
|
||||
self.triangleMesh.vertexes[i].y = self.triangleMesh.origonalVertexes[i].x * mat10 + self.triangleMesh.origonalVertexes[i].y * mat11
|
||||
self.triangleMesh.vertexes[i].z = self.triangleMesh.origonalVertexes[i].z * scaleZ
|
||||
self.moveModel()
|
||||
|
||||
def moveModel(self):
|
||||
if self.triangleMesh == None:
|
||||
return
|
||||
@ -159,6 +195,7 @@ class previewPanel(wx.Panel):
|
||||
v.y += self.machineCenter.y
|
||||
self.triangleMesh.getMinimumZ()
|
||||
self.modelDirty = True
|
||||
self.glCanvas.Refresh()
|
||||
|
||||
class PreviewGLCanvas(glcanvas.GLCanvas):
|
||||
def __init__(self, parent):
|
||||
@ -343,13 +380,17 @@ class PreviewGLCanvas(glcanvas.GLCanvas):
|
||||
v1 = self.parent.triangleMesh.vertexes[face.vertexIndexes[0]]
|
||||
v2 = self.parent.triangleMesh.vertexes[face.vertexIndexes[1]]
|
||||
v3 = self.parent.triangleMesh.vertexes[face.vertexIndexes[2]]
|
||||
if not hasattr(face, 'normal'):
|
||||
if face.normal == None:
|
||||
face.normal = (v2 - v1).cross(v3 - v1)
|
||||
face.normal.normalize()
|
||||
glNormal3f(face.normal.x, face.normal.y, face.normal.z)
|
||||
glVertex3f(v1.x, v1.y, v1.z)
|
||||
glVertex3f(v2.x, v2.y, v2.z)
|
||||
glVertex3f(v3.x, v3.y, v3.z)
|
||||
glNormal3f(-face.normal.x, -face.normal.y, -face.normal.z)
|
||||
glVertex3f(v1.x, v1.y, v1.z)
|
||||
glVertex3f(v3.x, v3.y, v3.z)
|
||||
glVertex3f(v2.x, v2.y, v2.z)
|
||||
glEnd()
|
||||
glEndList()
|
||||
if self.renderTransparent:
|
||||
@ -432,6 +473,7 @@ class PreviewGLCanvas(glcanvas.GLCanvas):
|
||||
glEnable(GL_LIGHTING)
|
||||
glEnable(GL_LIGHT0)
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
glEnable(GL_CULL_FACE)
|
||||
glDisable(GL_BLEND)
|
||||
|
||||
glClearColor(0.0, 0.0, 0.0, 1.0)
|
||||
|
@ -107,10 +107,12 @@ from fabmetheus_utilities import settings
|
||||
from fabmetheus_utilities import svg_writer
|
||||
from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
|
||||
from skeinforge_application.skeinforge_utilities import skeinforge_profile
|
||||
from fabmetheus_utilities.vector3 import Vector3
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import math
|
||||
|
||||
|
||||
__author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
|
||||
@ -178,6 +180,13 @@ class CarveRepository:
|
||||
settings.LabelSeparator().getFromRepository(self)
|
||||
self.executeTitle = 'Carve'
|
||||
|
||||
self.flipX = settings.BooleanSetting().getFromValue('FlipX', self, False)
|
||||
self.flipY = settings.BooleanSetting().getFromValue('FlipY', self, False)
|
||||
self.flipZ = settings.BooleanSetting().getFromValue('FlipZ', self, False)
|
||||
self.scale = settings.FloatSpin().getFromValue( 0.1, 'Scale', self, 10.0, 1.0 )
|
||||
self.rotate = settings.FloatSpin().getFromValue( -180.0, 'Rotate', self, 180.0, 0.0 )
|
||||
|
||||
|
||||
def execute(self):
|
||||
"Carve button has been clicked."
|
||||
fileNames = skeinforge_polyfile.getFileOrDirectoryTypes(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
|
||||
@ -189,6 +198,29 @@ class CarveSkein:
|
||||
"A class to carve a carving."
|
||||
def getCarvedSVG(self, carving, fileName, repository):
|
||||
"Parse gnu triangulated surface text and store the carved gcode."
|
||||
|
||||
scale = repository.scale.value
|
||||
rotate = repository.rotate.value / 180 * math.pi
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
scaleZ = scale
|
||||
if repository.flipX.value == 'True':
|
||||
scaleX = -scaleX
|
||||
if repository.flipY.value == 'True':
|
||||
scaleY = -scaleY
|
||||
if repository.flipZ.value == 'True':
|
||||
scaleZ = -scaleZ
|
||||
mat00 = math.cos(rotate) * scaleX
|
||||
mat01 =-math.sin(rotate) * scaleY
|
||||
mat10 = math.sin(rotate) * scaleX
|
||||
mat11 = math.cos(rotate) * scaleY
|
||||
|
||||
for i in xrange(0, len(carving.vertexes)):
|
||||
carving.vertexes[i] = Vector3(
|
||||
carving.vertexes[i].x * mat00 + carving.vertexes[i].y * mat01,
|
||||
carving.vertexes[i].x * mat10 + carving.vertexes[i].y * mat11,
|
||||
carving.vertexes[i].z * scaleZ)
|
||||
|
||||
layerHeight = repository.layerHeight.value
|
||||
edgeWidth = repository.edgeWidth.value
|
||||
carving.setCarveLayerHeight(layerHeight)
|
||||
|
Loading…
x
Reference in New Issue
Block a user