# import Boxer; reload(Boxer); import BoxerGui as bg; reload(bg); #import FreeCAD from FreeCAD import Base, Vector, Matrix, Placement, Rotation import Part from enum import Enum import math # These names are silly, should be X, Y, Z instead class MeasurementDirection(Enum): width = 1 # X height = 2 # Z depth = 3 # Y # X and Y rotation factors class LineDirection(Enum): right = (1, 0, 0, 1) down = (0, -1, 1, 0) left = (-1, 0, 0, -1) up = (0, 1, -1, 0) def __init__(self, x_x, x_y, y_x, y_y): self.x_x = x_x self.x_y = x_y self.y_x = y_x self.y_y = y_y # Configuration for each line in a box side class SideConfig(Enum): large = (False, True, False, True, False, True, False, True) medium = (True, True, False, False, True, True, False, False) small = (True, False, True, False, True, False, True, False) def __init__(self, rightInv, rightIncludeStart, downInv, downIncludeStart, leftInv, leftIncludeStart, upInv, upIncludeStart): self.rightInv = rightInv self.rightIncludeStart = rightIncludeStart self.downInv = downInv self.downIncludeStart = downIncludeStart self.leftInv = leftInv self.leftIncludeStart = leftIncludeStart self.upInv = upInv self.upIncludeStart = upIncludeStart def inv(self, direction): if direction == LineDirection.right: return self.rightInv elif direction == LineDirection.down: return self.downInv elif direction == LineDirection.left: return self.leftInv elif direction == LineDirection.up: return self.upInv else: raise Exception("Unknown direction: " + repr(direction)) def includeStart(self, direction): if direction == LineDirection.right: return self.rightIncludeStart elif direction == LineDirection.down: return self.downIncludeStart elif direction == LineDirection.left: return self.leftIncludeStart elif direction == LineDirection.up: return self.upIncludeStart else: raise Exception("Unknown direction: " + repr(direction)) class BoxSide(Enum): front = ('Front', MeasurementDirection.width, MeasurementDirection.height, SideConfig.large, (0, -1, 0)) back = ('Back', MeasurementDirection.width, MeasurementDirection.height, SideConfig.large, (0, 1, 0)) left = ('Left', MeasurementDirection.depth, MeasurementDirection.height, SideConfig.small, (-1, 0, 0)) right = ('Right', MeasurementDirection.depth, MeasurementDirection.height, SideConfig.small, (1, 0, 0)) top = ('Top', MeasurementDirection.width, MeasurementDirection.depth, SideConfig.medium, (0, 0, 1)) bottom = ('Bottom', MeasurementDirection.width, MeasurementDirection.depth, SideConfig.medium, (0, 0, -1)) def __init__(self, title, directionX, directionY, sideConfig, extrudeVector): self.title = title self.directionX = directionX self.directionY = directionY self.sideConfig = sideConfig self.extrudeVector = Vector(extrudeVector) class NotchConfig(object): def __init__(self, count, size): self.count = count self.size = size def __str__(self): return "count = " + str(self.count) + ", size = " + str(self.size) class BoxCfg(object): def __init__(self): self._thickness = 10 self._notchSize = 10 self.generateExtrudes = True self.generateCrossSections = False # Not very useful so off by default self.outerDimmensions(100, 100, 100) self.calculate() @property def thickness(self): return self._thickness @thickness.setter def thickness(self, thickness): self._thickness = thickness self.calculate() # TODO: replace this with a way to set number of notches in each direction # @property # def notches(self): # return self._notches # # @notches.setter # def notches(self, notches): # self._notches = notches # self.calculate() @property def notchSize(self): return self._notchSize @notchSize.setter def notchSize(self, notchSize): self._notchSize = notchSize def outerDimmensions(self, width, height, depth): self.outerWidth = width self.outerHeight = height self.outerDepth = depth self.calculate() return self def notchConfig(self, direction): if direction == MeasurementDirection.width: return self._notchConfigWidth elif direction == MeasurementDirection.height: return self._notchConfigHeight elif direction == MeasurementDirection.depth: return self._notchConfigDepth else: raise Exception("Unknown MeasurementDirection: " + str(direction)) def createNotchConfig(self, sideLength): count = sideLength / self._notchSize size = float(sideLength) / float(count * 2 + 1) return NotchConfig(count, size) def calculate(self): self.innerWidth = self.outerWidth - 2 * self._thickness self.innerHeight = self.outerHeight - 2 * self._thickness self.innerDepth = self.outerDepth - 2 * self._thickness self._notchConfigWidth = self.createNotchConfig(self.innerWidth) self._notchConfigHeight = self.createNotchConfig(self.innerHeight) self._notchConfigDepth = self.createNotchConfig(self.innerDepth) def prt(self): print("Box configuration") print("Thickness : " + str(self._thickness)) print("Outer w/h/d: " + str(self.outerWidth) + "/" + str(self.outerHeight) + "/" + str(self.outerDepth)) print("Inner w/h/d: " + str(self.innerWidth) + "/" + str(self.innerHeight) + "/" + str(self.innerDepth)) print("Notch size : " + str(self._notchSize)) print(" Width: " + str(self.notchConfig(MeasurementDirection.width))) print(" Height: " + str(self.notchConfig(MeasurementDirection.height))) print(" Depth: " + str(self.notchConfig(MeasurementDirection.depth))) class VGen(object): def __init__(self, current, dir): self.current = current self.dir = dir self.list = [] def move(self, x, y, z = 0): pos = Vector(x, y, z) self.current = self.current.add(pos) self.list.append(self.current) def moveX(self, value): self.move(self.dir.x_x * value, self.dir.x_y * value, 0) def moveY(self, value): self.move(self.dir.y_x * value, self.dir.y_y * value, 0) def line(cfg, start, lineDirection, sideConfig, notchConfig): inv = -1 if sideConfig.inv(lineDirection) else 1 includeStart = sideConfig.includeStart(lineDirection) g = VGen(start, lineDirection) if includeStart: g.moveX(cfg.thickness) g.moveX(notchConfig.size) for i in range(0, notchConfig.count): g.moveY(-cfg.thickness * inv) g.moveX(notchConfig.size) g.moveY(cfg.thickness * inv) g.moveX(notchConfig.size) if includeStart: g.moveX(cfg.thickness) return g.list def makeBoxSide(cfg, boxSide): notchConfigX = cfg.notchConfig(boxSide.directionX) notchConfigY = cfg.notchConfig(boxSide.directionY) # Offset the start so that the inner area starts at (0, 0) if boxSide.sideConfig is SideConfig.large: start = Vector(-cfg.thickness, cfg.thickness, 0) elif boxSide.sideConfig is SideConfig.medium: start = Vector(-cfg.thickness, 0, 0) else: start = Vector(0, 0, 0) v = [start] top = line(cfg, v[-1], LineDirection.right, boxSide.sideConfig, notchConfigX); v.extend(top) left = line(cfg, v[-1], LineDirection.down, boxSide.sideConfig, notchConfigY); v.extend(left) bottom = line(cfg, v[-1], LineDirection.left, boxSide.sideConfig, notchConfigX); v.extend(bottom) right = line(cfg, v[-1], LineDirection.up, boxSide.sideConfig, notchConfigY); v.extend(right) return v def makeBox(doc, cfg): objects = {} sep = cfg.outerWidth * 0.05 def m(rotX, rotY, moveX, moveY, moveZ): if rotX and rotY: p = Placement(Vector(0, 0, 0), Rotation(Vector(1, 1, 1), 120)) m = p.toMatrix() else: m = Matrix() if rotX: m.rotateX(math.radians(90)) if rotY: m.rotateY(math.radians(90)) v = Vector(cfg.innerWidth, cfg.innerDepth, cfg.innerHeight) v.scale(moveX, moveY, moveZ) m.move(v) return m # front back left # right top bottom s = Part.makePolygon(makeBoxSide(cfg, BoxSide.front)) s.transformShape(m(True, False, -0.5, -0.5, 0.5)) front = doc.addObject("Part::Feature", "Front") front.Shape = s s = Part.makePolygon(makeBoxSide(cfg, BoxSide.back)) s.transformShape(m(True, False, -0.5, 0.5, 0.5)) back = doc.addObject("Part::Feature", "Back") back.Shape = s l = Part.makePolygon(makeBoxSide(cfg, BoxSide.left)) l.transformShape(m(True, True, -0.5, -0.5, 0.5)) left = doc.addObject("Part::Feature", "Left") left.Shape = l l = Part.makePolygon(makeBoxSide(cfg, BoxSide.right)) l.transformShape(m(True, True, 0.5, -0.5, 0.5)) right = doc.addObject("Part::Feature", "Right") right.Shape = l l = Part.makePolygon(makeBoxSide(cfg, BoxSide.top)) l.transformShape(m(False, False, -0.5, 0.5, 0.5)) top = doc.addObject("Part::Feature", "Top") top.Shape = l l = Part.makePolygon(makeBoxSide(cfg, BoxSide.bottom)) l.transformShape(m(False, False, -0.5, 0.5, -0.5)) bottom = doc.addObject("Part::Feature", "Bottom") bottom.Shape = l #front.ViewObject.Visibility = False #back.ViewObject.Visibility = False #left.ViewObject.Visibility = False #right.ViewObject.Visibility = False #top.ViewObject.Visibility = False #bottom.ViewObject.Visibility = False return {'front': front, 'back': back, 'left': left, 'right': right, 'top': top, 'bottom': bottom} def makeExtrudes(doc, cfg): def makeExtrude(doc, cfg, side, part): extrude = doc.addObject("Part::Extrusion", side.title + "_Extrude") extrude.Base = part extrude.Dir = side.extrudeVector * cfg.thickness extrude.Solid = (True) extrude.TaperAngle = (0) extrude.Label = side.title + ' Extrude' partView = part.ViewObject extrudeView = extrude.ViewObject if partView is not None and extrudeView is not None: partView.Visibility = False extrudeView.ShapeColor = partView.ShapeColor extrudeView.LineColor = partView.LineColor extrudeView.PointColor = partView.PointColor return extrude objects = {} objects['front'] = makeExtrude(doc, cfg, BoxSide.front, doc.Front) objects['back'] = makeExtrude(doc, cfg, BoxSide.back, doc.Back) objects['left'] = makeExtrude(doc, cfg, BoxSide.left, doc.Left) objects['right'] = makeExtrude(doc, cfg, BoxSide.right, doc.Right) objects['top'] = makeExtrude(doc, cfg, BoxSide.top, doc.Top) objects['bottom'] = makeExtrude(doc, cfg, BoxSide.bottom, doc.Bottom) #objects['front'].ViewObject.Visibility = False #objects['back'].ViewObject.Visibility = False #objects['left'].ViewObject.Visibility = False #objects['right'].ViewObject.Visibility = False #objects['top'].ViewObject.Visibility = False #objects['bottom'].ViewObject.Visibility = False return objects def makeCrossSections(doc, cfg, extrudes): def makeCrossSection(extrude, name): wires = list() shape = extrude.Shape for i in shape.slice(Base.Vector(0, 0, 1), cfg.thickness / 2): wires.append(i) comp = Part.Compound(wires) cs = doc.addObject("Part::Feature", name + "_Cross_Section") cs.Shape = comp cs.ViewObject.Visibility = False cs.purgeTouched() return cs objects = {} for key, extrude in extrudes.items(): objects[key] = makeCrossSection(extrude, key.title()) return objects def make(doc, cfg): # Cretate the underlying parts parts = makeBox(doc, cfg) if cfg.generateExtrudes: extrudes = makeExtrudes(doc, cfg) box = doc.addObject("App::DocumentObjectGroup", "Box") for e in extrudes.values(): box.addObject(e) doc.recompute() if cfg.generateCrossSections: crossSections = makeCrossSections(doc, cfg, extrudes) crossSectionsGroup = doc.addObject("App::DocumentObjectGroup", "Cross Sections") [crossSectionsGroup.addObject(cs) for cs in crossSections.values()] def removeEverything(doc): def rm(name): if hasattr(doc, name): doc.removeObject(name) rm("Box") rm("Cross Sections") for key in ['Front', 'Back', 'Left', 'Right', 'Top', 'Bottom']: rm(key + '_Cross_Section') rm(key + '_Extrude') rm(key)