From: daid Date: Wed, 7 Mar 2012 15:58:04 +0000 (+0100) Subject: Added model transformation options (scale/flip/rotate) X-Git-Tag: RC1~128 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=ed61e889dc133eb80f1b798268453992f7f97b7a;p=cura.git Added model transformation options (scale/flip/rotate) --- diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/settings.py b/SkeinPyPy_NewUI/fabmetheus_utilities/settings.py index 32727e34..6cf7c00c 100644 --- a/SkeinPyPy_NewUI/fabmetheus_utilities/settings.py +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/settings.py @@ -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, diff --git a/SkeinPyPy_NewUI/newui/advancedConfig.py b/SkeinPyPy_NewUI/newui/advancedConfig.py index 7de21eff..dc54b30d 100644 --- a/SkeinPyPy_NewUI/newui/advancedConfig.py +++ b/SkeinPyPy_NewUI/newui/advancedConfig.py @@ -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() diff --git a/SkeinPyPy_NewUI/newui/configBase.py b/SkeinPyPy_NewUI/newui/configBase.py index 4cf0a169..39a914c0 100644 --- a/SkeinPyPy_NewUI/newui/configBase.py +++ b/SkeinPyPy_NewUI/newui/configBase.py @@ -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, '' + diff --git a/SkeinPyPy_NewUI/newui/configWizard.py b/SkeinPyPy_NewUI/newui/configWizard.py index 8c54188b..0995b068 100644 --- a/SkeinPyPy_NewUI/newui/configWizard.py +++ b/SkeinPyPy_NewUI/newui/configWizard.py @@ -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 diff --git a/SkeinPyPy_NewUI/newui/mainWindow.py b/SkeinPyPy_NewUI/newui/mainWindow.py index 64dbafc2..af7150f6 100644 --- a/SkeinPyPy_NewUI/newui/mainWindow.py +++ b/SkeinPyPy_NewUI/newui/mainWindow.py @@ -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') diff --git a/SkeinPyPy_NewUI/newui/preview3d.py b/SkeinPyPy_NewUI/newui/preview3d.py index d9f96f3a..992d729b 100644 --- a/SkeinPyPy_NewUI/newui/preview3d.py +++ b/SkeinPyPy_NewUI/newui/preview3d.py @@ -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) diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py index 7114caad..cb2a5ec3 100644 --- a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py @@ -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)