openscad scope issue a=a+1 workaround - scope

To my surprise the OpenSCAD User Manual states:
No, you still can't do a=a+1;
What is the workaround then?
I have multiple nested for loops where I want to change the position in the most inner loop:
module all(width,length,height) {
x=0;
y=0;
z=0;
for(with_left = [0:1:1]) {
for(with_right = [0:1:1]) {
for(with_opposite = [0:1:1]) {
for(with_bottom = [0:1:1]) {
for(with_45 = [0:1:1]) {
translate([x,y,z]){
rotate([0, 0, 0]){
ejcorner(width, length, height, with_left,with_right,with_opposite,with_bottom,with_45);
}
}
x=x+20;
if (x>200) {
y=y+20;
}
echo(str("x: ",x," y: ",y));
}
}
}
}
}
}
Currently I get:
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"
ECHO: "x: 20 y: 0"

You do it with math, something along the lines of the following snippet.
function is_bit_set(x, b) = floor(x / pow(2, b)) % 2;
for (a = [0:1:63]) {
with_45 = is_bit_set(a, 0);
with_bottom = is_bit_set(a, 1);
with_left = is_bit_set(a, 4);
x = (20 * a) % 220;
y = 20 * floor(a / 11);
echo(x = x, y = y, l = with_left, b = with_bottom, w45 = with_45);
}

A recursive solution seems to be close to the functional idea of openscad.
I have also renamed the boolean variables.
module showoff(x,y,z, size, offset, wall_thickness, with_x90, with_x270, with_y90, with_y270, with_z90,with_z270, with_45) {
translate([x, y, z]){
ejcorner(size, offset, wall_thickness, with_x90, with_x270, with_y90, with_y270, with_z90,with_z270, with_45);
}
if (with_x90) {
showoff(x+(size+offset)*2.5,y,z,size, offset, wall_thickness, false, with_x270, with_y90, with_y270, with_z90, with_z270,with_45);
}
if (with_x270) {
showoff(x,y+(size+offset)*2.5,z,size, offset, wall_thickness, with_x90, false, with_y90, with_y270, with_z90, with_z270,with_45);
}
if (with_y90) {
showoff(x+(size+offset)*5,y,z,size, offset, wall_thickness, with_x90, with_x270, false, with_y270, with_z90, with_z270,with_45);
}
if (with_y270) {
showoff(x,y+(size+offset)*5,z,size, offset, wall_thickness, with_x90, with_x270, with_y90, false, with_z90, with_z270,with_45);
}
if (with_z90) {
showoff(x,y+(size+offset)*10,z,size, offset, wall_thickness, with_x90, with_x270, with_y90, with_y270, false, with_z270, with_45);
}
if (with_z270) {
showoff(x,y+(size+offset)*10,z,size, offset, wall_thickness, with_x90, with_x270, with_y90, with_y270, with_z90, false, with_45);
}
if (with_45) {
showoff(x,y+(size+offset)*20,z,size, offset, wall_thickness, with_x90, with_x270, with_y90, with_y270, with_z90, with_z270, false);
}
}
showoff(-50,50,0,14,15,4,true,true,true,true,true,true,true) ;

Related

How can I draw a line while only using vertices and indices?

I use wgpu as my graphics backend for my project.
this is my current implementation:
pub fn draw_line(
points: Vec<[f32; 2]>,
) -> (Vec<Vertex>, Vec<u16>) {
let mut vertices: Vec<Vertex> = Vec::new();
let mut indices: Vec<u16> = Vec::new();
let w = WIDTH / 2.0;
let x1 = points[0][0];
let x2 = points[1][0];
let y1 = points[0][1];
let y2 = points[1][1];
let color: [f32; 3] = [1.0, 1.0, 1.0];
vertices.push(Vertex { position: [x1, y1 - w, 0.0], color });
vertices.push(Vertex { position: [x1, y1 + w, 0.0], color });
vertices.push(Vertex { position: [x2, y2 + w, 0.0], color });
vertices.push(Vertex { position: [x2, y2 - w, 0.0], color });
indices.push(2);
indices.push(1);
indices.push(0);
indices.push(2);
indices.push(0);
indices.push(3);
return (vertices, indices);
}
But when trying to draw a line between 2 points the width of the line gets distorted relative to the height difference of those points.
And the X and Y values of point1 must be smaller than the ones on point2 otherwise they dont show up because wgpu needs either Clockwise or CounterClockwise front faces
Is there any better function that that returns the vertices and indices, for a line between 2 Points
Untested but should work:
pub fn draw_line(
points: Vec<[f32; 2]>,
) -> (Vec<Vertex>, Vec<u16>) {
let mut vertices: Vec<Vertex> = Vec::new();
let mut indices: Vec<u16> = Vec::new();
let w = WIDTH / 2.0;
let x1 = points[0][0];
let x2 = points[1][0];
let y1 = points[0][1];
let y2 = points[1][1];
let color: [f32; 3] = [1.0, 1.0, 1.0];
let dx = x2 - x1;
let dy = y2 - y1;
let l = dx.hypot (dy);
let u = dx * WIDTH * 0.5 / l;
let v = dy * WIDTH * 0.5 / l;
vertices.push(Vertex { position: [x1 + v, y1 - u, 0.0], color });
vertices.push(Vertex { position: [x1 - v, y1 + u, 0.0], color });
vertices.push(Vertex { position: [x2 - v, y2 + u, 0.0], color });
vertices.push(Vertex { position: [x2 + v, y2 - u, 0.0], color });
indices.push(2);
indices.push(1);
indices.push(0);
indices.push(2);
indices.push(0);
indices.push(3);
return (vertices, indices);
}

