# SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * * # * Copyright (c) 2018 Yorik van Havre * # * * # * This file is part of FreeCAD. * # * * # * FreeCAD is free software: you can redistribute it and/or modify it * # * under the terms of the GNU Lesser General Public License as * # * published by the Free Software Foundation, either version 2.1 of the * # * License, or (at your option) any later version. * # * * # * FreeCAD is distributed in the hope that it will be useful, but * # * WITHOUT ANY WARRANTY; without even the implied warranty of * # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * # * Lesser General Public License for more details. * # * * # * You should have received a copy of the GNU Lesser General Public * # * License along with FreeCAD. If not, see * # * . * # * * # *************************************************************************** """The BIM Box command""" import FreeCAD import FreeCADGui QT_TRANSLATE_NOOP = FreeCAD.Qt.QT_TRANSLATE_NOOP translate = FreeCAD.Qt.translate class BIM_Box: def GetResources(self): return { "Pixmap": "BIM_Box", "MenuText": QT_TRANSLATE_NOOP("BIM_Box", "Box"), "ToolTip": QT_TRANSLATE_NOOP( "BIM_Box", "Graphically creates a generic box in the current document" ), } def IsActive(self): v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph") return v def Activated(self): import WorkingPlane import draftguitools.gui_trackers as DraftTrackers FreeCAD.activeDraftCommand = self # register as a Draft command for auto grid on/off self.doc = FreeCAD.ActiveDocument self.wp = WorkingPlane.get_working_plane() # here we will store our points self.points = [] # we build a special cube tracker which is a list of 4 rectangle trackers self.cubetracker = [] self.LengthValue = 0 self.WidthValue = 0 self.HeightValue = 0 self.currentpoint = None for i in range(4): self.cubetracker.append(DraftTrackers.rectangleTracker()) if hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper.getPoint( callback=self.PointCallback, movecallback=self.MoveCallback, extradlg=self.taskbox(), ) def MoveCallback(self, point, snapinfo): import DraftGeomUtils import DraftVecUtils self.currentpoint = point if len(self.points) == 1: # we have the base point already self.Length.setText( FreeCAD.Units.Quantity( self.points[-1].sub(point).Length, FreeCAD.Units.Length ).UserString ) self.Length.setFocus() self.Length.setSelection(0, FreeCADGui.draftToolBar.number_length(self.Length.text())) elif len(self.points) == 2: # now we already have our base line, we update the 1st rectangle p = point v1 = point.sub(self.points[1]) v4 = v1.cross(self.points[1].sub(self.points[0])) if v4 and v4.Length: n = (self.points[1].sub(self.points[0])).cross(v4) if n and n.Length: n = DraftVecUtils.project(v1, n) p = self.points[1].add(n) self.cubetracker[0].p3(p) self.Width.setText( FreeCAD.Units.Quantity( self.cubetracker[0].getSize()[1], FreeCAD.Units.Length ).UserString ) self.Width.setFocus() self.Width.setSelection(0, FreeCADGui.draftToolBar.number_length(self.Width.text())) elif len(self.points) == 3: h = DraftGeomUtils.distance_to_plane(point, self.cubetracker[0].p3(), self.normal) w = self.normal * h # then we update all rectangles self.cubetracker[1].p3((self.cubetracker[0].p2()).add(w)) self.cubetracker[2].p3((self.cubetracker[0].p4()).add(w)) self.cubetracker[3].p1((self.cubetracker[0].p1()).add(w)) self.cubetracker[3].p3((self.cubetracker[0].p3()).add(w)) self.Height.setText(FreeCAD.Units.Quantity(h, FreeCAD.Units.Length).UserString) self.Height.setFocus() self.Height.setSelection(0, FreeCADGui.draftToolBar.number_length(self.Height.text())) def PointCallback(self, point, snapinfo): if not point: # cancelled self._finish() return if len(self.points) == 0: # this is our first clicked point, nothing to do just yet self.points.append(point) FreeCADGui.Snapper.getPoint( last=point, callback=self.PointCallback, movecallback=self.MoveCallback, extradlg=self.taskbox(), ) elif len(self.points) == 1: # this is our second point baseline = point.sub(self.points[0]) self.points.append(point) self._setupForWidthInput(baseline) elif len(self.points) == 2: # this is our third point self.points.append(point) self._setupForHeightInput() elif len(self.points) == 3: # finally we have all our points. Let's create the actual cube self._makeBox() self._finish() def taskbox(self): "sets up a taskbox widget" from PySide import QtGui wid = QtGui.QWidget() ui = FreeCADGui.UiLoader() wid.setWindowTitle(translate("BIM", "Box dimensions")) grid = QtGui.QGridLayout(wid) label1 = QtGui.QLabel(translate("BIM", "Length")) self.Length = ui.createWidget("Gui::InputField") l = FreeCAD.Units.Quantity(self.LengthValue, FreeCAD.Units.Length) self.Length.setText(l.UserString) grid.addWidget(label1, 0, 0, 1, 1) grid.addWidget(self.Length, 0, 1, 1, 1) if self.LengthValue: self.Length.setEnabled(False) label2 = QtGui.QLabel(translate("BIM", "Width")) self.Width = ui.createWidget("Gui::InputField") l = FreeCAD.Units.Quantity(self.WidthValue, FreeCAD.Units.Length) self.Width.setText(l.UserString) grid.addWidget(label2, 1, 0, 1, 1) grid.addWidget(self.Width, 1, 1, 1, 1) if self.WidthValue or (not self.LengthValue): self.Width.setEnabled(False) label3 = QtGui.QLabel(translate("BIM", "Height")) self.Height = ui.createWidget("Gui::InputField") l = FreeCAD.Units.Quantity(self.HeightValue, FreeCAD.Units.Length) self.Height.setText(l.UserString) grid.addWidget(label3, 2, 0, 1, 1) grid.addWidget(self.Height, 2, 1, 1, 1) if not self.WidthValue: self.Height.setEnabled(False) self.Length.textEdited.connect(FreeCADGui.draftToolBar.checkSpecialChars) self.Width.textEdited.connect(FreeCADGui.draftToolBar.checkSpecialChars) self.Height.textEdited.connect(FreeCADGui.draftToolBar.checkSpecialChars) self.Length.valueChanged.connect(self.setLength) self.Width.valueChanged.connect(self.setWidth) self.Height.valueChanged.connect(self.setHeight) self.Length.returnPressed.connect(self.setLengthUI) self.Width.returnPressed.connect(self.setWidthUI) self.Height.returnPressed.connect(self.setHeightUI) return wid def setLength(self, d): self.LengthValue = d def setWidth(self, d): self.WidthValue = d def setHeight(self, d): self.HeightValue = d def setLengthUI(self): if (len(self.points) == 1) and self.currentpoint and self.LengthValue: baseline = self.currentpoint.sub(self.points[0]) baseline.normalize() baseline.multiply(self.LengthValue) p2 = self.points[0].add(baseline) self.points.append(p2) self._setupForWidthInput(baseline) def setWidthUI(self): if (len(self.points) == 2) and self.currentpoint and self.WidthValue: self.normal = self.cubetracker[0].getNormal() if self.normal: n = (self.points[1].sub(self.points[0])).cross(self.normal) if n and n.Length: n.normalize() n.multiply(self.WidthValue) p2 = self.points[1].add(n) self.cubetracker[0].p3(p2) self.points.append(p2) self._setupForHeightInput() def setHeightUI(self): if (len(self.points) == 3) and self.HeightValue: self._makeBox() self._finish() def _setupForWidthInput(self, baseline): # we turn on only one of the rectangles self.cubetracker[0].setPlane(baseline) self.cubetracker[0].p1(self.points[0]) self.cubetracker[0].on() FreeCADGui.Snapper.getPoint( last=self.points[-1], callback=self.PointCallback, movecallback=self.MoveCallback, extradlg=self.taskbox(), ) def _setupForHeightInput(self): # we can get the cubes Z axis from our first rectangle self.normal = self.cubetracker[0].getNormal() # we can therefore define the (u,v) planes of all rectangles u = self.cubetracker[0].u v = self.cubetracker[0].v self.cubetracker[1].setPlane(u, self.normal) self.cubetracker[2].setPlane(u, self.normal) self.cubetracker[3].setPlane(u, v) # and the origin points of the vertical rectangles self.cubetracker[1].p1(self.cubetracker[0].p1()) self.cubetracker[2].p1(self.cubetracker[0].p3()) # finally we turn all rectangles on for r in self.cubetracker: r.on() point = self.cubetracker[0].p3() axis = self.cubetracker[0].p2().sub(self.cubetracker[0].p3()) self.wp._save() self.wp.align_to_point_and_axis(point, axis, upvec=self.normal, _hist_add=False) FreeCADGui.Snapper.getPoint( last=point, callback=self.PointCallback, movecallback=self.MoveCallback, extradlg=self.taskbox(), ) def _makeBox(self): import DraftGeomUtils p1 = self.cubetracker[0].p1() p2 = self.cubetracker[0].p2() p3 = self.cubetracker[0].p4() pla = DraftGeomUtils.placement_from_points(p1, p2, p3) if self.normal.isEqual(pla.Rotation.multVec(FreeCAD.Vector(0, 0, 1)), 1e-6): if self.HeightValue < 0.0: pla = DraftGeomUtils.placement_from_points(p1, p3, p2) self.LengthValue, self.WidthValue = self.WidthValue, self.LengthValue else: if self.HeightValue > 0.0: pla = DraftGeomUtils.placement_from_points(p1, p3, p2) self.LengthValue, self.WidthValue = self.WidthValue, self.LengthValue self.doc.openTransaction(translate("Arch", "Create Box")) cube = self.doc.addObject("Part::Box", "Box") cube.Placement = pla cube.Length = self.LengthValue cube.Width = self.WidthValue cube.Height = abs(self.HeightValue) self.doc.commitTransaction() self.doc.recompute() def _finish(self): self.wp._restore() FreeCAD.activeDraftCommand = None FreeCADGui.Snapper.off() for c in self.cubetracker: c.finalize() FreeCADGui.addCommand("BIM_Box", BIM_Box())