def test_shadows__full_scene(self): """Test that we identify a shadow in a full scene""" # First sphere is at z=10 s1 = shapes.Sphere() s1.set_transform(transforms.Translate(0, 0, 10)) # Second sphere is at the origin s2 = shapes.Sphere() s2.material = materials.Material() # Light is at z=-10 l1 = lights.Light(position=points.Point(0, 0, -10), intensity=colors.Color(1, 1, 1)) scene = scenes.Scene(objects=[s1, s2], lights=[l1]) # The ray is at z=5 (i.e. between the spheres), pointing at the further # out sphere ray = rays.Ray(points.Point(0, 0, 5), vectors.Vector(0, 0, 1)) isection = intersections.Intersection(s2, 4) comps = isection.precompute(ray) result, _ = scene.shade_hit(comps) self.assertEqual(result, colors.Color(0.1, 0.1, 0.1))
def test_render_scene(self): """Test we can render a pixel in a simple scene""" # Inner sphere size 0.5, centered on the origin s1 = shapes.Sphere() s1.set_transform(transforms.Scale(0.5,0.5,0.5)) # Outer sphere centered on the origin, size 1.0 s2 = shapes.Sphere() s2.material = materials.Material( color=colors.Color(0.8, 1.0, 0.6), diffuse=0.7, specular=0.2) l1 = lights.Light( position=points.Point(-10, 10, -10), intensity=colors.Color(1, 1, 1) ) scene = scenes.Scene( objects = [s1, s2], lights = [l1] ) cam = cameras.Camera(11, 11, math.pi/2) from_point = points.Point(0, 0, -5) to_point = points.Point(0, 0, 0) up = vectors.Vector(0, 1, 0) cam.transform = transforms.ViewTransform(from_point, to_point, up) image = cam.render(scene) self.assertEqual(image.get(5, 5), colors.Color(0.3807, 0.4758, 0.2855))
def test_refractive_index_intersections(self): """Test we can calculate the refractive indices between intersections""" # Set up a scene with three glass spheres. One at the origin with size # 2 then inside of that 2 that are offset along z by different amounts A = shapes.Sphere(material=materials.Material(refractive_index=1.5, transparency=1.0)) B = shapes.Sphere(material=materials.Material(refractive_index=2.0, transparency=1.0)) C = shapes.Sphere(material=materials.Material(refractive_index=2.5, transparency=1.0)) A.set_transform(transforms.Scale(2, 2, 2)) B.set_transform(transforms.Translate(0, 0, -0.25)) C.set_transform(transforms.Translate(0, 0, 0.25)) r = rays.Ray(points.Point(0, 0, -4), vectors.Vector(0, 0, 1)) xs = intersections.Intersections(intersections.Intersection(A, 2), intersections.Intersection(B, 2.75), intersections.Intersection(C, 3.25), intersections.Intersection(B, 4.75), intersections.Intersection(C, 5.25), intersections.Intersection(A, 6)) expected_results = [ { "n1": 1.0, "n2": 1.5 }, { "n1": 1.5, "n2": 2.0 }, { "n1": 2.0, "n2": 2.5 }, { "n1": 2.5, "n2": 2.5 }, { "n1": 2.5, "n2": 1.5 }, { "n1": 1.5, "n2": 1.0 }, ] for index, expected in enumerate(expected_results): comps = xs.intersections[index].precompute(r, all_intersections=xs) self.assertDictEqual(expected, {"n1": comps.n1, "n2": comps.n2})
def test_reflective_transparent_material(self): """Test shade_hit with a reflective, transparent material""" world = copy.deepcopy(self.default_scene) floor = shapes.Plane(material=materials.Material( reflective=0.5, transparency=0.5, refractive_index=1.5)) floor.set_transform(transforms.Translate(0, -1, 0)) ball = shapes.Sphere(material=materials.Material( color=colors.Color(1, 0, 0), ambient=0.5)) ball.set_transform(transforms.Translate(0, -3.5, -0.5)) r = rays.Ray(points.Point(0, 0, -3), vectors.Vector(0, -math.sqrt(2) / 2, math.sqrt(2) / 2)) xs = intersections.Intersections( intersections.Intersection(floor, math.sqrt(2))) world.add_object(floor) world.add_object(ball) comps = xs.intersections[0].precompute(r, all_intersections=xs) color, _ = world.shade_hit(comps, remaining=5) self.assertEqual(color, colors.Color(0.93391, 0.69643, 0.69243))
def test_intersection_miss(self): """Test we handle the case where the ray misses the sphere""" s = shapes.Sphere() r = rays.Ray(points.Point(0, 2, -5), vectors.Vector(0, 0, 1)) self.assertEqual(s.intersect(r).intersections, [])
def test_hits__positive(self): """Test we get the correct hit for multiple positive intersections""" shape = shapes.Sphere() i1 = intersections.Intersection(shape, 2) i2 = intersections.Intersection(shape, 1) isections = intersections.Intersections(i1, i2) self.assertEqual(isections.hit(), i2)
def test_pattern_transformation(self): """Test that pattern is affected by a pattern transform""" shape = shapes.Sphere() p = patterns.StripePattern(WHITE, BLACK) p.set_transform(transforms.Scale(2, 2, 2)) self.assertEqual(p.pattern_at_shape(shape, points.Point(1.5, 0, 0)), WHITE)
def setUp(self): """Set up a default scene for quick testing""" # Inner sphere size 0.5, centered on the origin self.s1 = shapes.Sphere() self.s1.set_transform(transforms.Scale(0.5, 0.5, 0.5)) # Outer sphere centered on the origin, size 1.0 self.s2 = shapes.Sphere() self.s2.material = materials.Material(color=colors.Color( 0.8, 1.0, 0.6), diffuse=0.7, specular=0.2) self.l1 = lights.Light(position=points.Point(-10, 10, -10), intensity=colors.Color(1, 1, 1)) self.default_scene = scenes.Scene(objects=[self.s1, self.s2], lights=[self.l1])
def test_hits__some_negative(self): """Test we get the correct hit for some negative intersections""" shape = shapes.Sphere() i1 = intersections.Intersection(shape, -1) i2 = intersections.Intersection(shape, 1) isections = intersections.Intersections(i1, i2) self.assertEqual(isections.hit(), i2)
def test_hits__all_negative(self): """Test we get no hits for all negative intersections""" shape = shapes.Sphere() i1 = intersections.Intersection(shape, -1) i2 = intersections.Intersection(shape, -2) isections = intersections.Intersections(i1, i2) self.assertIsNone(isections.hit())
def test_pattern_object_transformation(self): """Test that pattern is affected by pattern and object transforms""" shape = shapes.Sphere() shape.set_transform(transforms.Scale(2, 2, 2)) p = patterns.StripePattern(WHITE, BLACK) p.set_transform(transforms.Translate(0.5, 0, 0)) self.assertEqual(p.pattern_at_shape(shape, points.Point(2.5, 0, 0)), WHITE)
def test_sphere(): sphere_center = [-0.5, 1, 0] s = shp.Sphere(1.5).shift(sphere_center) # plane Z=0, radius: 1.5 # plane, Y=0, radius: sqrt(1.5**2 - 1) = 1.118 # plane, X=0, redius: sqrt(1.5**2 - 0.25) = 1.41 assert s.inside([0, 0, 0]) assert not s.inside([1, 0, 0]) assert not s.inside([0, -1, 0]) assert not s.inside([0, 0, -1]) assert not s.inside([0, 0, 1])
def test_intersection_standard(self): """Test we can identify what points a ray intersects with a sphere""" s = shapes.Sphere() r = rays.Ray(points.Point(0, 0, -5), vectors.Vector(0, 0, 1)) result = s.intersect(r) self.assertEqual(result.intersections[0].t, 4) self.assertEqual(result.intersections[1].t, 6) self.assertEqual(result.intersections[0].shape, s) self.assertEqual(result.intersections[1].shape, s)
def test_intersection_behind(self): """Test we handle the case where the ray starts inside the sphere""" s = shapes.Sphere() r = rays.Ray(points.Point(0, 0, 5), vectors.Vector(0, 0, 1)) result = s.intersect(r) self.assertEqual(result.intersections[0].t, -6) self.assertEqual(result.intersections[1].t, -4) self.assertEqual(result.intersections[0].shape, s) self.assertEqual(result.intersections[1].shape, s)
def test_intersections_with_transformed_ray__translation(self): """Test we get the correct intersections after adding a translation to a shape """ s = shapes.Sphere() s.set_transform(transforms.Translate(5, 0, 0)) r = rays.Ray(points.Point(0, 0, -5), vectors.Vector(0, 0, 1)) result = s.intersect(r) self.assertTrue(len(result.intersections) == 0)
def test_hits__multiple(self): """Test we get the correct hit for some negative intersections""" shape = shapes.Sphere() i1 = intersections.Intersection(shape, -1) i2 = intersections.Intersection(shape, 7) i3 = intersections.Intersection(shape, -5) i4 = intersections.Intersection(shape, 2) i5 = intersections.Intersection(shape, 3) isections = intersections.Intersections(i1, i2, i3, i4, i5) self.assertEqual(isections.hit(), i4)
def test_normal_at__transformed(self): """Test we can calculate normal vectors on a transformed sphere""" s = shapes.Sphere() s.set_transform(transforms.Translate(0, 1, 0)) n = s.normal_at(points.Point(0, 1.70711, -0.70711)) self.assertEqual(n, vectors.Vector(0, 0.70711, -0.70711)) s.set_transform( transforms.Scale(1, 0.5, 1) * transforms.RotateZ(math.pi / 5)) n = s.normal_at(points.Point(0, math.sqrt(2) / 2, -math.sqrt(2) / 2)) self.assertEqual(n, vectors.Vector(0, 0.97014, -0.24254))
def test_intersections_with_transformed_ray__scaling(self): """Test we get the correct intersections after adding a scaling to a shape """ s = shapes.Sphere() s.set_transform(transforms.Scale(2, 2, 2)) r = rays.Ray(points.Point(0, 0, -5), vectors.Vector(0, 0, 1)) result = s.intersect(r) self.assertEqual(result.intersections[0].t, 3) self.assertEqual(result.intersections[1].t, 7) self.assertEqual(result.intersections[0].shape, s) self.assertEqual(result.intersections[1].shape, s)
def test_precompute__over_vector(self): """Test that we calculate a vector just inside of the surface of a shape """ r = rays.Ray(points.Point(0, 0, -5), vectors.Vector(0, 0, 1)) s = shapes.Sphere() s.set_transform(transforms.Translate(0, 0, 1)) i = intersections.Intersection(s, 5) computations = i.precompute(r) self.assertTrue(computations.under_point.z > computations.point.z) self.assertTrue(computations.under_point.z > intersections.EPSILON / 2)
def test_initialization(self): shape = shapes.Sphere() t1 = 3.5 t2 = -3.5 i1 = intersections.Intersection(shape, t1) self.assertEqual(i1.t, 3.5) self.assertEqual(i1.shape, shape) i2 = intersections.Intersection(shape, t2) self.assertEqual(i2.t, -3.5) i = intersections.Intersections(i1, i2) self.assertEqual(i.intersections, [i2, i1])
def test_precompute__inside(self): """Test that we can precompute vectors for an intersection and ray when inside of a shape""" r = rays.Ray(points.Point(0, 0, 0), vectors.Vector(0, 0, 1)) s = shapes.Sphere() i = intersections.Intersection(s, 1) computations = i.precompute(r) self.assertEqual(computations.t, 1) self.assertEqual(computations.point, points.Point(0, 0, 1)) self.assertEqual(computations.eyev, vectors.Vector(0, 0, -1)) self.assertEqual(computations.normalv, vectors.Vector(0, 0, -1)) self.assertTrue(computations.inside)
def test_normal_at__non_transformed(self): """Test we can calculate normal vectors on the unit sphere""" s = shapes.Sphere() n = s.normal_at(points.Point(1, 0, 0)) self.assertEqual(n, vectors.Vector(1, 0, 0)) n = s.normal_at(points.Point(0, 1, 0)) self.assertEqual(n, vectors.Vector(0, 1, 0)) n = s.normal_at(points.Point(0, 0, 1)) self.assertEqual(n, vectors.Vector(0, 0, 1)) sqrt3d3 = math.sqrt(3) / 3 n = s.normal_at(points.Point(sqrt3d3, sqrt3d3, sqrt3d3)) self.assertEqual(n, vectors.Vector(sqrt3d3, sqrt3d3, sqrt3d3))
def obj_repr(self, render_params): """ Returns complete representation of the point as a sphere. TESTS:: sage: P = point3d((1,2,3),size=3,color='purple') sage: P.obj_repr(P.default_render_params())[0][0:2] ['g obj_1', 'usemtl texture...'] """ T = render_params.transform if T is None: import transform T = transform.Transformation() render_params.push_transform(~T) S = shapes.Sphere(self.size / 200.0).translate(T(self.loc)) cmds = S.obj_repr(render_params) render_params.pop_transform() return cmds
def load_from_file(self): """Загрузить распределение из файла.""" try: loaded_file, _ = QtWidgets.QFileDialog.getOpenFileName( None, 'Введите имя файла', self.work_dir, 'Файлы распределений частиц (*.tsv)') self.loaded_files.append(loaded_file) import shapes with open(loaded_file, 'r') as f: loaded = [] for strings_list in csv.reader(f, delimiter='\t'): numbers_list = [] for string in strings_list: numbers_list.append(float(string)) loaded.append(tuple(numbers_list)) # определение размерности задачи for i in loaded: if i[2]: # хотя бы один параметр z != 0 self.options['dim_ind'] = 1 break for i in loaded: if self.options['dim_ind'] == 0: # 2D self.loaded_particles.append( shapes.Circle(x=i[0], y=i[1], d=i[3])) elif self.options['dim_ind'] == 1: # 3D self.loaded_particles.append( shapes.Sphere(x=i[0], y=i[1], z=i[2], d=i[3])) self.message('Загружен файл: {0}'.format(loaded_file)) self.all_particles.extend(self.loaded_particles) self.cleanDistributions.setEnabled(True) except FileNotFoundError: self.message( 'Распределение не загружено!\nПричина: не введено имя файла')
def calc_regular_distribution(self): """Calculation regular distribution.""" import shapes self.regular_particles = [] # очистка x0 = self.options['regular_distribution'][0][0] dx = self.options['regular_distribution'][1][0] nx = self.options['regular_distribution'][2][0] y0 = self.options['regular_distribution'][0][1] dy = self.options['regular_distribution'][1][1] ny = self.options['regular_distribution'][2][1] if self.options['dim_ind'] == 1: # 3D z0 = self.options['regular_distribution'][0][2] dz = self.options['regular_distribution'][1][2] nz = self.options['regular_distribution'][2][2] for x in [x0 + dx * i for i in range(nx)]: for y in [y0 + dy * i for i in range(ny)]: if self.options['dim_ind'] == 1: # 3D for z in [z0 + dz * i for i in range(nz)]: particle = shapes.Sphere( x, y, z, self.options['regular_distribution_diameter']) self.regular_particles.append(particle) else: # 2D # z = 0 particle = shapes.Circle( x, y, self.options['regular_distribution_diameter']) self.regular_particles.append(particle) self.all_particles.extend( self.regular_particles) # расширить список всех частиц self.button_save.setEnabled(True) self.clean_distributions.setEnabled(True) self.message('Регулярное распределение частиц посчитано')
class View2D: """View2D.""" def __init__(self, edge, data): plt.close('all') plt.figure(num='Микроструктура', figsize=(7, 7), dpi=100) ax = plt.axes() FrontView(axes=ax, data=data, matrix_edge=edge) plt.tight_layout() plt.show() if __name__ == '__main__': import shapes m = shapes.CubeMatrix(edge=100.) p1 = shapes.Sphere(x=0., y=20., z=40., d=10.) p2 = shapes.Sphere(x=50., y=50., z=50., d=20.) View3D(m.edge, [p1, p2]) p3 = shapes.Circle(x=10., y=40, d=30.) p4 = shapes.Circle(x=50., y=75., d=15.) View2D(m.edge, [p3, p4])
0.5 * (particle.d / 2) ** 2 * (alpha - np.sin(alpha)) return value def filling(matrix, space_): """Степень заполнения матрицы частицами.""" return space_ / matrix.space() if __name__ == '__main__': import shapes square = shapes.SquareMatrix(edge=5.) print(square) circle = shapes.Circle(x=2.5, y=2.5, d=2.) print(circle) print('S={0:.3f}'.format(space(square, circle))) print('KV={0:.3f}'.format(filling(square, space(square, circle)))) print('-'*50) cube = shapes.CubeMatrix(edge=5.) print(cube) sphere = shapes.Sphere(x=2.5, y=2.5, z=2.5, d=2.) print(sphere) print('V={0:.3f}'.format(space(cube, sphere))) print('KV={0:.3f}'.format(filling(cube, space(cube, sphere))))
p = shapes.Parallelogram(200, 100) p.printarea() p.get_sides() p.printperimeter() p.draw() print("\n HEXAGON \n") h = shapes.Hexagon(6, 150) h.printarea("Hexagon") h.get_sides("Hexagon") h.printperimeter("Hexagon") h.draw() print("\n CIRCLE \n") c = shapes.Circle(100) print("Area of circle is: ",c.area()) print("Circumference of circle is: ",c.circumference()) c.draw() print("\n ELLIPSE \n") c = shapes.Ellipse(100, 50) print("Area of ellipse is: ",c.area()) print("Circumference of ellipse is: ",c.circumference()) c.draw() print('\n Sphere \n') s = shapes.Sphere(100) print("Surface area of sphere is: ", s.surface_area())
def preset_filling_degree(app): """Создать распределение частиц по известной степени заполнения фракций.""" def fil(): if app.options['dim_ind'] == 0: # 2D m = shapes.SquareMatrix(app.options['matrix']) elif app.options['dim_ind'] == 1: # 3D m = shapes.CubeMatrix(app.options['matrix']) s = fill_deg.space(matrix=m, particle=particle) f = fill_deg.filling(matrix=m, space_=s) return f print_to_console(app, 'new') app.processRunning = True for diameter, max_filling in app.options['fractions_definition']: if diameter != 0: current_filling = 0 current_iteration = 1 # кол-во попыток подобрать распределение частиц в матрице while app.processRunning and current_filling < max_filling and\ current_iteration < app.options['max_iter']: app.message('<font color="teal">текущее заполнение = {0:.3f}, \ диаметр = {1:.1f}</font>'.format( current_filling, diameter)) if app.options['dim_ind'] == 0: # 2D point = shapes.Point2D() elif app.options['dim_ind'] == 1: # 3D point = shapes.Point3D() if app.options['var_ind'] == 0: # частицы целиком находятся в матрице point.randomize_particles_inside(app.options['matrix'], diameter) elif app.options['var_ind'] == 1: # центры частиц находятся в матрице point.randomize_centers_inside(app.options['matrix']) QtWidgets.qApp.processEvents() if app.processRunning: app.message('Итерация {0}: ({1})'.format( current_iteration, point)) app.current_event_label.setText( 'диаметр = {0:.1f}; текущее заполнение = {1:.3f}; \ итерация = {2}'.format(diameter, current_filling, current_iteration)) else: break if app.options['dim_ind'] == 0: # 2D particle = shapes.Circle(point.x, point.y, diameter) elif app.options['dim_ind'] == 1: # 3D particle = shapes.Sphere(point.x, point.y, point.z, diameter) # проверка кол-ва пересечения границ (допускается только одно!) if particle.crossing(matrix_edge=app.options['matrix']) > 1: current_iteration += 1 continue # проверка близости границ if check_boundary(app, particle): # недопустимо близко к границе current_iteration += 1 continue # добавление первой частицы if not app.random_particles: app.random_particles.append(particle) print_to_console(app, 'added') current_filling += fil() continue # добавление остальных частиц intersec = False # пересечение частиц for i in app.random_particles: if intersection(app, particle, i): print_to_console(app, 'overlap', i) intersec = True # установлено пересечение частиц current_iteration += 1 continue if not intersec: app.random_particles.append(particle) print_to_console(app, 'added') current_filling += fil() continue else: current_iteration += 1 else: continue if app.processRunning: app.message( '<font color="blue"><b>Все частицы успешно добавлены</b></font>') app.all_particles = app.random_particles.copy() # обновление общего набора частиц app.saveAll.setEnabled(True) else: app.message('<font color="red"><b>Процесс расчёта \ остановлен пользователем</b></font>') app.processStart.setEnabled(True) app.processStop.setDisabled(True) app.cleanDistributions.setEnabled(True) app.current_event_label.setText('')
def preset_particles_number(app): """Создать распределение частиц по известному их количеству.""" print_to_console(app, 'new') fails = {} # не удалось подобрать координаты частицы app.processRunning = True for diameter, remain in app.options['fractions_definition']: if diameter != 0: while app.processRunning and remain > 0: # осталось разместить частиц данного диаметра app.message('<font color="blue">Осталось разместить:</font> \ <font color="teal">кол-во = {0}, \ диаметр = {1:.1f}</font>'.format(remain, diameter)) current_iteration = 1 # кол-во попыток подобрать распределение частиц в матрице while current_iteration < app.options['max_iter']: if app.options['dim_ind'] == 0: # 2D point = shapes.Point2D() elif app.options['dim_ind'] == 1: # 3D point = shapes.Point3D() if app.options['var_ind'] == 0: # частицы целиком находятся в матрице point.randomize_particles_inside( app.options['matrix'], diameter) elif app.options['var_ind'] == 1: # центры частиц находятся в матрице point.randomize_centers_inside(app.options['matrix']) QtWidgets.qApp.processEvents() if app.processRunning: app.message('Итерация {0}: ({1})'.format( current_iteration, point)) app.current_event_label.setText( 'диаметр = {0:.1f}; осталось разместить = {1:.0f};\ итерация = {2}'.format(diameter, remain, current_iteration)) else: break if app.options['dim_ind'] == 0: # 2D particle = shapes.Circle(point.x, point.y, diameter) elif app.options['dim_ind'] == 1: # 3D particle = shapes.Sphere(point.x, point.y, point.z, diameter) # проверка кол-ва пересечения границ # (допускается только одно!) if particle.crossing(matrix_edge=app.options['matrix'])\ > 1: continue # проверка близости границ if check_boundary(app, particle): # недопустимо близко к границе current_iteration += 1 continue # добавление первой частицы if not app.random_particles: app.random_particles.append(particle) print_to_console(app, 'added') break # добавление остальных частиц intersec = False # пересечение частиц for i in app.random_particles: if intersection(app, particle, i): print_to_console(app, 'overlap', i) intersec = True # установлено пересечение частиц break if not intersec: app.random_particles.append(particle) print_to_console(app, 'added') break else: current_iteration += 1 else: if diameter in fails: fails[diameter] += 1 else: fails[diameter] = 1 app.message('<font color="red">Не удалось добавить \ частицу диаметра {0}</font>'.format(diameter)) remain -= 1 else: continue if app.processRunning: if fails: app.message( '<font color="red"><b>Не добавлены частицы:</b></font>') for diameter in fails: app.message('<font color="red">диаметр = {0:.1f}, \ кол-во = {1}</font>'.format(diameter, fails[diameter])) else: app.message('<font color="blue"><b>Все частицы \ успешно добавлены</b></font>') app.all_particles = app.random_particles.copy() # обновление общего набора частиц app.saveAll.setEnabled(True) else: app.message('<font color="red"><b>Процесс расчёта \ остановлен пользователем</b></font>') app.processStart.setEnabled(True) app.processStop.setDisabled(True) app.cleanDistributions.setEnabled(True) app.current_event_label.setText('')