the relationship between scale when centering one svg element in an other

I have written these functions to center one svg element in another:
import { Matrix, translate } from 'transformation-matrix';
export const getElementCenter = (element: SVGGraphicsElement) => {
const bBox = element.getBBox();
return { x: bBox.x + bBox.width / 2, y: bBox.y + bBox.height / 2 };
};
export const centerElementInOther = (
element: SVGGraphicsElement,
other: SVGGraphicsElement,
scaleFactor: number = 1
): Matrix => {
const elementCentre = getElementCenter(element);
const otherCentre = getElementCenter(other);
const x = elementCentre.x - otherCentre.x;
const y = elementCentre.y - otherCentre.y;
// how can I work out the scaleFactor? If it the actual scale is 0.5 then I need to divide by 2 but if it is 1 then I need to divide by 1
return translate(-x / scaleFactor, -y / scaleFactor);
};
Everything works unless the element is scaled then I need to apply some maths but I do not understand the ration.
Everything worked fine until I changed the scale of the element to 0.5 and then I had to divide the center x and center y by 2.
Actually you need to multiply x, and y with scaleFactor because scaleFactor is a Decimal Fraction:
return translate(-x * scaleFactor, -y * scaleFactor);
If it the actual scale is 0.5 then I need to divide by 2 but if it is 1 then I need to divide by 1
and what that means is:
x / 2 <=> x * 0.5
x / 1 <=> x * 1
so x * scaleFactor and y * scaleFactor will work just fine with all values of scaleFactor decimal fraction.
Here is some examples on some test x, y values:
const centerElementInOtherDiv = (x, y, scaleFactor = 1) => {
return {
x: -x / scaleFactor,
y: -y / scaleFactor
};
};
const centerElementInOtherMul = (x, y, scaleFactor = 1) => {
return {
x: -x * scaleFactor,
y: -y * scaleFactor
};
};
const svgCoords = [
{ x: 100, y: 100 },
{ x: 200, y: 200 },
{ x: 100, y: 300 }
];
for(svgCoord of svgCoords) {
let mulXY = centerElementInOtherMul(svgCoord.x, svgCoord.y);
let divXY = centerElementInOtherDiv(svgCoord.x, svgCoord.y);
console.log(`scaleFactor = 1 -> mul(x: ${mulXY.x}, y: ${mulXY.y}), div(x: ${divXY.x}, y: ${divXY.y})`)
mulXY = centerElementInOtherMul(svgCoord.x, svgCoord.y, 0.5);
divXY = centerElementInOtherDiv(svgCoord.x, svgCoord.y, 0.5);
console.log(`scaleFactor = 0.5 -> mul(x: ${mulXY.x}, y: ${mulXY.y}), div(x: ${divXY.x}, y: ${divXY.y})`)
}
and the result output will be:
scaleFactor = 1 -> mul(x: -100, y: -100), div(x: -100, y: -100)
scaleFactor = 0.5 -> mul(x: -50, y: -50), div(x: -200, y: -200)
scaleFactor = 1 -> mul(x: -200, y: -200), div(x: -200, y: -200)
scaleFactor = 0.5 -> mul(x: -100, y: -100), div(x: -400, y: -400)
scaleFactor = 1 -> mul(x: -100, y: -300), div(x: -100, y: -300)
scaleFactor = 0.5 -> mul(x: -50, y: -150), div(x: -200, y: -600)
as you can see, multiplication works as expected because you're dealing with decimal fractions.
I would have loved to see the SVG code.
One way to center an svg element inside another transformed one is to wrap both elements in a <g> and apply the transformation to the group like so:
const bBox1 = theRect1.getBBox();
c1.setAttributeNS(null,"cx",bBox1.x + bBox1.width / 2)
c1.setAttributeNS(null,"cy",bBox1.y + bBox1.height / 2);
svg{border:1px solid;}
<svg viewBox="0 0 200 100">
<g transform="scale(.5,.5) translate(150,50) rotate(30,30,10)">
<rect id="theRect1" x="30" y="10" width="80" height="40" />
<circle id="c1" r="2" fill="red" />
</g>
</svg>
If wrapping the elements in a group is not possible you may apply the same transformation to the element you want to center
const bBox2 = theRect2.getBBox();
// get the transformation applied to the rect
let theTransformation = theRect2.getAttribute("transform")
c2.setAttributeNS(null,"cx",bBox2.x + bBox2.width / 2)
c2.setAttributeNS(null,"cy",bBox2.y + bBox2.height / 2);
//apply the same transformation to the circle
c2.setAttributeNS(null,"transform",theTransformation)
svg{border:1px solid;}
<svg viewBox="0 0 200 100">
<rect id="theRect2" x="30" y="10" width="80" height="40" transform="scale(.5,.5) translate(150,50) rotate(30,30,10)" />
<circle id="c2" r="2" fill="red" />
</svg>
In the case you want only the rect to be downscaled but not the circle you can wrap the circle in a group <g>, use the group and upscale the circle the right amount:
const bBox3 = theRect3.getBBox();
// get the transformation applied to the rect
let theTransformation = theRect3.getAttribute("transform");
// get the scale applied
let scaleRy = theTransformation
.split("scale(")[1]
.split(")")[0]
.split(","); //[".5", ".5"]
//calculate the circle's scale
let circleScale = `scale(${1 / Number(scaleRy[0])}, ${1 / Number(scaleRy[1])})`;
theUse.setAttributeNS(null, "x", bBox3.x + bBox3.width / 2);
theUse.setAttributeNS(null, "y", bBox3.y + bBox3.height / 2);
//apply the same transformation to the circle
theUse.setAttributeNS(null, "transform", theTransformation);
//scale the circle
c3.setAttributeNS(null, "transform", circleScale);
svg{border:1px solid;}
<svg viewBox="0 0 200 100">
<defs>
<g id="cWrap">
<circle id="c3" r="2" fill="red" transform="scale(2,2)" ></circle>
</g>
</defs>
<rect id="theRect3" x="30" y="10" width="80" height="40" transform="scale(.5,.5) translate(150,50) rotate(30,30,10)" />
<use id="theUse" xlink:href="#cWrap" />
</svg>

