diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 666a32db1e..e26737159a 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1680,6 +1680,15 @@ def filterObjectsForModifiers(objects, isCopied=False): filteredObjects.append(object) return filteredObjects +def rotateVertex(object, vertex_index, angle, center, axis): + points = object.Points + v = object.Placement.multVec(points[vertex_index]) + rv = v.sub(center) + rv = DraftVecUtils.rotate(rv, math.radians(angle), axis) + v = center.add(rv) + points[vertex_index] = object.Placement.inverse().multVec(v) + object.Points = points + def rotate(objectslist,angle,center=Vector(0,0,0),axis=Vector(0,0,1),copy=False): '''rotate(objects,angle,[center,axis,copy]): Rotates the objects contained in objects (that can be a list of objects or an object) of the given angle diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 869fe30b52..d64727c067 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -2780,36 +2780,158 @@ class Rotate(Modifier): def Activated(self): Modifier.Activated(self,"Rotate") - self.ghost = None + if not self.ui: + return + self.ghosts = [] self.arctrack = None - if self.ui: - if not FreeCADGui.Selection.getSelection(): - self.ui.selectUi() - msg(translate("draft", "Select an object to rotate")+"\n") - self.call = self.view.addEventCallback("SoEvent",selectObject) - else: - self.proceed() + self.get_object_selection() + + def get_object_selection(self): + if FreeCADGui.Selection.getSelection(): + return self.proceed() + self.ui.selectUi() + msg(translate("draft", "Select an object to rotate")+"\n") + self.call = self.view.addEventCallback("SoEvent", selectObject) def proceed(self): - if self.call: self.view.removeEventCallback("SoEvent",self.call) - self.sel = FreeCADGui.Selection.getSelection() - self.sel = Draft.getGroupContents(self.sel,addgroups=True,spaces=True,noarchchild=True) + if self.call: + self.view.removeEventCallback("SoEvent", self.call) + self.selected_objects = FreeCADGui.Selection.getSelection() + self.selected_objects = Draft.getGroupContents(self.selected_objects, addgroups=True, spaces=True, noarchchild=True) + self.selected_subelements = FreeCADGui.Selection.getSelectionEx() self.step = 0 self.center = None self.ui.arcUi() self.ui.modUi() self.ui.setTitle("Rotate") self.arctrack = arcTracker() - self.ghost = ghostTracker(self.sel) self.call = self.view.addEventCallback("SoEvent",self.action) msg(translate("draft", "Pick rotation center:")+"\n") - def finish(self,closed=False,cont=False): + def action(self, arg): + "scene event handler" + if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE": + self.finish() + elif arg["Type"] == "SoLocation2Event": + self.handle_mouse_move_event(arg) + elif arg["Type"] == "SoMouseButtonEvent" \ + and arg["State"] == "DOWN" \ + and arg["Button"] == "BUTTON1": + self.handle_mouse_click_event(arg) + + def handle_mouse_move_event(self, arg): + for ghost in self.ghosts: + ghost.off() + self.point,ctrlPoint,info = getPoint(self,arg) + # this is to make sure radius is what you see on screen + if self.center and DraftVecUtils.dist(self.point,self.center): + viewdelta = DraftVecUtils.project(self.point.sub(self.center), plane.axis) + if not DraftVecUtils.isNull(viewdelta): + self.point = self.point.add(viewdelta.negative()) + if self.extendedCopy: + if not hasMod(arg,MODALT): + self.step = 3 + self.finish() + if (self.step == 0): + pass + elif (self.step == 1): + currentrad = DraftVecUtils.dist(self.point,self.center) + if (currentrad != 0): + angle = DraftVecUtils.angle(plane.u, self.point.sub(self.center), plane.axis) + else: angle = 0 + self.ui.setRadiusValue(math.degrees(angle),unit="Angle") + self.firstangle = angle + self.ui.radiusValue.setFocus() + self.ui.radiusValue.selectAll() + elif (self.step == 2): + currentrad = DraftVecUtils.dist(self.point,self.center) + if (currentrad != 0): + angle = DraftVecUtils.angle(plane.u, self.point.sub(self.center), plane.axis) + else: angle = 0 + if (angle < self.firstangle): + sweep = (2*math.pi-self.firstangle)+angle + else: + sweep = angle - self.firstangle + self.arctrack.setApertureAngle(sweep) + for ghost in self.ghosts: + ghost.rotate(plane.axis,sweep) + ghost.on() + self.ui.setRadiusValue(math.degrees(sweep), 'Angle') + self.ui.radiusValue.setFocus() + self.ui.radiusValue.selectAll() + redraw3DView() + + def handle_mouse_click_event(self, arg): + if not self.point: + return + if self.step == 0: + self.set_center() + elif self.step == 1: + self.set_start_point() + else: + self.set_rotation_angle(arg) + + def set_center(self): + if not self.ghosts: + self.set_ghosts() + self.center = self.point + self.node = [self.point] + self.ui.radiusUi() + self.ui.radiusValue.setText(FreeCAD.Units.Quantity(0,FreeCAD.Units.Angle).UserString) + self.ui.hasFill.hide() + self.ui.labelRadius.setText("Base angle") + self.arctrack.setCenter(self.center) + for ghost in self.ghosts: + ghost.center(self.center) + self.step = 1 + msg(translate("draft", "Pick base angle:")+"\n") + if self.planetrack: + self.planetrack.set(self.point) + + def set_start_point(self): + self.ui.labelRadius.setText("Rotation") + self.rad = DraftVecUtils.dist(self.point,self.center) + self.arctrack.on() + self.arctrack.setStartPoint(self.point) + for ghost in self.ghosts: + ghost.on() + self.step = 2 + msg(translate("draft", "Pick rotation angle:")+"\n") + + def set_rotation_angle(self, arg): + currentrad = DraftVecUtils.dist(self.point,self.center) + angle = self.point.sub(self.center).getAngle(plane.u) + if DraftVecUtils.project(self.point.sub(self.center), plane.v).getAngle(plane.v) > 1: + angle = -angle + if (angle < self.firstangle): + self.angle = (2*math.pi-self.firstangle)+angle + else: + self.angle = angle - self.firstangle + self.rotate(self.ui.isCopy.isChecked() or hasMod(arg,MODALT)) + if hasMod(arg,MODALT): + self.extendedCopy = True + else: + self.finish(cont=True) + + def set_ghosts(self): + if self.ui.isSubelementMode.isChecked(): + return self.set_subelement_ghosts() + self.ghosts = [ghostTracker(self.selected_objects)] + + def set_subelement_ghosts(self): + import Part + for object in self.selected_subelements: + for subelement in object.SubObjects: + if isinstance(subelement, Part.Vertex) \ + or isinstance(subelement, Part.Edge): + self.ghosts.append(ghostTracker(subelement)) + + def finish(self, closed=False, cont=False): "finishes the arc" if self.arctrack: self.arctrack.finalize() - if self.ghost: - self.ghost.finalize() + for ghost in self.ghosts: + ghost.finalize() if cont and self.ui: if self.ui.continueMode: todo.delayAfter(self.Activated,[]) @@ -2817,112 +2939,52 @@ class Rotate(Modifier): if self.doc: self.doc.recompute() - def rot (self,angle,copy=False): - "rotating the real shapes" - sel = '[' - for o in self.sel: - if len(sel) > 1: - sel += ',' - sel += 'FreeCAD.ActiveDocument.'+o.Name - sel += ']' - FreeCADGui.addModule("Draft") - if copy: - self.commit(translate("draft","Copy"), - ['Draft.rotate('+sel+','+str(math.degrees(angle))+','+DraftVecUtils.toString(self.center)+',axis='+DraftVecUtils.toString(plane.axis)+',copy='+str(copy)+')']) + def rotate(self, is_copy=False): + if self.ui.isSubelementMode.isChecked(): + self.rotate_subelements(is_copy) else: - self.commit(translate("draft","Rotate"), - ['Draft.rotate('+sel+','+str(math.degrees(angle))+','+DraftVecUtils.toString(self.center)+',axis='+DraftVecUtils.toString(plane.axis)+',copy='+str(copy)+')']) + self.rotate_object(is_copy) - def action(self,arg): - "scene event handler" - if arg["Type"] == "SoKeyboardEvent": - if arg["Key"] == "ESCAPE": - self.finish() - elif arg["Type"] == "SoLocation2Event": - if self.ghost: - self.ghost.off() - self.point,ctrlPoint,info = getPoint(self,arg) - # this is to make sure radius is what you see on screen - if self.center and DraftVecUtils.dist(self.point,self.center): - viewdelta = DraftVecUtils.project(self.point.sub(self.center), plane.axis) - if not DraftVecUtils.isNull(viewdelta): - self.point = self.point.add(viewdelta.negative()) - if self.extendedCopy: - if not hasMod(arg,MODALT): - self.step = 3 - self.finish() - if (self.step == 0): - pass - elif (self.step == 1): - currentrad = DraftVecUtils.dist(self.point,self.center) - if (currentrad != 0): - angle = DraftVecUtils.angle(plane.u, self.point.sub(self.center), plane.axis) - else: angle = 0 - self.ui.setRadiusValue(math.degrees(angle),unit="Angle") - self.firstangle = angle - self.ui.radiusValue.setFocus() - self.ui.radiusValue.selectAll() - elif (self.step == 2): - currentrad = DraftVecUtils.dist(self.point,self.center) - if (currentrad != 0): - angle = DraftVecUtils.angle(plane.u, self.point.sub(self.center), plane.axis) - else: angle = 0 - if (angle < self.firstangle): - sweep = (2*math.pi-self.firstangle)+angle - else: - sweep = angle - self.firstangle - self.arctrack.setApertureAngle(sweep) - if self.ghost: - self.ghost.rotate(plane.axis,sweep) - self.ghost.on() - self.ui.setRadiusValue(math.degrees(sweep), 'Angle') - self.ui.radiusValue.setFocus() - self.ui.radiusValue.selectAll() - redraw3DView() + def rotate_subelements(self, is_copy): + self.commit(translate("draft", "Rotate"), self.build_rotate_subelements_command()) + return + try: + if self.ui.isCopy.isChecked(): + self.commit(translate("draft", "Copy"), self.build_copy_subelements_command()) + else: + self.commit(translate("draft", "Rotate"), self.build_rotate_subelements_command()) + except: + FreeCAD.Console.PrintError(translate("draft", "Some subelements could not be moved.")) - elif arg["Type"] == "SoMouseButtonEvent": - if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): - if self.point: - if (self.step == 0): - self.center = self.point - self.node = [self.point] - self.ui.radiusUi() - self.ui.radiusValue.setText(FreeCAD.Units.Quantity(0,FreeCAD.Units.Angle).UserString) - self.ui.hasFill.hide() - self.ui.labelRadius.setText("Base angle") - self.arctrack.setCenter(self.center) - if self.ghost: - self.ghost.center(self.center) - self.step = 1 - msg(translate("draft", "Pick base angle:")+"\n") - if self.planetrack: - self.planetrack.set(self.point) - elif (self.step == 1): - self.ui.labelRadius.setText("Rotation") - self.rad = DraftVecUtils.dist(self.point,self.center) - self.arctrack.on() - self.arctrack.setStartPoint(self.point) - if self.ghost: - self.ghost.on() - self.step = 2 - msg(translate("draft", "Pick rotation angle:")+"\n") - else: - currentrad = DraftVecUtils.dist(self.point,self.center) - angle = self.point.sub(self.center).getAngle(plane.u) - if DraftVecUtils.project(self.point.sub(self.center), plane.v).getAngle(plane.v) > 1: - angle = -angle - if (angle < self.firstangle): - sweep = (2*math.pi-self.firstangle)+angle - else: - sweep = angle - self.firstangle - if self.ui.isCopy.isChecked() or hasMod(arg,MODALT): - self.rot(sweep,True) - else: - self.rot(sweep) - if hasMod(arg,MODALT): - self.extendedCopy = True - else: - self.finish(cont=True) + def build_copy_subelements_command(self): + pass + + def build_rotate_subelements_command(self): + import Part + command = [] + for object in self.selected_subelements: + for index, subelement in enumerate(object.SubObjects): + if isinstance(subelement, Part.Vertex): + command.append('Draft.rotateVertex(FreeCAD.ActiveDocument.{}, {}, {}, {}, {})'.format( + object.ObjectName, + int(object.SubElementNames[index][len("Vertex"):])-1, + math.degrees(self.angle), + DraftVecUtils.toString(self.center), + DraftVecUtils.toString(plane.axis))) + command.append('FreeCAD.ActiveDocument.recompute()') + return command + + def rotate_object(self, is_copy): + objects = '[' + ','.join(['FreeCAD.ActiveDocument.' + object.Name for object in self.selected_objects]) + ']' + FreeCADGui.addModule("Draft") + self.commit(translate("draft","Copy" if is_copy else "Rotate"), + ['Draft.rotate({},{},{},axis={},copy={})'.format( + objects, + math.degrees(self.angle), + DraftVecUtils.toString(self.center), + DraftVecUtils.toString(plane.axis), + is_copy + )]) def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when valid x, y, and z have been entered there" @@ -2949,7 +3011,7 @@ class Rotate(Modifier): self.step = 2 msg(translate("draft", "Pick rotation angle:")+"\n") else: - self.rot(math.radians(rad),self.ui.isCopy.isChecked()) + self.rotate(math.radians(rad),self.ui.isCopy.isChecked()) self.finish(cont=True)