class Rotate3DNode(Node): def __init__(self): super(Rotate3DNode, self).__init__() self.anchorNode = TranslateNode() self.rotXNode = RotateNode(axis=(1, 0, 0)) self.rotYNode = RotateNode(axis=(0, 1, 0)) self.rotZNode = RotateNode(axis=(0, 0, 1)) self.recenterNode = TranslateNode() super(Rotate3DNode, self).addChild(self.anchorNode) self.anchorNode.addChild(self.rotZNode) self.rotZNode.addChild(self.rotYNode) self.rotYNode.addChild(self.rotXNode) self.rotXNode.addChild(self.recenterNode) def addChild(self, node): self.recenterNode.addChild(node) def removeChild(self, node): self.recenterNode.removeChild(node) def setRotation(self, rots): rx, ry, rz = rots self.rotXNode.degrees = rx self.rotYNode.degrees = ry self.rotZNode.degrees = rz def setAnchor(self, point): self.anchorNode.translateOffset = point self.recenterNode.translateOffset = -point
def entityModelNode(ref, model, modelTex, chunk): modelVerts = numpy.array(model.vertices) modelVerts.shape = modelVerts.shape[0] // 4, 4, modelVerts.shape[1] # scale down modelVerts[..., :3] *= 1 / 16. modelVerts[..., 1] = -modelVerts[..., 1] + 1.5 + 1 / 64. modelVerts[..., 0] = -modelVerts[..., 0] vertexBuffer = QuadVertexArrayBuffer(len(modelVerts), lights=False, textures=True) vertexBuffer.vertex[:] = modelVerts[..., :3] vertexBuffer.texcoord[:] = modelVerts[..., 3:5] node = VertexNode([vertexBuffer]) rotateNode = RotateNode(ref.Rotation[0], (0., 1., 0.)) rotateNode.addChild(node) translateNode = TranslateNode( (ref.Position - (chunk.cx << 4, 0, chunk.cz << 4))) translateNode.addChild(rotateNode) textureNode = BindTextureNode( modelTex, (1. / model.texWidth, 1. / model.texHeight, 1)) textureNode.addChild(translateNode) return textureNode
def entityModelNode(ref, model, modelTex, chunk): modelVerts = numpy.array(model.vertices) modelVerts.shape = modelVerts.shape[0]//4, 4, modelVerts.shape[1] # scale down modelVerts[..., :3] *= 1/16. modelVerts[..., 1] = -modelVerts[..., 1] + 1.5 + 1/64. modelVerts[..., 0] = -modelVerts[..., 0] vertexBuffer = QuadVertexArrayBuffer(len(modelVerts), lights=False, textures=True) vertexBuffer.vertex[:] = modelVerts[..., :3] vertexBuffer.texcoord[:] = modelVerts[..., 3:5] node = VertexNode([vertexBuffer]) rotateNode = RotateNode(ref.Rotation[0], (0., 1., 0.)) rotateNode.addChild(node) translateNode = TranslateNode((ref.Position - (chunk.cx << 4, 0, chunk.cz << 4))) translateNode.addChild(rotateNode) textureNode = BindTextureNode(modelTex, (1./model.texWidth, 1./model.texHeight, 1)) textureNode.addChild(translateNode) return textureNode
def chestEntityModelNode(ref, model, modelTex, chunk, facing, largeX, largeZ): modelVerts = numpy.array(model.vertices) modelVerts.shape = modelVerts.shape[0]//4, 4, modelVerts.shape[1] # scale down modelVerts[..., :3] *= 1/16. # modelVerts[..., 1] = -modelVerts[..., 1] # modelVerts[..., 0] = -modelVerts[..., 0] vertexBuffer = QuadVertexArrayBuffer(len(modelVerts), lights=False, textures=True) vertexBuffer.vertex[:] = modelVerts[..., :3] vertexBuffer.texcoord[:] = modelVerts[..., 3:5] node = VertexNode([vertexBuffer]) rotations = { "north": 180, "east": 270, "south": 0, "west": 90 } decenterTranslateNode = TranslateNode((-0.5, -0.5, -0.5)) decenterTranslateNode.addChild(node) rotateNode = RotateNode(rotations[facing], (0., 1., 0.)) # rotateNode = RotateNode(0, (0., 1., 0.)) rotateNode.addChild(decenterTranslateNode) dx = dz = 0 if largeX and facing == "north": dx = 1. if largeZ and facing == "east": dz = -1. recenterTranslateNode = TranslateNode((0.5+dx, 0.5, 0.5+dz)) recenterTranslateNode.addChild(rotateNode) x, y, z = (ref.Position - (chunk.cx << 4, 0, chunk.cz << 4)) scaleNode = ScaleNode((1., -1., -1.)) scaleNode.addChild(recenterTranslateNode) posTranslateNode = TranslateNode((x, y+1., z+1.)) posTranslateNode.addChild(scaleNode) textureNode = BindTextureNode(modelTex, (1./model.texWidth, 1./model.texHeight, 1)) textureNode.addChild(posTranslateNode) return textureNode
def chestEntityModelNode(ref, model, modelTex, chunk, facing, largeX, largeZ): modelVerts = numpy.array(model.vertices) modelVerts.shape = modelVerts.shape[0] // 4, 4, modelVerts.shape[1] # scale down modelVerts[..., :3] *= 1 / 16. # modelVerts[..., 1] = -modelVerts[..., 1] # modelVerts[..., 0] = -modelVerts[..., 0] vertexBuffer = QuadVertexArrayBuffer(len(modelVerts), lights=False, textures=True) vertexBuffer.vertex[:] = modelVerts[..., :3] vertexBuffer.texcoord[:] = modelVerts[..., 3:5] node = VertexNode([vertexBuffer]) rotations = {"north": 180, "east": 270, "south": 0, "west": 90} decenterTranslateNode = TranslateNode((-0.5, -0.5, -0.5)) decenterTranslateNode.addChild(node) rotateNode = RotateNode(rotations[facing], (0., 1., 0.)) # rotateNode = RotateNode(0, (0., 1., 0.)) rotateNode.addChild(decenterTranslateNode) dx = dz = 0 if largeX and facing == "north": dx = 1. if largeZ and facing == "east": dz = -1. recenterTranslateNode = TranslateNode((0.5 + dx, 0.5, 0.5 + dz)) recenterTranslateNode.addChild(rotateNode) x, y, z = (ref.Position - (chunk.cx << 4, 0, chunk.cz << 4)) scaleNode = ScaleNode((1., -1., -1.)) scaleNode.addChild(recenterTranslateNode) posTranslateNode = TranslateNode((x, y + 1., z + 1.)) posTranslateNode.addChild(scaleNode) textureNode = BindTextureNode( modelTex, (1. / model.texWidth, 1. / model.texHeight, 1)) textureNode.addChild(posTranslateNode) return textureNode
class PendingImportNode(Node, QtCore.QObject): __node_id_counter = 0 def __init__(self, pendingImport, textureAtlas): """ A scenegraph node displaying an object that will be imported later, including live and deferred views of the object with transformed items, and a BoxHandle for moving the item. Parameters ---------- pendingImport: PendingImport The object to be imported. The PendingImportNode responds to changes in this object's position, rotation, and scale. textureAtlas: TextureAtlas The textures and block models used to render the preview of the object. Attributes ---------- basePosition: Vector The pre-transform position of the pending import. This is equal to `self.pendingImport.basePosition` except when the node is currently being dragged. transformedPosition: Vector The post-transform position of the pending import. This is equal to `self.pendingImport.importPos` except when the node is currently being dragged, scaled, or rotated. """ super(PendingImportNode, self).__init__() self.textureAtlas = textureAtlas self.pendingImport = pendingImport dim = pendingImport.sourceDim # positionTranslateNode contains the non-transformed preview of the imported # object, including its world scene. This preview will be rotated model-wise # while the user is dragging the rotate controls. self.positionTranslateNode = TranslateNode() self.rotateNode = Rotate3DNode() self.addChild(self.positionTranslateNode) self.positionTranslateNode.addChild(self.rotateNode) self.rotateNode.setAnchor(self.pendingImport.selection.size * 0.5) # worldSceneTranslateNode is contained by positionTranslateNode, and # serves to translate the world scene back to 0, 0, 0 so the positionTranslateNode # can translate by the current position. self.worldSceneTranslateNode = TranslateNode() self.worldScene = WorldScene(dim, textureAtlas, bounds=pendingImport.selection) self.worldScene.depthOffsetNode.depthOffset = DepthOffset.PreviewRenderer # transformedWorldTranslateNode contains the transformed preview of the imported # object, including a world scene that displays the object wrapped by a # DimensionTransform. self.transformedWorldTranslateNode = TranslateNode() self.transformedWorldScene = None self.addChild(self.transformedWorldTranslateNode) self.worldSceneTranslateNode.translateOffset = -self.pendingImport.selection.origin self.worldSceneTranslateNode.addChild(self.worldScene) self.rotateNode.addChild(self.worldSceneTranslateNode) # handleNode displays a bounding box that can be moved around, and responds # to mouse events. box = BoundingBox(pendingImport.importPos, pendingImport.importBounds.size) self.handleNode = BoxHandle() self.handleNode.bounds = box self.handleNode.resizable = False self.updateTransformedScene() self.basePosition = pendingImport.basePosition self.handleNode.boundsChanged.connect(self.handleBoundsChanged) self.handleNode.boundsChangedDone.connect(self.handleBoundsChangedDone) self.addChild(self.handleNode) # loads the non-transformed world scene asynchronously. self.loader = WorldLoader(self.worldScene, list(pendingImport.selection.chunkPositions())) self.loader.startLoader() self.pendingImport.positionChanged.connect(self.setPosition) self.pendingImport.rotationChanged.connect(self.setRotation) # Emitted when the user finishes dragging the box handle and releases the mouse # button. Arguments are (newPosition, oldPosition). importMoved = QtCore.Signal(object, object) def handleBoundsChangedDone(self, bounds, oldBounds): point = self.getBaseFromTransformed(bounds.origin) oldPoint = self.getBaseFromTransformed(oldBounds.origin) if point != oldPoint: self.importMoved.emit(point, oldPoint) def handleBoundsChanged(self, bounds): point = self.getBaseFromTransformed(bounds.origin) if self.basePosition != point: self.basePosition = point def getBaseFromTransformed(self, point): offset = self.pendingImport.basePosition - self.pendingImport.importPos return point + offset def setPreviewRotation(self, rots): self.rotateNode.visible = True self.worldSceneTranslateNode.visible = True self.transformedWorldTranslateNode.visible = False self.rotateNode.setRotation(rots) def setRotation(self, rots): self.updateTransformedScene() self.updateBoxHandle() self.rotateNode.setRotation(rots) def updateTransformedScene(self): if self.pendingImport.transformedDim is not None: log.info("Showing transformed scene") self.rotateNode.visible = False self.worldSceneTranslateNode.visible = False self.transformedWorldTranslateNode.visible = True if self.transformedWorldScene: self.transformedWorldTranslateNode.removeChild(self.transformedWorldScene) self.transformedWorldScene = WorldScene(self.pendingImport.transformedDim, self.textureAtlas) self.transformedWorldScene.depthOffsetNode.depthOffset = DepthOffset.PreviewRenderer self.transformedWorldTranslateNode.addChild(self.transformedWorldScene) self.updateTransformedSceneOffset() cPos = list(self.pendingImport.transformedDim.chunkPositions()) self.loader = WorldLoader(self.transformedWorldScene, cPos) self.loader.startLoader() else: log.info("Hiding transformed scene") self.rotateNode.visible = True self.worldSceneTranslateNode.visible = True self.transformedWorldTranslateNode.visible = False if self.transformedWorldScene: self.transformedWorldTranslateNode.removeChild(self.transformedWorldScene) self.transformedWorldScene = None def updateTransformedSceneOffset(self): self.transformedWorldTranslateNode.translateOffset = self.transformedPosition - self.pendingImport.importDim.bounds.origin @property def transformedPosition(self): return self.basePosition + self.pendingImport.importPos - self.pendingImport.basePosition @property def basePosition(self): return self.positionTranslateNode.translateOffset @basePosition.setter def basePosition(self, value): if value == self.positionTranslateNode.translateOffset: return self.positionTranslateNode.translateOffset = Vector(*value) self.updateTransformedSceneOffset() self.updateBoxHandle() def setPosition(self, pos): self.basePosition = pos def updateBoxHandle(self): if self.transformedWorldScene is None: bounds = BoundingBox(self.basePosition, self.pendingImport.bounds.size) else: origin = self.transformedPosition bounds = BoundingBox(origin, self.pendingImport.importBounds.size) #if self.handleNode.bounds.size != bounds.size: self.handleNode.bounds = bounds # --- Mouse events --- # inherit from BoxHandle? def mouseMove(self, event): self.handleNode.mouseMove(event) def mousePress(self, event): self.handleNode.mousePress(event) def mouseRelease(self, event): self.handleNode.mouseRelease(event)
class PendingImportNode(Node, QtCore.QObject): __node_id_counter = 0 def __init__(self, pendingImport, textureAtlas): """ A scenegraph node displaying an object that will be imported later, including live and deferred views of the object with transformed items, and a BoxHandle for moving the item. Parameters ---------- pendingImport: PendingImport The object to be imported. The PendingImportNode responds to changes in this object's position, rotation, and scale. textureAtlas: TextureAtlas The textures and block models used to render the preview of the object. Attributes ---------- basePosition: Vector The pre-transform position of the pending import. This is equal to `self.pendingImport.basePosition` except when the node is currently being dragged. transformedPosition: Vector The post-transform position of the pending import. This is equal to `self.pendingImport.importPos` except when the node is currently being dragged, scaled, or rotated. """ super(PendingImportNode, self).__init__() self.textureAtlas = textureAtlas self.pendingImport = pendingImport dim = pendingImport.sourceDim # positionTranslateNode contains the non-transformed preview of the imported # object, including its world scene. This preview will be rotated model-wise # while the user is dragging the rotate controls. self.positionTranslateNode = TranslateNode() self.rotateNode = Rotate3DNode() self.addChild(self.positionTranslateNode) self.positionTranslateNode.addChild(self.rotateNode) self.rotateNode.setAnchor(self.pendingImport.selection.size * 0.5) # worldSceneTranslateNode is contained by positionTranslateNode, and # serves to translate the world scene back to 0, 0, 0 so the positionTranslateNode # can translate by the current position. self.worldSceneTranslateNode = TranslateNode() self.worldScene = WorldScene(dim, textureAtlas, bounds=pendingImport.selection) self.worldScene.depthOffsetNode.depthOffset = DepthOffset.PreviewRenderer # transformedWorldTranslateNode contains the transformed preview of the imported # object, including a world scene that displays the object wrapped by a # DimensionTransform. self.transformedWorldTranslateNode = TranslateNode() self.transformedWorldScene = None self.addChild(self.transformedWorldTranslateNode) self.worldSceneTranslateNode.translateOffset = -self.pendingImport.selection.origin self.worldSceneTranslateNode.addChild(self.worldScene) self.rotateNode.addChild(self.worldSceneTranslateNode) # handleNode displays a bounding box that can be moved around, and responds # to mouse events. box = BoundingBox(pendingImport.importPos, pendingImport.importBounds.size) self.handleNode = BoxHandle() self.handleNode.bounds = box self.handleNode.resizable = False self.updateTransformedScene() self.basePosition = pendingImport.basePosition self.handleNode.boundsChanged.connect(self.handleBoundsChanged) self.handleNode.boundsChangedDone.connect(self.handleBoundsChangedDone) self.addChild(self.handleNode) # loads the non-transformed world scene asynchronously. self.loader = WorldLoader( self.worldScene, list(pendingImport.selection.chunkPositions())) self.loader.startLoader() self.pendingImport.positionChanged.connect(self.setPosition) self.pendingImport.rotationChanged.connect(self.setRotation) # Emitted when the user finishes dragging the box handle and releases the mouse # button. Arguments are (newPosition, oldPosition). importMoved = QtCore.Signal(object, object) def handleBoundsChangedDone(self, bounds, oldBounds): point = self.getBaseFromTransformed(bounds.origin) oldPoint = self.getBaseFromTransformed(oldBounds.origin) if point != oldPoint: self.importMoved.emit(point, oldPoint) def handleBoundsChanged(self, bounds): point = self.getBaseFromTransformed(bounds.origin) if self.basePosition != point: self.basePosition = point def getBaseFromTransformed(self, point): offset = self.pendingImport.basePosition - self.pendingImport.importPos return point + offset def setPreviewRotation(self, rots): self.rotateNode.visible = True self.worldSceneTranslateNode.visible = True self.transformedWorldTranslateNode.visible = False self.rotateNode.setRotation(rots) def setRotation(self, rots): self.updateTransformedScene() self.updateBoxHandle() self.rotateNode.setRotation(rots) def updateTransformedScene(self): if self.pendingImport.transformedDim is not None: log.info("Showing transformed scene") self.rotateNode.visible = False self.worldSceneTranslateNode.visible = False self.transformedWorldTranslateNode.visible = True if self.transformedWorldScene: self.transformedWorldTranslateNode.removeChild( self.transformedWorldScene) self.transformedWorldScene = WorldScene( self.pendingImport.transformedDim, self.textureAtlas) self.transformedWorldScene.depthOffsetNode.depthOffset = DepthOffset.PreviewRenderer self.transformedWorldTranslateNode.addChild( self.transformedWorldScene) self.updateTransformedSceneOffset() cPos = list(self.pendingImport.transformedDim.chunkPositions()) self.loader = WorldLoader(self.transformedWorldScene, cPos) self.loader.startLoader() else: log.info("Hiding transformed scene") self.rotateNode.visible = True self.worldSceneTranslateNode.visible = True self.transformedWorldTranslateNode.visible = False if self.transformedWorldScene: self.transformedWorldTranslateNode.removeChild( self.transformedWorldScene) self.transformedWorldScene = None def updateTransformedSceneOffset(self): self.transformedWorldTranslateNode.translateOffset = self.transformedPosition - self.pendingImport.importDim.bounds.origin @property def transformedPosition(self): return self.basePosition + self.pendingImport.importPos - self.pendingImport.basePosition @property def basePosition(self): return self.positionTranslateNode.translateOffset @basePosition.setter def basePosition(self, value): if value == self.positionTranslateNode.translateOffset: return self.positionTranslateNode.translateOffset = Vector(*value) self.updateTransformedSceneOffset() self.updateBoxHandle() def setPosition(self, pos): self.basePosition = pos def updateBoxHandle(self): if self.transformedWorldScene is None: bounds = BoundingBox(self.basePosition, self.pendingImport.bounds.size) else: origin = self.transformedPosition bounds = BoundingBox(origin, self.pendingImport.importBounds.size) #if self.handleNode.bounds.size != bounds.size: self.handleNode.bounds = bounds # --- Mouse events --- # inherit from BoxHandle? def mouseMove(self, event): self.handleNode.mouseMove(event) def mousePress(self, event): self.handleNode.mousePress(event) def mouseRelease(self, event): self.handleNode.mouseRelease(event)
class BrushTool(EditorTool): name = "Brush" iconName = "brush" maxBrushSize = 512 def __init__(self, editorSession, *args, **kwargs): super(BrushTool, self).__init__(editorSession, *args, **kwargs) self.toolWidget = load_ui("editortools/brush.ui") self.brushMode = None self.brushLoader = None self.brushModesByName = {cls.name:cls() for cls in BrushModeClasses} modes = self.brushModesByName.values() modes.sort(key=lambda m: m.name) self.toolWidget.brushModeInput.setModes(modes) BrushModeSetting.connectAndCall(self.modeSettingChanged) self.cursorWorldScene = None self.cursorNode = TranslateNode() self.toolWidget.xSpinSlider.setMinimum(1) self.toolWidget.ySpinSlider.setMinimum(1) self.toolWidget.zSpinSlider.setMinimum(1) self.toolWidget.xSpinSlider.valueChanged.connect(self.setX) self.toolWidget.ySpinSlider.valueChanged.connect(self.setY) self.toolWidget.zSpinSlider.valueChanged.connect(self.setZ) self.toolWidget.brushShapeInput.shapeChanged.connect(self.updateCursor) self.toolWidget.brushShapeInput.shapeOptionsChanged.connect(self.updateCursor) self.fillBlock = editorSession.worldEditor.blocktypes["stone"] self.brushSize = BrushSizeSetting.value(QtGui.QVector3D(5, 5, 5)).toTuple() # calls updateCursor self.toolWidget.xSpinSlider.setValue(self.brushSize[0]) self.toolWidget.ySpinSlider.setValue(self.brushSize[1]) self.toolWidget.zSpinSlider.setValue(self.brushSize[2]) @property def hoverDistance(self): return self.toolWidget.hoverSpinSlider.value() _brushSize = (0, 0, 0) @property def brushSize(self): return self._brushSize @brushSize.setter def brushSize(self, value): self._brushSize = value BrushSizeSetting.setValue(QtGui.QVector3D(*self.brushSize)) self.updateCursor() def setX(self, val): x, y, z = self.brushSize x = float(val) self.brushSize = x, y, z def setY(self, val): x, y, z = self.brushSize y = float(val) self.brushSize = x, y, z def setZ(self, val): x, y, z = self.brushSize z = float(val) self.brushSize = x, y, z def setBlocktypes(self, types): if len(types) == 0: return self.fillBlock = types[0] self.updateCursor() def mousePress(self, event): pos = event.blockPosition vector = (event.blockFace.vector * self.hoverDistance) command = BrushCommand(self.editorSession, [pos + vector], self.options) self.editorSession.pushCommand(command) def mouseMove(self, event): if event.blockPosition: vector = (event.blockFace.vector * self.hoverDistance) assert isinstance(vector, Vector), "vector isa %s" % type(vector) self.cursorNode.translateOffset = event.blockPosition + vector @property def options(self): options = {'brushSize': self.brushSize, 'brushShape': self.brushShape, 'brushMode': self.brushMode} options.update(self.brushMode.getOptions()) return options def modeSettingChanged(self, value): self.brushMode = self.brushModesByName[value] stack = self.toolWidget.modeOptionsStack while stack.count(): stack.removeWidget(stack.widget(0)) widget = self.brushMode.createOptionsWidget(self) if widget: stack.addWidget(widget) @property def brushShape(self): return self.toolWidget.brushShapeInput.currentShape def updateCursor(self): log.info("Updating brush cursor") if self.cursorWorldScene: self.brushLoader.timer.stop() self.cursorNode.removeChild(self.cursorWorldScene) self.cursorNode.removeChild(self.cursorBoxNode) cursorLevel = self.brushMode.createCursorLevel(self) cursorBox = self.brushMode.brushBoxForPoint((0, 0, 0), self.options) self.cursorBoxNode = SelectionBoxNode() self.cursorBoxNode.selectionBox = cursorBox self.cursorBoxNode.filled = False self.cursorWorldScene = worldscene.WorldScene(cursorLevel, self.editorSession.textureAtlas) self.cursorWorldScene.depthOffsetNode.depthOffset = DepthOffset.PreviewRenderer self.cursorNode.addChild(self.cursorWorldScene) self.cursorNode.addChild(self.cursorBoxNode) self.brushLoader = WorldLoader(self.cursorWorldScene) self.brushLoader.startLoader()
class WorkplaneNode(scenenode.Node): def __init__(self): super(WorkplaneNode, self).__init__() self.translateNode = TranslateNode() self.addChild(self.translateNode) self.axis = 1 vertexNode = None _axis = 1 @property def axis(self): return self._axis @axis.setter def axis(self, axis): self._axis = axis self.dirty = True gridSize = 64 left = -gridSize // 2 right = gridSize // 2 gridArrayBuffer = VertexArrayBuffer((gridSize * 4, ), GL.GL_LINES, textures=False, lights=False) gridArrayBuffer.rgba[:] = 255, 255, 255, 100 # y=0, move by translating gridArrayBuffer.vertex[:, axis] = 0 axis1 = (axis - 1) % 3 axis2 = (axis + 1) % 3 # left edge gridArrayBuffer.vertex[0:gridSize * 2:2, axis2] = left gridArrayBuffer.vertex[0:gridSize * 2:2, axis1] = range(left, right) # right edge gridArrayBuffer.vertex[1:gridSize * 2:2, axis2] = right - 1 gridArrayBuffer.vertex[1:gridSize * 2:2, axis1] = range(left, right) # bottom edge gridArrayBuffer.vertex[gridSize * 2::2, axis1] = left gridArrayBuffer.vertex[gridSize * 2::2, axis2] = range(left, right) # top edge gridArrayBuffer.vertex[gridSize * 2 + 1::2, axis1] = right - 1 gridArrayBuffer.vertex[gridSize * 2 + 1::2, axis2] = range(left, right) if self.vertexNode: self.translateNode.removeChild(self.vertexNode) self.vertexNode = VertexNode([gridArrayBuffer]) self.translateNode.addChild(self.vertexNode) @property def position(self): return self.translateNode.translateOffset @position.setter def position(self, value): self.translateNode.translateOffset = value
class WorkplaneNode(scenenode.Node): def __init__(self): super(WorkplaneNode, self).__init__() self.translateNode = TranslateNode() self.addChild(self.translateNode) self.axis = 1 vertexNode = None _axis = 1 @property def axis(self): return self._axis @axis.setter def axis(self, axis): self._axis = axis self.dirty = True gridSize = 64 left = -gridSize // 2 right = gridSize // 2 gridArrayBuffer = VertexArrayBuffer((gridSize * 4,), GL.GL_LINES, textures=False, lights=False) gridArrayBuffer.rgba[:] = 255, 255, 255, 100 # y=0, move by translating gridArrayBuffer.vertex[:, axis] = 0 axis1 = (axis - 1) % 3 axis2 = (axis + 1) % 3 # left edge gridArrayBuffer.vertex[0 : gridSize * 2 : 2, axis2] = left gridArrayBuffer.vertex[0 : gridSize * 2 : 2, axis1] = range(left, right) # right edge gridArrayBuffer.vertex[1 : gridSize * 2 : 2, axis2] = right - 1 gridArrayBuffer.vertex[1 : gridSize * 2 : 2, axis1] = range(left, right) # bottom edge gridArrayBuffer.vertex[gridSize * 2 :: 2, axis1] = left gridArrayBuffer.vertex[gridSize * 2 :: 2, axis2] = range(left, right) # top edge gridArrayBuffer.vertex[gridSize * 2 + 1 :: 2, axis1] = right - 1 gridArrayBuffer.vertex[gridSize * 2 + 1 :: 2, axis2] = range(left, right) if self.vertexNode: self.translateNode.removeChild(self.vertexNode) self.vertexNode = VertexNode([gridArrayBuffer]) self.translateNode.addChild(self.vertexNode) @property def position(self): return self.translateNode.translateOffset @position.setter def position(self, value): self.translateNode.translateOffset = value
class BrushTool(EditorTool): name = "Brush" iconName = "brush" maxBrushSize = 512 def __init__(self, editorSession, *args, **kwargs): super(BrushTool, self).__init__(editorSession, *args, **kwargs) self.toolWidget = load_ui("editortools/brush.ui") self.brushMode = None self.brushLoader = None self.brushModesByName = {cls.name: cls() for cls in BrushModeClasses} modes = self.brushModesByName.values() modes.sort(key=lambda m: m.name) self.toolWidget.brushModeInput.setModes(modes) BrushModeSetting.connectAndCall(self.modeSettingChanged) self.cursorWorldScene = None self.cursorNode = TranslateNode() self.toolWidget.xSpinSlider.setMinimum(1) self.toolWidget.ySpinSlider.setMinimum(1) self.toolWidget.zSpinSlider.setMinimum(1) self.toolWidget.xSpinSlider.valueChanged.connect(self.setX) self.toolWidget.ySpinSlider.valueChanged.connect(self.setY) self.toolWidget.zSpinSlider.valueChanged.connect(self.setZ) self.toolWidget.brushShapeInput.shapeChanged.connect(self.updateCursor) self.toolWidget.brushShapeInput.shapeOptionsChanged.connect( self.updateCursor) self.fillBlock = editorSession.worldEditor.blocktypes["stone"] self.brushSize = BrushSizeSetting.value(QtGui.QVector3D( 5, 5, 5)).toTuple() # calls updateCursor self.toolWidget.xSpinSlider.setValue(self.brushSize[0]) self.toolWidget.ySpinSlider.setValue(self.brushSize[1]) self.toolWidget.zSpinSlider.setValue(self.brushSize[2]) @property def hoverDistance(self): return self.toolWidget.hoverSpinSlider.value() _brushSize = (0, 0, 0) @property def brushSize(self): return self._brushSize @brushSize.setter def brushSize(self, value): self._brushSize = value BrushSizeSetting.setValue(QtGui.QVector3D(*self.brushSize)) self.updateCursor() def setX(self, val): x, y, z = self.brushSize x = float(val) self.brushSize = x, y, z def setY(self, val): x, y, z = self.brushSize y = float(val) self.brushSize = x, y, z def setZ(self, val): x, y, z = self.brushSize z = float(val) self.brushSize = x, y, z def setBlocktypes(self, types): if len(types) == 0: return self.fillBlock = types[0] self.updateCursor() def mousePress(self, event): pos = event.blockPosition vector = (event.blockFace.vector * self.hoverDistance) command = BrushCommand(self.editorSession, [pos + vector], self.options) self.editorSession.pushCommand(command) def mouseMove(self, event): if event.blockPosition: vector = (event.blockFace.vector * self.hoverDistance) assert isinstance(vector, Vector), "vector isa %s" % type(vector) self.cursorNode.translateOffset = event.blockPosition + vector @property def options(self): options = { 'brushSize': self.brushSize, 'brushShape': self.brushShape, 'brushMode': self.brushMode } options.update(self.brushMode.getOptions()) return options def modeSettingChanged(self, value): self.brushMode = self.brushModesByName[value] stack = self.toolWidget.modeOptionsStack while stack.count(): stack.removeWidget(stack.widget(0)) widget = self.brushMode.createOptionsWidget(self) if widget: stack.addWidget(widget) @property def brushShape(self): return self.toolWidget.brushShapeInput.currentShape def updateCursor(self): log.info("Updating brush cursor") if self.cursorWorldScene: self.brushLoader.timer.stop() self.cursorNode.removeChild(self.cursorWorldScene) self.cursorNode.removeChild(self.cursorBoxNode) cursorLevel = self.brushMode.createCursorLevel(self) cursorBox = self.brushMode.brushBoxForPoint((0, 0, 0), self.options) self.cursorBoxNode = SelectionBoxNode() self.cursorBoxNode.selectionBox = cursorBox self.cursorBoxNode.filled = False self.cursorWorldScene = worldscene.WorldScene( cursorLevel, self.editorSession.textureAtlas) self.cursorWorldScene.depthOffsetNode.depthOffset = DepthOffset.PreviewRenderer self.cursorNode.addChild(self.cursorWorldScene) self.cursorNode.addChild(self.cursorBoxNode) self.brushLoader = WorldLoader(self.cursorWorldScene) self.brushLoader.startLoader()
class GenerateTool(EditorTool): name = "Generate" iconName = "generate" instantDisplayChunks = 32 def __init__(self, *args, **kwargs): EditorTool.__init__(self, *args, **kwargs) self.livePreview = False self.blockPreview = False self.glPreview = True toolWidget = QtGui.QWidget() self.toolWidget = toolWidget column = [] self.generatorTypes = [pluginClass(self) for pluginClass in _pluginClasses] self.currentGenerator = None if len(self.generatorTypes): self.currentGenerator = self.generatorTypes[0] self.generatorTypeInput = QtGui.QComboBox() self.generatorTypesChanged() self.generatorTypeInput.currentIndexChanged.connect(self.currentTypeChanged) self.livePreviewCheckbox = QtGui.QCheckBox("Live Preview") self.livePreviewCheckbox.setChecked(self.livePreview) self.livePreviewCheckbox.toggled.connect(self.livePreviewToggled) self.blockPreviewCheckbox = QtGui.QCheckBox("Block Preview") self.blockPreviewCheckbox.setChecked(self.blockPreview) self.blockPreviewCheckbox.toggled.connect(self.blockPreviewToggled) self.glPreviewCheckbox = QtGui.QCheckBox("GL Preview") self.glPreviewCheckbox.setChecked(self.glPreview) self.glPreviewCheckbox.toggled.connect(self.glPreviewToggled) self.optionsHolder = QtGui.QStackedWidget() self.optionsHolder.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) self.generateButton = QtGui.QPushButton(self.tr("Generate")) self.generateButton.clicked.connect(self.generateClicked) column.append(self.generatorTypeInput) column.append(self.livePreviewCheckbox) column.append(self.blockPreviewCheckbox) column.append(self.glPreviewCheckbox) column.append(self.optionsHolder) column.append(self.generateButton) self.toolWidget.setLayout(Column(*column)) self.overlayNode = scenenode.Node() self.sceneHolderNode = TranslateNode() self.overlayNode.addChild(self.sceneHolderNode) self.previewNode = None self.boxHandleNode = BoxHandle() self.boxHandleNode.boundsChanged.connect(self.boundsDidChange) self.boxHandleNode.boundsChangedDone.connect(self.boundsDidChangeDone) self.overlayNode.addChild(self.boxHandleNode) self.worldScene = None self.loader = None self.previewBounds = None self.schematicBounds = None self.currentSchematic = None self.currentTypeChanged(0) # Name of last selected generator plugin is saved after unloading # so it can be reselected if it is immediately reloaded self._lastTypeName = None _GeneratePlugins.instance.pluginAdded.connect(self.addPlugin) _GeneratePlugins.instance.pluginRemoved.connect(self.removePlugin) def removePlugin(self, cls): log.info("Removing plugin %s", cls.__name__) self.generatorTypes[:] = [gt for gt in self.generatorTypes if not isinstance(gt, cls)] self.generatorTypesChanged() if self.currentGenerator not in self.generatorTypes: lastTypeName = self.currentGenerator.__class__.__name__ self.currentTypeChanged(0) # resets self._lastTypeName self._lastTypeName = lastTypeName def addPlugin(self, cls): log.info("Adding plugin %s", cls.__name__) self.generatorTypes.append(cls(self)) self.generatorTypesChanged() if self._lastTypeName is not None: if cls.__name__ == self._lastTypeName: self.currentTypeChanged(len(self.generatorTypes)-1) def generatorTypesChanged(self): self.generatorTypeInput.clear() for gt in self.generatorTypes: if hasattr(gt, 'displayName'): displayName = gt.displayName else: displayName = gt.__class__.__name__ self.generatorTypeInput.addItem(displayName, gt) def livePreviewToggled(self, value): self.livePreview = value def blockPreviewToggled(self, value): self.blockPreview = value if value: if self.currentSchematic: self.displaySchematic(self.currentSchematic, self.schematicBounds.origin) else: self.updateBlockPreview() else: self.clearSchematic() def glPreviewToggled(self, value): self.glPreview = value if value: self.updateNodePreview() # xxx cache previewNode? else: self.clearNode() def currentTypeChanged(self, index): # user selected generator type after old type was unloaded, so forget the old type self._lastTypeName = None self.optionsHolder.removeWidget(self.optionsHolder.widget(0)) if index < len(self.generatorTypes): self.currentGenerator = self.generatorTypes[index] self.optionsHolder.addWidget(self.currentGenerator.getOptionsWidget()) self.updatePreview() else: self.currentGenerator = None self.clearSchematic() self.clearNode() log.info("Chose generator %s", repr(self.currentGenerator)) def mousePress(self, event): self.boxHandleNode.mousePress(event) def mouseMove(self, event): self.boxHandleNode.mouseMove(event) def mouseRelease(self, event): self.boxHandleNode.mouseRelease(event) def boundsDidChange(self, bounds): # box still being resized if not self.currentGenerator: return if not self.livePreview: return self.previewBounds = bounds self.currentGenerator.boundsChanged(bounds) self.updatePreview() def boundsDidChangeDone(self, bounds, oldBounds): # box finished resize if not self.currentGenerator: return self.previewBounds = bounds self.schematicBounds = bounds self.currentGenerator.boundsChanged(bounds) self.updatePreview() def clearNode(self): if self.previewNode: self.overlayNode.removeChild(self.previewNode) self.previewNode = None def updatePreview(self): if self.blockPreview: self.updateBlockPreview() else: self.clearSchematic() if self.glPreview: self.updateNodePreview() else: self.clearNode() def updateBlockPreview(self): bounds = self.previewBounds if bounds is not None and bounds.volume > 0: self.generateNextSchematic(bounds) else: self.clearSchematic() def updateNodePreview(self): bounds = self.previewBounds if self.currentGenerator is None: return if bounds is not None and bounds.volume > 0: try: node = self.currentGenerator.getPreviewNode(bounds) except Exception: log.exception("Error while getting scene nodes from generator:") else: if node is not None: self.clearNode() if isinstance(node, list): nodes = node node = scenenode.Node() for c in nodes: node.addChild(c) self.overlayNode.addChild(node) self.previewNode = node def generateNextSchematic(self, bounds): if bounds is None: self.clearSchematic() return if self.currentGenerator is None: return try: schematic = self.currentGenerator.generate(bounds, self.editorSession.worldEditor.blocktypes) self.currentSchematic = schematic self.displaySchematic(schematic, bounds.origin) except Exception as e: log.exception("Error while running generator %s: %s", self.currentGenerator, e) QtGui.QMessageBox.warning(QtGui.qApp.mainWindow, "Error while running generator", "An error occurred while running the generator: \n %s.\n\n" "Traceback: %s" % (e, traceback.format_exc())) self.livePreview = False def displaySchematic(self, schematic, offset): if schematic is not None: dim = schematic.getDimension() if self.worldScene: self.sceneHolderNode.removeChild(self.worldScene) self.loader.timer.stop() self.loader = None atlas = self.editorSession.textureAtlas self.worldScene = WorldScene(dim, atlas) self.sceneHolderNode.translateOffset = offset self.sceneHolderNode.addChild(self.worldScene) self.loader = WorldLoader(self.worldScene) if dim.chunkCount() <= self.instantDisplayChunks: exhaust(self.loader.work()) else: self.loader.startLoader() else: self.clearSchematic() def clearSchematic(self): if self.worldScene: self.sceneHolderNode.removeChild(self.worldScene) self.worldScene = None self.loader.timer.stop() self.loader = None def generateClicked(self): if self.currentGenerator is None: return if self.schematicBounds is None: log.info("schematicBounds is None, not generating") return if self.currentSchematic is None: log.info("Generating new schematic for import") currentSchematic = self.currentGenerator.generate(self.schematicBounds, self.editorSession.worldEditor.blocktypes) else: log.info("Importing previously generated schematic.") currentSchematic = self.currentSchematic command = GenerateCommand(self, self.schematicBounds) try: with command.begin(): if currentSchematic is not None: task = self.editorSession.currentDimension.importSchematicIter(currentSchematic, self.schematicBounds.origin) showProgress(self.tr("Importing generated object..."), task) else: task = self.currentGenerator.generateInWorld(self.schematicBounds, self.editorSession.currentDimension) showProgress(self.tr("Generating object in world..."), task) except Exception as e: log.exception("Error while importing or generating in world: %r" % e) command.undo() else: self.editorSession.pushCommand(command)