# SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * * # * Copyright (c) 2024 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 * # * . * # * * # *************************************************************************** """BIM Panel-related Arch_""" import FreeCAD import FreeCADGui QT_TRANSLATE_NOOP = FreeCAD.Qt.QT_TRANSLATE_NOOP translate = FreeCAD.Qt.translate # Description l w t Presets = [ None, ["Plywood 12mm, 1220 x 2440", 1220, 2440, 12], ["Plywood 15mm, 1220 x 2440", 1220, 2440, 15], ["Plywood 18mm, 1220 x 2440", 1220, 2440, 18], ["Plywood 25mm, 1220 x 2440", 1220, 2440, 25], ["MDF 3mm, 900 x 600", 900, 600, 3], ["MDF 6mm, 900 x 600", 900, 600, 6], ["OSB 18mm, 1220 x 2440", 1220, 2440, 18], ] class Arch_Panel: "the Arch Panel Arch_ definition" def GetResources(self): return { "Pixmap": "Arch_Panel", "MenuText": QT_TRANSLATE_NOOP("Arch_Panel", "Panel"), "Accel": "P, A", "ToolTip": QT_TRANSLATE_NOOP( "Arch_Panel", "Creates a panel object from scratch or from a selected object (sketch, wire, face or solid)", ), } def IsActive(self): v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph") return v def Activated(self): import WorkingPlane import Draft import draftguitools.gui_trackers as DraftTrackers from draftutils import params self.doc = FreeCAD.ActiveDocument self.Length = params.get_param_arch("PanelLength") self.Width = params.get_param_arch("PanelWidth") self.Thickness = params.get_param_arch("PanelThickness") self.Profile = None self.featureName = "Panel" self.rotated = False sel = FreeCADGui.Selection.getSelection() if sel: if len(sel) == 1: if Draft.getType(sel[0]) == "Panel": return self.doc.openTransaction(str(translate("Arch", "Create Panel"))) FreeCADGui.addModule("Arch") FreeCADGui.addModule("Draft") for obj in sel: FreeCADGui.doCommand( "obj = Arch.makePanel(FreeCAD.ActiveDocument." + obj.Name + ",thickness=" + str(self.Thickness) + ")" ) FreeCADGui.doCommand("Draft.autogroup(obj)") self.doc.commitTransaction() self.doc.recompute() return # interactive mode FreeCAD.activeDraftCommand = self # register as a Draft command for auto grid on/off WorkingPlane.get_working_plane() self.points = [] self.tracker = DraftTrackers.boxTracker() self.tracker.width(self.Width) self.tracker.height(self.Thickness) self.tracker.length(self.Length) self.tracker.on() FreeCADGui.Snapper.getPoint( callback=self.getPoint, movecallback=self.update, extradlg=self.taskbox() ) FreeCADGui.draftToolBar.continueCmd.show() def getPoint(self, point=None, obj=None): "this function is called by the snapper when it has a 3D point" import DraftVecUtils FreeCAD.activeDraftCommand = None FreeCADGui.Snapper.off() self.tracker.finalize() if point is None: return self.doc.openTransaction(translate("Arch", "Create Panel")) FreeCADGui.addModule("Arch") if self.Profile: pr = Presets[self.Profile] FreeCADGui.doCommand( "p = Arch.makeProfile(" + str(pr[2]) + "," + str(pr[3]) + "," + str(pr[4]) + "," + str(pr[5]) + ")" ) FreeCADGui.doCommand("s = Arch.makePanel(p,thickness=" + str(self.Thickness) + ")") # FreeCADGui.doCommand('s.Placement.Rotation = FreeCAD.Rotation(-0.5,0.5,-0.5,0.5)') else: FreeCADGui.doCommand( "s = Arch.makePanel(length=" + str(self.Length) + ",width=" + str(self.Width) + ",thickness=" + str(self.Thickness) + ")" ) FreeCADGui.doCommand("s.Placement.Base = " + DraftVecUtils.toString(point)) if self.rotated: FreeCADGui.doCommand( "s.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(1.00,0.00,0.00),90.00)" ) self.doc.commitTransaction() self.doc.recompute() from PySide import QtCore QtCore.QTimer.singleShot(100, self.check_continueMode) def check_continueMode(self): "checks if continueMode is true and restarts Panel" if FreeCADGui.draftToolBar.continueMode: self.Activated() def taskbox(self): "sets up a taskbox widget" from draftutils import params from PySide import QtCore, QtGui w = QtGui.QWidget() ui = FreeCADGui.UiLoader() w.setWindowTitle(translate("Arch", "Panel options")) grid = QtGui.QGridLayout(w) # presets box labelp = QtGui.QLabel(translate("Arch", "Preset")) valuep = QtGui.QComboBox() fpresets = [" "] for p in Presets[1:]: fpresets.append(translate("Arch", p[0])) valuep.addItems(fpresets) grid.addWidget(labelp, 0, 0, 1, 1) grid.addWidget(valuep, 0, 1, 1, 1) # length label1 = QtGui.QLabel(translate("Arch", "Length")) self.vLength = ui.createWidget("Gui::InputField") self.vLength.setText(FreeCAD.Units.Quantity(self.Length, FreeCAD.Units.Length).UserString) grid.addWidget(label1, 1, 0, 1, 1) grid.addWidget(self.vLength, 1, 1, 1, 1) # width label2 = QtGui.QLabel(translate("Arch", "Width")) self.vWidth = ui.createWidget("Gui::InputField") self.vWidth.setText(FreeCAD.Units.Quantity(self.Width, FreeCAD.Units.Length).UserString) grid.addWidget(label2, 2, 0, 1, 1) grid.addWidget(self.vWidth, 2, 1, 1, 1) # height label3 = QtGui.QLabel(translate("Arch", "Thickness")) self.vHeight = ui.createWidget("Gui::InputField") self.vHeight.setText( FreeCAD.Units.Quantity(self.Thickness, FreeCAD.Units.Length).UserString ) grid.addWidget(label3, 3, 0, 1, 1) grid.addWidget(self.vHeight, 3, 1, 1, 1) # horizontal button value4 = QtGui.QPushButton(translate("Arch", "Rotate")) grid.addWidget(value4, 4, 0, 1, 2) valuep.currentIndexChanged.connect(self.setPreset) self.vLength.valueChanged.connect(self.setLength) self.vWidth.valueChanged.connect(self.setWidth) self.vHeight.valueChanged.connect(self.setThickness) value4.pressed.connect(self.rotate) return w def update(self, point, info): "this function is called by the Snapper when the mouse is moved" if FreeCADGui.Control.activeDialog(): self.tracker.pos(point) if self.rotated: self.tracker.width(self.Thickness) self.tracker.height(self.Width) self.tracker.length(self.Length) else: self.tracker.width(self.Width) self.tracker.height(self.Thickness) self.tracker.length(self.Length) def setWidth(self, d): from draftutils import params self.Width = d.Value params.set_param_arch("PanelWidth", d) def setThickness(self, d): from draftutils import params self.Thickness = d.Value params.set_param_arch("PanelThickness", d) def setLength(self, d): from draftutils import params self.Length = d.Value params.set_param_arch("PanelLength", d) def setPreset(self, i): if i > 0: self.vLength.setText( FreeCAD.Units.Quantity(float(Presets[i][1]), FreeCAD.Units.Length).UserString ) self.vWidth.setText( FreeCAD.Units.Quantity(float(Presets[i][2]), FreeCAD.Units.Length).UserString ) self.vHeight.setText( FreeCAD.Units.Quantity(float(Presets[i][3]), FreeCAD.Units.Length).UserString ) def rotate(self): self.rotated = not self.rotated class Arch_PanelCut: "the Arch Panel Cut command definition" def GetResources(self): return { "Pixmap": "Arch_Panel_Cut", "MenuText": QT_TRANSLATE_NOOP("Arch_Panel_Cut", "Panel Cut"), "Accel": "P, C", "ToolTip": QT_TRANSLATE_NOOP("Arch_Panel_Cut", "Creates 2D views of selected panels"), } def IsActive(self): v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph") return v def Activated(self): import Draft if FreeCADGui.Selection.getSelection(): FreeCAD.ActiveDocument.openTransaction(str(translate("Arch", "Create Panel Cut"))) FreeCADGui.addModule("Arch") for obj in FreeCADGui.Selection.getSelection(): if Draft.getType(obj) == "Panel": FreeCADGui.doCommand( "Arch.makePanelCut(FreeCAD.ActiveDocument." + obj.Name + ")" ) FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() class Arch_PanelSheet: "the Arch Panel Sheet definition" def GetResources(self): return { "Pixmap": "Arch_Panel_Sheet", "MenuText": QT_TRANSLATE_NOOP("Arch_Panel_Sheet", "Panel Sheet"), "Accel": "P, S", "ToolTip": QT_TRANSLATE_NOOP( "Arch_Panel_Sheet", "Creates a 2D sheet which can contain panel cuts" ), } def IsActive(self): v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph") return v def Activated(self): FreeCAD.ActiveDocument.openTransaction(str(translate("Arch", "Create Panel Sheet"))) FreeCADGui.addModule("Arch") if FreeCADGui.Selection.getSelection(): l = "[" for obj in FreeCADGui.Selection.getSelection(): l += "FreeCAD.ActiveDocument." + obj.Name + "," l += "]" FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() FreeCADGui.doCommand("__objs__ = " + l) FreeCADGui.doCommand("Arch.makePanelSheet(__objs__)") FreeCADGui.doCommand("del __objs__") else: FreeCADGui.doCommand("Arch.makePanelSheet()") FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() class Arch_Nest: "the Arch Panel definition" def GetResources(self): return { "Pixmap": "Arch_Nest", "MenuText": QT_TRANSLATE_NOOP("Arch_Nest", "Nest"), "Accel": "N, E", "ToolTip": QT_TRANSLATE_NOOP( "Arch_Nest", "Nests a series of selected shapes in a container" ), } def IsActive(self): v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph") return v def Activated(self): FreeCADGui.Control.closeDialog() FreeCADGui.Control.showDialog(NestTaskPanel()) class NestTaskPanel: """The TaskPanel for Arch Nest""" def __init__(self, obj=None): import ArchNesting self.form = FreeCADGui.PySideUic.loadUi(":/ui/ArchNest.ui") self.form.progressBar.hide() self.form.ButtonPreview.setEnabled(False) self.form.ButtonStop.setEnabled(False) self.form.ButtonContainer.pressed.connect(self.getContainer) self.form.ButtonShapes.pressed.connect(self.getShapes) self.form.ButtonRemove.pressed.connect(self.removeShapes) self.form.ButtonStart.pressed.connect(self.start) self.form.ButtonStop.pressed.connect(self.stop) self.form.ButtonPreview.pressed.connect(self.preview) self.shapes = [] self.container = None self.nester = None self.temps = [] def reject(self): self.stop() self.clearTemps() return True def accept(self): self.stop() self.clearTemps() if self.nester: FreeCAD.ActiveDocument.openTransaction("Nesting") self.nester.apply() FreeCAD.ActiveDocument.commitTransaction() return True def clearTemps(self): for t in self.temps: if FreeCAD.ActiveDocument.getObject(t.Name): FreeCAD.ActiveDocument.removeObject(t.Name) self.temps = [] def getContainer(self): import Draft s = FreeCADGui.Selection.getSelection() if len(s) == 1: if hasattr(s[0], "Shape"): if len(s[0].Shape.Faces) == 1: if not (s[0] in self.shapes): self.form.Container.clear() self.addObject(s[0], self.form.Container) self.container = s[0] else: FreeCAD.Console.PrintError(translate("Arch", "This object has no face")) if Draft.getType(s[0]) == "PanelSheet": if hasattr(s[0], "Rotations"): if s[0].Rotations: self.form.Rotations.setText(str(s[0].Rotations)) def getShapes(self): s = FreeCADGui.Selection.getSelection() for o in s: if hasattr(o, "Shape"): if not o in self.shapes: if o != self.container: self.addObject(o, self.form.Shapes) self.shapes.append(o) def addObject(self, obj, form): from PySide import QtGui i = QtGui.QListWidgetItem() i.setText(obj.Label) i.setToolTip(obj.Name) if hasattr(obj.ViewObject, "Proxy"): if hasattr(obj.ViewObject.Proxy, "getIcon"): i.setIcon(QtGui.QIcon(obj.ViewObject.Proxy.getIcon())) elif hasattr(obj.ViewObject, "Icon"): i.setIcon(QtGui.QIcon(obj.ViewObject.Icon)) else: i.setIcon(QtGui.QIcon(":/icons/Part_3D_object.svg")) form.addItem(i) def removeShapes(self): for i in self.form.Shapes.selectedItems(): o = FreeCAD.ActiveDocument.getObject(i.toolTip()) if o: if o in self.shapes: self.shapes.remove(o) self.form.Shapes.takeItem(self.form.Shapes.row(i)) def setCounter(self, value): self.form.progressBar.setValue(value) def start(self): from PySide import QtGui import ArchNesting self.clearTemps() self.form.progressBar.setFormat("pass 1: %p%") self.form.progressBar.setValue(0) self.form.progressBar.show() tolerance = self.form.Tolerance.value() discretize = self.form.Subdivisions.value() rotations = [float(x) for x in self.form.Rotations.text().split(",")] ArchNesting.TOLERANCE = tolerance ArchNesting.DISCRETIZE = discretize ArchNesting.ROTATIONS = rotations self.nester = ArchNesting.Nester() self.nester.addContainer(self.container) self.nester.addObjects(self.shapes) self.nester.setCounter = self.setCounter self.form.ButtonStop.setEnabled(True) self.form.ButtonStart.setEnabled(False) self.form.ButtonPreview.setEnabled(False) QtGui.QApplication.processEvents() result = self.nester.run() self.form.progressBar.hide() self.form.ButtonStart.setEnabled(True) self.form.ButtonStop.setEnabled(False) if result: self.form.ButtonPreview.setEnabled(True) def stop(self): if self.nester: self.nester.stop() self.form.ButtonStart.setEnabled(True) self.form.ButtonStop.setEnabled(False) self.form.ButtonPreview.setEnabled(False) self.form.progressBar.hide() def preview(self): self.clearTemps() if self.nester: t = self.nester.show() if t: self.temps.extend(t) class Arch_PanelGroup: def GetCommands(self): return tuple(["Arch_Panel", "Arch_Panel_Cut", "Arch_Panel_Sheet", "Arch_Nest"]) def GetResources(self): return { "MenuText": QT_TRANSLATE_NOOP("Arch_PanelTools", "Panel Tools"), "ToolTip": QT_TRANSLATE_NOOP("Arch_PanelTools", "Panel tools"), } def IsActive(self): v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph") return v FreeCADGui.addCommand("Arch_Panel", Arch_Panel()) FreeCADGui.addCommand("Arch_Panel_Cut", Arch_PanelCut()) FreeCADGui.addCommand("Arch_Panel_Sheet", Arch_PanelSheet()) FreeCADGui.addCommand("Arch_Nest", Arch_Nest()) FreeCADGui.addCommand("Arch_PanelTools", Arch_PanelGroup())