This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-1.1-deprecated/extensions/fablabchemnitz_elliptical_box.py

278 lines
11 KiB
Python
Raw Normal View History

2020-07-30 01:16:18 +02:00
#!/usr/bin/env python3
from inkscape_helper.Coordinate import Coordinate
import inkscape_helper.Effect as eff
import inkscape_helper.SVG as svg
from inkscape_helper.Ellipse import Ellipse
from inkscape_helper.Line import Line
from inkscape_helper.EllipticArc import EllipticArc
from math import *
import inkex
#Note: keep in mind that SVG coordinates start in the top-left corner i.e. with an inverted y-axis
# first define some SVG primitives
greenStyle = svg.green_style
def _makeCurvedSurface(topLeft, w, h, cutSpacing, hCutCount, thickness, parent, invertNotches = False, centralRib = False):
width = Coordinate(w, 0)
height = Coordinate(0, h)
wCutCount = int(floor(w / cutSpacing))
if wCutCount % 2 == 0:
wCutCount += 1 # make sure we have an odd number of cuts
xCutDist = w / wCutCount
xSpacing = Coordinate(xCutDist, 0)
ySpacing = Coordinate(0, cutSpacing)
cut = height / hCutCount - ySpacing
plateThickness = Coordinate(0, thickness)
notchEdges = []
topHCuts = []
bottomHCuts = []
p = svg.Path()
for cutIndex in range(wCutCount):
if (cutIndex % 2 == 1) != invertNotches: # make a notch here
inset = plateThickness
else:
inset = Coordinate(0, 0)
# A-column of cuts
aColStart = topLeft + xSpacing * cutIndex
notchEdges.append((aColStart - topLeft).x)
if cutIndex > 0: # no cuts at x == 0
p.move_to(aColStart, True)
p.line_to(cut / 2)
for j in range(hCutCount - 1):
pos = aColStart + cut / 2 + ySpacing + (cut + ySpacing) * j
p.move_to(pos, True)
p.line_to(cut)
p.move_to(aColStart + height - cut / 2, True)
p.line_to(cut / 2)
# B-column of cuts, offset by half the cut length; these cuts run in the opposite direction
bColStart = topLeft + xSpacing * cutIndex + xSpacing / 2
for j in reversed(range(hCutCount)):
end = bColStart + ySpacing / 2 + (cut + ySpacing) * j
start = end + cut
if centralRib and hCutCount % 2 == 0 and cutIndex % 2 == 1:
holeTopLeft = start + (ySpacing - plateThickness - xSpacing) / 2
if j == hCutCount // 2 - 1:
start -= plateThickness / 2
p.move_to(holeTopLeft + plateThickness + xSpacing, True)
p.line_to(-xSpacing)
p.move_to(holeTopLeft, True)
p.line_to(xSpacing)
elif j == hCutCount // 2:
end += plateThickness / 2
if j == 0: # first row
end += inset
elif j == hCutCount - 1: # last row
start -= inset
p.move_to(start, True)
p.line_to(end, True)
#horizontal cuts (should be done last)
topHCuts.append((aColStart + inset, aColStart + inset + xSpacing))
bottomHCuts.append((aColStart + height - inset, aColStart + height - inset + xSpacing))
# draw the outline
for c in reversed(bottomHCuts):
p.move_to(c[1], True)
p.line_to(c[0], True)
p.move_to(topLeft + height, True)
p.line_to(-height)
for c in topHCuts:
p.move_to(c[0], True)
p.line_to(c[1], True)
p.move_to(topLeft + width, True)
p.line_to(height)
group = svg.group(parent)
p.path(group)
notchEdges.append(w)
return notchEdges
def _makeNotchedEllipse(center, ellipse, start_theta, thickness, notches, parent, invertNotches):
start_theta += pi # rotate 180 degrees to put the lid on the topside
ell_radius = Coordinate(ellipse.x_radius, ellipse.y_radius)
ell_radius_t = ell_radius + Coordinate(thickness, thickness)
theta = ellipse.theta_from_dist(start_theta, notches[0])
ell_point = center + ellipse.coordinate_at_theta(theta)
prev_offset = ellipse.tangent(theta) * thickness
p = svg.Path()
p.move_to(ell_point, absolute=True)
for n in range(len(notches) - 1):
theta = ellipse.theta_from_dist(start_theta, notches[n + 1])
ell_point = center + ellipse.coordinate_at_theta(theta)
notch_offset = ellipse.tangent(theta) * thickness
notch_point = ell_point + notch_offset
if (n % 2 == 0) != invertNotches:
p.arc_to(ell_radius, ell_point, absolute=True)
prev_offset = notch_offset
else:
p.line_to(prev_offset)
p.arc_to(ell_radius_t, notch_point, absolute=True)
p.line_to(-notch_offset)
p.path(parent)
class EllipticalBox(eff.Effect):
"""
Creates a new layer with the drawings for a parametrically generaded box.
"""
def __init__(self):
options = [
['unit', str, 'mm', 'Unit, one of: cm, mm, in, ft, ...'],
['thickness', float, '3.0', 'Material thickness'],
['width', float, '100', 'Box width'],
['height', float, '100', 'Box height'],
['depth', float, '100', 'Box depth'],
['cut_dist', float, '1.5', 'Distance between cuts on the wrap around. Note that this value will change slightly to evenly fill up the available space.'],
['auto_cut_dist', inkex.Boolean, 'false', 'Automatically set the cut distance based on the curvature.'], # in progress
['cut_nr', int, '3', 'Number of cuts across the depth of the box.'],
['lid_angle', float, '120', 'Angle that forms the lid (in degrees, measured from centerpoint of the ellipse)'],
['body_ribcount', int, '0', 'Number of ribs in the body'],
['lid_ribcount', int, '0', 'Number of ribs in the lid'],
['invert_lid_notches', inkex.Boolean, 'false', 'Invert the notch pattern on the lid (keeps the lid from sliding sideways)'],
['central_rib_lid', inkex.Boolean, 'false', 'Create a central rib in the lid'],
['central_rib_body', inkex.Boolean, 'false', 'Create a central rib in the body']
]
eff.Effect.__init__(self, options)
def effect(self):
"""
Draws as basic elliptical box, based on provided parameters
"""
# input sanity check
error = False
if min(self.options.height, self.options.width, self.options.depth) == 0:
eff.errormsg('Error: Dimensions must be non zero')
error = True
if self.options.cut_nr < 1:
eff.errormsg('Error: Number of cuts should be at least 1')
error = True
if (self.options.central_rib_lid or self.options.central_rib_body) and self.options.cut_nr % 2 == 1:
eff.errormsg('Error: Central rib is only valid with an even number of cuts')
error = True
if self.options.unit not in self.knownUnits:
eff.errormsg('Error: unknown unit. '+ self.options.unit)
error = True
if error:
exit()
# convert units
unit = self.options.unit
H = self.svg.unittouu(str(self.options.height) + unit)
W = self.svg.unittouu(str(self.options.width) + unit)
D = self.svg.unittouu(str(self.options.depth) + unit)
thickness = self.svg.unittouu(str(self.options.thickness) + unit)
cutSpacing = self.svg.unittouu(str(self.options.cut_dist) + unit)
cutNr = self.options.cut_nr
doc_root = self.document.getroot()
layer = svg.layer(doc_root, 'Elliptical Box')
ell = Ellipse(W/2, H/2)
#body and lid
lidAngleRad = self.options.lid_angle * 2 * pi / 360
lid_start_theta = ell.theta_at_angle(pi / 2 - lidAngleRad / 2)
lid_end_theta = ell.theta_at_angle(pi / 2 + lidAngleRad / 2)
lidLength = ell.dist_from_theta(lid_start_theta, lid_end_theta)
bodyLength = ell.dist_from_theta(lid_end_theta, lid_start_theta)
# do not put elements right at the edge of the page
xMargin = 3
yMargin = 3
bottom_grp = svg.group(layer)
top_grp = svg.group(layer)
bodyNotches = _makeCurvedSurface(Coordinate(xMargin, yMargin), bodyLength, D, cutSpacing, cutNr,
thickness, bottom_grp, False, self.options.central_rib_body)
lidNotches = _makeCurvedSurface(Coordinate(xMargin, D + 2 * yMargin), lidLength, D, cutSpacing, cutNr,
thickness, top_grp, not self.options.invert_lid_notches,
self.options.central_rib_lid)
# create elliptical sides
sidesGrp = svg.group(layer)
elCenter = Coordinate(xMargin + thickness + W / 2, 2 * D + H / 2 + thickness + 3 * yMargin)
# indicate the division between body and lid
p = svg.Path()
if self.options.invert_lid_notches:
p.move_to(elCenter + ell.coordinate_at_theta(lid_start_theta + pi), True)
p.line_to(elCenter, True)
p.line_to(elCenter + ell.coordinate_at_theta(lid_end_theta + pi), True)
else:
angleA = ell.theta_from_dist(lid_start_theta, lidNotches[1])
angleB = ell.theta_from_dist(lid_start_theta, lidNotches[-2])
p.move_to(elCenter + ell.coordinate_at_theta(angleA + pi), True)
p.line_to(elCenter, True)
p.line_to(elCenter + ell.coordinate_at_theta(angleB + pi), True)
_makeNotchedEllipse(elCenter, ell, lid_end_theta, thickness, bodyNotches, sidesGrp, False)
_makeNotchedEllipse(elCenter, ell, lid_start_theta, thickness, lidNotches, sidesGrp,
not self.options.invert_lid_notches)
p.path(sidesGrp, greenStyle)
# ribs
if self.options.central_rib_lid or self.options.central_rib_body:
innerRibCenter = Coordinate(xMargin + thickness + W / 2, 2 * D + 1.5 * (H + 2 *thickness) + 4 * yMargin)
innerRibGrp = svg.group(layer)
outerRibCenter = Coordinate(2 * xMargin + 1.5 * (W + 2 * thickness),
2 * D + 1.5 * (H + 2 * thickness) + 4 * yMargin)
outerRibGrp = svg.group(layer)
if self.options.central_rib_lid:
_makeNotchedEllipse(innerRibCenter, ell, lid_start_theta, thickness, lidNotches, innerRibGrp, False)
_makeNotchedEllipse(outerRibCenter, ell, lid_start_theta, thickness, lidNotches, outerRibGrp, True)
if self.options.central_rib_body:
spacer = Coordinate(0, 10)
_makeNotchedEllipse(innerRibCenter + spacer, ell, lid_end_theta, thickness, bodyNotches, innerRibGrp, False)
_makeNotchedEllipse(outerRibCenter + spacer, ell, lid_end_theta, thickness, bodyNotches, outerRibGrp, True)
if self.options.central_rib_lid or self.options.central_rib_body:
svg.text(sidesGrp, elCenter, 'side (duplicate this)')
svg.text(innerRibGrp, innerRibCenter, 'inside rib')
svg.text(outerRibGrp, outerRibCenter, 'outside rib')
# Create effect instance and apply it.
EllipticalBox().run()