How to find all point around center point in 2D

I have center point x: 0 and y: 0.
How to get all points distance Up to 5?
My code is not perfect:
function getPoints(startX, startY, distance) {
var res = []
for (var i = 1; i < distance; i++) {
res.push({ x: startX + i, y: startY })
res.push({ x: startX - i, y: startY })
res.push({ x: startX, y: startY + i })
res.push({ x: startX, y: startY - i })
res.push({ x: startX + i, y: startY + i })
res.push({ x: startX - i, y: startY - i })
}
console.log(res)
console.log(res.length)
}
getPoints(0, 0, 3)
Pseudocode (R=5 for your case)
for dy = 0 to R
for dx = 0 to Floor(Sqrt(R*R - dy*dy))
// or dx = 0
// while dx*dx+dy*dy<=R*R do
put startX + dx, startY + dy
put startX - dx, startY + dy
put startX + dx, startY - dy
put startX - dx, startY - dy
//dx++
If you need square, code is very simple:
for y = centerY - size to centerY + size
for x = centerX - size to centerX + size
put x, y

Variable number of nested for loops with fixed range

I'm looking for a method to have a variable number of nested for loops instead of the following code. For example if the variable n represents the number of nested for loops and n = 3, my code would be:
p = []
for i in range(26):
for j in range(26):
for k in range(26):
p.append([i,j,k])
Instead, if n = 2 my code would be:
p = []
for i in range(26):
for j in range(26):
p.append([i,j])
I understand this can be done using recursion but I'm a bit confused as to how I'd go about doing this.
It's important for one to develop the skills to reason about these problems. In this case, Python includes itertools.product but what happens the next time you need to write a behaviour specific to your program? Will there be another magical built-in function? Maybe someone else will have published a 3rd party library to solve your problem?
Below, we design product as a simple recursive function that accepts 1 or more lists.
def product (first, *rest):
if not rest:
for x in first:
yield (x,)
else:
for p in product (*rest):
for x in first:
yield (x, *p)
for p in product (range(2), range(2), range(2)):
print ('x: %d, y: %d z: %d' % p)
# x: 0, y: 0 z: 0
# x: 1, y: 0 z: 0
# x: 0, y: 1 z: 0
# x: 1, y: 1 z: 0
# x: 0, y: 0 z: 1
# x: 1, y: 0 z: 1
# x: 0, y: 1 z: 1
# x: 1, y: 1 z: 1
Assuming you want a more conventional iteration ordering, you can accomplish do so by using an auxiliary loop helper
def product (first, *rest):
def loop (acc, first, *rest):
if not rest:
for x in first:
yield (*acc, x)
else:
for x in first:
yield from loop ((*acc, x), *rest)
return loop ((), first, *rest)
for p in product (range(2), range(2), range(2)):
print ('x: %d, y: %d z: %d' % p)
# x: 0, y: 0 z: 0
# x: 0, y: 0 z: 1
# x: 0, y: 1 z: 0
# x: 0, y: 1 z: 1
# x: 1, y: 0 z: 0
# x: 1, y: 0 z: 1
# x: 1, y: 1 z: 0
# x: 1, y: 1 z: 1
Something like this should work:
import itertools
n=3
fixed=26
p = list(itertools.product(range(fixed), repeat=n))
This solution uses the optimized functions of itertools, so it should be quite fast.
Mind that itertools.product returns an iterator, so one needs to transform it to get an array.

