OpenSCAD 教程/第 7 章
在上一章中,您使用了 if 语句来控制设计中的某些部分是否应该被创建。在本节中,您将了解如何创建形成特定模式的多个部分或对象。
以以下汽车模型为例,您将学习如何创建此类模式。
single_car.scad use <vehicle_parts.scad> $fa=1; $fs=0.4; // Variables track = 30; wheelbase=40; // Body body(); // Front left wheel translate([-wheelbase/2,-track/2,0]) rotate([0,0,0]) simple_wheel(); // Front right wheel translate([-wheelbase/2,track/2,0]) rotate([0,0,0]) simple_wheel(); // Rear left wheel translate([wheelbase/2,-track/2,0]) rotate([0,0,0]) simple_wheel(); // Rear right wheel translate([wheelbase/2,track/2,0]) rotate([0,0,0]) simple_wheel(); // Front axle translate([-wheelbase/2,0,0]) axle(track=track); // Rear axle translate([wheelbase/2,0,0]) axle(track=track); |
使用上述汽车示例,对其进行修改以创建另一辆汽车。为了避免重复创建汽车的代码,您应该创建一个汽车模块。该模块应该有两个输入参数,汽车的轨距和轴距。轨距和轴距的默认值分别为 30 和 40 个单位。第一辆车应该位于原点,如上例所示,第二辆车应该沿着 Y 轴的正方向平移 50 个单位。 |
two_cars.scad use <vehicle_parts.scad> $fa=1; $fs=0.4; module car(track=30, wheelbase=40) { // Body body(); // Front left wheel translate([-wheelbase/2,-track/2,0]) rotate([0,0,0]) simple_wheel(); // Front right wheel translate([-wheelbase/2,track/2,0]) rotate([0,0,0]) simple_wheel(); // Rear left wheel translate([wheelbase/2,-track/2,0]) rotate([0,0,0]) simple_wheel(); // Rear right wheel translate([wheelbase/2,track/2,0]) rotate([0,0,0]) simple_wheel(); // Front axle translate([-wheelbase/2,0,0]) axle(track=track); // Rear axle translate([wheelbase/2,0,0]) axle(track=track); } car(); translate([0,50,0]) car(); |
沿着 Y 轴的正方向创建另外八辆汽车,使总共十辆汽车。与前一辆车相比,每辆下一辆车应该沿着 Y 轴的正方向平移 50 个单位。 |
row_of_ten_cars_along_y_axis.scad … car(); translate([0,50,0]) car(); translate([0,100,0]) car(); translate([0,150,0]) car(); translate([0,200,0]) car(); translate([0,250,0]) car(); translate([0,300,0]) car(); translate([0,350,0]) car(); translate([0,400,0]) car(); translate([0,450,0]) car(); … |
在完成上一个练习后,您可能已经意识到以这种方式创建汽车模式效率不高;每辆车都需要编写一个新的语句,这会导致脚本中大量代码重复。通常,您可以使用循环来更轻松地实现相同的结果。循环提供了一种方法,可以重复执行相同的一组语句一定次数,每次执行时都应用小的、可预测的变化。请看下面的例子。
row_of_ten_cars_along_y_axis_with_for_loop.scad … for (dy=[0:50:450]) { translate([0,dy,0]) car(); } … |
关于循环语法,您应该注意一些事项。首先,键入关键字 for,然后跟一对圆括号。在圆括号内,定义循环的变量。建议在适用时为循环变量赋予描述性的名称。在本例中,变量名为 dy,因为它代表每辆车沿 Y 轴平移的单位数。变量的定义表明它的第一个值为 0 个单位,所有后续值都将每次递增 50 个单位,直到达到值 450。这意味着变量 dy 在循环执行的整个过程中将总共取十个不同的值。这些值形成一个向量,与单个值不同,它是一个值的序列。在第一次重复中,变量将取向量的第一个值,即 0。在第二次重复中,取第二个值,即 50。依此类推。循环变量在循环重复过程中取的不同连续值是循环适合于创建多个零件或模型模式的关键概念。最后,在闭合圆括号之后,是一对花括号。在花括号内,存在将重复执行的语句,执行次数与循环变量的值数量相同。在本例中,花括号内的单个语句将执行 10 次,这是 dy 变量将取的值的数量。为了避免创建 10 辆完全重叠的汽车,循环每次重复时沿 Y 轴的平移量使用 dy 变量进行参数化。dy 变量在循环的每次重复时都有不同的值,从而创建了所需的模式。
使用循环创建一个汽车模式。第一辆车应该以原点为中心。每辆下一辆车应该放在前一辆车的后面。具体而言,每辆下一辆车应该与前一辆车相比沿着 X 轴的正方向平移 70 个单位。该模式应该由总共 8 辆汽车组成。循环变量应该命名为 dx。 |
row_of_eight_cars_along_x_axis.scad … for (dx=[0:70:490]) { translate([dx,0,0]) car(); } … |
在前面的示例中,循环变量 dy 被直接用于修改构成模式的每个单独模型的某些方面。唯一被修改的方面是每个模型沿 Y 轴或 X 轴的平移。在每次重复中,dy 变量的值都等于每个模型所需的平移量。
当需要修改模型的多个方面时,最好让循环变量取整数 0、1、2、3 等。然后从循环变量取的这些整数值生成修改模型不同方面所需的值(例如,沿某个轴的平移、某个部分的缩放)。在下面的示例中,这个概念被用来同时沿着 Y 轴和 X 轴的正方向将每辆车分别平移 50 个单位和 70 个单位。
diagonal_row_of_five_cars.scad … for (i=[0:1:4]) { translate([i*70,i*50,0]) car(); } … |
您应该注意一些事项。循环变量现在命名为 i。当循环变量以这种方式使用时,它通常被称为索引,并被赋予名称 i。由于循环变量取整数值,因此您需要将其乘以适当的数字以生成所需的平移量。具体来说,所需的沿 Y 轴的平移量是通过将循环变量乘以 50 生成的。同样,所需的沿 X 轴的平移量是通过将循环变量乘以 70 生成的。
在前面的示例中添加旋转变换,以便围绕 Z 轴旋转汽车。第一辆车不应旋转。每辆下一辆车应该与前一辆车相比围绕 Z 轴的正旋转方向旋转 90 度。Z 轴的正旋转方向是将 X 轴旋转到 Y 轴的方向。为了保持汽车处于相同位置,旋转变换需要在平移变换之前还是之后应用? |
diagonal_row_of_five_twisted_cars.scad … for (i=[0:1:4]) { translate([i*70,i*50,0]) rotate([0,0,i*90]) car(); } … |
您正在创建的模式已经变得越来越酷,这是一个有趣的模式。
circular_pattern_of_ten_cars.scad … r = 200; for (i=[0:36:359]) { translate([r*cos(i),r*sin(i),0]) car(); } … |
在上面的模式中,汽车被放置在半径为 200 个单位的完美圆周上等间距的点上。如果您希望创建此类模式,则应该注意一些要点。
首先,为了创建一个圆形模式,您需要使用极坐标。根据您的背景,您可能只看一眼代码就能注意到极坐标的使用,或者您可能不知道它是什么。在后一种情况下,您只需要知道极坐标是一种方法,可以根据圆的半径和对应于该点的角度生成圆上给定点的 X 和 Y 坐标。角度 0 对应于圆上属于 X 轴正方向的点。角度的正计数方向是从 X 轴到 Y 轴。这意味着正 Y 轴对应于 90 度,负 X 轴对应于 180 度,负 Y 轴对应于 270 度,如果您完成圆,则正 X 轴对应于 360 度。根据极坐标,X 坐标可以通过将圆的半径乘以角度的余弦来计算,而 Y 坐标可以通过将圆的半径乘以角度的正弦来计算。这就是生成所需的沿 X 轴和 Y 轴的平移量的方式。
您应该注意到的第二件事是 for 循环变量 i 的取值。变量 i 从 0 开始,每次循环递增 36,以便在圆周上等距放置 10 辆汽车(360/10 = 36)。第一辆汽车在 0 度角处创建,与 360 度对应的汽车将完全重叠。为了避免这种情况,您需要指示 for 循环变量在达到 360 之前停止递增。如果您懒得计算 360 - 36 = 324,您可以直接将限制设置为 359。这将正常工作,因为 for 循环变量将只取值 0、36、72、108、144、180、216、252、288 和 324,因为再递增 36 个单位将导致 360,超过 359。
通过使用额外的变量并为其命名,您可以使脚本更具描述性和可用性,以便任何人(甚至您在以后的时间点)都能更容易地理解它们的作用以及如何使用它们。例如,之前的脚本可以采用以下形式。
… r = 200; // pattern radius n = 10; // number of cars step = 360/n; for (i=[0:step:359]) { angle = i; dx = r*cos(angle); dy = r*sin(angle); translate([dx,dy,0]) car(); } … |
在上面的脚本中,for 循环变量 i 对应于角度,这是不言自明的。平移沿每个轴的量也更加清晰。此外,通过更改半径和/或汽车数量,可以轻松地自定义此模式。
修改上述脚本中的适当值,以创建半径为 160 个单位的圆周上等距放置的 14 辆汽车的图案。 |
parametric_circular_pattern_of_fourteen_cars.scad … r = 160; // pattern radius n = 14; // number of cars step = 360/n; for (i=[0:step:359]) { angle = i; dx = r*cos(angle); dy = r*sin(angle); translate([dx,dy,0]) car(); } … |
如果您不熟悉极坐标,请使用以下脚本进行尝试。尝试将不同的值分配给半径和角度变量,并查看汽车的最终位置。 |
car_positioned_with_polar_coordinates.scad … radius = 100; angle = 30; // degrees dx = radius*cos(angle); dy = radius*sin(angle); translate([dx,dy,0]) car(); … |
parametric_circular_pattern_of_fourteen_cars.scad … r = 160; // pattern radius n = 14; // number of cars step = 360/n; for (i=[0:step:359]) { angle = i; dx = r*cos(angle); dy = r*sin(angle); translate([dx,dy,0]) car(); } … |
上面的脚本用于创建圆形汽车图案。通过添加旋转变换修改上面的脚本,使所有汽车都面向原点。使用您修改后的脚本创建一个图案,该图案具有 12 辆汽车和 140 个单位的半径。 |
cars_facing_at_the_origin.scad … r = 140; // pattern radius n = 12; // number of cars step = 360/n; for (i=[0:step:359]) { angle = i; dx = r*cos(angle); dy = r*sin(angle); translate([dx,dy,0]) rotate([0,0,angle]) car(); } … |
对上述脚本进行适当的更改以创建:i) 所有汽车都背离原点的图案 ii) 所有汽车都以切线方向定向,就像在圆周上逆时针行驶一样 iii) 以及像在圆周上顺时针行驶一样。 |
- 背离原点
cars_facing_away_from_the_origin.scad … translate([dx,dy,0]) rotate([0,0,angle - 180]) car(); … |
- 逆时针行驶
cars_driving_counter_clockwise.scad … translate([dx,dy,0]) rotate([0,0,angle - 90]) car(); … |
- 顺时针行驶
cars_driving_clockwise.scad … translate([dx,dy,0]) rotate([0,0,angle - 270]) car(); … |
现在您已经掌握了使用 for 循环创建图案的方法,是时候将您的新技能用于开发更复杂的轮毂设计了!
如果您对 OpenSCAD 技能感到自信,或者想进行更多实验,请尝试构建一个名为 spoked_wheel 的新模块,以创建以下轮毂设计。如果您想获得更多关于创建此模块的指导,请完成以下练习。 |
如果您对一些额外的指导感到更舒服,那就很好。创建一个名为 spoked_wheel 的新模块,该模块具有 5 个输入参数。输入参数应分别命名为 radius、width、thickness、number_of_spokes 和 spoke_radius。分别为这些变量指定 12、5、5、7 和 1.5 的默认值。使用 cylinder 和 difference 命令通过从较大的圆柱体中减去较小的圆柱体来创建轮毂的环形。您需要创建的模型可以在以下图像中看到。在此步骤中,您将只使用 radius、width 和 thickness 变量。请记住,当您从另一个物体中减去一个物体时,它需要清除另一个物体的表面以避免任何错误。在定义小圆柱体的高度时请牢记这一点。您还需要从 radius 和 thickness 变量中计算出小圆柱体的半径。您可以使用名为 inner_radius 的变量来存储相应计算的结果,然后使用它来定义较小圆柱体的半径。 |
ring_of_spoked_wheel.scad … module spoked_wheel(radius=12, width=5, thickness=5, number_of_spokes=7, spoke_radius=1.5) { // Ring inner_radius = radius - thickness/2; difference() { cylinder(h=width,r=radius,center=true); cylinder(h=width + 1,r=inner_radius,center=true); } } spoked_wheel(); … |
扩展之前的模块以另外创建轮毂的辐条,如以下图像所示。轮毂的辐条需要是圆柱形的。辐条的长度需要适当,以便每根辐条从环形的中心延伸到其半厚度。您将不得不使用 for 循环以图案形式创建辐条。随意查看之前的 for 循环示例,这些示例可以帮助您完成此操作。 |
spoked_wheel_horizontal.scad … module spoked_wheel(radius=12, width=5, thickness=5, number_of_spokes=7, spoke_radius=1.5) { // Ring inner_radius = radius - thickness/2; difference() { cylinder(h=width,r=radius,center=true); cylinder(h=width + 1,r=inner_radius,center=true); } // Spokes spoke_length = radius - thickness/4; step = 360/number_of_spokes; for (i=[0:step:359]) { angle = i; rotate([0,90,angle]) cylinder(h=spoke_length,r=spoke_radius); } } spoked_wheel(); … |
为了使新的轮毂设计与您在整个教程中创建的现有轮毂设计和模块兼容,它需要旋转以直立,如以下图像所示。添加适当的旋转变换以执行此操作。 |
spoked_wheel.scad … module spoked_wheel(radius=12, width=5, thickness=5, number_of_spokes=7, spoke_radius=1.5) { rotate([90,0,0]) { // Ring inner_radius = radius - thickness/2; difference() { cylinder(h=width,r=radius,center=true); cylinder(h=width + 1,r=inner_radius,center=true); } // Spokes spoke_length = radius - thickness/4; step = 360/number_of_spokes; for (i=[0:step:359]) { angle = i; rotate([0,90,angle]) cylinder(h=spoke_length,r=spoke_radius); } } } … spoked_wheel(); … |
在 vehicle_parts.scad 脚本中添加 spoked_wheel 模块。在您的汽车模型之一中使用新的轮毂设计。如果您没有想法,可以尝试复制以下模型。 |
car_with_spoked_wheels.scad use <vehicle_parts.scad> $fa = 1; $fs = 0.4; front_track = 24; rear_track = 34; wheelbase = 60; front_wheels_radius = 10; front_wheels_width = 4; front_wheels_thickness = 3; front_spoke_radius = 1; front_axle_radius = 1.5; // Round car body resize([90,20,12]) sphere(r=10); translate([10,0,5]) resize([50,15,15])sphere(r=10); // Wheels translate([-wheelbase/2,-front_track/2,0]) spoked_wheel(radius=front_wheels_radius, width=front_wheels_width, thickness=front_wheels_thickness, spoke_radius=front_spoke_radius); translate([-wheelbase/2,front_track/2,0]) spoked_wheel(radius=front_wheels_radius, width=front_wheels_width, thickness=front_wheels_thickness, spoke_radius=front_spoke_radius); translate([wheelbase/2,-rear_track/2,0]) spoked_wheel(); translate([wheelbase/2,rear_track/2,0]) spoked_wheel(); // Axles translate([-wheelbase/2,0,0]) axle(track=front_track, radius=front_axle_radius); translate([wheelbase/2,0,0]) axle(track=rear_track); |
以下脚本沿 Y 轴创建一行汽车。
row_of_six_cars_along_y_axis.scad … n = 6; // number of cars y_spacing = 50; for (dy=[0:y_spacing:n*y_spacing-1]) { translate([0,dy,0]) car(); } … |
修改上面的脚本以创建 4 行额外的汽车。与前一行相比,每一行都应该沿 X 轴的正方向平移 70 个单位。 |
five_rows_of_six_cars.scad … n = 6; // number of cars y_spacing = 50; for (dy=[0:y_spacing:n*y_spacing-1]) { translate([0,dy,0]) car(); } for (dy=[0:y_spacing:n*y_spacing-1]) { translate([70,dy,0]) car(); } for (dy=[0:y_spacing:n*y_spacing-1]) { translate([140,dy,0]) car(); } for (dy=[0:y_spacing:n*y_spacing-1]) { translate([210,dy,0]) car(); } for (dy=[0:y_spacing:n*y_spacing-1]) { translate([280,dy,0]) car(); } … |
如果您一直在密切关注本教程,您可能已经注意到上面的脚本效率不高。它有很多代码重复,行数不容易修改。您在本章开头想要创建一行汽车时就遇到了类似的情况。为了解决这个问题,您将创建图案一部分(一辆汽车)的语句包装在一个 for 循环中。这生成了整个图案(一行汽车),而无需为每辆单独的汽车键入语句。同样的原则可以应用在这里。在本例中,重复的图案将是汽车行,它本身是单个汽车的重复图案。遵循与之前相同的过程,创建汽车行的语句将被放置在一个 for 循环中,以创建汽车行的图案。结果是,一个 for 循环被放置在另一个 for 循环中。以这种方式使用的 for 循环称为嵌套 for 循环。以下示例演示了此概念。
five_rows_of_six_cars_with_nested_for_loops.scad … n_cars = 6; y_spacing = 50; n_rows = 5; x_spacing = 70; for (dx=[0:x_spacing:n_rows*x_spacing-1]) { for (dy=[0:y_spacing:n_cars*y_spacing-1]) { translate([dx,dy,0]) car(); } } … |
您应该注意以下概念。在外层 for 循环的第一次循环中,内层 for 循环的所有循环都会被执行,从而创建第一行汽车。在外层 for 循环的第二次循环中,内层 for 循环的所有循环都会被执行,从而创建第二行汽车。依此类推。每一行都由 dx 变量定位,dx 变量保存沿 X 轴的参量化平移。在外层循环的每次循环中,都会使用新的 dx 值。然后,此值保持不变,而内层循环执行并修改 dy 值。这样,在每个 dx 值处都会生成一行汽车。
使用嵌套 for 循环创建三个类似于下图的圆形汽车图案。外层循环的 for 循环变量应用于参数化每个图案的半径。圆形图案的半径应分别为 140、210 和 280 个单位。每个图案应由 12 辆汽车组成。 |
three_circular_patterns.scad … n = 12; // number of cars step = 360/n; for (r=[140:70:280]) { for (angle=[0:step:359]) { dx = r*cos(angle); dy = r*sin(angle); translate([dx,dy,0]) rotate(angle) car(); } } … |
修改之前练习的脚本,使每个图案不仅半径不同,而且汽车数量也不同。为此,使用索引变量 i 作为外层循环的变量,而不是对应于半径的变量 r。应根据公式 r = 70 + i*70 在外层 for 循环的每次循环中计算变量 r。此外,在外层 for 循环的每次循环中,变量 n 应根据公式 n = 12 + i*2 取不同的值。步长变量也需要在外层 for 循环的每次循环中更新。变量 i 应取值 1、2 和 3。 |
three_circular_patterns_with_increasing_number_of_cars.scad … for (i=[1:1:3]) { r = 70 + i*70; n = 12 + i*2; step = 360/n; for (angle=[0:step:359]) { dx = r*cos(angle); dy = r*sin(angle); translate([dx,dy,0]) rotate(angle) car(); } } … |