// Openscad, animatronic dragon, work in progress, see http://github.com/zzorn/dragon
$fn = 40;
cutCurveFn = 60;
holeMargin = 1;
//testSpine(xNum=2, yNum=2);
//dragon();
neckSegment(30,
20,
30,
10.3,
3,
spikeSize=1);
// Slice outline for a neck / spine piece.
// Use a polygon editor to define this, e.g. http://daid.mine.nu/~daid/3d/
spineOutline = [[0,18],[1,16],[5,13],[10,11],[14,12],[13,9],[14,5],[16,2],[18,0],[16,-1],[14,-5],[12,-11],[11,-12],[6,-15],[0,-16],[-6,-15],[-11,-12],[-12,-11],[-14,-5],[-16,-1],[-18,0],[-16,2],[-14,5],[-13,9],[-14,12],[-10,11],[-5,13],[-1,16]];
spineScaleX = 1.0/32.0;
spineScaleZ = 1.0/32.0;
// Profile for a neck / spline segment
// Small: spineProfile = [[15,32],[16,31],[16,30],[15,28],[14,23],[14,21],[15,21],[15,19],[13,16],[12,13],[12,11],[13,11],[13,9],[11,6],[10,2],[10,0]];
spineProfile = [[13,32/*1:0,0,0,0*/] ,[14,31.88] ,[14.88,31.39],[15,31/*1:0,1,1,-3*/] ,[15.13,29.95] ,[14.98,28.9] ,[14.69,27.92] ,[14.32,26.93] ,[13.95,25.96] ,[13.61,24.93] ,[13.43,23.9] ,[13.63,22.89] ,[14.37,22.22],[15,22/*1:-4,1,-1,-6*/] ,[14.79,20.95] ,[14.53,19.97] ,[14.17,18.89] ,[13.79,17.89] ,[13.39,16.97] ,[12.95,16] ,[12.5,15] ,[12.12,14.01] ,[11.91,12.96] ,[12.09,11.97] ,[12.75,11.18],[13,11/*1:-3,2,-1,-5*/] ,[12.78,10.01] ,[12.49,8.99] ,[12.15,7.98] ,[11.75,6.97] ,[11.35,6.04] ,[10.94,5.05] ,[10.58,4.03] ,[10.3,3.02] ,[10.12,2.01] ,[10.03,0.99],[10,0/*1:0,5,-4,0*/] ,[8.88,0] ,[7.83,0] ,[6.75,0] ,[5.67,0] ,[4.62,0] ,[3.62,0] ,[2.58,0] ,[1.58,0] ,[0.54,0]];
spineProfileScaleX = 0.5*1.0/15.0;
spineProfileScaleY = 1.0/32.0;
spineProfileLen = 46; // Works in later openscad versions only? len(spineProfile);
// Test setup for spines
module testSpine(xNum = 3, yNum = 3, step = 10, startSize = 20) {
spacing = max(xNum,yNum)*step*1.8+startSize+10;
w = xNum*spacing;
h = yNum*spacing;
for (x = [0 : xNum-1], y = [0 : yNum-1])
translate([(x-(xNum-1)/2)*spacing, (y-(yNum-1)/2) * spacing, 0]) {
neckSegment(x*step + startSize,
y*step + startSize,
(x+y) * 0.5 *step + startSize,
10.3,
3);
translate([0,0,(x+y) * 0.5 *step + startSize])
neckSegment(x*step + startSize,
y*step + startSize,
(x+y) * 0.5 *step + startSize,
10.3,
3);
}
}
module dragon(width = 100, length = 700, wingspan = 500, tubeDiam = 8, tendonDiam = 3) {
spacing = 0;
// Head
headWidth = width * 0.6;
headLength = length * 0.125;
translate([-headWidth/2, bodyLength/2 + spacing + neckLength + spacing*neckSegments, 0]) {
head(headWidth, headWidth*0.7, headLength);
}
// Neck
neckWidth = width * 0.3;
neckLength = length * 0.2;
neckSegments = 4;
translate([0, bodyLength/2 + spacing, 0]) {
spine(neckWidth*1.1, neckWidth, neckLength, 1.2, 0.8, tubeDiam, tendonDiam, neckSegments, spacing, startSpikeSize=1, startSpikeAngle=28, endSpikeSize=0.9, endSpikeAngle=26);
}
// Body
bodyWidth = width*1;
bodyLength = length * 0.175;
translate([0,-bodyLength/2,0]) {
body(bodyWidth, bodyLength, wingspan);
}
// Tail
tailWidth = width * 0.25;
tailLength = length * 0.5;
tailSegments = 8;
translate([0, -tailLength - spacing*tailSegments-bodyLength/2, 0]) {
spine(tailWidth*1.2, tailWidth, tailLength, 0.6, 1.2, tubeDiam, tendonDiam, tailSegments, spacing, startSpikeSize=0.5, startSpikeAngle=30);
}
}
module body(width, length, wingspan) {
height = width * 0.5;
wingBoneSize = 10;
translate([-width/2,0,0])
cube([width,length,height]);
translate([width/2, 0, height/2])
wing((wingspan-width)/2, length, wingBoneSize);
translate([-width/2, 0, height/2])
scale([-1, 1, 1])
wing((wingspan-width)/2, length, wingBoneSize);
}
module wing(span, width, wingBoneSize) {
cube([span,width,wingBoneSize]);
}
module spine(width, height, length, startSize, endSize, tubeDiam, tendonDiam, neckSegments, spacing, startSpikeSize=1, endSpikeSize=1, startSpikeAngle=28, endSpikeAngle=28) {
segmentLength = length / neckSegments;
for (segment = [1 : neckSegments]) {
translate([0, segmentLength + (segment - 1) * (segmentLength+spacing), width/2]) {
rotate([90, 0,0])
neckSegment(width * mix(segment/neckSegments, startSize, endSize),
height * mix(segment/neckSegments, startSize, endSize),
segmentLength,
tubeDiam,
tendonDiam,
spikeSize=mix(segment/neckSegments, startSpikeSize, endSpikeSize),
spikeAngle=mix(segment/neckSegments, startSpikeAngle, endSpikeAngle));
}
}
}
function mix(pos, start, end) = start + pos * (end -start);
function profileScale(relativePos, scale, startScale, endScale) = mix(relativePos, startScale, endScale) * scale * (spineProfile[spineProfileLen - 1 - relativePos * (spineProfileLen - 1)][0]);
module neckSegment(sizeX, sizeY, length, tubeDiam, tendonDiam, spikeSize = 1, spikeAngle = 40, bendAmount = 0.3, supportSpacing = 0.75) {
tendonX = (sizeX/2 - tendonDiam/2) - holeMargin;
tendonY = (sizeY/2 - tendonDiam/2) - holeMargin;
sliceCount = spineProfileLen;
sliceH = length / sliceCount;
depressionRelativePos = 0.5;
depressionDepth = length * depressionRelativePos;
bumpHeight = 0.5 * max(sizeX, sizeY) * bendAmount;
connectorLength = depressionDepth*0.5;
//bumpWidth = tubeDiam;// + 2 * holeMargin*2;
//bumpLen = (width - bumpWidth) * bendAmount;
difference() {
union() {
// Basic body
difference() {
translate([0,0,connectorLength])
carvedSegment(sizeX, sizeY, length, spikeSize, spikeAngle = spikeAngle);
// Depression for connecting to the previous segment
translate([0,0,length - bumpHeight])
scale([sizeX, sizeY, 1])
cylinder(r1=0.5, r2= 0.6 + bendAmount * depressionRelativePos, h=depressionDepth+bumpHeight);
}
// Bump to pivot previous segment on
translate([0,0,length - bumpHeight])
scale([sizeX, sizeY, 1])
cylinder(r1=0.5, r2=0, h=bumpHeight);
// Socket to connect to next segment
translate([0,0,0])
scale([sizeX, sizeY, length - bumpHeight])
cylinder(r=0.5, h=1);
}
// Hole for central tube carrying electronic wires
translate([0,0,-1])
cylinder(r=tubeDiam/2, h=length+connectorLength + 2);
// Holes for tendons along each edge
cylinderCircle(tendonDiam, length+connectorLength, tendonX, angleNum=2, startAngle=0);
cylinderCircle(tendonDiam, length+connectorLength, tendonY, angleNum=2, startAngle=90);
}
}
function polarR(a, x, y) = sqrt(pow(sin(a) * y * 0.5, 2) + pow(cos(a) * x * 0.5, 2));
module carvedSegment(origX,
origY,
sizeZ,
spikeSize = 1,
spikeAngle = 28,
padding = 1.7,
bottomCarveDepth = 0.4,
bumpCount = 1) {
extraSideCarveAngle = 15;
carveAreaAngle = 180.0+extraSideCarveAngle*2;
carvePadding = padding * 5;
carveDepth = 1.0 - 1.0 / carvePadding;
maxSizeX = origX * padding;
maxSizeY = origY * padding;
cutSharpness = 5;
cutLength = 7;
cutOffset = -6;
spikeRootSize = 0.4;
bottomCutAspect = 1.1;
bottomCutRadius = 0.9;
bottomCutDist = sizeZ * 1.45;
difference() {
union() {
scale([origX, origY, sizeZ]) {
translate([0,0,-0.2])
cylinder(r1=0.5, r2=padding/2*0.65, h=0.25);
translate([0,0,-0.1])
cylinder(r1=0.5, r2=padding/2*0.74, h=0.25);
translate([0,0,1])
scale([1,1,1.5])
sphere(r=padding/2);
// Spike
translate([0,0.5,0.6])
scale([spikeSize, spikeSize, spikeSize])
rotate([270+spikeAngle,0,0])
cylinder(r1 = spikeRootSize, r2 = 0.05, h=1);
}
}
scale([origX, origY, sizeZ]) {
// Cut behind spike
translate([0,0.5,1.2])
scale([spikeSize, spikeSize, spikeSize])
rotate([270,0,0])
cylinder(r1 = spikeRootSize, r2 = 0.05, h=1);
// Carve sides
sideCarver(padding-0.35,
cutRadiusFactor = 0.85,
cutRootScale = 0.05,
cutAngle=25,
carveStartAngle = -extraSideCarveAngle,
carvingCount=4,
carveAreaAngle = carveAreaAngle);
sideCarver(padding-0.04,
cutRadiusFactor = 1.20,
cutAngle=24,
cutSharpness = 1,
cutRootScale=0.75,
carveStartAngle = -extraSideCarveAngle,
carvingCount=2,
carveAreaAngle = carveAreaAngle);
}
// Carve bottom
translate([0,0,bottomCutDist])
scale([origX*bottomCutAspect, origY/bottomCutAspect, sizeZ])
sphere(r=bottomCutRadius, $fn=cutCurveFn);
translate([0,0,bottomCutDist*1.35])
scale([origX*bottomCutAspect*1.4, origY/bottomCutAspect*1.4, sizeZ])
rotate([0,90,0])
cylinder(r=1, h=2, center=true, $fn=cutCurveFn);
}
}
module sideCarver(cutDistanceFactor,
cutRadiusFactor = 0.9,
cutAngle = 25,
cutRootScale = 0.1,
cutSharpness = 1.5,
carveStartAngle = 90,
carveAreaAngle = 360.0,
carvingCount = 8) {
carveStep = carveAreaAngle / carvingCount;
for (ca = [carveStep*0.5 : carveStep : carveAreaAngle + 0.01 - carveStep*0.5]) {
rotate([0,0,ca + carveStartAngle]) {
translate([cutDistanceFactor, 0, 0]) {
rotate([0, cutAngle, 0])
scale([cutSharpness, 1/cutSharpness, 1])
cylinder(r1=cutRootScale*cutRadiusFactor, r2=cutRadiusFactor, h=2, center=true, $fn=cutCurveFn);
}
}
}
}
module cylinderCircle(diameter, length, centerDistance, angleNum=4, startAngle = 0, extend = 1) {
angleStep = 360.0 / angleNum;
for (ai = [0 : angleNum - 1]) {
rotate([0,0,startAngle + angleStep * ai])
translate([centerDistance, 0, -extend])
cylinder(r = diameter/2, h = length + extend*2);
}
}
module neckEnd(radius, thickness) {
cylinder(r=radius, h=thickness);
}
module rib(radius, thickness) {
cylinder(r=radius, h=thickness);
}
module head(headWidth, headHeight, headLength) {
cube([headWidth, headLength, headHeight]);
}
// A box rounded along the ground plane only, with rounding equal to the size of the smallest side
module stretchedCylinder(width, depth, height, centerHeight=false) {
heightOffset = centerHeight ? -height/2 : 0;
if (depth > width) {
union() {
translate([0, -0.5*(depth-width), heightOffset])
cylinder(r=width/2, h = height);
translate([0, 0.5*(depth-width), heightOffset])
cylinder(r=width/2, h = height);
translate([0, 0, height/2 + heightOffset])
cube([width, depth-width, height], center=true);
}
}
else if (width == depth) {
translate([0,0, heightOffset])
cylinder(r=width/2, h = height);
}
else {
union() {
translate([-0.5*(width-depth), 0, heightOffset])
cylinder(r=depth/2, h = height);
translate([ 0.5*(width-depth), 0, heightOffset])
cylinder(r=depth/2, h = height);
translate([0, 0, height/2 + heightOffset])
cube([width-depth, depth, height], center=true);
}
}
}