How to connect 4 points on a plane so they do not fold on themselves(they always create a Quadrilateral)

I have created a small Raphael app to showcase my struggle.
I created four handles which can be moved. A 'sheet' is covering the entire screen except for the square between the 4 handles.
Whenever the handles are dragged the sheet is placed accordingly.
What ends up happening is that in certain situations, the sheet folds on itself.
It's best if you just see the fiddle. You'll get what I'm talking about
http://jsfiddle.net/8qtffq0s/
How can I avoid this?
Notice: The screen is white. The black part is the sheet, and the white part is a gap in the sheet and not the other way around.
//raphael object
var paper = Raphael(0, 0, 600, 600)
//create 4 handles
h1 = paper.circle(50, 50, 10).attr("fill","green")
h2 = paper.circle(300, 50, 10).attr("fill", "blue")
h3 = paper.circle(300, 300, 10).attr("fill", "yellow")
h4 = paper.circle(50, 300, 10).attr("fill", "red")
//create covering sheet
path = ["M", 0, 0, "L", 600, 0, 600, 600, 0, 600, 'z', "M", h1.attrs.cx, h1.attrs.cy,"L", h4.attrs.cx, h4.attrs.cy, h3.attrs.cx, h3.attrs.cy, h2.attrs.cx, h2.attrs.cy,'z']
sheet = paper.path(path).attr({ "fill": "black", "stroke": "white" }).toBack()
//keep starting position of each handle on dragStart
var startX,startY
function getPos(handle) {
startX= handle.attrs.cx
startY = handle.attrs.cy
}
//Redraw the sheet to match the new handle placing
function reDrawSheet() {
path = ["M", 0, 0, "L", 600, 0, 600, 600, 0, 600, 'z', "M", h1.attrs.cx, h1.attrs.cy, "L", h4.attrs.cx, h4.attrs.cy, h3.attrs.cx, h3.attrs.cy, h2.attrs.cx, h2.attrs.cy, 'z']
sheet.attr("path",path)
}
//enable handle dragging
h1.drag(function (dx, dy) {
this.attr("cx", startX + dx)
this.attr("cy", startY + dy)
reDrawSheet()
},
function () {
getPos(this)
})
h2.drag(function (dx, dy) {
this.attr("cx", startX + dx)
this.attr("cy", startY + dy)
reDrawSheet()
},
function () {
getPos(this)
})
h3.drag(function (dx, dy) {
this.attr("cx", startX + dx)
this.attr("cy", startY + dy)
reDrawSheet()
},
function () {
getPos(this)
})
h4.drag(function (dx, dy) {
this.attr("cx", startX + dx)
this.attr("cy", startY + dy)
reDrawSheet()
},
function () {
getPos(this)
})
Update: I improved the function "reDrawSheet" so now it can classify the points on the strings as top left, bottom left, bottom right, and top right
This solved many of my problems, but in some cases the sheet still folds on it self.
new fiddle: http://jsfiddle.net/1kj06co4/
new code:
function reDrawSheet() {
//c stands for coordinates
c = [{ x: h1.attrs.cx, y: h1.attrs.cy }, { x: h4.attrs.cx, y: h4.attrs.cy }, { x: h3.attrs.cx, y: h3.attrs.cy }, { x: h2.attrs.cx, y: h2.attrs.cy }]
//arrange the 4 points by height
c.sort(function (a, b) {
return a.y - b.y
})
//keep top 2 points
cTop = [c[0], c[1]]
//arrange them from left to right
cTop.sort(function (a, b) {
return a.x - b.x
})
//keep bottom 2 points
cBottom = [c[2], c[3]]
//arrange them from left to right
cBottom.sort(function (a, b) {
return a.x - b.x
})
//top left most point
tl = cTop[0]
//bottom left most point
bl = cBottom[0]
//top right most point
tr = cTop[1]
//bottom right most point
br = cBottom[1]
path = ["M", 0, 0, "L", 600, 0, 600, 600, 0, 600, 'z', "M", tl.x,tl.y, "L", bl.x,bl.y, br.x,br.y, tr.x,tr.y, 'z']
sheet.attr("path",path)
}
To make things super clear, this is what I'm trying to avoid:
Update 2:
I was able to avoid the vertices from crossing by checking which path out of the three possible paths is the shortest and choosing it.
To do so, I added a function that checks the distance between two points
function distance(a, b) {
return Math.sqrt(Math.pow(b.x - a.x, 2) + (Math.pow(b.y - a.y, 2)))
}
And altered the code like so:
function reDrawSheet() {
//c stands for coordinates
c = [{ x: h1.attrs.cx, y: h1.attrs.cy }, { x: h4.attrs.cx, y: h4.attrs.cy }, { x: h3.attrs.cx, y: h3.attrs.cy }, { x: h2.attrs.cx, y: h2.attrs.cy }]
//d stands for distance
d=distance
//get the distance of all possible paths
d1 = d(c[0], c[1]) + d(c[1], c[2]) + d(c[2], c[3]) + d(c[3], c[0])
d2 = d(c[0], c[2]) + d(c[2], c[3]) + d(c[3], c[1]) + d(c[1], c[0])
d3 = d(c[0], c[2]) + d(c[2], c[1]) + d(c[1], c[3]) + d(c[3], c[0])
//choose the shortest distance
if (d1 <= Math.min(d2, d3)) {
tl = c[0]
bl = c[1]
br = c[2]
tr = c[3]
}
else if (d2 <= Math.min(d1, d3)) {
tl = c[0]
bl = c[2]
br = c[3]
tr = c[1]
}
else if (d3 <= Math.min(d1, d2)) {
tl = c[0]
bl = c[2]
br = c[1]
tr = c[3]
}
path = ["M", 0, 0, "L", 600, 0, 600, 600, 0, 600, 'z', "M", tl.x,tl.y, "L", bl.x,bl.y, br.x,br.y, tr.x,tr.y, 'z']
sheet.attr("path",path)
}
Now the line does not cross itself like the image I attached about, but the sheet "flips" so everything turns black.
You can see the path is drawn correctly to connect the for points by the white stroke, but it does not leave a gap
new fiddle: http://jsfiddle.net/1kj06co4/1/
Picture of problem:
So... the trouble is to tell the inside from the outside.
You need the following functions:
function sub(a, b) {
return { x: a.x - b.x , y: a.y - b.y };
}
function neg(a) {
return { x: -a.x , y: -a.y };
}
function cross_prod(a, b) {
// 2D vecs, so z==0.
// Therefore, x and y components are 0.
// Return the only important result, z.
return (a.x*b.y - a.y*b.x);
}
And then you need to do the following once you've found tl,tr,br, and bl:
tlr = sub(tr,tl);
tbl = sub(bl,tl);
brl = sub(bl,br);
btr = sub(tr,br);
cropTL = cross_prod( tbl, tlr );
cropTR = cross_prod(neg(tlr),neg(btr));
cropBR = cross_prod( btr, brl );
cropBL = cross_prod(neg(brl),neg(tbl));
cwTL = cropTL > 0;
cwTR = cropTR > 0;
cwBR = cropBR > 0;
cwBL = cropBL > 0;
if (cwTL) {
tmp = tr;
tr = bl;
bl = tmp;
}
if (cwTR == cwBR && cwBR == cwBL && cwTR!= cwTL) {
tmp = tr;
tr = bl;
bl = tmp;
}
My version of the fiddle is here. :) http://jsfiddle.net/1kj06co4/39/

Resources