├── sceneKit demo ├── 01_ship │ ├── ship.scn │ └── shipDemo.py ├── 02_Corvette-F3 │ ├── Corvette-F3.scn │ ├── SF_Corvette-F3_bump.jpg │ ├── SF_Corvette-F3_glow.jpg │ ├── SF_Corvette-F3_diffuse.jpg │ ├── SF_Corvette-F3_specular.jpg │ └── Corvette-F3-Demo.py ├── 03_Feisar_ship │ ├── Feisar_Ship.scn │ └── Feisar_ShipDemo.py ├── 17_cube-puzzle │ ├── resources │ │ ├── wood.png │ │ ├── marble.jpg │ │ ├── Five_Piece-shapes.P │ │ ├── Half_Hour-shapes.P │ │ ├── Soma_Cube-shapes.P │ │ ├── Five_Piece-solution.P │ │ ├── Half_Hour-solution.P │ │ ├── Soma_Cube-solution.P │ │ ├── Diabolical_Cube-shapes.P │ │ ├── Diabolical_Cube-solution.P │ │ ├── Mikusinskis_Cube-shapes.P │ │ └── Mikusinskis_Cube-solution.P │ ├── README.md │ ├── main.py │ ├── selector.py │ ├── data.py │ ├── hud.py │ ├── piece.py │ ├── solver.py │ ├── manual.py │ └── setup.py ├── 16_AppleVehicleDemo │ ├── resources │ │ ├── ball.jpg │ │ ├── click.caf │ │ ├── icon.png │ │ ├── smoke.png │ │ ├── spark.png │ │ ├── tire.jpg │ │ ├── wall.jpg │ │ ├── wheel.png │ │ ├── wood.png │ │ ├── carpet.jpg │ │ ├── krStar.png │ │ ├── launch.png │ │ ├── needle.png │ │ ├── reactor.scnp │ │ ├── smoke.scnp │ │ ├── WoodCubeA.jpg │ │ ├── WoodCubeB.jpg │ │ ├── WoodCubeC.jpg │ │ ├── book_back.jpg │ │ ├── book_front.jpg │ │ ├── book_side.jpg │ │ ├── carpetOld.jpg │ │ ├── rc_car_PY.scn │ │ ├── speedGauge.png │ │ ├── starBurst.skn │ │ ├── tex_smoke.png │ │ ├── train_wood.jpg │ │ ├── train_flat_PY.scn │ │ ├── video_camera.png │ │ ├── book_side_title.jpg │ │ ├── rc_car_texture.png │ │ └── video_camera@2x.png │ └── OverlayScene.py ├── 07_textDemo.py ├── 04_Lucy │ └── MDLDemo.py ├── 12_particlesDemo.py ├── 08_tetrahedron.py ├── 11_avoidOccluderDemo.py ├── 10_morpherDemo.py ├── 05_photoCube.py ├── 13_physicsDemo-1.py ├── 09_shapeDemo.py ├── 14_physicsDemo-2.py └── 06_photoCubeWithRendererDelegate.py ├── .travis.yml ├── LICENSE └── sceneKit ├── sceneKitAudio.py ├── sceneKitLight.py ├── sceneKitEnv.py ├── sceneKitMaterial.py ├── sceneKitGeometrySource.py ├── sceneKitConstraints.py └── __init__.py /sceneKit demo/01_ship/ship.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/01_ship/ship.scn -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | install: pip install flake8 3 | script: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 4 | -------------------------------------------------------------------------------- /sceneKit demo/02_Corvette-F3/Corvette-F3.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/02_Corvette-F3/Corvette-F3.scn -------------------------------------------------------------------------------- /sceneKit demo/03_Feisar_ship/Feisar_Ship.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/03_Feisar_ship/Feisar_Ship.scn -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/resources/wood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/17_cube-puzzle/resources/wood.png -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/resources/marble.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/17_cube-puzzle/resources/marble.jpg -------------------------------------------------------------------------------- /sceneKit demo/02_Corvette-F3/SF_Corvette-F3_bump.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/02_Corvette-F3/SF_Corvette-F3_bump.jpg -------------------------------------------------------------------------------- /sceneKit demo/02_Corvette-F3/SF_Corvette-F3_glow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/02_Corvette-F3/SF_Corvette-F3_glow.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/ball.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/ball.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/click.caf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/click.caf -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/icon.png -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/smoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/smoke.png -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/spark.png -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/tire.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/tire.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/wall.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/wall.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/wheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/wheel.png -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/wood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/wood.png -------------------------------------------------------------------------------- /sceneKit demo/02_Corvette-F3/SF_Corvette-F3_diffuse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/02_Corvette-F3/SF_Corvette-F3_diffuse.jpg -------------------------------------------------------------------------------- /sceneKit demo/02_Corvette-F3/SF_Corvette-F3_specular.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/02_Corvette-F3/SF_Corvette-F3_specular.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/carpet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/carpet.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/krStar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/krStar.png -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/launch.png -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/needle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/needle.png -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/reactor.scnp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/reactor.scnp -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/smoke.scnp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/smoke.scnp -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/WoodCubeA.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/WoodCubeA.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/WoodCubeB.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/WoodCubeB.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/WoodCubeC.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/WoodCubeC.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/book_back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/book_back.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/book_front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/book_front.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/book_side.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/book_side.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/carpetOld.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/carpetOld.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/rc_car_PY.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/rc_car_PY.scn -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/speedGauge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/speedGauge.png -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/starBurst.skn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/starBurst.skn -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/tex_smoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/tex_smoke.png -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/train_wood.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/train_wood.jpg -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/resources/Five_Piece-shapes.P: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/17_cube-puzzle/resources/Five_Piece-shapes.P -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/resources/Half_Hour-shapes.P: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/17_cube-puzzle/resources/Half_Hour-shapes.P -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/resources/Soma_Cube-shapes.P: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/17_cube-puzzle/resources/Soma_Cube-shapes.P -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/train_flat_PY.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/train_flat_PY.scn -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/video_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/video_camera.png -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/resources/Five_Piece-solution.P: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/17_cube-puzzle/resources/Five_Piece-solution.P -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/resources/Half_Hour-solution.P: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/17_cube-puzzle/resources/Half_Hour-solution.P -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/resources/Soma_Cube-solution.P: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/17_cube-puzzle/resources/Soma_Cube-solution.P -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/book_side_title.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/book_side_title.jpg -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/rc_car_texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/rc_car_texture.png -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/resources/video_camera@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/16_AppleVehicleDemo/resources/video_camera@2x.png -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/resources/Diabolical_Cube-shapes.P: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/17_cube-puzzle/resources/Diabolical_Cube-shapes.P -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/resources/Diabolical_Cube-solution.P: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/17_cube-puzzle/resources/Diabolical_Cube-solution.P -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/resources/Mikusinskis_Cube-shapes.P: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/17_cube-puzzle/resources/Mikusinskis_Cube-shapes.P -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/resources/Mikusinskis_Cube-solution.P: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulbrich/sceneKit-wrapper-for-Pythonista/HEAD/sceneKit demo/17_cube-puzzle/resources/Mikusinskis_Cube-solution.P -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Peter Ulbrich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/README.md: -------------------------------------------------------------------------------- 1 | #Objective 2 | 3 | Build a 3x3x3 sized cube from the available pieces. There are five typical configurations to choose from. Click on the selected puzzle box to start solving it. 4 | 5 | #Solver interface 6 | Click on a piece to move it to a position above the destination cube. Touch the red spheres to rotate and the yellow cones to shift the piece. Clicking on another piece will swap the one in the middle with the newly selected shape. Touching an empty (ghosty) location clears the manipulation area. 7 | 8 | Once a piece is in the desired posisiton in the manipulation area, tap the gray cube to drop it. If the piece fits, you can choose the next piece to work with, otherwise touching any of the spheres or cones will lift the piece back to furter adjust it. The piece last dropped can be lifted by touching the gray cube again (undo last drop), if the manipulation area is empty. 9 | 10 | In the bottom left corner there is a `restart` button. The `?` in the bottom right corner gives you a hint about the next possible moves. If the manipulation area is empty, possible continuation pieces will be highlighted green. If there is already a selected piece in the middle, you can circle through possible positions by repeatedly tapping the `?`. 11 | 12 | The top-right `back arrow` takes you back to the puzzle selection screen, whereas the top-left `x` closes the application. 13 | 14 | #Camera control 15 | You can look around the central area as well as the location of the pieces by the following gestures: 16 | 17 | - ```Pan with one finger``` to rotate the camera around the scene 18 | 19 | - ```Pan with two fingers``` to translate the camera on its local xy-plane 20 | 21 | - ```Pan with three fingers``` vertically to move the the camera forward backward 22 | 23 | - ```Double-tap``` to reset the camera 24 | 25 | - ```Rotate with two fingers``` to roll the camera (rotate on the camera node's z-axis) 26 | 27 | - ```Pinch``` to zoom in or zoom out (change the camera's fieldOfView) 28 | 29 | 30 | 31 | **All puzzles can be solved, so have fun!** 32 | 33 | (c) Peter Ulbrich, 2019 34 | 35 | 36 | -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/main.py: -------------------------------------------------------------------------------- 1 | '''Cube puzzles - five typical 3x3x3 3D puzzles. 2 | 3 | (c) Peter Ulbrich, 2019 4 | 5 | a demo script for the sceneKit wrapper package for Pythonista 6 | 7 | See the README.md for details. 8 | 9 | ''' 10 | 11 | import sceneKit as scn 12 | import ui 13 | import Gestures 14 | 15 | from data import * 16 | from hud import * 17 | import manual 18 | import selector 19 | import setup 20 | 21 | class MainViewController: 22 | 23 | class MainView(ui.View): 24 | def will_close(self): 25 | data.gestures_instance.remove_all_gestures(data.main_view) 26 | for aSceneView in data.active_stage.scene_views(): 27 | aSceneView.scene.paused = True 28 | aSceneView.removeFromSuperview() 29 | for aView in data.main_view.subviews: 30 | data.main_view.remove_subview(aView) 31 | 32 | def layout(self): 33 | _, _, w, h = data.main_view.frame 34 | data.horizontal = w > h 35 | data.hud_layer.layout() 36 | data.active_stage.layout() 37 | 38 | 39 | @classmethod 40 | def run(cls): 41 | scn.clearCache() 42 | cls().main() 43 | 44 | @on_main_thread 45 | def main(self): 46 | data.main_view = self.MainView() 47 | w, h = ui.get_window_size() 48 | data.main_view.frame = (0, 0, w, h) 49 | 50 | data.gestures_instance = Gestures.Gestures() 51 | data.gestures_instance.add_tap(data.main_view, self.simple_tap) 52 | 53 | data.hud_layer = Hud() 54 | self.next_stage(selector.SelectorStage()) 55 | 56 | data.main_view.present(style='fullscreen', hide_title_bar=True) 57 | 58 | @classmethod 59 | def next_stage(cls, stage_instance): 60 | if data.active_stage is not None: 61 | for aSceneView in data.active_stage.scene_views(): 62 | aSceneView.scene.paused = True 63 | aSceneView.removeFromSuperview() 64 | data.active_stage = stage_instance 65 | data.hud_layer.bring_to_front() 66 | 67 | def simple_tap(self, tap_data): 68 | data.active_stage.tap(tap_data.location) 69 | 70 | 71 | if __name__ == '__main__': 72 | MainViewController.run() 73 | -------------------------------------------------------------------------------- /sceneKit demo/07_textDemo.py: -------------------------------------------------------------------------------- 1 | ''' 2 | based on the code in the thread https://forum.omz-software.com/topic/1686/3d-in-pythonista by omz 3 | ''' 4 | from objc_util import * 5 | import sceneKit as scn 6 | import ui 7 | import math 8 | 9 | @on_main_thread 10 | def demo(): 11 | main_view = ui.View() 12 | w, h = ui.get_screen_size() 13 | main_view.frame = (0,0,w,h) 14 | main_view.name = 'textDemo' 15 | 16 | scene_view = scn.View(main_view.frame, superView=main_view) 17 | scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleRightMargin 18 | scene_view.antialiasingMode = scn.AntialiasingMode.Multisampling16X 19 | 20 | scene_view.allowsCameraControl = True 21 | 22 | scene_view.backgroundColor = 'white' 23 | 24 | scene_view.scene = scn.Scene() 25 | 26 | root_node = scene_view.scene.rootNode 27 | text_mesh = scn.Text.textWithString('Pythonista', 6.0) 28 | text_mesh.flatness = 0.2 29 | text_mesh.chamferRadius = 0.4 30 | text_mesh.font = ('HelveticaNeue-Bold', 18) 31 | bbox_min, bbox_max = text_mesh.boundingBox 32 | text_width = bbox_max.x - bbox_min.x 33 | text_node = scn.Node.nodeWithGeometry(text_mesh) 34 | text_node.castsShadow = True 35 | text_container = scn.Node.node() 36 | text_container.addChildNode(text_node) 37 | text_container.position = (0, 40, 0) 38 | text_node.position = (-text_width/2, 0, 0) 39 | box = scn.Box(width=150, height=4, length=150, chamferRadius=1) 40 | box_node = scn.Node.nodeWithGeometry(box) 41 | root_node.addChildNode(box_node) 42 | rotate_action = scn.Action.repeatActionForever(scn.Action.rotateBy(0, math.pi*2, math.pi*2, 10)) 43 | text_container.runAction(rotate_action) 44 | root_node.addChildNode(text_container) 45 | light_node = scn.Node.node() 46 | light_node.position = (0, 105, 5) 47 | light_node.rotation = (1, 0, 0, -math.pi/2) 48 | light = scn.Light.light() 49 | light.type = 'spot' 50 | light.spotOuterAngle = 65 51 | light.castsShadow = True 52 | light.shadowSampleCount = 16 53 | light.color = 'cyan' 54 | light_node.light = light 55 | root_node.addChildNode(light_node) 56 | 57 | main_view.present(style='fullscreen', hide_title_bar=False) 58 | 59 | demo() 60 | 61 | -------------------------------------------------------------------------------- /sceneKit demo/04_Lucy/MDLDemo.py: -------------------------------------------------------------------------------- 1 | """ 2 | MDL import demo 3 | """ 4 | 5 | from objc_util import * 6 | import sceneKit as scn 7 | import ui 8 | import math 9 | 10 | MDLAsset, MDLMesh, MDLSubmesh = ObjCClass('MDLAsset'), ObjCClass('MDLMesh'), ObjCClass('MDLSubmesh') 11 | 12 | 13 | 14 | class Demo: 15 | 16 | @classmethod 17 | def run(cls): 18 | cls().main() 19 | 20 | @on_main_thread 21 | def main(self): 22 | main_view = ui.View() 23 | w, h = ui.get_screen_size() 24 | main_view.frame = (0,0,w,h) 25 | main_view.name = 'MDL import demo' 26 | 27 | scene_view = scn.View(main_view.frame, superView=main_view) 28 | scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleWidth 29 | scene_view.allowsCameraControl = True 30 | scene_view.backgroundColor = 'white' 31 | scene_view.rendersContinuously = True 32 | scene_view.scene = scn.Scene() 33 | 34 | root_node = scene_view.scene.rootNode 35 | 36 | floor_geometry = scn.Floor() 37 | floor_node = scn.Node.nodeWithGeometry(floor_geometry) 38 | root_node.addChildNode(floor_node) 39 | 40 | asset = MDLAsset.alloc().initWithURL_(nsurl('Lucy.obj')) 41 | mesh = asset.objectAtIndex_(0) 42 | lucy_geometry = scn.Geometry.geometryWithMDLMesh(mesh) 43 | 44 | lucy_node_1 = scn.Node.nodeWithGeometry(lucy_geometry) 45 | root_node.addChildNode(lucy_node_1) 46 | 47 | lucy_node_2 = scn.Node.nodeWithMDLObject(mesh) 48 | lucy_node_2.position = (10., 0., 0.) 49 | root_node.addChildNode(lucy_node_2) 50 | 51 | camera_node = scn.Node() 52 | camera_node.camera = scn.Camera() 53 | camera_node.position = (10., 10., 10.) 54 | camera_node.lookAt(root_node.position) 55 | root_node.addChildNode(camera_node) 56 | 57 | light_node = scn.Node() 58 | light_node.position = (-20., 20., 20) 59 | light = scn.Light() 60 | light.type = scn.LightTypeDirectional 61 | light.castsShadow = True 62 | light.shadowSampleCount = 32 63 | light.color = (.99, 1.0, .86) 64 | light_node.light = light 65 | light_node.lookAt(root_node.position) 66 | root_node.addChildNode(light_node) 67 | 68 | main_view.present(style='fullscreen', hide_title_bar=False) 69 | 70 | Demo.run() 71 | -------------------------------------------------------------------------------- /sceneKit demo/02_Corvette-F3/Corvette-F3-Demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | using the Corvette-F3.scn file 3 | """ 4 | 5 | from objc_util import * 6 | import ctypes 7 | import sceneKit as scn 8 | import ui 9 | import math 10 | 11 | 12 | class Demo: 13 | def __init__(self): 14 | self.name = 'Corvette-F3' 15 | pass 16 | 17 | @classmethod 18 | def run(cls): 19 | cls().main() 20 | 21 | @on_main_thread 22 | def main(self): 23 | main_view = ui.View() 24 | w, h = ui.get_screen_size() 25 | main_view.frame = (0,0,w,h) 26 | main_view.name = 'Corvette-F3 demo' 27 | 28 | scene_view = scn.View(main_view.frame, superView=main_view) 29 | scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleWidth 30 | scene_view.allowsCameraControl = True 31 | 32 | scene_view.scene = scn.Scene.sceneWithURL(url='Corvette-F3.scn') 33 | 34 | root_node = scene_view.scene.rootNode 35 | corvette_node = root_node.childNodes[0] 36 | 37 | corvette_geometry = corvette_node.geometry 38 | corvette_material = corvette_geometry.firstMaterial 39 | 40 | corvette_material.diffuse.contents = ui.Image.named('SF_Corvette-F3_diffuse.jpg') 41 | corvette_material.emission.contents = ui.Image.named('SF_Corvette-F3_glow.jpg') 42 | corvette_material.specular.contents = ui.Image.named('SF_Corvette-F3_specular.jpg') 43 | corvette_material.normal.contents = ui.Image.named('SF_Corvette-F3_bump.jpg') 44 | 45 | constraint = scn.LookAtConstraint.lookAtConstraintWithTarget(corvette_node) 46 | constraint.gimbalLockEnabled = True 47 | 48 | light_node = scn.Node() 49 | light_node.position = (-800, -800, -2100) 50 | light = scn.Light() 51 | light.type = scn.LightTypeDirectional 52 | light.castsShadow = True 53 | light.color = (.89, .89, .89) 54 | light.intensity = 500 55 | light_node.light = light 56 | light_node.constraints = constraint 57 | root_node.addChildNode(light_node) 58 | 59 | ambient_light = scn.Light() 60 | ambient_light.type = scn.LightTypeAmbient 61 | ambient_light.name = 'ambient light' 62 | ambient_light.color = (.99, 1.0, .86) 63 | ambient_light.intensity = 300 64 | ambient_node = scn.Node() 65 | ambient_node.light = ambient_light 66 | root_node.addChildNode(ambient_node) 67 | 68 | 69 | main_view.present(style='fullscreen', hide_title_bar=False) 70 | 71 | Demo.run() 72 | -------------------------------------------------------------------------------- /sceneKit demo/01_ship/shipDemo.py: -------------------------------------------------------------------------------- 1 | """ 2 | using the ship.scn file from XCode 3 | """ 4 | 5 | from objc_util import * 6 | import ctypes 7 | import sceneKit as scn 8 | import ui 9 | import math 10 | 11 | 12 | class Demo: 13 | def __init__(self): 14 | self.name = 'ship!' 15 | pass 16 | 17 | @classmethod 18 | def run(cls): 19 | cls().main() 20 | 21 | @on_main_thread 22 | def main(self): 23 | main_view = ui.View() 24 | w, h = ui.get_screen_size() 25 | main_view.frame = (0,0,w,h) 26 | main_view.name = 'ship demo' 27 | 28 | scene_view = scn.View(main_view.frame, superView=main_view) 29 | scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleWidth 30 | scene_view.allowsCameraControl = True 31 | 32 | scene_view.scene = scn.Scene.sceneWithURL(url='ship.scn') 33 | 34 | root_node = scene_view.scene.rootNode 35 | 36 | ship_node = root_node.childNodes[0] 37 | ship_mesh = ship_node.childNodes[0] 38 | ship_emitter = ship_mesh.childNodes[0] 39 | ship_emitter_light = ship_emitter.light 40 | ship_camera = ship_mesh.camera 41 | scrap_meshShape = ship_mesh.geometry 42 | lambert1 = scrap_meshShape.materials[0] 43 | 44 | camera_node = scn.Node() 45 | camera_node.camera = scn.Camera() 46 | camera_node.position = (0,0,5) 47 | root_node.addChildNode(camera_node) 48 | 49 | # Add a constraint to the camera to keep it pointing to the target geometry 50 | constraint = scn.LookAtConstraint.lookAtConstraintWithTarget(root_node) 51 | constraint.gimbalLockEnabled = True 52 | camera_node.constraints = constraint 53 | 54 | light_node = scn.Node() 55 | light_node.position = (100, 50, 100) 56 | light = scn.Light() 57 | light.type = scn.LightTypeDirectional 58 | light.castsShadow = True 59 | light.color = 'white' 60 | light_node.light = light 61 | light_node.constraints = constraint 62 | root_node.addChildNode(light_node) 63 | 64 | ambient_light = scn.Light() 65 | ambient_light.type = scn.LightTypeAmbient 66 | ambient_light.name = 'ambient light' 67 | ambient_light.color = (.99, 1.0, .86) 68 | ambient_light.intensity = 100 69 | ambient_node = scn.Node() 70 | ambient_node.light = ambient_light 71 | root_node.addChildNode(ambient_node) 72 | 73 | main_view.present(style='fullscreen', hide_title_bar=False) 74 | 75 | Demo.run() 76 | -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/selector.py: -------------------------------------------------------------------------------- 1 | '''Cube puzzles - five typical 3x3x3 3D puzzles. 2 | 3 | (c) Peter Ulbrich, 2019 4 | 5 | a demo script for the sceneKit wrapper package for Pythonista 6 | 7 | See the README.md for details. 8 | 9 | ''' 10 | 11 | import math 12 | 13 | import sceneKit as scn 14 | 15 | from data import * 16 | import main 17 | import setup 18 | from piece import * 19 | import manual 20 | 21 | class SelectorStage: 22 | def __init__(self): 23 | self.selector_views = setup.setup_selector_views() 24 | self.animation = setup.setup_show_drawer_animation() 25 | self.animation.repeatCount = 1 26 | self.animation.animationDidStop = self.next_selector_animation 27 | self.animation_player = scn.AnimationPlayer(self.animation) 28 | 29 | self.selector_views[0].scene.background.addAnimationPlayer(self.animation_player, 'highlight') 30 | self.animation_player.play() 31 | 32 | self.animation_index = 0 33 | 34 | data.hud_layer.back_button.hidden = True 35 | data.hud_layer.restart_button.hidden = True 36 | data.hud_layer.set_title('Cube puzzles') 37 | 38 | def next_selector_animation(self, animation, receiver, completed): 39 | if completed: 40 | self.selector_views[self.animation_index].scene.background.removeAllAnimations() 41 | self.animation_index += 1 42 | if self.animation_index == len(self.selector_views): 43 | self.animation_index = 0 44 | self.selector_views[self.animation_index].scene.background.addAnimationPlayer(self.animation_player, 'highlight') 45 | 46 | def scene_views(self): 47 | return self.selector_views 48 | 49 | def tap(self, location): 50 | for index, aView in enumerate(self.selector_views): 51 | aFrame = aView.frame 52 | if aFrame[0] < location.x < aFrame[0] + aFrame[2] and aFrame[1] < location.y < aFrame[1] + aFrame[3]: 53 | data.hud_layer.back_button.hidden = False 54 | data.hud_layer.restart_button.hidden = False 55 | main.MainViewController.next_stage(manual.ManualStage(Puzzle_variant(index))) 56 | 57 | def hint(self): 58 | data.hud_layer.show_generic_help() 59 | 60 | def layout(self): 61 | _, _, w, h = data.main_view.frame 62 | rows = int(math.sqrt(len(self.selector_views))) 63 | columns = (len(self.selector_views)-1)//rows + 1 64 | missing = rows*columns - len(self.selector_views) 65 | if not data.horizontal: 66 | rows, columns = columns, rows 67 | width = int(w / (columns + 0.8 + 0.3*(columns-1))) 68 | height = int(h / (rows + 0.8 + 0.3*(rows-1))) 69 | for i in range(len(self.selector_views)): 70 | row = i // columns 71 | column = i % columns 72 | if row != rows - 1: 73 | self.selector_views[i].frame = (int(0.4*width + 1.3*column*width), int(0.4*height + 1.3*row*height), width, height) 74 | else: 75 | self.selector_views[i].frame = (int(0.4*width + missing/2*1.3*width + 1.3*column*width), int(0.4*height + 1.3*row*height), width, height) 76 | camera_node = self.selector_views[i].scene.rootNode.childNodeWithName('selector_camera') 77 | camera_node.position = (0., 7., 11.) if width/height > 0.8 else (0., 9.5, 14.5) 78 | 79 | 80 | if __name__ == '__main__': 81 | main.MainViewController.run() 82 | -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/data.py: -------------------------------------------------------------------------------- 1 | '''Cube puzzles - five typical 3x3x3 3D puzzles. 2 | 3 | (c) Peter Ulbrich, 2019 4 | 5 | a demo script for the sceneKit wrapper package for Pythonista 6 | 7 | See the README.md for details. 8 | 9 | ''' 10 | 11 | from collections import namedtuple 12 | from enum import Enum 13 | import ui 14 | 15 | @ui.in_background 16 | def print_in_background(*text): 17 | print(*text) 18 | 19 | Cube = namedtuple('Cube', 'x y z') 20 | 21 | class Puzzle_variant(Enum): 22 | Half_Hour = 0 23 | Soma_Cube = 1 24 | Diabolical_Cube = 2 25 | Mikusinskis_Cube = 3 26 | Five_Piece = 4 27 | 28 | class Data: 29 | __slots__ = ('main_view', 'horizontal', 'gestures_instance', 'hud_layer', 'active_stage', 'reference_pieces') 30 | 31 | def __init__(self): 32 | self.reference_pieces = self.create_reference_pieces() 33 | self.active_stage = None 34 | 35 | def create_reference_pieces(self): 36 | piece_definitions = [ 37 | [ 38 | [((2., 0., 0.), (1., 1., 0.), (1., 1., -1.)), '1'], 39 | [((2., 0., 0.), (2., 0., -1.)), '2'], 40 | [((2., 0., 0.), (0., 0., -1.), (1., 1., 0.)), '3'], 41 | [((1., 1., 0.), (2., 1., 0.), (1., 0., -1.)), '4'], 42 | [((2., 0., 0.), (1., 0., -1.)), '5'], 43 | [((0., 0., -1.), (0., 1., -1.)), '6'] 44 | ], [ 45 | [((0., 0., -1.), (0., 1., -1.)), '1'], 46 | [((0., 1., 0.), (0., 0., -1.)), '2'], 47 | [((1., 1., 0.), (0., 0., -1.)), '3'], 48 | [((2., 0., 0.), (2., 0., -1.)), '4'], 49 | [((2., 0., 0.), (1., 0., -1.)), '5'], 50 | [((1., 0., -1.),), '6'], 51 | [((1., 0., -1.), (2., 0., -1.)), '7'] 52 | ], [ 53 | [tuple(), '1'], 54 | [((0., 0., -1.), (1., 0., -1.)), '2'], 55 | [((2., 0., 0.,), (0., 0., -1.), (1., 0., -1.), (0., 0., -2.)), '3'], 56 | [((0., 0., -1),), '4'], 57 | [((2., 0., 0.), (0., 0., -1.), (2., 0., -1.)), '5'], 58 | [((2., 0., 0.), (0., 0., -1.), (1., 0., -1.), (2., 0., -1.), (0., 0., -2.)), '6'] 59 | ], [ 60 | [((2., 0., 0.), (0., 0., -1.)), '1'], 61 | [((2., 0., 0.), (0., 0., -1.), (0., 1., -1.)), '2'], 62 | [((0., 0., -1.), (0., 1., -1.)), '3'], 63 | [((1., 0., -1.), (1., 1., -1.)), '4'], 64 | [((2., 0., 0.), (2., 1., 0.), (1., 0., -1.)), '5'], 65 | [((1., 0., -1.), (1., 1., -1.), (2., 1., -1.)), '6'] 66 | ], [ 67 | [((1., 0., -1.), (2., 0., 0.), (2., 1., 0.)), '1'], 68 | [((2., 0., 0.), (2., 1., 0.), (2., 1., -1.)), '2'], 69 | [((2., 0., 0.), (2., 0., -1.), (1., 1., 0.)), '3'], 70 | [((0., 1., 0.), (2., 0., 0.), (2., 0., -1.), (2., 1., -1.)), '4'], 71 | [((0., 1., 0.), (2., 0., 0.), (2., 1., 0.), (2., 1., -1.)), '5'] 72 | ] 73 | ] 74 | 75 | reference_pieces = [] 76 | for aDefinition in piece_definitions: 77 | pieces = {} 78 | for aPiece in aDefinition: 79 | name = aPiece[1] 80 | nodes = [Cube(0., 0., 0.)] 81 | 82 | nodes.append(Cube(1., 0., 0.)) 83 | 84 | for aCubePosition in aPiece[0]: 85 | nodes.append(Cube._make(aCubePosition)) 86 | 87 | pieces[name] = nodes 88 | reference_pieces.append(pieces) 89 | return reference_pieces 90 | 91 | data = Data() 92 | 93 | if __name__ == '__main__': 94 | import main 95 | main.MainViewController.run() 96 | -------------------------------------------------------------------------------- /sceneKit demo/12_particlesDemo.py: -------------------------------------------------------------------------------- 1 | ''' 2 | based on the code in the thread https://forum.omz-software.com/topic/1686/3d-in-pythonista by omz 3 | ''' 4 | from objc_util import * 5 | import sceneKit as scn 6 | import ui 7 | import math 8 | 9 | @on_main_thread 10 | def demo(): 11 | main_view = ui.View() 12 | w, h = ui.get_screen_size() 13 | main_view.frame = (0,0,w,h) 14 | main_view.name = 'particles demo' 15 | 16 | scene_view = scn.View(main_view.frame, superView=main_view) 17 | scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleWidth 18 | scene_view.antialiasingMode = scn.AntialiasingMode.Multisampling16X 19 | 20 | scene_view.allowsCameraControl = True 21 | 22 | scene_view.backgroundColor = 'black' 23 | 24 | scene_view.scene = scn.Scene() 25 | 26 | root_node = scene_view.scene.rootNode 27 | text_mesh = scn.Text.textWithString('Pythonista', 6.0) 28 | text_mesh.flatness = 0.2 29 | text_mesh.chamferRadius = 0.4 30 | text_mesh.font = ('HelveticaNeue-Bold', 18) 31 | bbox_min, bbox_max = text_mesh.boundingBox 32 | text_width = bbox_max.x - bbox_min.x 33 | text_node = scn.Node.nodeWithGeometry(text_mesh) 34 | text_node.castsShadow = False 35 | text_container = scn.Node.node() 36 | text_container.addChildNode(text_node) 37 | text_container.position = (0, 40, 0) 38 | text_node.position = (-text_width/2, 0, 0) 39 | 40 | fire = scn.ParticleSystem() 41 | fire.birthRate = 300000 42 | fire.loops = True 43 | fire.emissionDuration = 8 44 | fire.emissionDurationVariation = 4 45 | fire.idleDuration = 2 46 | fire.idleDurationVariation = 0.5 47 | fire.emittingDirection = (0, 1, 0) 48 | fire.spreadingAngle = 15 49 | fire.particleDiesOnCollision = False 50 | fire.particleLifeSpan = 0.4 51 | fire.particleLifeSpanVariation = 0.5 52 | fire.particleVelocity = 20 53 | fire.particleVelocityVariation = 30 54 | fire.particleImage = ui.Image.named('shp:WhitePuff05') 55 | fire.particleSize = 0.4 56 | fire.particleSizeVariation = 0.2 57 | fire.particleIntensity = 1.5 58 | fire.particleIntensityVariation = 2 59 | fire.stretchFactor = 0.02 60 | colorAnim = scn.CoreKeyframeAnimation() 61 | colorAnim.values = [(.99, 1.0, .71, 0.8), (1.0, .52, .0, 0.8), (1., .0, .1, 1.), (.78, .0, .0, 0.3)] 62 | colorAnim.keyTimes = (0., 0.1, 0.8, 1.) 63 | fire.timingFunctions = [scn.CoreMediaTimingFunction.functionWithName(aFunc) for aFunc in [scn.MediaTimingFunctionEaseOut, scn.MediaTimingFunctionEaseInEaseOut, scn.MediaTimingFunctionEaseIn]] 64 | prop_con = scn.ParticlePropertyController.controllerWithAnimation(colorAnim) 65 | fire.propertyControllers = {scn.SCNParticlePropertyColor:prop_con} 66 | fire.emitterShape = text_mesh 67 | fire.birthLocation = scn.ParticleBirthLocation.SCNParticleBirthLocationSurface 68 | text_node.addParticleSystem(fire) 69 | 70 | root_node.addChildNode(text_container) 71 | 72 | light_node = scn.Node.node() 73 | light_node.position = (0, 105, 5) 74 | light_node.rotation = (1, 0, 0, -math.pi/2) 75 | light = scn.Light.light() 76 | light.type = 'spot' 77 | light.spotOuterAngle = 90 78 | light.castsShadow = True 79 | light.shadowSampleCount = 16 80 | light.color = 'white' 81 | light_node.light = light 82 | root_node.addChildNode(light_node) 83 | 84 | main_view.present(style='fullscreen', hide_title_bar=False) 85 | 86 | demo() 87 | 88 | -------------------------------------------------------------------------------- /sceneKit demo/16_AppleVehicleDemo/OverlayScene.py: -------------------------------------------------------------------------------- 1 | ''' 2 | In the original version this was an SKScene. For easier integration it was rewritten with objects from the Phytonista ui module. 3 | ''' 4 | import ui 5 | import io 6 | import math 7 | from PIL import Image 8 | 9 | import sceneKit as scn 10 | 11 | M_PI_2 = math.pi/2 12 | 13 | class SpeedNeedle: 14 | def __init__(self): 15 | self.view = ui.ImageView() 16 | self.view.content_mode = ui.CONTENT_CENTER 17 | 18 | self.image_base = Image.open("resources/needle.png") 19 | size = self.image_base.size 20 | self.image_base = self.image_base.resize((int(size[0]*0.55), int(size[1]*0.55))) 21 | size = self.image_base.size 22 | self.image = Image.new("RGBA", (2*size[1], 2*size[1]), None) 23 | self.image.paste(self.image_base, (size[1], 0)) 24 | self.image = self.image.rotate(90) 25 | 26 | self.zRotation = 0.0 27 | 28 | 29 | def get_zRotation(self): 30 | return self._zRotation/180.*math.pi 31 | def set_zRotation(self, angle): 32 | self._zRotation = angle/math.pi*180. 33 | self.draw_needle() 34 | zRotation = property(get_zRotation, set_zRotation) 35 | 36 | 37 | def draw_needle(self): 38 | needle = self.image.rotate(self._zRotation) 39 | with io.BytesIO() as bIO: 40 | needle.save(bIO, 'PNG') 41 | ret_im = ui.Image.from_data(bIO.getvalue()) 42 | self.view.image = ret_im 43 | 44 | class CameraButton: 45 | def __init__(self): 46 | self.view = ui.Button() 47 | self.image = ui.Image.named("resources/video_camera.png") 48 | self.view.background_image = self.image 49 | self.view.action = self.setClicked 50 | self._clicked = False 51 | 52 | self.sound = scn.AudioSource("resources/click.caf") 53 | self.clickAction = scn.Action.playAudioSource(self.sound, True) 54 | 55 | def setClicked(self, sender): 56 | self._clicked = True 57 | 58 | @property 59 | def clicked(self): 60 | ret = self._clicked 61 | self._clicked = False 62 | return ret 63 | 64 | class OverlayScene: 65 | def __init__(self, main_view): 66 | 67 | #add the speed gauge 68 | 69 | myImage = ui.Image.named("resources/speedGauge.png") 70 | self.myImage_view = ui.ImageView() 71 | self.myImage_view.frame = (20, int(main_view.frame[3]-1.5*myImage.size.height), myImage.size.width, myImage.size.height) 72 | self.myImage_view.image = myImage 73 | main_view.add_subview(self.myImage_view) 74 | 75 | #add the needle 76 | 77 | self.mySpeedNeedle = SpeedNeedle() 78 | self.mySpeedNeedle.view.frame = (self.myImage_view.frame[0], self.myImage_view.frame[1]+self.myImage_view.frame[3]//2-8, self.myImage_view.frame[2], self.myImage_view.frame[3]) 79 | main_view.add_subview(self.mySpeedNeedle.view) 80 | 81 | #add the camera button 82 | 83 | self.myCameraButton = CameraButton() 84 | self.myCameraButton.view.frame = (main_view.frame[2]-20-self.myCameraButton.image.size.width, int(main_view.frame[3]-1.5*myImage.size.height+10), self.myCameraButton.image.size.width, self.myCameraButton.image.size.height) 85 | main_view.add_subview(self.myCameraButton.view) 86 | 87 | #add a close button 88 | self.close_button = ui.Button() 89 | self.close_button.action = self.closeClick 90 | self.close_button.frame = (20, 40, 40, 40) 91 | self.close_button.background_image = ui.Image.named('emj:No_Entry_2') 92 | self.close = False 93 | main_view.add_subview(self.close_button) 94 | 95 | def closeClick(self, sender): 96 | self.close = True 97 | 98 | -------------------------------------------------------------------------------- /sceneKit demo/08_tetrahedron.py: -------------------------------------------------------------------------------- 1 | """ 2 | based on: 3 | a tetrahedron by cvp at https://forum.omz-software.com/topic/3922/for-the-fun-a-photos-cube 4 | 5 | """ 6 | 7 | from objc_util import * 8 | import ctypes 9 | import sceneKit as scn 10 | import ui 11 | import math 12 | 13 | @on_main_thread 14 | def demo(): 15 | main_view = ui.View() 16 | w, h = ui.get_screen_size() 17 | main_view.frame = (0,0,w,h) 18 | main_view.name = 'Tetrahedron' 19 | 20 | scene_view = scn.View(main_view.frame, superView=main_view) 21 | scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleRightMargin 22 | scene_view.allowsCameraControl = True 23 | 24 | scene_view.scene = scn.Scene() 25 | 26 | root_node = scene_view.scene.rootNode 27 | 28 | camera_node = scn.Node() 29 | camera_node.camera = scn.Camera() 30 | camera_node.position = (0,0,5) 31 | root_node.addChildNode(camera_node) 32 | 33 | verts = [ 34 | scn.Vector3(0, 1, 0), 35 | scn.Vector3(-0.5, 0, 0.5), 36 | scn.Vector3(0.5, 0, 0.5), 37 | scn.Vector3(0.5, 0, -0.5), 38 | scn.Vector3(-0.5, 0, -0.5), 39 | scn.Vector3(0, -1, 0)] 40 | 41 | source = scn.GeometrySource.geometrySourceWithVertices(verts) 42 | 43 | indexes = [3,3,3,3,3,3,3,3, 44 | 0, 1, 2, 45 | 2, 3, 0, 46 | 3, 4, 0, 47 | 4, 1, 0, 48 | 1, 5, 2, 49 | 2, 5, 3, 50 | 3, 5, 4, 51 | 4, 5, 1] 52 | 53 | elements = scn.GeometryElement.geometryElementWithData(indexes, scn.GeometryPrimitiveType.Polygon) 54 | 55 | geometry = scn.Geometry.geometryWithSources(source, elements) 56 | 57 | material = scn.Material() 58 | material.contents = scn.RGBA(1,0.9,0.9,1.0) 59 | geometry.materials = material 60 | 61 | geometry_node = scn.Node.nodeWithGeometry(geometry) 62 | root_node.addChildNode(geometry_node) 63 | 64 | elements2 = [] 65 | materials = [] 66 | colors = ['red','blue','green','yellow','orange','pink','cyan','orchid'] 67 | for i in range(8): 68 | j = 8 + i*3 69 | indexes2 = [indexes[j],indexes[j+1],indexes[j+2]] 70 | element = scn.GeometryElement.geometryElementWithData(indexes2, scn.GeometryPrimitiveType.Triangles) 71 | elements2.append(element) 72 | material = scn.Material() 73 | material.contents= colors[i] 74 | materials.append(material) 75 | geometry2 = scn.Geometry.geometryWithSources(source, elements2) 76 | geometry2.materials = materials 77 | geometry2_node = scn.Node.nodeWithGeometry(geometry2) 78 | tx,ty,tz = (-1.5,0,0) 79 | translation = (1,0,0,0, 0,1,0,0, 0,0,1,0, tx,ty,tz,1) 80 | geometry2_node.pivot = translation 81 | root_node.addChildNode(geometry2_node) 82 | 83 | constraint = scn.LookAtConstraint.lookAtConstraintWithTarget(geometry_node) 84 | constraint.gimbalLockEnabled = True 85 | camera_node.constraints = constraint 86 | 87 | light_node = scn.Node() 88 | light_node.position = (100, 0, -10) 89 | light = scn.Light() 90 | light.type = scn.LightTypeDirectional 91 | light.castsShadow = True 92 | light.color = 'white' 93 | light_node.light = light 94 | root_node.addChildNode(light_node) 95 | 96 | rotate_action = scn.Action.repeatActionForever(scn.Action.rotateBy(0, math.pi*2, 0, 10)) 97 | geometry_node.runAction(rotate_action) 98 | geometry2_node.runAction(rotate_action) 99 | 100 | main_view.present(style='fullscreen', hide_title_bar=False) 101 | 102 | 103 | demo() 104 | -------------------------------------------------------------------------------- /sceneKit demo/11_avoidOccluderDemo.py: -------------------------------------------------------------------------------- 1 | """ 2 | avoid occluder demo 3 | """ 4 | 5 | from objc_util import * 6 | import sceneKit as scn 7 | import ui 8 | import math 9 | 10 | def dot(v1, v2): 11 | return sum(x*y for x,y in zip(list(v1),list(v2))) 12 | 13 | def det2(v1, v2): 14 | return v1[0]*v2[1] - v1[1]*v2[0] 15 | 16 | class Demo: 17 | 18 | @classmethod 19 | def run(cls): 20 | cls().main() 21 | 22 | @on_main_thread 23 | def main(self): 24 | main_view = ui.View() 25 | w, h = ui.get_screen_size() 26 | main_view.frame = (0,0,w,h) 27 | main_view.name = 'avoid occluder demo' 28 | 29 | scene_view = scn.View(main_view.frame, superView=main_view) 30 | scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleWidth 31 | scene_view.allowsCameraControl = True 32 | scene_view.delegate = self 33 | scene_view.backgroundColor = 'white' 34 | scene_view.rendersContinuously = True 35 | scene_view.scene = scn.Scene() 36 | 37 | root_node = scene_view.scene.rootNode 38 | 39 | floor_geometry = scn.Floor() 40 | floor_node = scn.Node.nodeWithGeometry(floor_geometry) 41 | root_node.addChildNode(floor_node) 42 | 43 | ball_radius = 0.2 44 | ball_geometry = scn.Sphere(radius=ball_radius) 45 | ball_geometry.firstMaterial.diffuse.contents = (.48, .48, .48) 46 | ball_geometry.firstMaterial.specular.contents = (.88, .88, .88) 47 | self.ball_node_1 = scn.Node.nodeWithGeometry(ball_geometry) 48 | self.ball_node_2 = scn.Node.nodeWithGeometry(ball_geometry) 49 | 50 | root_node.addChildNode(self.ball_node_1) 51 | root_node.addChildNode(self.ball_node_2) 52 | 53 | occluder_geometry = scn.Box(0.3, 2., 15., 0.2) 54 | occluder_geometry.firstMaterial.diffuse.contents = (.91, .91, .91) 55 | occluder_node = scn.Node.nodeWithGeometry(occluder_geometry) 56 | occluder_node.position = (0., 0.8, 0.) 57 | root_node.addChildNode(occluder_node) 58 | 59 | self.orbit_r = 10 60 | self.omega_speed_1 = math.pi/1500 61 | self.omega_speed_2 = 1.5*self.omega_speed_1 62 | self.ball_node_1.position = (self.orbit_r, 0.5, 0.) 63 | self.ball_node_2.position = (0., 0.5, self.orbit_r) 64 | 65 | constraint = scn.AvoidOccluderConstraint.avoidOccluderConstraintWithTarget(self.ball_node_1) 66 | self.ball_node_2.constraints = [constraint] 67 | 68 | camera_node = scn.Node() 69 | camera_node.camera = scn.Camera() 70 | camera_node.position = (0.5*self.orbit_r , 0.5*self.orbit_r, 1.5*self.orbit_r) 71 | camera_node.lookAt(root_node.position) 72 | root_node.addChildNode(camera_node) 73 | 74 | light_node = scn.Node() 75 | light_node.position = (self.orbit_r, self.orbit_r, self.orbit_r) 76 | light = scn.Light() 77 | light.type = scn.LightTypeDirectional 78 | light.castsShadow = True 79 | light.shadowSampleCount = 32 80 | light.color = (.99, 1.0, .86) 81 | light_node.light = light 82 | light_node.lookAt(root_node.position) 83 | root_node.addChildNode(light_node) 84 | 85 | main_view.present(style='fullscreen', hide_title_bar=False) 86 | 87 | def update(self, view, atTime): 88 | pos_1 = self.ball_node_1.presentationNode.position 89 | pos_2 = self.ball_node_2.presentationNode.position 90 | self.omega_1 = -math.atan2(det2((pos_1.x, pos_1.z), (1., 0.)), dot((pos_1.x, pos_1.z), (1., 0.))) 91 | self.omega_2 = -math.atan2(det2((pos_2.x, pos_2.z), (1., 0.)), dot((pos_2.x, pos_2.z), (1., 0.))) 92 | self.omega_1 += self.omega_speed_1 93 | self.omega_2 += self.omega_speed_2 94 | self.ball_node_1.position = (self.orbit_r*math.cos(self.omega_1), 0.5, self.orbit_r*math.sin(self.omega_1)) 95 | self.ball_node_2.position = (self.orbit_r*math.cos(self.omega_2), 0.5, self.orbit_r*math.sin(self.omega_2)) 96 | 97 | Demo.run() 98 | -------------------------------------------------------------------------------- /sceneKit demo/03_Feisar_ship/Feisar_ShipDemo.py: -------------------------------------------------------------------------------- 1 | """ 2 | using the Feisar_Ship.scn file 3 | """ 4 | 5 | from objc_util import * 6 | import ctypes 7 | import sceneKit as scn 8 | import ui 9 | import math 10 | 11 | 12 | class Demo: 13 | def __init__(self): 14 | self.name = 'Feisar_Ship' 15 | pass 16 | 17 | @classmethod 18 | def run(cls): 19 | cls().main() 20 | 21 | @on_main_thread 22 | def main(self): 23 | main_view = ui.View() 24 | w, h = ui.get_screen_size() 25 | main_view.frame = (0,0,w,h) 26 | main_view.name = 'Feisar_Ship demo' 27 | 28 | scene_view = scn.View(main_view.frame, superView=main_view) 29 | scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleWidth 30 | scene_view.allowsCameraControl = True 31 | 32 | scene_view.backgroundColor = 'black' 33 | 34 | scene_view.scene = scn.Scene.sceneWithURL(url='Feisar_Ship.scn') 35 | 36 | root_node = scene_view.scene.rootNode 37 | 38 | feisar_node = root_node.childNodes[0] 39 | 40 | feisar_node.light = scn.nil 41 | root_node.light = scn.nil 42 | 43 | feisar_geometry = feisar_node.geometry 44 | geometry_sources = feisar_geometry.geometrySources 45 | 46 | feisar_material = feisar_geometry.firstMaterial 47 | feisar_material.emission.intensity = 0.05 48 | feisar_material.roughness.contents = (.79, .79, .79) 49 | feisar_material.metalness.contents = (.11, .11, .11) 50 | 51 | pulse_action = scn.Action.repeatActionForever(scn.Action.sequence([scn.Action.fadeInWithDuration(1.2), scn.Action.fadeOutWithDuration(0.5)])) 52 | 53 | blue_emitter = scn.Material() 54 | blue_emitter.diffuse.contents = 'black' 55 | blue_emitter.emission.contents = (.0, .35, 1.0) 56 | white_emitter = scn.Material() 57 | white_emitter.diffuse.contents = 'black' 58 | white_emitter.emission.contents = (.86, .98, 1.0) 59 | 60 | nose_geometry = scn.Sphere(radius=1.5) 61 | nose_geometry.firstMaterial = blue_emitter 62 | nose_node = scn.Node.nodeWithGeometry(nose_geometry) 63 | nose_node.position = (0., 4.35, 161.) 64 | nose_node.castsShadow = False 65 | nose_node.runAction(pulse_action) 66 | feisar_node.addChildNode(nose_node) 67 | 68 | front_grille_p0 = scn.vector3Make((0., 10., 137.6)) 69 | front_grille_p1 = scn.vector3Make((0., 20., 153.5)) 70 | front_grille_ds1 = scn.vector3Make((-0.25, 0.7, -0.25)) 71 | grille_nr = 15 72 | dt = 0.8/grille_nr 73 | front_grille_node = scn.Node() 74 | feisar_node.addChildNode(front_grille_node) 75 | 76 | front_grille_geometry = scn.Cylinder(0.45, 13.7) 77 | front_grille_geometry.firstMaterial = white_emitter 78 | 79 | for i in range(grille_nr): 80 | aNode = scn.Node.nodeWithGeometry(front_grille_geometry) 81 | aNode.position = (0., front_grille_p0.y+(front_grille_p1.y-front_grille_p0.y)*i/grille_nr, front_grille_p0.z+(front_grille_p1.z-front_grille_p0.z)*i/grille_nr) 82 | aNode.scale = (1.+front_grille_ds1.x*i/grille_nr, 1.+front_grille_ds1.y*i/grille_nr, 1.+front_grille_ds1.z*i/grille_nr) 83 | aNode.rotation = (0., 0., 1., math.pi/2) 84 | grille_action = scn.Action.sequence([scn.Action.waitForDuration(i*dt), scn.Action.fadeOutWithDuration(0.7*dt), scn.Action.waitForDuration(dt), scn.Action.fadeInWithDuration(0.3*dt), scn.Action.waitForDuration((grille_nr-2-i)*dt)]) 85 | aNode.runAction(scn.Action.repeatActionForever(scn.Action.sequence([grille_action, grille_action.reversedAction()]))) 86 | front_grille_node.addChildNode(aNode) 87 | 88 | constraint = scn.LookAtConstraint.lookAtConstraintWithTarget(feisar_node) 89 | constraint.gimbalLockEnabled = True 90 | 91 | 92 | light_node = scn.Node() 93 | light_node.position = (150, 60, -20) 94 | 95 | light = scn.Light() 96 | light.type = scn.LightTypeDirectional 97 | light.castsShadow = True 98 | light.color = (.99, 1.0, .86) 99 | light_node.light = light 100 | light_node.constraints = constraint 101 | root_node.addChildNode(light_node) 102 | 103 | main_view.present(style='fullscreen', hide_title_bar=False) 104 | 105 | Demo.run() 106 | -------------------------------------------------------------------------------- /sceneKit demo/10_morpherDemo.py: -------------------------------------------------------------------------------- 1 | """ 2 | based on: 3 | a tetrahedron by cvp at https://forum.omz-software.com/topic/3922/for-the-fun-a-photos-cube 4 | 5 | """ 6 | 7 | from objc_util import * 8 | import ctypes 9 | import sceneKit as scn 10 | import ui 11 | import math 12 | 13 | 14 | class Demo: 15 | def __init__(self): 16 | self.name = 'my name is Demo' 17 | pass 18 | 19 | @classmethod 20 | def run(cls): 21 | cls().main() 22 | 23 | @on_main_thread 24 | def main(self): 25 | # actually you need only to preserve those properties that are needed after the main_view.present call, 26 | # in this case the self.morpher. All the other self. prefixes are not needed for the same functionality 27 | self.main_view = ui.View() 28 | w, h = ui.get_screen_size() 29 | self.main_view.frame = (0,0,w,h) 30 | self.main_view.name = 'Morpher demo' 31 | 32 | self.scene_view = scn.View(self.main_view.frame, superView=self.main_view) 33 | self.scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleRightMargin 34 | self.scene_view.allowsCameraControl = True 35 | 36 | self.scene_view.scene = scn.Scene() 37 | 38 | self.scene_view.delegate = self 39 | 40 | self.root_node = self.scene_view.scene.rootNode 41 | 42 | self.camera_node = scn.Node() 43 | self.camera_node.camera = scn.Camera() 44 | self.camera_node.position = (0,0,5) 45 | self.root_node.addChildNode(self.camera_node) 46 | 47 | verts = [ 48 | scn.Vector3(0, 1, 0), 49 | scn.Vector3(-0.5, 0, 0.5), 50 | scn.Vector3(0.5, 0, 0.5), 51 | scn.Vector3(0.5, 0, -0.5), 52 | scn.Vector3(-0.5, 0, -0.5), 53 | scn.Vector3(0, -1, 0)] 54 | 55 | verts_2 = [ 56 | scn.Vector3(0, 2.5, 0), 57 | scn.Vector3(-0.4, 0, 0.4), 58 | scn.Vector3(0.4, 0, 0.4), 59 | scn.Vector3(0.4, 0, -0.4), 60 | scn.Vector3(-0.4, 0, -0.4), 61 | scn.Vector3(0, -1.5, 0)] 62 | 63 | self.source = scn.GeometrySource.geometrySourceWithVertices(verts) 64 | self.source_2 = scn.GeometrySource.geometrySourceWithVertices(verts_2) 65 | 66 | indexes = [ 67 | 0, 1, 2, 68 | 2, 3, 0, 69 | 3, 4, 0, 70 | 4, 1, 0, 71 | 1, 5, 2, 72 | 2, 5, 3, 73 | 3, 5, 4, 74 | 4, 5, 1] 75 | 76 | self.elements = scn.GeometryElement.geometryElementWithData(indexes, scn.GeometryPrimitiveType.Triangles) 77 | 78 | self.geometry = scn.Geometry.geometryWithSources(self.source, self.elements) 79 | self.geometry_2 = scn.Geometry.geometryWithSources(self.source_2, self.elements) 80 | 81 | self.material = scn.Material() 82 | self.material.contents = scn.RGBA(1, 0.9, 0.9, 1.0) 83 | self.geometry.materials = self.material 84 | 85 | self.morpher = scn.Morpher() 86 | self.morpher.targets = [self.geometry_2] 87 | self.morpher.setWeightForTargetAtIndex(0.0, 0) 88 | 89 | self.geometry_node = scn.Node.nodeWithGeometry(self.geometry) 90 | self.geometry_node.name = 'hero' 91 | self.geometry_node.morpher = self.morpher 92 | self.root_node.addChildNode(self.geometry_node) 93 | 94 | # Add a constraint to the camera to keep it pointing to the target geometry 95 | constraint = scn.LookAtConstraint.lookAtConstraintWithTarget(self.geometry_node) 96 | constraint.gimbalLockEnabled = True 97 | self.camera_node.constraints = constraint 98 | 99 | self.light_node = scn.Node() 100 | self.light_node.position = (100, 0, -10) 101 | self.light = scn.Light() 102 | self.light.type = scn.LightTypeDirectional 103 | self.light.castsShadow = True 104 | self.light.color = 'white' 105 | self.light_node.light = self.light 106 | self.root_node.addChildNode(self.light_node) 107 | 108 | self.rotate_action = scn.Action.repeatActionForever(scn.Action.rotateBy(0, math.pi*2, 0, 10)) 109 | self.geometry_node.runAction(self.rotate_action) 110 | 111 | self.main_view.present(style='fullscreen', hide_title_bar=False) 112 | 113 | def update(self, view, atTime): 114 | tick = (int(atTime*1000) % 314)/100. 115 | weight = math.sin(tick) 116 | self.morpher.setWeightForTargetAtIndex(weight, 0) 117 | 118 | # also correct: 119 | # hero_node = view.scene.rootNode.childNodeWithName('hero') 120 | # morpher = hero_node.morpher 121 | # morpher.setWeightForTargetAtIndex(weight, 0) 122 | 123 | 124 | 125 | Demo.run() 126 | -------------------------------------------------------------------------------- /sceneKit demo/05_photoCube.py: -------------------------------------------------------------------------------- 1 | """ 2 | based on: 3 | photoCube at https://forum.omz-software.com/topic/3922/for-the-fun-a-photos-cube 4 | For the fun, a Photos cube by cvp at https://forum.omz-software.com/topic/3922/for-the-fun-a-photos-cube 5 | """ 6 | 7 | from objc_util import * 8 | import sceneKit as scn 9 | import ui 10 | 11 | cube_image_names = ['emj:Airplane', 'emj:Cat_Face', 'emj:Anchor', 'emj:Basketball', 'emj:Aubergine', 'emj:Baby_Chick_1'] 12 | cube_images_orig = [ui.Image.named(cube_image_names[i]) for i in range(6)] 13 | 14 | (image_width_orig, image_height_orig) = cube_images_orig[0].size 15 | (image_width, image_height) = (image_width_orig * 1.5, image_height_orig * 1.5) 16 | i_x = (image_width - image_width_orig)/2 17 | i_y = (image_height - image_height_orig)/2 18 | 19 | cube_images = [] 20 | for i in range(6): 21 | with ui.ImageContext(image_width, image_height) as ctx: 22 | cube_images_orig[i].draw(i_x, i_y, image_width_orig, image_height_orig) 23 | cube_images.append(ctx.get_image()) 24 | 25 | with ui.ImageContext(image_width, image_height) as ctx: 26 | rrect = ui.Path.rounded_rect(0, 0, image_width, image_height, 2) 27 | rrect.line_width = 4 28 | ui.set_color((.91, .91, .91)) 29 | rrect.fill() 30 | ui.set_color('black') 31 | rrect.stroke() 32 | d = 2 33 | rrect = ui.Path.rounded_rect(0+d, 0+d, image_width-2*d, image_height-2*d, 2) 34 | rrect.line_width = 2 35 | ui.set_color((.6, .6, .6)) 36 | rrect.stroke() 37 | inside_image = ctx.get_image() 38 | 39 | @on_main_thread 40 | def main(): 41 | main_view = ui.View() 42 | w, h = ui.get_screen_size() 43 | main_view.frame = (0,0,w,h) 44 | main_view.name = 'Photos cube' 45 | 46 | scene_view = scn.View(main_view.frame) 47 | scene_view.autoresizingMask = (scn.ViewAutoresizing.FlexibleHeight, scn.ViewAutoresizing.FlexibleRightMargin) 48 | 49 | scene_view.allowsCameraControl = True 50 | 51 | scene_view.backgroundColor = 'white' 52 | 53 | scene_view.addToSuperview(main_view) 54 | 55 | scene = scn.Scene() 56 | scene_view.scene = scene 57 | 58 | root_node = scene.rootNode 59 | 60 | cube_geometry = scn.Box(width=1, height=1, length=1, chamferRadius=0.05) 61 | cube_geometry_inside = scn.Box(width=1-0.001, height=1-0.001, length=1-0.001, chamferRadius=0.04) 62 | 63 | Material_inside = scn.Material() 64 | Material_inside.diffuse.contents = inside_image 65 | 66 | cube_geometry_inside.materials =[Material_inside for i in range(6)] 67 | 68 | cube_geometry_materials = [scn.Material() for i in range(6)] 69 | for i in range(6): 70 | cube_geometry_materials[i].diffuse.contents = cube_images[i] 71 | 72 | 73 | cube_geometry.materials = cube_geometry_materials 74 | 75 | cube_node = scn.Node.nodeWithGeometry(cube_geometry) 76 | cube_node_inside = scn.Node.nodeWithGeometry(cube_geometry_inside) 77 | cube_node.addChildNode(cube_node_inside) 78 | 79 | cube_action = scn.Action.scaleTo(1.5, 3.0) 80 | cube_reversed_action = scn.Action.scaleTo(1.0, 1.0) 81 | cube_wait_action = scn.Action.waitForDuration(0.8) 82 | cube_y_rot_action1 = scn.Action.rotateBy(0, 0.2, 0, 0.3) 83 | cube_y_rot_action2 = scn.Action.rotateBy(0, -0.4, 0, 0.3) 84 | cube_y_rot_action3 = scn.Action.rotateBy(0, 0.2, 0, 0.3) 85 | cube_sequence = scn.Action.sequence([cube_action, cube_reversed_action, cube_wait_action, cube_y_rot_action1, cube_y_rot_action2, cube_y_rot_action3, cube_wait_action]) 86 | cube_forever = scn.Action.repeatActionForever(cube_sequence) 87 | 88 | cube_node.runAction(cube_forever) 89 | 90 | constraint = scn.LookAtConstraint.lookAtConstraintWithTarget(cube_node) 91 | constraint.gimbalLockEnabled = True 92 | 93 | camera = scn.Camera() 94 | camera_node = scn.Node() 95 | camera_node.name = 'camera!' 96 | camera_node.camera = camera 97 | camera_node.position = (-3.0,3.0,3.0) 98 | camera_node.constraints = [constraint] 99 | 100 | lights_node = scn.Node() 101 | 102 | ambient_light = scn.Light() 103 | ambient_light.type = scn.LightTypeAmbient 104 | ambient_light.name = 'ambient light' 105 | ambient_light.color = (.99, 1.0, .86) 106 | ambient_node = scn.Node() 107 | ambient_node.light = ambient_light 108 | lights_node.addChildNode(ambient_node) 109 | 110 | directional_light = scn.Light() 111 | directional_light.type = scn.LightTypeDirectional 112 | directional_light.name = 'directional light' 113 | directional_light.color = 'white' 114 | directional_node = scn.Node() 115 | directional_node.light = directional_light 116 | directional_node.position = camera_node.position 117 | directional_node.constraints = [constraint] 118 | 119 | lights_node.addChildNode(directional_node) 120 | 121 | root_node.addChildNode(camera_node) 122 | root_node.addChildNode(lights_node) 123 | root_node.addChildNode(cube_node) 124 | scene_view.pointOfView = camera_node 125 | 126 | 127 | scn.Transaction.setAnimationDuration(10.0) 128 | directional_light.color = 'yellow' 129 | 130 | 131 | main_view.present(style='fullscreen', hide_title_bar=False) 132 | 133 | if __name__ == '__main__': 134 | main() 135 | -------------------------------------------------------------------------------- /sceneKit demo/13_physicsDemo-1.py: -------------------------------------------------------------------------------- 1 | """ 2 | physics experiments Nr. 1 3 | """ 4 | 5 | from objc_util import * 6 | import ctypes 7 | import sceneKit as scn 8 | import ui 9 | import math 10 | 11 | 12 | class Demo: 13 | 14 | @classmethod 15 | def run(cls): 16 | cls().main() 17 | 18 | @on_main_thread 19 | def main(self): 20 | main_view = ui.View() 21 | w, h = ui.get_screen_size() 22 | main_view.frame = (0,0,w,h) 23 | main_view.name = 'physics experiment demo - 1' 24 | 25 | scene_view = scn.View(main_view.frame, superView=main_view) 26 | scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleRightMargin 27 | scene_view.allowsCameraControl = True 28 | 29 | scene_view.backgroundColor = 'white' 30 | scene_view.scene = scn.Scene() 31 | 32 | physics_world = scene_view.scene.physicsWorld 33 | 34 | root_node = scene_view.scene.rootNode 35 | 36 | floor_geometry = scn.Floor() 37 | floor_geometry.reflectivity = 0.1 38 | floor_node = scn.Node.nodeWithGeometry(floor_geometry) 39 | root_node.addChildNode(floor_node) 40 | 41 | ball_radius = 0.2 42 | ball_geometry = scn.Sphere(radius=ball_radius) 43 | ball_geometry.firstMaterial.diffuse.contents = (.48, .48, .48) 44 | ball_geometry.firstMaterial.specular.contents = (.88, .88, .88) 45 | ball_node = scn.Node.nodeWithGeometry(ball_geometry) 46 | 47 | rope_seg_nr = 45 48 | rope_length = 5.0 49 | rope_radius = 0.025 50 | rope_seg_length = rope_length/rope_seg_nr 51 | 52 | rope_element_geometry = scn.Capsule(rope_radius, rope_seg_length) 53 | rope_element_geometry.firstMaterial.diffuse.content = (.77, .67, .09) 54 | 55 | rope_nodes = [] 56 | for i in range(rope_seg_nr): 57 | rope_nodes.append(scn.Node.nodeWithGeometry(rope_element_geometry)) 58 | if i > 0: 59 | rope_nodes[-1].position = (0., -rope_seg_length, 0.) 60 | rope_nodes[-2].addChildNode(rope_nodes[-1]) 61 | aConstraint = scn.DistanceConstraint.distanceConstraintWithTarget(rope_nodes[-2]) 62 | aConstraint.maximumDistance = rope_seg_length 63 | aConstraint.minimumDistance = rope_seg_length 64 | rope_nodes[-1].constraints = aConstraint 65 | else: 66 | rope_nodes[0].position = (0, rope_length+1.0-rope_seg_length/2, 0) 67 | aConstraint = scn.DistanceConstraint.distanceConstraintWithTarget(root_node) 68 | aConstraint.maximumDistance = rope_length+1.0-rope_seg_length/2 69 | aConstraint.minimumDistance = rope_length+1.0-rope_seg_length/2 70 | rope_nodes[0].constraints = aConstraint 71 | 72 | ball_node.position = (0, -rope_seg_length/2-rope_radius-ball_radius, 0) 73 | aConstraint = scn.DistanceConstraint.distanceConstraintWithTarget(rope_nodes[-1]) 74 | aConstraint.maximumDistance = rope_seg_length/2 + rope_radius + ball_radius 75 | aConstraint.minimumDistance = rope_seg_length/2 + rope_radius + ball_radius 76 | ball_node.constraints = aConstraint 77 | 78 | rope_nodes[-1].addChildNode(ball_node) 79 | root_node.addChildNode(rope_nodes[0]) 80 | 81 | ball_physicsBody = scn.PhysicsBody.dynamicBody() 82 | ball_physicsBody.physicsShape = scn.PhysicsShape.shapeWithGeometry(ball_geometry) 83 | ball_physicsBody.mass = 5.0 84 | ball_physicsBody.damping = 0.005 85 | ball_physicsBody.angularDamping = 0.00 86 | ball_node.physicsBody = ball_physicsBody 87 | 88 | rope_element_physicsShape = scn.PhysicsShape.shapeWithGeometry(rope_element_geometry) 89 | 90 | for i in range(rope_seg_nr): 91 | rope_nodes[i].physicsBody = scn.PhysicsBody.dynamicBody() 92 | rope_nodes[i].physicsBody.physicsShape = rope_element_physicsShape 93 | rope_nodes[i].physicsBody.mass = 1.1 94 | rope_nodes[i].physicsBody.damping = 0.00 95 | rope_nodes[i].physicsBody.angularDamping = 0.00 96 | if i > 0: 97 | physics_world.addBehavior(scn.PhysicsBallSocketJoint.joint(rope_nodes[i-1].physicsBody, (0, -rope_seg_length/2, 0), rope_nodes[i].physicsBody, (0, rope_seg_length/2, 0))) 98 | 99 | physics_world.addBehavior(scn.PhysicsBallSocketJoint.joint(rope_nodes[0].physicsBody, (0, rope_seg_length/2, 0))) 100 | physics_world.addBehavior(scn.PhysicsBallSocketJoint.joint(rope_nodes[-1].physicsBody, (0, -rope_seg_length/2, 0), ball_node.physicsBody, (0, ball_radius, 0))) 101 | 102 | ball_node.physicsBody.applyForce((45.0, 0.0, 0.0), True) 103 | 104 | camera_node = scn.Node() 105 | camera_node.camera = scn.Camera() 106 | camera_node.position = (0 , rope_length+1.0 ,rope_length) 107 | camera_node.lookAt((0, rope_length/2+1.0, 0)) 108 | root_node.addChildNode(camera_node) 109 | 110 | light_node = scn.Node() 111 | light_node.position = (rope_length, rope_length, rope_length) 112 | 113 | light = scn.Light() 114 | light.type = scn.LightTypeDirectional 115 | light.castsShadow = True 116 | light.shadowSampleCount = 32 117 | light.color = (.99, 1.0, .86) 118 | light_node.light = light 119 | light_node.lookAt((0, rope_length/2+1.0, 0)) 120 | root_node.addChildNode(light_node) 121 | 122 | main_view.present(style='fullscreen', hide_title_bar=False) 123 | 124 | Demo.run() 125 | -------------------------------------------------------------------------------- /sceneKit/sceneKitAudio.py: -------------------------------------------------------------------------------- 1 | '''audio modul, to be included in sceneKit''' 2 | 3 | from objc_util import nsurl 4 | import os 5 | 6 | import sceneKit 7 | from .sceneKitEnv import * 8 | from .sceneKitNode import * 9 | 10 | class AudioSource(CInst): 11 | def __init__(self, url=None, fileName=None, named=None, ID=None): 12 | if named is not None: fileName=named 13 | if url is not None: 14 | self.ID = SCNAudioSource.alloc().initWithURL_(nsurl(self.convertURL(url))) 15 | elif fileName is not None: 16 | self.ID = SCNAudioSource.audioSourceNamed_(fileName) 17 | elif ID is not None: 18 | self.ID = ID 19 | else: 20 | self.ID = SCNAudioSource.alloc() 21 | 22 | # for Pythonista built-in sounds 23 | def convertURL(self, url): 24 | pythonistaSoundDir = os.path.abspath(os.path.join(os.__file__, '../../../..')+'/Media/Sounds/') 25 | soundFileExtention = '.caf' 26 | (root, ext) = os.path.splitext(url) 27 | if len(ext) == 0: 28 | root = root.replace(':', '/') 29 | url = pythonistaSoundDir+'/'+root+soundFileExtention 30 | return url 31 | 32 | @classmethod 33 | def audioSourceNamed(cls, fileName=None, named=None): 34 | return cls(fileName=fileName, named=named) 35 | 36 | def initWithFileNamed(self, name=None, fileNamed=None): 37 | if fileNamed is not None: name=fileNamed 38 | self.ID.initWithFileNamed_(name) 39 | initWithFile = initWithFileNamed 40 | 41 | def initWithURL(self, url=None): 42 | return self.ID.initWithURL_(nsurl(self.convertURL(url))) 43 | 44 | def setPositional(self, aBool): 45 | self.ID.setPositional_(aBool) 46 | def isPositional(self): 47 | return self.ID.positional() 48 | positional = property(isPositional, setPositional) 49 | 50 | def load(self): 51 | self.ID.load() 52 | 53 | def setVolume(self, aVolume): 54 | self.ID.setVolume_(aVolume) 55 | def getVolume(self): 56 | return self.ID.volume() 57 | volume = property(getVolume, setVolume) 58 | 59 | def setRate(self, aRate): 60 | self.ID.setRate_(aRate) 61 | def getRate(self): 62 | return self.ID.rate() 63 | rate = property(getRate, setRate) 64 | 65 | def setReverbBlend(self, aReverbBlend): 66 | self.ID.setReverbBlend_(aReverbBlend) 67 | def getReverbBlend(self): 68 | return self.ID.reverbBlend() 69 | reverbBlend = property(getReverbBlend, setReverbBlend) 70 | 71 | def setLoops(self, aBool): 72 | self.ID.setLoops_(aBool) 73 | def getLoops(self): 74 | return self.ID.loops() 75 | loops = property(getLoops, setLoops) 76 | 77 | def setShouldStream(self, aBool): 78 | self.ID.setShouldStream_(aBool) 79 | def getShouldStream(self): 80 | return self.ID.shouldStream() 81 | shouldStream = property(getShouldStream, setShouldStream) 82 | 83 | class AudioPlayer(CInst): 84 | def __init__(self, source=None, audioNode=None, ID=None): 85 | if source is not None: 86 | self.ID = SCNAudioPlayer.audioPlayerWithSource_(source.ID) 87 | elif audioNode is not None: 88 | self.ID = SCNAudioPlayer.audioPlayerWithAVAudioNode_(audioNode) 89 | elif ID is not None: 90 | self.ID = ID 91 | else: 92 | self.ID = SCNAudioPlayer.alloc() 93 | 94 | @classmethod 95 | def audioPlayerWithSource(cls, source=None): 96 | return cls(source=source) 97 | 98 | @classmethod 99 | def audioPlayerWithAVAudioNode(cls, audioNode=None, AVAudioNode=None, avAudioNode=None): 100 | if AVAudioNode is not None: audioNode = AVAudioNode 101 | if avAudioNode is not none: audioNode = avAudioNode 102 | return cls(audioNode=audioNode) 103 | 104 | def initWithSource(self, source=None): 105 | self.ID.initWithSource_(source.ID) 106 | 107 | def initWithAVAudioNode(self, audioNode=None, AVAudioNode=None, avAudioNode=None): 108 | if AVAudioNode is not None: audioNode = AVAudioNode 109 | if avAudioNode is not None: audioNode = avAudioNode 110 | self.ID.initWithAVAudioNode_(audioNode) 111 | 112 | def getAudioSource(self): 113 | return sceneKit.AudioSource.outof(self.ID.audioSource()) 114 | audioSource = property(getAudioSource, None) 115 | 116 | def getAudioNode(self): 117 | return self.ID.audioNode() 118 | audioNode = property(getAudioNode, None) 119 | 120 | def setWillStartPlayback(self, aBlock): 121 | self.audioSourceWillStartPlayback = AudioSourceWillStartStopPlaybackBlock(aBlock) 122 | self.ID.setWillStartPlayback_(self.audioSourceWillStartPlayback.blockCode) 123 | def getWillStartPlayback(self): 124 | return self.audioSourceWillStartPlayback.pCode 125 | willStartPlayback = property(getWillStartPlayback, setWillStartPlayback) 126 | 127 | def setDidFinishPlayback(self, aBlock): 128 | self.audioSourceDidFinishPlayback = AudioSourceWillStartStopPlaybackBlock(aBlock) 129 | self.ID.setDidFinishPlayback_(self.audioSourceDidFinishPlayback.blockCode) 130 | def getDidFinishPlayback(self): 131 | return self.audioSourceDidFinishPlayback.pCode 132 | didFinishPlayback = property(getDidFinishPlayback, setDidFinishPlayback) 133 | 134 | 135 | class AudioSourceWillStartStopPlaybackBlock: 136 | def __init__(self, block): 137 | self.blockCode = ObjCBlock(self.blockInterface, restype=None, argtypes=[c_void_p]) 138 | self.pCode = block 139 | 140 | def blockInterface(self, _cmd): 141 | self.pCode() 142 | -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/hud.py: -------------------------------------------------------------------------------- 1 | '''Cube puzzles - five typical 3x3x3 3D puzzles. 2 | 3 | (c) Peter Ulbrich, 2019 4 | 5 | a demo script for the sceneKit wrapper package for Pythonista 6 | 7 | See the README.md for details. 8 | 9 | ''' 10 | 11 | from objc_util import * 12 | import ui 13 | from markdown2 import markdown 14 | 15 | from data import * 16 | import main 17 | import selector 18 | 19 | TEMPLATE = ''' 20 | 21 | 22 | 23 | 24 | 25 | Preview 26 | 33 | 34 | {{CONTENT}} 35 | 36 | ''' 37 | 38 | class Hud: 39 | def __init__(self): 40 | self.views = [] 41 | 42 | _, _, w, h = data.main_view.frame 43 | 44 | self.close_button = ui.Button() 45 | self.close_button.action = self.closeClick 46 | self.close_button.image = ui.Image.named('iob:close_round_24') 47 | data.main_view.add_subview(self.close_button) 48 | self.views.append(self.close_button) 49 | 50 | self.back_button = ui.Button() 51 | self.back_button.action = self.backClick 52 | self.back_button.image = ui.Image.named('iob:arrow_left_a_24') 53 | self.back_button.hidden = True 54 | data.main_view.add_subview(self.back_button) 55 | self.views.append(self.back_button) 56 | 57 | self.restart_button = ui.Button() 58 | self.restart_button.action = self.restartClick 59 | self.restart_button.image = ui.Image.named('iob:refresh_24') 60 | self.restart_button.hidden = True 61 | data.main_view.add_subview(self.restart_button) 62 | self.views.append(self.restart_button) 63 | 64 | self.hint_button = ui.Button() 65 | self.hint_button.action = self.hintClick 66 | self.hint_button.image = ui.Image.named('iob:help_24') 67 | data.main_view.add_subview(self.hint_button) 68 | self.views.append(self.hint_button) 69 | 70 | self.alert_label = ui.Label() 71 | self.alert_label.background_color = 'transparent' 72 | self.alert_label.text = '' 73 | self.alert_label.font = ('', 96) 74 | self.alert_label.alignment = ui.ALIGN_CENTER 75 | self.alert_label.hidden = True 76 | self.alert_label.alpha = 0.0 77 | data.main_view.add_subview(self.alert_label) 78 | self.views.append(self.alert_label) 79 | 80 | self.status_label = ui.Label() 81 | self.status_label.background_color = 'transparent' 82 | self.status_label.alignment = ui.ALIGN_CENTER 83 | self.status_label.line_break_mode = ui.LB_CHAR_WRAP 84 | self.status_label.number_of_lines = 2 85 | self.status_label.text = '' 86 | self.status_label.font = ('', 8) 87 | data.main_view.add_subview(self.status_label) 88 | self.views.append(self.status_label) 89 | 90 | self.title_label = ui.Label() 91 | self.title_label.background_color = 'transparent' 92 | self.title_label.alignment = ui.ALIGN_CENTER 93 | self.title_label.number_of_lines = 1 94 | self.title_label.text = '' 95 | self.title_label.font = ('', 22) 96 | data.main_view.add_subview(self.title_label) 97 | self.views.append(self.title_label) 98 | 99 | @on_main_thread 100 | def show_alert(self, message_symbol): 101 | def animation_fade_in(): 102 | self.alert_label.alpha = 1.0 103 | def animation_fade_out(): 104 | self.alert_label.alpha = 0.0 105 | def animate_fade_out(): 106 | ui.animate(animation_fade_out, duration=2.0) 107 | def hide_alert(): 108 | self.alert_label.hidden = True 109 | 110 | ui.cancel_delays() 111 | self.alert_label.text = message_symbol 112 | self.alert_label.hidden = False 113 | ui.animate(animation_fade_in, duration=0.8) 114 | ui.delay(animate_fade_out, 2) 115 | ui.delay(hide_alert, 5) 116 | 117 | def set_status(self, text): 118 | self.status_label.text = text 119 | 120 | def set_title(self, text): 121 | self.title_label.text = text 122 | 123 | def bring_to_front(self): 124 | for aView in self.views: 125 | aView.bring_to_front() 126 | 127 | @on_main_thread 128 | def hintClick(self, sender): 129 | data.active_stage.hint() 130 | 131 | @on_main_thread 132 | def restartClick(self, sender): 133 | data.active_stage.restart() 134 | 135 | @on_main_thread 136 | def backClick(self, sender): 137 | data.hud_layer.set_title('') 138 | main.MainViewController.next_stage(selector.SelectorStage()) 139 | 140 | @on_main_thread 141 | def closeClick(self, sender): 142 | data.main_view.close() 143 | ui.delay(self.exit, 2.0) 144 | 145 | def exit(self): 146 | raise SystemExit() 147 | 148 | def show_generic_help(self): 149 | with open('README.md', 'r') as fp: 150 | text = fp.read() 151 | converted = markdown(text) 152 | html = TEMPLATE.replace('{{CONTENT}}', converted) 153 | webview = ui.WebView(name='Read me') 154 | webview.load_html(html) 155 | webview.present() 156 | 157 | def layout(self): 158 | _, _, w, h = data.main_view.frame 159 | self.close_button.frame = (0, 12, 40, 40) 160 | self.back_button.frame = (w - 40, 12, 40, 40) 161 | self.restart_button.frame = (0, h - 12 - 40, 40, 40) 162 | self.hint_button.frame = (w - 40, h - 12 - 40, 40, 40) 163 | self.title_label.frame = ((w - w // 2) // 2, 12, w // 2, 40) 164 | self.status_label.frame = ((w - w // 2) // 2, h - 12 - 40, w // 2, 40) 165 | self.alert_label.frame = ((w - 160) / 2, (h - 160) / 2, 160, 160) 166 | 167 | 168 | if __name__ == '__main__': 169 | main.MainViewController.run() 170 | 171 | -------------------------------------------------------------------------------- /sceneKit demo/09_shapeDemo.py: -------------------------------------------------------------------------------- 1 | ''' 2 | shape: A geometry based on a two-dimensional path, optionally extruded to create a three-dimensional object 3 | ''' 4 | from objc_util import * 5 | import sceneKit as scn 6 | import ui 7 | from math import * 8 | 9 | w, h = ui.get_screen_size() 10 | title_bar = 88+44 # you should get these programatically in a real app 11 | if w > h: 12 | image_frame = (0, 0, w/2, h-title_bar) 13 | scene_frame = (w/2, 0, w/2, h) 14 | box_bubble = 1.4 15 | else: 16 | image_frame = (0, 0, w, (h-title_bar)/2) 17 | scene_frame = (0, (h-title_bar)/2, w, (h+title_bar)/2) 18 | box_bubble = 1.0 19 | 20 | image_size = CGSize(image_frame[2], image_frame[3]) 21 | image_center = CGPoint(image_size.width/2, image_size.height/2) 22 | leaves = 20 23 | r0 = min(image_size.width, image_size.height)/2*0.1 24 | r1 = min(image_size.width, image_size.height)/2*0.8 25 | c1 = r0+(r1-r0)*0.5 26 | c2 = r0+(r1-r0)*0.95 27 | dalpha = 2*pi/leaves/2 28 | 29 | path = ui.Path() 30 | for i in range(leaves): 31 | alpha = i*2*pi/leaves 32 | x0, y0 = image_center.x+r0*cos(alpha), image_center.y+r0*sin(alpha) 33 | x1, y1 = image_center.x+r1*cos(alpha), image_center.y+r1*sin(alpha) 34 | cx11, cy11 = image_center.x+c2*cos(alpha+dalpha), image_center.y+c2*sin(alpha+dalpha) 35 | cx21, cy21 = image_center.x+c1*cos(alpha+dalpha), image_center.y+c1*sin(alpha+dalpha) 36 | cx12, cy12 = image_center.x+c1*cos(alpha+dalpha), image_center.y+c1*sin(alpha+dalpha) 37 | cx22, cy22 = image_center.x+c2*cos(alpha-dalpha), image_center.y+c2*sin(alpha-dalpha) 38 | path.move_to(x0, y0) 39 | path.add_curve(x1, y1, cx11, cy11, cx21, cy21) 40 | path.add_curve(x0, y0, cx12, cy12, cx22, cy22) 41 | 42 | x0, y0 = image_center.x+r0*cos(0), image_center.y+r0*sin(0) 43 | path.move_to(x0, y0) 44 | path.add_arc(image_center.x, image_center.y, r0, 0, 2*pi) 45 | path.close() 46 | 47 | with ui.ImageContext(image_size.width, image_size.height) as ctx: 48 | ui.set_color('red') 49 | path.stroke() 50 | img = ctx.get_image() 51 | 52 | @on_main_thread 53 | def demo(): 54 | main_view = ui.View() 55 | main_view.frame = (0,0,w,h) 56 | main_view.name = 'shapeDemo' 57 | main_view.background_color = 'white' 58 | 59 | image_view = ui.ImageView() 60 | image_view.frame = image_frame 61 | image_view.background_color = 'white' 62 | image_view.content_mode = ui.CONTENT_CENTER 63 | image_view.image = img 64 | 65 | main_view.add_subview(image_view) 66 | 67 | scene_view = scn.View(scene_frame, superView=main_view) 68 | scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleRightMargin 69 | scene_view.antialiasingMode = scn.AntialiasingMode.Multisampling16X 70 | scene_view.allowsCameraControl = True 71 | scene_view.backgroundColor = 'white' 72 | scene_view.scene = scn.Scene() 73 | 74 | root_node = scene_view.scene.rootNode 75 | 76 | shape = scn.Shape.shapeWithPath(path, 40) 77 | shape.chamferRadius = 8 78 | shape.firstMaterial.contents = 'yellow' 79 | 80 | shape_node = scn.Node.nodeWithGeometry(shape) 81 | shape_node.castsShadow = True 82 | bbox_min, bbox_max = shape.boundingBox 83 | shape_width = bbox_max.x - bbox_min.x 84 | shape_height = bbox_max.y - bbox_min.y 85 | shape_tr = list(scn.Matrix4Identity) 86 | shape_tr[13] = bbox_min.y+0.5*shape_height 87 | shape_node.pivot = shape_tr 88 | shape_node.position = (-bbox_min.x-0.5*shape_width, 0, 0) 89 | 90 | shape_container = scn.Node() 91 | shape_container.addChildNode(shape_node) 92 | 93 | scale = image_size.width/shape_width 94 | shape_container.scale = (scale, scale, 1) 95 | shape_width *= scale 96 | shape_height *= scale 97 | 98 | shape_container.position = (0, 0.2*shape_height, 0.5*shape_height) 99 | shape_container.rotation = (0, 1, 0, pi) # compensates for the differen coordinate systems of drawing vs. scene 100 | 101 | rotate_action = scn.Action.rotateBy(0, 0, 2*pi, 10) 102 | rotate_action.timingMode = scn.ActionTimingMode.EaseInEaseOut 103 | invers_action = rotate_action.reversedAction() 104 | combined_action = scn.Action.sequence([rotate_action, invers_action]) 105 | shape_container.runAction(scn.Action.repeatActionForever(combined_action)) 106 | 107 | box = scn.Box(width=2*shape_width, height=4, length=1.6*shape_width, chamferRadius=1) 108 | box.firstMaterial.contents = (.76, .91, 1.0) 109 | 110 | boxBoxMin, boxBoxMax = box.boundingBox 111 | box.boundingBox = ((box_bubble*boxBoxMin.x, boxBoxMin.y, boxBoxMin.z), (box_bubble*boxBoxMax.x, boxBoxMax.y, boxBoxMax.z)) 112 | box_node = scn.Node.nodeWithGeometry(box) 113 | box_node.rotation = (1, 0, 0, pi/4) 114 | 115 | root_node.addChildNode(box_node) 116 | root_node.addChildNode(shape_container) 117 | 118 | constraint = scn.LookAtConstraint.lookAtConstraintWithTarget(shape_container) 119 | constraint.gimbalLockEnabled = True 120 | 121 | light_node = scn.Node() 122 | light_z = boxBoxMax.z + 0.0*boxBoxMax.z 123 | light_y = (shape_container.position.y+0.5*shape_height/2)/shape_container.position.z*light_z 124 | light_node.position = (20, light_y, light_z) 125 | light_node.constraints = [constraint] 126 | light = scn.Light() 127 | light.type = 'spot' 128 | light.spotOuterAngle = 90 129 | light.castsShadow = True 130 | light.zFar = 1500 131 | light.shadowSampleCount = 16 132 | light.color = 'white' 133 | light_node.light = light 134 | root_node.addChildNode(light_node) 135 | 136 | ambient_light = scn.Light() 137 | ambient_light.type = scn.LightTypeAmbient 138 | ambient_light.color = (.41, .32, .0) 139 | ambient_node = scn.Node() 140 | ambient_node.light = ambient_light 141 | root_node.addChildNode(ambient_node) 142 | 143 | main_view.present(style='fullscreen', hide_title_bar=False) 144 | 145 | demo() 146 | -------------------------------------------------------------------------------- /sceneKit demo/14_physicsDemo-2.py: -------------------------------------------------------------------------------- 1 | """ 2 | physics experiments Nr. 2 3 | """ 4 | 5 | from objc_util import * 6 | import ctypes 7 | import sceneKit as scn 8 | import ui 9 | import math 10 | import random 11 | from scene import * 12 | 13 | class Counter(Scene): 14 | def setup(self): 15 | self.background_color = 'midnightblue' 16 | self.counter = -1 17 | self.counter_node = LabelNode(str(self.counter)) 18 | self.counter_node.position = (self.size.w/2, self.size.h/2) 19 | self.add_child(self.counter_node) 20 | 21 | def update(self): 22 | self.counter_node.text = str(self.counter) 23 | 24 | class Demo: 25 | 26 | @classmethod 27 | def run(cls): 28 | cls().main() 29 | 30 | @on_main_thread 31 | def main(self): 32 | main_view = ui.View() 33 | w, h = ui.get_screen_size() 34 | main_view.frame = (0,0,w,h) 35 | main_view.name = 'physics experiment demo - 2' 36 | 37 | scene_view = scn.View(main_view.frame, superView=main_view) 38 | scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleRightMargin 39 | scene_view.allowsCameraControl = True 40 | 41 | self.counter_scene = Counter() 42 | counter_view = SceneView() 43 | counter_view.frame = (0., 0., 100., 50.) 44 | counter_view.scene = self.counter_scene 45 | main_view.add_subview(counter_view) 46 | 47 | # scene_view.debugOptions = scn.DebugOption.ShowBoundingBoxes | scn.DebugOption.ShowCameras | scn.DebugOption.ShowLightInfluences 48 | 49 | # scene_view.debugOptions = scn.DebugOption.ShowPhysicsShapes 50 | 51 | scene_view.backgroundColor = 'white' 52 | scene_view.scene = scn.Scene() 53 | 54 | physics_world = scene_view.scene.physicsWorld 55 | physics_world.contactDelegate = self 56 | 57 | root_node = scene_view.scene.rootNode 58 | 59 | box_size = 10 60 | box = scn.Box(box_size, box_size, box_size, 0.0) 61 | box.firstMaterial.diffuse.contents = (.86, .86, .86) 62 | box.firstMaterial.transparency = 0.15 63 | box.firstMaterial.cullMode = scn.CullMode.Front 64 | box_node = scn.Node.nodeWithGeometry(box) 65 | root_node.addChildNode(box_node) 66 | 67 | self.particle_number = 25 68 | particle_max_r = box_size/20 69 | particle_colors = ['black', 'blue', 'green', 'pink', 'yellow', 'red', 'cyan', 'gray', 'magenta', 'brown', 'crimson', 'gold', 'indigo', 'olive'] 70 | particles_node = scn.Node() 71 | particles_node.physicsField = scn.PhysicsField.electricField() 72 | particles_node.physicsField.strength = 5.0 73 | 74 | root_node.addChildNode(particles_node) 75 | for i in range(self.particle_number): 76 | r = random.uniform(0.35*particle_max_r, particle_max_r) 77 | particle = scn.Sphere(r) 78 | particle.firstMaterial.diffuse.contents = random.choice(particle_colors) 79 | particle.firstMaterial.specular.contents = (.86, .94, 1.0, 0.8) 80 | particle_node = scn.Node.nodeWithGeometry(particle) 81 | particle_node.position = (random.uniform(-(box_size/2-particle_max_r)*0.95, (box_size/2-particle_max_r)*0.95), random.uniform(-(box_size/2-particle_max_r)*0.95, (box_size/2-particle_max_r)*0.95), random.uniform(-(box_size/2-particle_max_r)*0.95, (box_size/2-particle_max_r)*0.95)) 82 | particle_node.physicsBody = scn.PhysicsBody.dynamicBody() 83 | particle_node.physicsBody.mass = 1.2*r 84 | particle_node.physicsBody.charge = random.uniform(-10*r, +0*r) 85 | particle_node.physicsBody.restitution = 1.0 86 | particle_node.physicsBody.damping = 0.0 87 | particle_node.physicsBody.angularDamping = 0.0 88 | particle_node.physicsBody.continuousCollisionDetectionThreshold = 1*r 89 | particle_node.physicsBody.affectedByGravity = False 90 | particle_node.physicsBody.contactTestBitMask = scn.PhysicsCollisionCategory.Default.value 91 | particle_node.physicsBody.velocity = (random.uniform(-box_size, box_size), random.uniform(-box_size, box_size), random.uniform(-box_size, box_size)) 92 | particles_node.addChildNode(particle_node) 93 | 94 | box_nodes = scn.Node() 95 | d = box_size/2 96 | for pos in [(d,0,0), (-d,0,0), (0,d,0), (0,-d,0), (0,0,d), (0,0,-d)]: 97 | aNode = scn.Node() 98 | aNode.position = pos 99 | aNode.lookAt((0,0,0)) 100 | aNode.physicsBody = scn.PhysicsBody.staticBody() 101 | aNode.physicsBody.physicsShape = scn.PhysicsShape.shapeWithGeometry(scn.Plane(box_size, box_size)) 102 | box_nodes.addChildNode(aNode) 103 | root_node.addChildNode(box_nodes) 104 | 105 | constraint = scn.LookAtConstraint.lookAtConstraintWithTarget(root_node) 106 | constraint.gimbalLockEnabled = True 107 | 108 | camera_node = scn.Node() 109 | camera_node.camera = scn.Camera() 110 | camera_node.position = (-2.5*box_size, 1.5*box_size, 2*box_size) 111 | camera_node.constraints = constraint 112 | root_node.addChildNode(camera_node) 113 | 114 | light_node = scn.Node() 115 | light_node.position = (box_size, box_size, box_size) 116 | light = scn.Light() 117 | light.type = scn.LightTypeDirectional 118 | light.castsShadow = True 119 | light.shadowSampleCount = 32 120 | light.color = (.95, 1.0, .98) 121 | light_node.light = light 122 | light_node.constraints = constraint 123 | root_node.addChildNode(light_node) 124 | 125 | main_view.present(style='fullscreen', hide_title_bar=False) 126 | 127 | def didEndContact(self, aWorld, aContact): 128 | big, small = (aContact.nodeA, aContact.nodeB) if aContact.nodeA.physicsBody.mass > aContact.nodeB.physicsBody.mass else (aContact.nodeB, aContact.nodeA) 129 | m1, m2 = big.physicsBody.mass, small.physicsBody.mass 130 | v1, v2 = big.physicsBody.velocity, small.physicsBody.velocity 131 | m = m1 + m2 132 | big.physicsBody.mass = m 133 | big.physicsBody.charge += small.physicsBody.charge 134 | big.geometry.radius += 0.4*math.log1p(small.geometry.radius) 135 | big.physicsBody.physicsShape = scn.PhysicsShape.shapeWithGeometry(big.geometry) 136 | big.physicsBody.continuousCollisionDetectionThreshold = 1*big.geometry.radius 137 | big.physicsBody.velocity = ((m1*v1.x+m2*v2.x)/(m), (m1*v1.y+m2*v2.y)/(m), (m1*v1.z+m2*v2.z)/(m)) 138 | small.removeFromParentNode() 139 | self.particle_number -= 1 140 | self.counter_scene.counter = self.particle_number 141 | 142 | Demo.run() 143 | -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/piece.py: -------------------------------------------------------------------------------- 1 | '''Cube puzzles - five typical 3x3x3 3D puzzles. 2 | 3 | (c) Peter Ulbrich, 2019 4 | 5 | a demo script for the sceneKit wrapper package for Pythonista 6 | 7 | See the README.md for details. 8 | 9 | ''' 10 | 11 | from enum import Enum, auto 12 | import math 13 | import weakref 14 | 15 | import sceneKit as scn 16 | 17 | from data import * 18 | import setup 19 | 20 | class Piece_location(Enum): 21 | Drawer = auto() 22 | Tray = auto() 23 | Puzzle = auto() 24 | Solver = auto() 25 | 26 | class Piece_set: 27 | def __init__(self, puzzle_variant): 28 | self.pieces = setup.setup_pieces(self, puzzle_variant) 29 | self.puzzle_variant = puzzle_variant 30 | self.previous_piece_in_tray = [] 31 | 32 | def solved(self): 33 | unplaced_pieces = sum(1 for aPiece in self.pieces if aPiece.location != Piece_location.Puzzle) 34 | return unplaced_pieces == 0 35 | 36 | def reset_pieces(self): 37 | for aPiece in self.pieces: 38 | aPiece.reset() 39 | 40 | class Piece: 41 | def __init__(self, piece_set, name=None, handle_node=None): 42 | self.piece_set = weakref.ref(piece_set)() 43 | self.name = name 44 | self.drawer_handle_node = handle_node 45 | self.location = Piece_location.Drawer 46 | self.piece_handle_node = handle_node.clone() 47 | 48 | self.tray_handle_node = scn.Node() 49 | self.tray_handle_node.addChildNode(self.piece_handle_node) 50 | self.drawer_view = None 51 | self.tray_handle_node.opacity = 0. 52 | 53 | def __str__(self): 54 | return 'piece ' + self.name + ' ' + str(self.location) 55 | 56 | def reset(self): 57 | self.location = Piece_location.Drawer 58 | self.tray_handle_node.opacity = 0. 59 | self.drawer_handle_node.opacity = 1.0 60 | self.piece_handle_node.eulerAngles = (0, 0, 0) 61 | self.piece_handle_node.position = (0, 0, 0) 62 | 63 | def from_drawer_to_tray(self): 64 | self.drawer_handle_node.opacity = 0.5 65 | self.tray_handle_node.opacity = 1.0 66 | self.piece_handle_node.eulerAngles = (0, 0, 0) 67 | self.piece_handle_node.position = (0, 0, 0) 68 | self.location = Piece_location.Tray 69 | 70 | def from_tray_to_drawer(self): 71 | self.drawer_handle_node.opacity = 1.0 72 | self.tray_handle_node.opacity = 0.0 73 | self.location = Piece_location.Drawer 74 | 75 | def rotate(self, axis, constraint=True, zero_align=False): 76 | thetaOver2 = math.pi / 4 77 | if axis == 'x' and data.horizontal or axis == 'y' and not data.horizontal: 78 | new_rotation = (math.sin(thetaOver2), 0, 0, math.cos(thetaOver2)) 79 | elif axis == 'y' and data.horizontal or axis == 'x' and not data.horizontal: 80 | if not data.horizontal: thetaOver2 *= -1 81 | new_rotation = (0, math.sin(thetaOver2), 0, math.cos(thetaOver2)) 82 | elif axis == 'z': 83 | new_rotation = (0, 0, math.sin(thetaOver2), math.cos(thetaOver2)) 84 | self.piece_handle_node.rotateBy(new_rotation, self.piece_handle_node.worldPosition) 85 | if constraint: 86 | self.constraint(zero_align) 87 | 88 | def shift(self, axis, distance): 89 | pos = self.piece_handle_node.position 90 | if axis == 'x': new_shift = scn.Vector3(distance, 0, 0) 91 | elif axis == 'y': new_shift = scn.Vector3(0, distance, 0) 92 | elif axis == 'z': new_shift = scn.Vector3(0, 0, distance) 93 | self.piece_handle_node.position = (pos.x + new_shift.x, pos.y + new_shift.y, pos.z + new_shift.z) 94 | self.constraint() 95 | 96 | def constraint(self, zero_align=False): 97 | pos = self.piece_handle_node.position 98 | min_box, max_box = self.tray_handle_node.boundingBox 99 | min_box = scn.Vector3(round(min_box.x + 0.5), round(min_box.y + 0.5), round(min_box.z + 0.5)) 100 | max_box = scn.Vector3(round(max_box.x - 0.5), round(max_box.y - 0.5), round(max_box.z - 0.5)) 101 | new_pos = [round(pos.x), round(pos.y), round(pos.z)] 102 | 103 | if not zero_align: 104 | if min_box.x < 0: new_pos[0] = pos.x - min_box.x 105 | if max_box.x > 2: new_pos[0] = pos.x - (max_box.x - 2) 106 | if min_box.y < -2: new_pos[1] = pos.y - (min_box.y + 2) 107 | if max_box.y > 0: new_pos[1] = pos.y - (max_box.y - 0) 108 | if min_box.z < -2: new_pos[2] = pos.z - (min_box.z + 2) 109 | if max_box.z > 0: new_pos[2] = pos.z - max_box.z 110 | else: 111 | new_pos[0] = pos.x - min_box.x 112 | new_pos[1] = pos.y - min_box.y 113 | new_pos[2] = pos.z - max_box.z 114 | 115 | self.piece_handle_node.position = new_pos 116 | 117 | def drop(self): 118 | tray_handle = self.tray_handle_node.parentNode 119 | starting_positions = [self.piece_handle_node.convertPosition(aNode.position, toNode=tray_handle) for aNode in self.piece_handle_node.childNodes] 120 | 121 | pieces_resting_set = set() 122 | for aPiece in (_ for _ in self.piece_set.pieces if _.location == Piece_location.Puzzle): 123 | for aNode in aPiece.piece_handle_node.childNodes: 124 | aPos = aPiece.piece_handle_node.convertPosition(aNode.position, toNode=tray_handle) 125 | pieces_resting_set.add(scn.Vector3(round(aPos.x), round(aPos.y), round(aPos.z))) 126 | 127 | for downShift in range(16): 128 | new_positions = {scn.Vector3(round(aStartPos.x), round(aStartPos.y - downShift), round(aStartPos.z)) for aStartPos in starting_positions} 129 | break_value = min(aPos.y for aPos in new_positions) 130 | 131 | if break_value < -6: break 132 | if not new_positions.isdisjoint(pieces_resting_set): break 133 | 134 | downShift -= 1 135 | current_position = self.piece_handle_node.position 136 | 137 | self.piece_handle_node.position = scn.Vector3(current_position.x, current_position.y - downShift, current_position.z) 138 | 139 | top_value = max(aPos.y for aPos in new_positions) + 1 140 | if top_value <= -4: 141 | self.location = Piece_location.Puzzle 142 | self.piece_set.previous_piece_in_tray.append(self) 143 | return True 144 | else: return False 145 | 146 | def lift(self): 147 | pos = self.piece_handle_node.position 148 | self.piece_handle_node.position = (pos.x, 0, pos.z) 149 | self.constraint() 150 | self.location = Piece_location.Tray 151 | 152 | 153 | if __name__ == '__main__': 154 | import main 155 | main.MainViewController.run() 156 | -------------------------------------------------------------------------------- /sceneKit demo/06_photoCubeWithRendererDelegate.py: -------------------------------------------------------------------------------- 1 | """ 2 | based on: 3 | For the fun, a Photos cube by cvp at https://forum.omz-software.com/topic/3922/for-the-fun-a-photos-cube 4 | 5 | includes a renderer delegate demo 6 | """ 7 | import random 8 | import math 9 | from objc_util import * 10 | import sceneKit as scn 11 | import ui 12 | 13 | cube_image_names = ['emj:Airplane', 'emj:Cat_Face', 'emj:Anchor', 'emj:Basketball', 'emj:Aubergine', 'emj:Baby_Chick_1', 'emj:Alarm_Clock', 'emj:Ambulance', 'emj:American_Football', 'emj:Artist_Palette', 'emj:Athletic_Shoe', 'emj:Baby_Chick_2', 'emj:Baby_Chick_3', 'emj:Bactrian_Camel', 'emj:Balloon', 'emj:Banana', 'emj:Baseball', 'emj:Bird', 'emj:Blue_Book'] 14 | 15 | cube_images_orig = [ui.Image.named(cube_image_names[i]) for i in range(len(cube_image_names))] 16 | 17 | (image_width_orig, image_height_orig) = cube_images_orig[0].size 18 | (image_width, image_height) = (image_width_orig * 1.5, image_height_orig * 1.5) 19 | i_x = (image_width - image_width_orig)/2 20 | i_y = (image_height - image_height_orig)/2 21 | 22 | cube_images = [] 23 | for i in range(len(cube_image_names)): 24 | with ui.ImageContext(image_width, image_height) as ctx: 25 | cube_images_orig[i].draw(i_x, i_y, image_width_orig, image_height_orig) 26 | cube_images.append(ctx.get_image()) 27 | 28 | with ui.ImageContext(image_width, image_height) as ctx: 29 | rrect = ui.Path.rounded_rect(0, 0, image_width, image_height, 2) 30 | rrect.line_width = 4 31 | ui.set_color((.91, .91, .91)) 32 | rrect.fill() 33 | ui.set_color('black') 34 | rrect.stroke() 35 | d = 2 36 | rrect = ui.Path.rounded_rect(0+d, 0+d, image_width-2*d, image_height-2*d, 2) 37 | rrect.line_width = 2 38 | ui.set_color((.6, .6, .6)) 39 | rrect.stroke() 40 | inside_image = ctx.get_image() 41 | 42 | tile_image = ui.Image.named('plf:Tile_Water') 43 | tile_number = 15 44 | tile_factor = scn.Matrix4(tile_number, 0.0, 0.0, 0.0, 0.0, tile_number, 0.0, 0.0, 0.0, 0.0, tile_number, 0.0, 0.0, 0.0, 0.0, 1.0) 45 | 46 | class RendererDelegateForMyScene: 47 | # delete unnecessary methods for performance improvement 48 | def update(self, view, atTime): 49 | tick = int(atTime*10) % 50 50 | if tick == 0: 51 | cube_node = view.scene.rootNode.childNodeWithName('cube node') 52 | cube_geometry_materials = cube_node.geometry.materials 53 | for i in range(6): 54 | cube_geometry_materials[i].diffuse.contents = cube_images[random.randrange(len(cube_image_names))] 55 | cube_node.geometry.materials = cube_geometry_materials 56 | else: 57 | pass 58 | 59 | def didApplyAnimations(self, view, atTime): 60 | pass 61 | 62 | def didSimulatePhysics(self, view, atTime): 63 | pass 64 | 65 | def didApplyConstraints(self, view, atTime): 66 | pass 67 | 68 | def willRenderScene(self, view, scene, atTime): 69 | pass 70 | 71 | def didRenderScene(self, view, scene, atTime): 72 | pass 73 | 74 | @on_main_thread 75 | def main(): 76 | main_view = ui.View() 77 | w, h = ui.get_screen_size() 78 | main_view.frame = (0,0,w,h) 79 | main_view.name = 'Photos cube' 80 | 81 | scene_view = scn.View(main_view.frame, superView=main_view) 82 | scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleRightMargin 83 | scene_view.antialiasingMode = scn.AntialiasingMode.Multisampling16X 84 | 85 | 86 | scene_view.allowsCameraControl = True 87 | 88 | scene_view.backgroundColor = 'white' 89 | 90 | scene_view.delegate = RendererDelegateForMyScene() 91 | 92 | scene = scn.Scene() 93 | scene_view.scene = scene 94 | 95 | scn.Transaction.disableActions = True 96 | 97 | scene.background.contents = tile_image 98 | scene.background.contentsTransform = tile_factor 99 | scene.background.wrapS, scene.background.wrapT = scn.WrapMode.Repeat, scn.WrapMode.Repeat 100 | 101 | root_node = scene.rootNode 102 | 103 | cube_geometry = scn.Box(width=1, height=1, length=1, chamferRadius=0.05) 104 | cube_geometry_inside = scn.Box(width=1-0.001, height=1-0.001, length=1-0.001, chamferRadius=0.04) 105 | 106 | material_inside = scn.Material() 107 | material_inside.diffuse.contents = inside_image 108 | 109 | cube_geometry_inside.materials =[material_inside for i in range(6)] 110 | 111 | cube_geometry_materials = [scn.Material() for i in range(6)] 112 | for i in range(6): 113 | cube_geometry_materials[i].diffuse.contents = cube_images[random.randrange(len(cube_image_names))] 114 | cube_geometry.materials = cube_geometry_materials 115 | 116 | cube_node = scn.Node.nodeWithGeometry(cube_geometry) 117 | cube_node.name = 'cube node' 118 | cube_node_inside = scn.Node.nodeWithGeometry(cube_geometry_inside) 119 | cube_node.addChildNode(cube_node_inside) 120 | 121 | cube_node.position = (0., 0., -5.) 122 | 123 | cube_action = scn.Action.scaleTo(1.5, 3.0) 124 | cube_reversed_action = scn.Action.scaleTo(1.0, 1.0) 125 | cube_wait_action = scn.Action.waitForDuration(0.8) 126 | cube_y_rot_action1 = scn.Action.rotateBy(0, 0.2, 0, 0.3) 127 | cube_y_rot_action2 = scn.Action.rotateBy(0, -0.4, 0, 0.3) 128 | cube_y_rot_action3 = scn.Action.rotateBy(0, 0.2, 0, 0.3) 129 | 130 | cube_sequence = scn.Action.sequence([cube_action, cube_reversed_action, cube_wait_action, cube_y_rot_action1, cube_y_rot_action2, cube_y_rot_action3, cube_wait_action]) 131 | cube_forever = scn.Action.repeatActionForever(cube_sequence) 132 | 133 | cube_node.runAction(cube_forever) 134 | 135 | 136 | constraint = scn.LookAtConstraint.lookAtConstraintWithTarget(cube_node) 137 | constraint.gimbalLockEnabled = True 138 | 139 | camera = scn.Camera() 140 | camera_node = scn.Node() 141 | camera_node.name = 'camera!' 142 | camera_node.camera = camera 143 | camera_node.position = (-3.0,3.0,3.0) 144 | camera_node.constraints = [constraint] 145 | 146 | lights_node = scn.Node() 147 | 148 | ambient_light = scn.Light() 149 | ambient_light.type = scn.LightTypeAmbient 150 | ambient_light.name = 'ambient light' 151 | ambient_light.color = (.99, 1.0, .86) 152 | ambient_node = scn.Node() 153 | ambient_node.light = ambient_light 154 | lights_node.addChildNode(ambient_node) 155 | 156 | directional_light = scn.Light() 157 | directional_light.type = scn.LightTypeDirectional 158 | directional_light.name = 'directional light' 159 | directional_light.color = 'white' 160 | directional_node = scn.Node() 161 | directional_node.light = directional_light 162 | directional_node.position = camera_node.position 163 | directional_node.constraints = [constraint] 164 | 165 | lights_node.addChildNode(directional_node) 166 | 167 | root_node.addChildNode(camera_node) 168 | root_node.addChildNode(lights_node) 169 | root_node.addChildNode(cube_node) 170 | scene_view.pointOfView = camera_node 171 | 172 | 173 | scn.Transaction.begin() 174 | scn.Transaction.disableActions = False 175 | scn.Transaction.animationDuration = 10.0 176 | directional_light.color = 'yellow' 177 | scn.Transaction.commit() 178 | 179 | 180 | scene_view.playing = True 181 | 182 | main_view.present(style='fullscreen', hide_title_bar=False) 183 | 184 | if __name__ == '__main__': 185 | main() 186 | -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/solver.py: -------------------------------------------------------------------------------- 1 | '''Cube puzzles - five typical 3x3x3 3D puzzles. 2 | 3 | (c) Peter Ulbrich, 2019 4 | 5 | a demo script for the sceneKit wrapper package for Pythonista 6 | 7 | See the README.md for details. 8 | 9 | ''' 10 | 11 | import math 12 | import pickle 13 | 14 | try: 15 | from pyrr import Vector3, Quaternion 16 | except ImportError: 17 | Vector3 = Quaternion = None 18 | 19 | from data import * 20 | 21 | class Piece: 22 | def __init__(self, name, shapes): 23 | self.name = name 24 | self.puzzle_shape = None 25 | self.shapes = shapes 26 | 27 | class Solver: 28 | def __init__(self, puzzle_variant): 29 | self.puzzle_cubes = sum(len(nodes) for nodes in data.reference_pieces[puzzle_variant.value].values()) 30 | 31 | try: 32 | with open('resources/'+puzzle_variant.name+'-shapes.P', 'rb') as fp: 33 | self.shapes_dict = pickle.load(fp) 34 | for name, nodes in data.reference_pieces[puzzle_variant.value].items(): 35 | if len(list(self.shapes_dict[name].keys())[0]) != len(nodes): 36 | raise pickle.UnpicklingError 37 | except (FileNotFoundError, pickle.UnpicklingError, KeyError): 38 | if Vector3 is None: 39 | raise ImportError('Module Pyrr is needed to recreate the shapes.P file. Ether re-download the shapes.P file or run "pip install pyrr".') 40 | self.shapes_dict = {} 41 | for name, nodes in data.reference_pieces[puzzle_variant.value].items(): 42 | self.shapes_dict[name] = self.generate_shapes(nodes) 43 | with open('resources/'+puzzle_variant.name+'-shapes.P', 'wb') as fp: 44 | pickle.dump(self.shapes_dict, fp) 45 | 46 | self.pieces = [Piece(name, self.shapes_dict[name]) for name in data.reference_pieces[puzzle_variant.value].keys()] 47 | 48 | try: 49 | with open('resources/'+puzzle_variant.name+'-solution.P', 'rb') as fs: 50 | self.empty_solutions = pickle.load(fs) 51 | if self.empty_solutions: 52 | if len(data.reference_pieces[puzzle_variant.value][self.empty_solutions[0][0][0][0]]) != len(self.empty_solutions[0][0][0][1]): 53 | raise pickle.UnpicklingError 54 | except (FileNotFoundError, pickle.UnpicklingError, KeyError): 55 | self.generate_solutions() 56 | self.empty_solutions = self.path 57 | with open('resources/'+puzzle_variant.name+'-solution.P', 'wb') as fs: 58 | pickle.dump(self.empty_solutions, fs) 59 | self.path = [] 60 | self.solution = [] 61 | 62 | def solve_it(self, input): 63 | for aPiece in self.pieces: 64 | aPiece.puzzle_shape = None 65 | aPiece.shapes = self.shapes_dict[aPiece.name] 66 | 67 | for aPiece_name in input: 68 | aPiece = [_ for _ in self.pieces if _.name == aPiece_name][0] 69 | shape = input[aPiece_name] 70 | aPiece.puzzle_shape = frozenset([Cube(int(round(aNode.x)), int(round(aNode.y)), int(round(aNode.z))) for aNode in shape]) 71 | aPiece.shapes = {aPiece.puzzle_shape:self.shapes_dict[aPiece.name][aPiece.puzzle_shape]} 72 | 73 | self.solution = [] 74 | for aPath in self.empty_solutions: 75 | aSolution = [] 76 | valid_path = True 77 | distance = 0 78 | for aStep in aPath: 79 | next_step_items = [] 80 | for aPiece_name, aShape in aStep: 81 | aPiece = [_ for _ in self.pieces if _.name == aPiece_name][0] 82 | if aPiece_name in input and aShape != aPiece.puzzle_shape: 83 | valid_path = False 84 | break 85 | if aPiece_name not in input: 86 | next_step_items.append((aPiece_name, aPiece.shapes[aShape])) 87 | elif distance < len(input): 88 | pass 89 | else: 90 | valid_path = False 91 | break 92 | else: 93 | distance += len(aStep) 94 | if not valid_path: 95 | break 96 | elif next_step_items: 97 | aSolution.append(next_step_items) 98 | if aSolution and valid_path: 99 | self.solution.append(aSolution) 100 | 101 | return self.solution 102 | 103 | def generate_solutions(self): 104 | for aPiece in self.pieces: 105 | aPiece.puzzle_shape = None 106 | aPiece.shapes = self.shapes_dict[aPiece.name] 107 | self.path = [] 108 | self.solve(set(), len(self.pieces) - 1) 109 | 110 | def solve(self, cubes_used, piece_ind): 111 | if piece_ind < 0: 112 | if len(cubes_used) != self.puzzle_cubes: 113 | return 114 | else: 115 | new_path = [] 116 | solver_pieces = self.pieces[:] 117 | while solver_pieces: 118 | next_removals = [] 119 | for aPiece in solver_pieces: 120 | larger_shape = set() 121 | for aNode in aPiece.puzzle_shape: 122 | larger_shape.add(Cube(aNode.x, aNode.y + 1, aNode.z)) 123 | larger_shape.add(Cube(aNode.x, aNode.y + 2, aNode.z)) 124 | 125 | remainder_cubes = set() 126 | for aRemainder_piece in (_ for _ in solver_pieces if _ != aPiece): 127 | remainder_cubes.update(aRemainder_piece.puzzle_shape) 128 | 129 | if remainder_cubes.isdisjoint(larger_shape): 130 | next_removals.append(aPiece) 131 | 132 | if next_removals: 133 | path_entry = [] 134 | for aPiece in next_removals: 135 | path_entry.append((aPiece.name, aPiece.puzzle_shape)) 136 | solver_pieces.remove(aPiece) 137 | new_path.append(path_entry) 138 | 139 | else: 140 | new_path = [] 141 | break 142 | 143 | if not solver_pieces: 144 | new_path.reverse() 145 | self.path.append(new_path) 146 | return 147 | 148 | for next_shape in self.pieces[piece_ind].shapes: 149 | if cubes_used.isdisjoint(next_shape): 150 | self.pieces[piece_ind].puzzle_shape = next_shape 151 | solved = self.solve(cubes_used | next_shape, piece_ind - 1) 152 | self.pieces[piece_ind].puzzle_shape = None 153 | 154 | return 155 | 156 | def generate_shapes(self, nodes): 157 | nodes = [Vector3(aNode) for aNode in nodes] 158 | theta = math.pi / 2 159 | quaternions = {} 160 | for i in range(4): 161 | quaternions[(i, 0, 0)] = Quaternion.from_x_rotation(i * theta) 162 | quaternions[(0, i, 0)] = Quaternion.from_y_rotation(i * theta) 163 | quaternions[(0, 0, i)] = Quaternion.from_z_rotation(i * theta) 164 | shapes = {} 165 | for x_rot in (3, 2, 1, 0): 166 | new_rotation = quaternions[(x_rot, 0, 0)] 167 | rotated_nodes_x = self.shape_constrained([new_rotation * aNode for aNode in nodes], zero_align=True) 168 | for y_rot in (3, 2, 1, 0): 169 | new_rotation = quaternions[(0, y_rot, 0)] 170 | rotated_nodes_y = self.shape_constrained([new_rotation * aNode for aNode in rotated_nodes_x], zero_align=True) 171 | for z_rot in (3, 2, 1, 0): 172 | new_rotation = quaternions[(0, 0, z_rot)] 173 | rotated_nodes = self.shape_constrained([new_rotation * aNode for aNode in rotated_nodes_y], zero_align=True) 174 | for x_shift in (2, 1, 0): 175 | for y_shift in (2, 1, 0): 176 | for z_shift in (-2, -1, 0): 177 | new_shift = Vector3([float(x_shift), float(y_shift), float(z_shift)]) 178 | shifted_nodes = [new_shift + aNode for aNode in rotated_nodes] 179 | shape = self.shape_constrained(shifted_nodes) 180 | shape = frozenset([Cube(int(round(aNode.x)), int(round(aNode.y)), int(round(aNode.z))) for aNode in shape]) 181 | shapes[shape] = (x_rot, y_rot, z_rot, x_shift, y_shift, z_shift) 182 | return shapes 183 | 184 | def shape_constrained(self, shape_nodes, zero_align=False): 185 | min_box = Vector3([100., 100., 100.]) 186 | max_box = Vector3([-100., -100., -100.]) 187 | for aNode in shape_nodes: 188 | min_box.x = round(min(min_box.x, aNode.x)) 189 | min_box.y = round(min(min_box.y, aNode.y)) 190 | min_box.z = round(min(min_box.z, aNode.z)) 191 | max_box.x = round(max(max_box.x, aNode.x)) 192 | max_box.y = round(max(max_box.y, aNode.y)) 193 | max_box.z = round(max(max_box.z, aNode.z)) 194 | 195 | offset = Vector3() 196 | if not zero_align: 197 | if min_box.x < 0: offset.x = float(min_box.x) 198 | elif max_box.x > 2: offset.x = float(max_box.x - 2.) 199 | if min_box.y < -6: offset.y = float(min_box.y + 6.) 200 | elif max_box.y > -4: offset.y = float(max_box.y + 4.) 201 | if min_box.z < -2: offset.z = float(min_box.z + 2.) 202 | elif max_box.z > 0: offset.z = float(max_box.z) 203 | else: 204 | offset.x = float(min_box.x) 205 | offset.y = float(min_box.y + 6.) 206 | offset.z = float(max_box.z) 207 | 208 | return [aNode - offset for aNode in shape_nodes] 209 | 210 | if __name__ == '__main__': 211 | import main 212 | main.MainViewController.run() 213 | 214 | -------------------------------------------------------------------------------- /sceneKit/sceneKitLight.py: -------------------------------------------------------------------------------- 1 | '''light modul, to be included in sceneKit''' 2 | 3 | from ui import parse_color 4 | 5 | import sceneKit 6 | from .sceneKitEnv import * 7 | from .sceneKitMaterial import * 8 | from .sceneKitAnimation import * 9 | 10 | _lightTypes = [] 11 | for aLightType in ['SCNLightTypeIES', 'SCNLightTypeAmbient', 'SCNLightTypeDirectional', 'SCNLightTypeOmni', 'SCNLightTypeProbe', 'SCNLightTypeSpot']: 12 | _lightTypes.append(str(ObjCInstance(c_void_p.in_dll(c, aLightType)))) 13 | _lightTypes = tuple(_lightTypes) 14 | 15 | SCNLightTypeIES, SCNLightTypeAmbient, SCNLightTypeDirectional, \ 16 | SCNLightTypeOmni, SCNLightTypeProbe, SCNLightTypeSpot = _lightTypes 17 | LightTypeIES, LightTypeAmbient, LightTypeDirectional, \ 18 | LightTypeOmni, LightTypeProbe, LightTypeSpot = _lightTypes 19 | 20 | class ShadowMode(Enum): 21 | Forward = 0 22 | Deferred = 1 23 | Modulated = 2 24 | SCNShadowModeForward = 0 25 | SCNShadowModeDeferred = 1 26 | SCNShadowModeModulated = 2 27 | 28 | 29 | class Light(Animatable, CInst): 30 | def __init__(self, mdlLight=None, ID=None): 31 | if ID is not None: 32 | self.ID = ID 33 | elif mdlLight is not None: 34 | self.ID = SCNLight.lightWithMDLLight_(mdlLight) 35 | if self.ID is None: 36 | raise RuntimeError('lightWithMDLLight failed. Wrong MDL asset?') 37 | else: 38 | self.ID = SCNLight.light() 39 | 40 | @classmethod 41 | def light(cls): 42 | return cls() 43 | 44 | @classmethod 45 | def lightWithMDLLight(cls, mdlLight=None): 46 | return cls(mdlLight=mdlLight) 47 | 48 | def setName(self, aString): 49 | self.ID.setName_(aString) 50 | def getName(self): 51 | return str(self.ID.name()) 52 | name = property(getName, setName) 53 | 54 | def setType(self, aLightType): 55 | self.ID.setType_(aLightType) 56 | def getType(self): 57 | return self.ID.type() 58 | type = property(getType, setType) 59 | 60 | def setColor(self, aColor): 61 | r, g, b, a = parse_color(aColor) 62 | self.ID.setColor_(UIColor.color(red=r, green=g, blue=b, alpha=a)) 63 | def getColor(self): 64 | color = self.ID.color() 65 | return RGBA(color.red(), color.green(), color.blue(), color.alpha()) 66 | color = property(getColor, setColor) 67 | 68 | def setTemperature(self, aTemp): 69 | self.ID.setTemperature_(aTemp) 70 | def getTemperature(self): 71 | return self.ID.temperature() 72 | temperature = property(getTemperature, setTemperature) 73 | 74 | def setIntensity(self, anIntensity): 75 | self.ID.setIntensity_(anIntensity) 76 | def getIntensity(self): 77 | return self.ID.intensity() 78 | intensity = property(getIntensity, setIntensity) 79 | 80 | def getSphericalHarmonicsCoefficients(self): 81 | return self.ID.sphericalHarmonicsCoefficients() 82 | sphericalHarmonicsCoefficients = property(getSphericalHarmonicsCoefficients, None) 83 | 84 | def setAttenuationStartDistance(self, aDistance): 85 | self.ID.setAttenuationStartDistance_(aDistance) 86 | def getAttenuationStartDistance(self): 87 | return self.ID.attenuationStartDistance() 88 | attenuationStartDistance = property(getAttenuationStartDistance, setAttenuationStartDistance) 89 | 90 | def setAttenuationEndDistance(self, aDistance): 91 | self.ID.setAttenuationEndDistance_(aDistance) 92 | def getAttenuationEndDistance(self): 93 | return self.ID.attenuationEndDistance() 94 | attenuationEndDistance = property(getAttenuationEndDistance, setAttenuationEndDistance) 95 | 96 | def setAttenuationFalloffExponent(self, aFallOff): 97 | self.ID.setAttenuationFalloffExponent_(aFallOff) 98 | def getAttenuationFalloffExponent(self): 99 | return self.ID.attenuationFalloffExponent() 100 | attenuationFalloffExponent = property(getAttenuationFalloffExponent, setAttenuationFalloffExponent) 101 | 102 | def setSpotInnerAngle(self, anAngle): 103 | self.ID.setSpotInnerAngle_(anAngle) 104 | def getSpotInnerAngle(self): 105 | return self.ID.spotInnerAngle() 106 | spotInnerAngle = property(getSpotInnerAngle, setSpotInnerAngle) 107 | 108 | def setSpotOuterAngle(self, anAngle): 109 | self.ID.setSpotOuterAngle_(anAngle) 110 | def getSpotOuterAngle(self): 111 | return self.ID.spotOuterAngle() 112 | spotOuterAngle = property(getSpotOuterAngle, setSpotOuterAngle) 113 | 114 | def setCastsShadow(self, aBool): 115 | self.ID.setCastsShadow_(aBool) 116 | def getCastsShadow(self): 117 | return self.ID.castsShadow() 118 | castsShadow = property(getCastsShadow, setCastsShadow) 119 | 120 | def setShadowRadius(self, aRadius): 121 | self.ID.setShadowRadius_(aRadius) 122 | def getShadowRadius(self): 123 | return self.ID.shadowRadius() 124 | shadowRadius = property(getShadowRadius, setShadowRadius) 125 | 126 | def setShadowColor(self, aColor): 127 | r, g, b, a = parse_color(aColor) 128 | self.ID.setShadowColor_(ObjCClass('UIColor').color(red=r, green=g, blue=b, alpha=a)) 129 | def getShadowColor(self): 130 | color = self.ID.shadowColor() 131 | return RGBA(color.red(), color.green(), color.blue(), color.alpha()) 132 | shadowColor = property(getShadowColor, setShadowColor) 133 | 134 | def setShadowMapSize(self, aGCSize): 135 | self.ID.setShadowMapSize_(aGCSize) 136 | def getShadowMapSize(self): 137 | map = self.ID.shadowMapSize() 138 | return Size(map.width, map.height) 139 | shadowMapSize = property(getShadowMapSize, setShadowMapSize) 140 | 141 | def setShadowSampleCount(self, aCount): 142 | self.ID.setShadowSampleCount_(aCount) 143 | def getShadowSampleCount(self): 144 | return self.ID.shadowSampleCount() 145 | shadowSampleCount = property(getShadowSampleCount, setShadowSampleCount) 146 | 147 | def setShadowMode(self, aMode): 148 | self.ID.setShadowMode_(aMode.value) 149 | def getShadowMode(self): 150 | return ShadowMode(self.ID.shadowMode()) 151 | shadowMode = property(getShadowMode, setShadowMode) 152 | 153 | def setShadowBias(self, aBias): 154 | self.ID.setShadowBias_(aBias) 155 | def getShadowBias(self): 156 | return self.ID.shadowBias() 157 | shadowBias = property(getShadowBias, setShadowBias) 158 | 159 | def setOrthographicScale(self, aScale): 160 | self.ID.setOrthographicScale_(aScale) 161 | def getOrthographicScale(self): 162 | return self.ID.orthographicScale() 163 | orthographicScale = property(getOrthographicScale, setOrthographicScale) 164 | 165 | def setZFar(self, aDistance): 166 | self.ID.setZFar_(aDistance) 167 | def getZFar(self): 168 | return self.ID.zFar() 169 | zFar = property(getZFar, setZFar) 170 | 171 | def setZNear(self, aDistance): 172 | self.ID.setZNear_(aDistance) 173 | def getZNear(self): 174 | return self.ID.zNear() 175 | zNear = property(getZNear, setZNear) 176 | 177 | def setCategoryBitMask(self, aBitMask): 178 | self.ID.setCategoryBitMask_(aBitMask) 179 | def getCategoryBitMask(self): 180 | return self.ID.categoryBitMask() 181 | categoryBitMask = property(getCategoryBitMask, setCategoryBitMask) 182 | 183 | def setIESProfileURL(self, anURL): 184 | self.ID.setIESProfileURL_(nsurl(anURL)) 185 | def getIESProfileURL(self): 186 | return str(self.ID.IESProfileURL().absoluteString()) 187 | IESProfileURL = property(getIESProfileURL, setIESProfileURL) 188 | 189 | def setAutomaticallyAdjustsShadowProjection(self, aBool): 190 | self.ID.setAutomaticallyAdjustsShadowProjection_(aBool) 191 | def getAutomaticallyAdjustsShadowProjection(self): 192 | return self.ID.automaticallyAdjustsShadowProjection() 193 | automaticallyAdjustsShadowProjection = property(getAutomaticallyAdjustsShadowProjection, setAutomaticallyAdjustsShadowProjection) 194 | 195 | def setForcesBackFaceCasters(self, aBool): 196 | self.ID.setForcesBackFaceCasters_(aBool) 197 | def getForcesBackFaceCasters(self): 198 | return self.ID.forcesBackFaceCasters() 199 | forcesBackFaceCasters = property(getForcesBackFaceCasters, setForcesBackFaceCasters) 200 | 201 | def setMaximumShadowDistance(self, aDistance): 202 | self.ID.setMaximumShadowDistance_(aDistance) 203 | def getMaximumShadowDistance(self): 204 | return self.ID.maximumShadowDistance() 205 | maximumShadowDistance = property(getMaximumShadowDistance, setMaximumShadowDistance) 206 | 207 | def setSampleDistributedShadowMaps(self, aBool): 208 | self.ID.setSampleDistributedShadowMaps_(aBool) 209 | def getSampleDistributedShadowMaps(self): 210 | return self.ID.sampleDistributedShadowMaps() 211 | sampleDistributedShadowMaps = property(getSampleDistributedShadowMaps, setSampleDistributedShadowMaps) 212 | 213 | def setShadowCascadeCount(self, aCount): 214 | self.ID.setShadowCascadeCount_(aCount) 215 | def getShadowCascadeCount(self): 216 | return self.ID.shadowCascadeCount() 217 | shadowCascadeCount = property(getShadowCascadeCount, setShadowCascadeCount) 218 | 219 | def setShadowCascadeSplittingFactor(self, aFactor): 220 | self.ID.setShadowCascadeSplittingFactor_(aFactor) 221 | def getShadowCascadeSplittingFactor(self): 222 | return self.ID.shadowCascadeSplittingFactor() 223 | shadowCascadeSplittingFactor = property(getShadowCascadeSplittingFactor, setShadowCascadeSplittingFactor) 224 | 225 | def setGobo(self, aMaterialProperty): 226 | self.ID.setGobo_(aMaterialProperty._materialProperty) 227 | def getGobo(self): 228 | return sceneKit.MaterialProperty.outof(self.ID.gobo()) 229 | gobo = property(getGobo, setGobo) 230 | -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/manual.py: -------------------------------------------------------------------------------- 1 | '''Cube puzzles - five typical 3x3x3 3D puzzles. 2 | 3 | (c) Peter Ulbrich, 2019 4 | 5 | a demo script for the sceneKit wrapper package for Pythonista 6 | 7 | See the README.md for details. 8 | 9 | ''' 10 | 11 | import math 12 | 13 | import sceneKit as scn 14 | 15 | from data import * 16 | import main 17 | import setup 18 | from piece import * 19 | import solver 20 | 21 | class ManualStage: 22 | def __init__(self, puzzle_variant): 23 | self.piece_set = Piece_set(puzzle_variant) 24 | self.center_view = setup.setup_center_view(self.piece_set) 25 | setup.setup_drawer_views(self.piece_set) 26 | self.solver = solver.Solver(puzzle_variant) 27 | 28 | self.show_drawer_animation = setup.setup_show_drawer_animation() 29 | 30 | self.solved_tray_piece = None 31 | self.tray_piece_transforms = [] 32 | self.tray_piece_transforms_index = -1 33 | 34 | data.hud_layer.set_title(str(puzzle_variant.name).replace('_', ' ')) 35 | 36 | def restart(self): 37 | scn.Transaction.begin() 38 | scn.Transaction.animationDuration = 1. 39 | self.piece_set.reset_pieces() 40 | self.piece_set.previous_piece_in_tray = [] 41 | scn.Transaction.commit() 42 | self.reset_show_next_tray_position() 43 | 44 | def scene_views(self): 45 | return [self.center_view] + [aPiece.drawer_view for aPiece in self.piece_set.pieces] 46 | 47 | def tap(self, location): 48 | for aPiece in self.piece_set.pieces: 49 | drawer_frame = aPiece.drawer_view.frame 50 | if drawer_frame[0] < location.x < drawer_frame[0] + drawer_frame[2] and drawer_frame[1] < location.y < drawer_frame[1] + drawer_frame[3]: 51 | self.drawer_tapped(aPiece) 52 | return 53 | if self.center_view.frame[0] < location.x < self.center_view.frame[0] + self.center_view.frame[2] and self.center_view.frame[1] < location.y < self.center_view.frame[1] + self.center_view.frame[3]: 54 | self.center_view_tapped(location) 55 | return 56 | 57 | def center_view_tapped(self, location): 58 | x = location.x - self.center_view.frame[0] 59 | y = location.y - self.center_view.frame[1] 60 | hit_list = self.center_view.hitTest((x, y), {scn.HitTestOptionSearchMode:scn.HitTestSearchMode.Closest, scn.HitTestOptionCategoryBitMask:2}) 61 | if not hit_list: return 62 | 63 | hit_name = hit_list[0].node.name 64 | tray_piece = next((aCandidate for aCandidate in self.piece_set.pieces if aCandidate.location == Piece_location.Tray), None) 65 | scn.Transaction.begin() 66 | scn.Transaction.animationDuration = 1. 67 | try: 68 | if hit_name[0] == 'r': 69 | tray_piece.rotate(hit_name[1]) 70 | elif hit_name[0] == 's': 71 | shift = 1 if hit_name[2] == '1' else -1 72 | tray_piece.shift(hit_name[1], shift) 73 | except (IndexError, AttributeError): pass 74 | scn.Transaction.commit() 75 | 76 | if hit_name == 'box': 77 | scn.Transaction.begin() 78 | scn.Transaction.animationDuration = 1.5 79 | if tray_piece is not None: 80 | tray_piece.drop() 81 | # if len([_ for _ in self.piece_set.pieces if _.location != Piece_location.Puzzle]) == 0: 82 | if self.piece_set.solved(): 83 | self.show_finished() 84 | else: 85 | try: 86 | tray_piece = self.piece_set.previous_piece_in_tray.pop() 87 | tray_piece.lift() 88 | tray_piece.constraint() 89 | except IndexError: pass 90 | scn.Transaction.commit() 91 | self.reset_show_next_tray_position() 92 | 93 | def drawer_tapped(self, aPiece): 94 | self.reset_show_next_tray_position() 95 | scn.Transaction.begin() 96 | scn.Transaction.animationDuration = 0.3 97 | onTray = next((aCandidate for aCandidate in self.piece_set.pieces if aCandidate.location == Piece_location.Tray), None) 98 | if onTray is not None: 99 | onTray.from_tray_to_drawer() 100 | if aPiece.location == Piece_location.Drawer and aPiece != onTray: 101 | aPiece.from_drawer_to_tray() 102 | scn.Transaction.commit() 103 | 104 | def hint(self): 105 | if self.piece_set.solved(): 106 | self.show_finished() 107 | return 108 | 109 | tray_pieces = [aPiece for aPiece in self.piece_set.pieces if aPiece.location == Piece_location.Tray] 110 | if tray_pieces and tray_pieces[0] == self.solved_tray_piece: 111 | self.show_next_tray_position() 112 | return 113 | 114 | solver_input = {} 115 | for aPiece in self.piece_set.pieces: 116 | if aPiece.location == Piece_location.Puzzle: 117 | tray_handle = aPiece.tray_handle_node.parentNode 118 | positions = [] 119 | for aNode in aPiece.piece_handle_node.childNodes: 120 | aPos = aPiece.piece_handle_node.convertPosition(aNode.position, toNode=tray_handle) 121 | positions.append(aPos) 122 | 123 | solver_input[aPiece.name] = tuple(positions) 124 | 125 | solution = self.solver.solve_it(solver_input) 126 | 127 | if not solution: 128 | self.show_dead_end() 129 | return 130 | 131 | candidate_names = set() 132 | for aSolution in solution: 133 | for anOption in aSolution[0]: 134 | candidate_names.add(anOption[0]) 135 | 136 | candidate_pieces = [aPiece for aPiece in self.piece_set.pieces if aPiece.name in candidate_names] 137 | 138 | 139 | if not tray_pieces or tray_pieces[0] not in candidate_pieces: 140 | self.show_candidates(candidate_pieces) 141 | return 142 | 143 | if tray_pieces and tray_pieces[0] in candidate_pieces: 144 | tray_piece = tray_pieces[0] 145 | tray_piece_transforms = set() 146 | for aSolution in self.solver.solution: 147 | for aStepItem in aSolution[0]: 148 | if aStepItem[0] == tray_piece.name: 149 | tray_piece_transforms.add(aStepItem[1]) 150 | self.setup_show_next_tray_position(tray_piece, list(tray_piece_transforms)) 151 | self.show_next_tray_position() 152 | return 153 | 154 | 155 | def show_dead_end(self): 156 | message_symbol = '🤷🏻‍♀️' 157 | data.hud_layer.show_alert(message_symbol) 158 | 159 | def show_finished(self): 160 | message_symbol = '👍' 161 | data.hud_layer.show_alert(message_symbol) 162 | 163 | 164 | def show_candidates(self, candidate_pieces): 165 | for aPiece in candidate_pieces: 166 | aPiece.drawer_view.scene.background.removeAllAnimations() 167 | aPiece.drawer_view.scene.background.addAnimation(self.show_drawer_animation) 168 | 169 | def setup_show_next_tray_position(self, tray_piece, tray_piece_transforms): 170 | self.solved_tray_piece = tray_piece 171 | self.tray_piece_transforms = tray_piece_transforms 172 | self.tray_piece_transforms_index = -1 173 | data.hud_layer.set_status('') 174 | 175 | def reset_show_next_tray_position(self): 176 | self.solved_tray_piece = None 177 | self.tray_piece_transforms = [] 178 | self.tray_piece_transforms_index = -1 179 | data.hud_layer.set_status('') 180 | 181 | def show_next_tray_position(self): 182 | self.tray_piece_transforms_index += 1 183 | if self.tray_piece_transforms_index == len(self.tray_piece_transforms): 184 | self.tray_piece_transforms_index = 0 185 | 186 | tray_piece = self.solved_tray_piece 187 | transform = self.tray_piece_transforms[self.tray_piece_transforms_index] 188 | 189 | original_transform = tray_piece.piece_handle_node.transform 190 | 191 | tray_piece.from_drawer_to_tray() 192 | for ind, axis in enumerate(['x', 'y', 'z']): 193 | for _ in range(transform[ind]): 194 | tray_piece.rotate(axis, constraint=False) 195 | tray_piece.constraint(zero_align=True) 196 | tray_piece.shift('x', transform[3]) 197 | tray_piece.shift('y', transform[4]) 198 | tray_piece.shift('z', transform[5]) 199 | 200 | new_transform = tray_piece.piece_handle_node.transform 201 | tray_piece.piece_handle_node.transform = original_transform 202 | 203 | scn.Transaction.begin() 204 | scn.Transaction.animationDuration = 1. 205 | tray_piece.piece_handle_node.transform = new_transform 206 | scn.Transaction.commit() 207 | 208 | lead = "".join('⚫️' for i in range(self.tray_piece_transforms_index)) 209 | tail = "".join('⚫️' for i in range(len(self.tray_piece_transforms) - self.tray_piece_transforms_index - 1)) 210 | data.hud_layer.set_status(lead + '🔵' + tail) 211 | 212 | def layout(self): 213 | _, _, w, h = data.main_view.frame 214 | half = (len(self.piece_set.pieces) + 1) // 2 215 | odd = 2 * half - len(self.piece_set.pieces) 216 | side_with_gap = min(int(min(w, h) / (half+0.2)), int(min(w, h) / 3.5)) 217 | side = int(0.8 * side_with_gap) 218 | gap = side_with_gap - side 219 | for i in range(len(self.piece_set.pieces)): 220 | tile = i % half 221 | left_side = top_side = i < half 222 | if data.horizontal: 223 | x = int(2 * gap) if left_side else w - int(2*gap) - side 224 | y = h - (odd * side_with_gap if not left_side else 0)//2 - (h - half*side - (half-1) * gap)//2 - side - tile*side_with_gap 225 | else: 226 | x = (odd * side_with_gap if not top_side else 0)//2 + (w - half*side - (half-1) * gap)//2 + tile*side_with_gap 227 | y = 20 + side//2 if top_side else h - 20 - side//2 - side 228 | 229 | self.piece_set.pieces[i].drawer_view.frame = (x, y, (side), (side)) 230 | 231 | x = 2*gap + side_with_gap if data.horizontal else gap 232 | y = (h - half*side - (half-1)*gap)//2 + 10 if data.horizontal else 20 + side//2 + side + gap 233 | 234 | self.center_view.frame = (x, y, w - 2*x, h - 2*y) if data.horizontal else (x, y, w - 2*x, h - 2*y) 235 | 236 | box_node = self.center_view.scene.rootNode.childNodeWithName('box') 237 | box_node.rotation = (0, 0, 1, 0) if data.horizontal else (0, 0, 1, -math.pi/2) 238 | box_node.position = (1., 1., -1.) if data.horizontal else (-0.5, 3, -1) 239 | box_node.scale = (1., 1., 1.) if data.horizontal else (1.3, 1.3, 1.3) 240 | 241 | camera_node = self.center_view.scene.rootNode.childNodeWithName('center_camera') 242 | camera_node.position = (6., 8.5, 9.5) if data.horizontal else (9.2, 10., 13.) 243 | 244 | if __name__ == '__main__': 245 | main.MainViewController.run() 246 | -------------------------------------------------------------------------------- /sceneKit demo/17_cube-puzzle/setup.py: -------------------------------------------------------------------------------- 1 | '''Cube puzzles - five typical 3x3x3 3D puzzles. 2 | 3 | (c) Peter Ulbrich, 2019 4 | 5 | a demo script for the sceneKit wrapper package for Pythonista 6 | 7 | See the README.md for details. 8 | 9 | ''' 10 | 11 | import math 12 | import sceneKit as scn 13 | 14 | import main 15 | from data import * 16 | from piece import * 17 | 18 | def setup_elementary_cube_geometry(): 19 | geometry = scn.Box(1., 1., 1., 0.) 20 | material = scn.Material() 21 | material.diffuse.contents = 'resources/wood.png' 22 | geometry.firstMaterial = material 23 | return geometry 24 | 25 | def setup_pieces(piece_set, puzzle_variant): 26 | pieces = [] 27 | for name, cubes in data.reference_pieces[puzzle_variant.value].items(): 28 | handle_node = scn.Node() 29 | handle_node.name = name 30 | nodes = [] 31 | for aCubePosition in cubes: 32 | nodes.append(scn.Node.nodeWithGeometry(setup_elementary_cube_geometry())) 33 | nodes[-1].position = aCubePosition 34 | 35 | for aNode in nodes: 36 | aNode.geometry.firstMaterial.diffuse.intensity = (0.7 - len(pieces)/len(data.reference_pieces[puzzle_variant.value])) + 0.3 37 | handle_node.addChildNode(aNode) 38 | 39 | pieces.append(Piece(piece_set, name, handle_node)) 40 | 41 | return pieces 42 | 43 | rotate_action = scn.Action.repeatActionForever(scn.Action.rotateBy(0, math.pi*2, 0, 25.)) 44 | 45 | def setup_selector_views(): 46 | selector_views = [] 47 | for aVariant in Puzzle_variant: 48 | selector_view = scn.View(None, superView=data.main_view) 49 | selector_view.preferredFramesPerSecond = 30 50 | selector_view.rendersContinuously = False 51 | selector_view.autoenablesDefaultLighting = True 52 | selector_view.allowsCameraControl = False 53 | 54 | selector_view.scene = scn.Scene() 55 | selector_view.scene.background.contents = 'beige' 56 | 57 | title_geometry = scn.Text(str(aVariant.name).replace('_', ' '), 0.3) 58 | title_geometry.font = ('Chalkboard SE', 4.) 59 | title_geometry.firstMaterial.contents = (.48, .14, .14) 60 | title_geometry.flatness = 0.1 61 | title_geometry.chamferRadius = 0.08 62 | bbox_min, bbox_max = title_geometry.boundingBox 63 | title_size = bbox_max.x - bbox_min.x 64 | title_node = scn.Node.nodeWithGeometry(title_geometry) 65 | title_node.position = (-title_size/2, -1., -25.) 66 | selector_view.scene.rootNode.addChildNode(title_node) 67 | 68 | puzzle_node = scn.Node() 69 | 70 | table_geometry = scn.Cylinder(6., 0.5) 71 | table_geometry.firstMaterial.diffuse.contents = 'resources/marble.jpg' 72 | table_node = scn.Node.nodeWithGeometry(table_geometry) 73 | table_node.position = (0., -0.75, 0.) 74 | puzzle_node.addChildNode(table_node) 75 | 76 | puzzle_node.position = (0., -0.25, 0.) 77 | piece_set = Piece_set(aVariant) 78 | pieces = len(piece_set.pieces) 79 | for index, aPiece in enumerate(piece_set.pieces): 80 | show_piece = aPiece.drawer_handle_node 81 | show_piece.rotation = (0, 1, 0, index/pieces*2*math.pi - math.pi/4.) 82 | show_piece.position = scn.Vector3(4.*math.sin(index/pieces*2*math.pi), 0., 4.*math.cos(index/pieces*2*math.pi)) 83 | puzzle_node.addChildNode(show_piece) 84 | 85 | puzzle_node.runAction(rotate_action) 86 | selector_view.scene.rootNode.addChildNode(puzzle_node) 87 | 88 | camera_node = scn.Node() 89 | camera_node.name = 'selector_camera' 90 | camera_node.position = (0., 7., 11.) 91 | camera_node.lookAt((0., 0., 0.)) 92 | camera_node.camera = scn.Camera() 93 | selector_view.scene.rootNode.addChildNode(camera_node) 94 | 95 | selector_views.append(selector_view) 96 | return selector_views 97 | 98 | def setup_center_view(piece_set): 99 | center_view = scn.View(None, superView=data.main_view) 100 | center_view.preferredFramesPerSecond = 30 101 | center_view.rendersContinuously = False 102 | center_view.autoenablesDefaultLighting = True 103 | center_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleWidth 104 | center_view.allowsCameraControl = True 105 | center_view.jitteringEnabled = True 106 | 107 | center_view.backgroundColor = 'beige' 108 | 109 | box_size = 3.0 110 | box = scn.Box(box_size, box_size, box_size, 0.0) 111 | box.firstMaterial.diffuse.contents = (.86, .86, .86) 112 | box.firstMaterial.transparency = 0.15 113 | box.firstMaterial.cullMode = scn.CullMode.Front 114 | box_node = scn.Node.nodeWithGeometry(box) 115 | box_node.name = 'box' 116 | box_node.categoryBitMask = 2 117 | 118 | tray_handle = setup_tray(piece_set) 119 | axis_handle = setup_axes() 120 | tray_handle.addChildNode(axis_handle) 121 | box_node.addChildNode(tray_handle) 122 | 123 | scene = scn.Scene() 124 | scene.rootNode.addChildNode(box_node) 125 | 126 | camera_node = scn.Node() 127 | camera_node.name = 'center_camera' 128 | camera_node.position = (8., 10., 12) 129 | camera_node.lookAt((1., 3., -1.5)) 130 | camera_node.camera = scn.Camera() 131 | camera_node.camera.fieldOfView = 50 132 | scene.rootNode.addChildNode(camera_node) 133 | 134 | center_view.scene = scene 135 | return center_view 136 | 137 | def setup_drawer_views(piece_set): 138 | for aPiece in piece_set.pieces: 139 | drawer_view = scn.View(None, superView=data.main_view) 140 | drawer_view.preferredFramesPerSecond = 30 141 | drawer_view.rendersContinuously = False 142 | drawer_view.autoenablesDefaultLighting = True 143 | drawer_view.allowsCameraControl = True 144 | 145 | drawer_view.scene = scn.Scene() 146 | drawer_view.scene.background.contents = 'beige' 147 | show_piece = aPiece.drawer_handle_node 148 | show_piece.rotation = (0, 1, 0, math.pi/2.5) 149 | drawer_view.scene.rootNode.addChildNode(show_piece) 150 | 151 | min, max = drawer_view.scene.rootNode.boundingBox 152 | 153 | camera_node = scn.Node() 154 | camera_node.position = (2.5, 3., 2.5) 155 | camera_node.lookAt(((min.x+max.x)/2+1., (min.y+max.y)/2+0.5, 0.)) 156 | camera_node.camera = scn.Camera() 157 | drawer_view.scene.rootNode.addChildNode(camera_node) 158 | 159 | aPiece.drawer_view = drawer_view 160 | 161 | def setup_show_drawer_animation(): 162 | core_animation = scn.CoreBasicAnimation('contents') 163 | core_animation.toValue = (.4, 1.0, .0, 0.9) 164 | core_animation.removedOnCompletion = False 165 | 166 | animation = scn.Animation.animationWithCAAnimation(core_animation) 167 | animation.removedOnCompletion = False 168 | animation.autoreverses = True 169 | animation.duration = 1. 170 | animation.repeatCount = 3 171 | 172 | return animation 173 | 174 | def setup_tray(piece_set): 175 | tray_handle = scn.Node() 176 | tray_handle.position = (-1, 5, 1) 177 | tray_handle.name = 'tray' 178 | 179 | for aPiece in piece_set.pieces: 180 | tray_handle.addChildNode(aPiece.tray_handle_node) 181 | 182 | return tray_handle 183 | 184 | def setup_axes(): 185 | axis_rode = scn.Cylinder(0.015, 7.5) 186 | axis_rode.firstMaterial.contents = 'blue' 187 | 188 | vertical_axis_rode = scn.Cylinder(0.015, 11.) 189 | vertical_axis_rode.firstMaterial.contents = 'blue' 190 | 191 | rotate_control_geometry = scn.Sphere(0.35) 192 | rotate_control_geometry.firstMaterial.contents = 'red' 193 | 194 | cube_rotate_control_geometry = scn.Sphere(0.35) 195 | cube_rotate_control_geometry.firstMaterial.contents = 'green' 196 | 197 | shift_control_geometry = scn.Cone(0, 0.38, 0.7) 198 | shift_control_geometry.firstMaterial.contents = 'orange' 199 | 200 | axis_handle = scn.Node() 201 | axis_handle.name = 'origo' 202 | axis_handle.position = (-0.5, -0.5, 0.5) 203 | 204 | x_axis_node = scn.Node.nodeWithGeometry(axis_rode) 205 | x_axis_node.rotation = (0, 0, 1, math.pi/2) 206 | x_axis_node.position = (1.5, 0, 0) 207 | 208 | x_axis_rotate_control_node_1 = scn.Node.nodeWithGeometry(rotate_control_geometry) 209 | x_axis_rotate_control_node_1.position = (0., -axis_rode.height / 2 + 1, 0.) 210 | x_axis_rotate_control_node_1.name = 'rx1' 211 | x_axis_rotate_control_node_1.categoryBitMask = 2 212 | x_axis_node.addChildNode(x_axis_rotate_control_node_1) 213 | x_axis_rotate_control_node_2 = scn.Node.nodeWithGeometry(rotate_control_geometry) 214 | x_axis_rotate_control_node_2.position = (0., axis_rode.height / 2 - 1, 0.) 215 | x_axis_rotate_control_node_2.name = 'rx2' 216 | x_axis_rotate_control_node_2.categoryBitMask = 2 217 | x_axis_node.addChildNode(x_axis_rotate_control_node_2) 218 | 219 | x_axis_shift_control_node_1 = scn.Node.nodeWithGeometry(shift_control_geometry) 220 | x_axis_shift_control_node_1.position = (0., -axis_rode.height / 2, 0.) 221 | x_axis_shift_control_node_1.rotation = (0., 0., 1., -math.pi) 222 | x_axis_shift_control_node_1.name = 'sx1' 223 | x_axis_shift_control_node_1.categoryBitMask = 2 224 | x_axis_node.addChildNode(x_axis_shift_control_node_1) 225 | x_axis_shift_control_node_2 = scn.Node.nodeWithGeometry(shift_control_geometry) 226 | x_axis_shift_control_node_2.position = (0., axis_rode.height / 2, 0.) 227 | x_axis_shift_control_node_2.name = 'sx2' 228 | x_axis_shift_control_node_2.categoryBitMask = 2 229 | x_axis_node.addChildNode(x_axis_shift_control_node_2) 230 | 231 | axis_handle.addChildNode(x_axis_node) 232 | 233 | y_axis_node = scn.Node.nodeWithGeometry(vertical_axis_rode) 234 | y_axis_node.rotation = (1, 0, 0, -math.pi) 235 | y_axis_node.position = (0, -3, 0) 236 | y_axis_rotate_control_node_1 = scn.Node.nodeWithGeometry(rotate_control_geometry) 237 | y_axis_rotate_control_node_1.position = (0., -vertical_axis_rode.height / 2, 0.) 238 | y_axis_rotate_control_node_1.name = 'ry1' 239 | y_axis_rotate_control_node_1.categoryBitMask = 2 240 | y_axis_node.addChildNode(y_axis_rotate_control_node_1) 241 | y_axis_rotate_control_node_2 = scn.Node.nodeWithGeometry(rotate_control_geometry) 242 | y_axis_rotate_control_node_2.position = (0., vertical_axis_rode.height / 2, 0.) 243 | y_axis_rotate_control_node_2.name = 'ry2' 244 | y_axis_rotate_control_node_2.categoryBitMask = 2 245 | y_axis_node.addChildNode(y_axis_rotate_control_node_2) 246 | 247 | axis_handle.addChildNode(y_axis_node) 248 | 249 | z_axis_node = scn.Node.nodeWithGeometry(axis_rode) 250 | z_axis_node.rotation = (1, 0, 0, -math.pi/2) 251 | z_axis_node.position = (0, 0, -1.5) 252 | z_axis_rotate_control_node_1 = scn.Node.nodeWithGeometry(rotate_control_geometry) 253 | z_axis_rotate_control_node_1.position = (0., -axis_rode.height / 2 + 1, 0.) 254 | z_axis_rotate_control_node_1.name = 'rz1' 255 | z_axis_rotate_control_node_1.categoryBitMask = 2 256 | z_axis_node.addChildNode(z_axis_rotate_control_node_1) 257 | z_axis_rotate_control_node_2 = scn.Node.nodeWithGeometry(rotate_control_geometry) 258 | z_axis_rotate_control_node_2.position = (0., axis_rode.height / 2 - 1, 0.) 259 | z_axis_rotate_control_node_2.name = 'rz2' 260 | z_axis_rotate_control_node_2.categoryBitMask = 2 261 | z_axis_node.addChildNode(z_axis_rotate_control_node_2) 262 | 263 | z_axis_shift_control_node_1 = scn.Node.nodeWithGeometry(shift_control_geometry) 264 | z_axis_shift_control_node_1.position = (0., -axis_rode.height / 2, 0.) 265 | z_axis_shift_control_node_1.rotation = (0., 0., 1., math.pi) 266 | z_axis_shift_control_node_1.name = 'sz1' 267 | z_axis_shift_control_node_1.categoryBitMask = 2 268 | z_axis_node.addChildNode(z_axis_shift_control_node_1) 269 | z_axis_shift_control_node_2 = scn.Node.nodeWithGeometry(shift_control_geometry) 270 | z_axis_shift_control_node_2.position = (0., axis_rode.height / 2, 0.) 271 | z_axis_shift_control_node_2.name = 'sz2' 272 | z_axis_shift_control_node_2.categoryBitMask = 2 273 | z_axis_node.addChildNode(z_axis_shift_control_node_2) 274 | 275 | axis_handle.addChildNode(z_axis_node) 276 | 277 | return axis_handle 278 | 279 | if __name__ == '__main__': 280 | main.MainViewController.run() 281 | -------------------------------------------------------------------------------- /sceneKit/sceneKitEnv.py: -------------------------------------------------------------------------------- 1 | '''environment for sceneKit''' 2 | 3 | from objc_util import * 4 | from collections import namedtuple, Iterable 5 | from ui import parse_color, Image 6 | import ui 7 | from types import MethodType 8 | from enum import Enum, Flag 9 | import weakref 10 | 11 | import sceneKit 12 | 13 | load_framework('SceneKit') 14 | load_framework('SpriteKit') 15 | 16 | NSValue = ObjCClass('NSValue') 17 | NSError = ObjCClass('NSError') 18 | UIImage = ObjCClass('UIImage') 19 | UIFont = ObjCClass('UIFont') 20 | 21 | 22 | CAMediaTimingFunction, CAValueFunction, CAAnimation, CABasicAnimation, CAKeyframeAnimation, CAAnimationGroup = map(ObjCClass, ['CAMediaTimingFunction', 'CAValueFunction', 'CAAnimation', 'CABasicAnimation', 'CAKeyframeAnimation', 'CAAnimationGroup']) 23 | 24 | SCNView, SCNScene, SCNBox, SCNNode, SCNSceneSource, SCNMaterial, SCNCamera, SCNLight, \ 25 | SCNGeometry, SCNFloor, SCNPlane, SCNBox, SCNCapsule, SCNCone, \ 26 | SCNCylinder, SCNPyramid, SCNSphere, SCNTorus, SCNTube, SCNMaterialProperty, SCNConstraint, \ 27 | SCNBillboardConstraint, SCNLookAtConstraint, SCNDistanceConstraint, SCNAvoidOccluderConstraint, \ 28 | SCNAccelerationConstraint, SCNSliderConstraint, SCNReplicatorConstraint, SCNIKConstraint, \ 29 | SCNAction, SCNTransaction, SCNAnimation, SCNAnimationPlayer, SCNAnimationEvent, \ 30 | SCNTimingFunction, SKTransition, SCNMorpher, SCNReferenceNode, SCNGeometrySource, SCNGeometryElement, \ 31 | SCNText, SCNShape, SCNLevelOfDetail, SCNGeometryTessellator, SCNSkinner, SCNPhysicsShape, \ 32 | SCNPhysicsBody, SCNPhysicsContact, SCNPhysicsWorld, SCNPhysicsField, SCNPhysicsBehavior, SCNPhysicsHingeJoint, \ 33 | SCNPhysicsSliderJoint, SCNPhysicsBallSocketJoint, SCNPhysicsConeTwistJoint, SCNPhysicsVehicle, SCNPhysicsVehicleWheel, \ 34 | SCNParticleSystem, SCNParticlePropertyController, SCNAnimationPlayer, SCNAudioSource, SCNAudioPlayer, SCNSceneSource \ 35 | = map(ObjCClass, ['SCNView', 'SCNScene', 'SCNBox', 'SCNNode', 'SCNSceneSource', 'SCNMaterial', 'SCNCamera', 'SCNLight', 'SCNGeometry', 'SCNFloor', 'SCNPlane', 'SCNBox', 'SCNCapsule', 'SCNCone', 'SCNCylinder', 'SCNPyramid', 'SCNSphere', 'SCNTorus', 'SCNTube', 'SCNMaterialProperty', 'SCNConstraint', 'SCNBillboardConstraint', 'SCNLookAtConstraint', 'SCNDistanceConstraint', 'SCNAvoidOccluderConstraint', 'SCNAccelerationConstraint', 'SCNSliderConstraint', 'SCNReplicatorConstraint', 'SCNIKConstraint', 'SCNAction', 'SCNTransaction', 'SCNAnimation', 'SCNAnimationPlayer', 'SCNAnimationEvent', 'SCNTimingFunction', 'SKTransition', 'SCNMorpher', 'SCNReferenceNode', 'SCNGeometrySource', 'SCNGeometryElement', 'SCNText', 'SCNShape', 'SCNLevelOfDetail', 'SCNGeometryTessellator', 'SCNSkinner', 'SCNPhysicsShape', 'SCNPhysicsBody', 'SCNPhysicsContact', 'SCNPhysicsWorld', 'SCNPhysicsField', 'SCNPhysicsBehavior', 'SCNPhysicsHingeJoint', 'SCNPhysicsSliderJoint', 'SCNPhysicsBallSocketJoint', 'SCNPhysicsConeTwistJoint', 'SCNPhysicsVehicle', 'SCNPhysicsVehicleWheel', 'SCNParticleSystem', 'SCNParticlePropertyController', 'SCNAnimationPlayer', 'SCNAudioSource', 'SCNAudioPlayer', 'SCNSceneSource']) 36 | 37 | _hitTestOptions = [] 38 | for anOption in ['SCNHitTestBackFaceCullingKey', 'SCNHitTestBoundingBoxOnlyKey', 'SCNHitTestOptionCategoryBitMask', \ 39 | 'SCNHitTestClipToZRangeKey', 'SCNHitTestIgnoreChildNodesKey', 'SCNHitTestIgnoreHiddenNodesKey', \ 40 | 'SCNHitTestRootNodeKey', 'SCNHitTestOptionSearchMode']: 41 | _hitTestOptions.append(str(ObjCInstance(c_void_p.in_dll(c, anOption)))) 42 | _hitTestOptions = tuple(_hitTestOptions) 43 | 44 | SCNHitTestBackFaceCullingKey, SCNHitTestBoundingBoxOnlyKey, SCNHitTestOptionCategoryBitMask, \ 45 | SCNHitTestClipToZRangeKey, SCNHitTestIgnoreChildNodesKey, SCNHitTestIgnoreHiddenNodesKey, \ 46 | SCNHitTestRootNodeKey, SCNHitTestOptionSearchMode = _hitTestOptions 47 | HitTestBackFaceCullingKey, HitTestBoundingBoxOnlyKey, HitTestOptionCategoryBitMask, \ 48 | HitTestClipToZRangeKey, HitTestIgnoreChildNodesKey, HitTestIgnoreHiddenNodesKey, \ 49 | HitTestRootNodeKey, HitTestOptionSearchMode = _hitTestOptions 50 | 51 | class HitTestSearchMode(Enum): 52 | Closest = 0 53 | All = 1 54 | Any = 2 55 | SCNHitTestSearchModeClosest = 0 56 | SCNHitTestSearchModeAll = 1 57 | SCNHitTestSearchModeAny =2 58 | 59 | def hitTestOptions(inDict): 60 | outDict = {} 61 | for k, v in inDict.items(): 62 | if k == HitTestBackFaceCullingKey: 63 | outDict[k] = bool(v) 64 | elif k == HitTestBoundingBoxOnlyKey: 65 | outDict[k] = bool(v) 66 | elif k == HitTestOptionCategoryBitMask: 67 | outDict[k] = int(v) 68 | elif k == HitTestClipToZRangeKey: 69 | outDict[k] = bool(v) 70 | elif k == HitTestIgnoreChildNodesKey: 71 | outDict[k] = bool(v) 72 | elif k == HitTestIgnoreHiddenNodesKey: 73 | outDict[k] = bool(v) 74 | elif k == HitTestRootNodeKey: 75 | outDict[k] = v.ID 76 | elif k == HitTestOptionSearchMode: 77 | outDict[k] = v.value 78 | return outDict 79 | 80 | Point = namedtuple('Point', 'x y') 81 | Vector2 = namedtuple('Vector2', 'x y') 82 | Vector3 = namedtuple('Vector3', 'x y z') 83 | class SCNVector3(Structure): 84 | _fields_ = [("x", c_float), ("y", c_float), ("z", c_float)] 85 | 86 | Vector4 = namedtuple('Vector4', 'x y z w') 87 | Quaternion = namedtuple('Quaternion', 'x y z w') 88 | class SCNVector4(Structure): 89 | _fields_ = [("x", c_float), ("y", c_float), ("z", c_float), ("w", c_float)] 90 | 91 | Matrix4 = namedtuple('Matrix4', 'm11 m12 m13 m14 m21 m22 m23 m24 m31 m32 m33 m34 m41 m42 m43 m44') 92 | Matrix4Identity = Matrix4(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0) 93 | 94 | class SCNMatrix4(Structure): 95 | _fields_ = [('m11', c_float), ('m12', c_float), ('m13', c_float), ('m14', c_float), ('m21', c_float), ('m22', c_float), ('m23', c_float), ('m24', c_float), ('m31', c_float), ('m32', c_float), ('m33', c_float), ('m34', c_float), ('m41', c_float), ('m42', c_float), ('m43', c_float), ('m44', c_float)] 96 | 97 | 98 | RGBA = namedtuple('RGBA', 'red green blue alpha') 99 | 100 | Size = namedtuple('Size', 'width height') 101 | 102 | def getCStructValuesAsList(aCStruct): 103 | fieldList = [aField[0] for aField in aCStruct._fields_] 104 | return [getattr(aCStruct, aField) for aField in fieldList] 105 | 106 | def vector2Make(iter): 107 | return Vector2._make(iter) 108 | 109 | def vector3Make(iter): 110 | return Vector3._make(iter) 111 | 112 | def quaternionMake(iter): 113 | return Quaternion._make(iter) 114 | 115 | def vector4Make(iter): 116 | return Vector4._make(iter) 117 | 118 | def matrix4Make(iter): 119 | return Matrix4._make(iter) 120 | 121 | def RGBAMake(color): 122 | r, g, b, a = parse_color(color) 123 | return RGBA(r, g, b, a) 124 | 125 | class BoundingVolume: 126 | def setBoundingBox(self, minMax): 127 | (minV, maxV) = minMax 128 | bbox_min, bbox_max = SCNVector3(), SCNVector3() 129 | bbox_min.x, bbox_min.y, bbox_min.z = minV[0], minV[1], minV[2] 130 | bbox_max.x, bbox_max.y, bbox_max.z = maxV[0], maxV[1], maxV[2] 131 | self.ID.setBoundingBoxMin_max_(byref(bbox_min), byref(bbox_max), restype=None, argtypes=[POINTER(SCNVector3), POINTER(SCNVector3)]) 132 | def getBoundingBox(self): 133 | bbox_min, bbox_max = SCNVector3(), SCNVector3() 134 | retFlag = self.ID.getBoundingBoxMin_max_(byref(bbox_min), byref(bbox_max), restype=c_bool, argtypes=[POINTER(SCNVector3), POINTER(SCNVector3)]) 135 | if retFlag: 136 | return (vector3Make(getCStructValuesAsList(bbox_min)), vector3Make(getCStructValuesAsList(bbox_max))) 137 | else: 138 | return None 139 | def getBoundingBoxMin(self): 140 | return self.getBoundingBox(self) 141 | boundingBox = property(getBoundingBox, setBoundingBox) 142 | 143 | def getBoundingSphere(self): 144 | center = SCNVector3() 145 | radius = c_double(0.0) 146 | retFlag = self.ID.getBoundingSphereCenter_radius_(byref(center), byref(radius), restype=c_bool, argtypes=[POINTER(SCNVector3), POINTER(c_double)]) 147 | if retFlag: 148 | return (vector3Make(getCStructValuesAsList(center)), float(radius.value)) 149 | else: 150 | return None 151 | boundingSphere = property(getBoundingSphere, None) 152 | 153 | class _NoneClass: 154 | ID = None 155 | nil = _NoneClass() 156 | null = nil 157 | Nil = nil 158 | Null = nil 159 | 160 | _CInstCache = dict() 161 | 162 | def printCache(): 163 | print('_CInstCache') 164 | for anItem in list(_CInstCache.items()): 165 | print('-----', '\n', anItem) 166 | print('&&&&&&&&&END') 167 | 168 | def cacheSize(): 169 | return len(_CInstCache) 170 | 171 | def clearCache(): 172 | _CInstCache.clear() 173 | 174 | class CInst: 175 | def setID(self, aCInst): 176 | self._ID = aCInst 177 | _CInstCache[aCInst] = self 178 | def getID(self): 179 | return self._ID 180 | ID = property(getID, setID) 181 | 182 | @classmethod 183 | def outof(cls, fromC): 184 | if fromC is None: 185 | return nil 186 | elif fromC in _CInstCache: 187 | return _CInstCache[fromC] 188 | else: 189 | try: 190 | return cls.subclassOutof(ID=fromC) 191 | except AttributeError: 192 | return cls(ID=fromC) 193 | 194 | def __str__(self): 195 | return 'CInst instance with ' + str(self.ID) 196 | 197 | 198 | class CInst_NoCache: 199 | def setID(self, aCInst): 200 | self._ID = aCInst 201 | def getID(self): 202 | return self._ID 203 | ID = property(getID, setID) 204 | 205 | @classmethod 206 | def outof(cls, fromC): 207 | if fromC is None: 208 | return nil 209 | else: 210 | try: 211 | return cls.subclassOutof(ID=fromC) 212 | except AttributeError: 213 | return cls(ID=fromC) 214 | 215 | def __str__(self): 216 | return 'CInst_NoCache instance with ' + str(self.ID) 217 | 218 | 219 | def contentFromPy(aPContent): 220 | def isImage(aPContent): 221 | return isinstance(aPContent, Image) 222 | def isImageArray(aPContent): 223 | if isinstance(aPContent, Iterable): 224 | for aPContentElement in aPContent: 225 | if not isImage(aPContentElement): return False 226 | return True 227 | return False 228 | def isNumber(aPContent): 229 | return isinstance(aPContent, float) or isinstance(aPContent, int) 230 | def isURL(aPContent): 231 | if isinstance(aPContent, str): 232 | if ' ' in aPContent: return False 233 | if len(aPContent) > 8 or ':' in aPContent or '/' in aPContent or '.' in aPContent: return True 234 | return False 235 | def isColor(aPContent): 236 | try: 237 | r, g, b, a = parse_color(aPContent) 238 | return True 239 | except: 240 | return False 241 | 242 | if isImage(aPContent): 243 | aPng = aPContent.to_png() 244 | return ObjCClass('UIImage').imageWithData_(aPng) 245 | elif isImageArray(aPContent): 246 | imageList = [] 247 | for anImage in aPContent: 248 | aPng = anImage.to_png() 249 | imageList.append(ObjCClass('UIImage').imageWithData_(aPng)) 250 | return imageList 251 | elif isNumber(aPContent): 252 | return ns(aPContent) 253 | elif isURL(aPContent): 254 | return nsurl(aPContent) 255 | elif isColor(aPContent): 256 | r, g, b, a = parse_color(aPContent) #parse_color accepts anything, might result in random colors 257 | return UIColor.color(red=r, green=g, blue=b, alpha=a) 258 | else: 259 | return aPContent 260 | 261 | def contentFromC(aCContent): 262 | if aCContent is None: return None 263 | elif aCContent.isKindOfClass(UIColor): 264 | return RGBA(aCContent.red(), aCContent.green(), aCContent.blue(), aCContent.alpha()) 265 | elif aCContent.isKindOfClass(ObjCClass('UIImage')): 266 | c.UIImagePNGRepresentation.argtypes = [c_void_p] 267 | c.UIImagePNGRepresentation.restype = c_void_p 268 | data = ObjCInstance(c.UIImagePNGRepresentation(aCContent.ptr)) 269 | return Image.from_data(nsdata_to_bytes(data),2.0) 270 | elif aCContent.isKindOfClass(NSArray): 271 | if aCContent.objectAtIndex(0).isKindOfClass(ObjCClass('UIImage')): 272 | imageArray = [] 273 | c.UIImagePNGRepresentation.argtypes = [c_void_p] 274 | c.UIImagePNGRepresentation.restype = c_void_p 275 | for i in range(aCContent.count()): 276 | aCImg = aCContent.objectAtIndex(i) 277 | if aCImg.isKindOfClass(ObjCClass('UIImage')): 278 | data = ObjCInstance(c.UIImagePNGRepresentation(aCImg.ptr)) 279 | imageArray.append(Image.from_data(nsdata_to_bytes(data),2.0)) 280 | return imageArray 281 | elif aCContent.isKindOfClass(NSURL): return str(aCContent.absoluteString()) 282 | else: pass 283 | return aCContent 284 | -------------------------------------------------------------------------------- /sceneKit/sceneKitMaterial.py: -------------------------------------------------------------------------------- 1 | '''material modul, to be included in sceneKit''' 2 | 3 | from ui import parse_color, Image 4 | from collections import OrderedDict, Iterable 5 | 6 | import sceneKit 7 | from .sceneKitEnv import * 8 | from .sceneKitAnimation import * 9 | 10 | _lightingModels = [] 11 | for aModel in ['SCNLightingModelBlinn', 'SCNLightingModelConstant', 'SCNLightingModelLambert', 'SCNLightingModelPhong', 'SCNLightingModelPhysicallyBased']: 12 | _lightingModels.append(str(ObjCInstance(c_void_p.in_dll(c, aModel)))) 13 | 14 | SCNLightingModelBlinn, SCNLightingModelConstant, SCNLightingModelLambert, SCNLightingModelPhong, SCNLightingModelPhysicallyBased = _lightingModels 15 | LightingModelBlinn, LightingModelConstant, LightingModelLambert, LightingModelPhong, \ 16 | LightingModelPhysicallyBased = _lightingModels 17 | 18 | class WrapMode(Enum): 19 | Clamp = 1 20 | Repeat = 2 21 | ClampToBorder = 3 22 | Mirror = 4 23 | SCNWrapModeClamp = 1 24 | SCNWrapModeRepeat = 2 25 | SCNWrapModeClampToBorder = 3 26 | SCNWrapModeMirror = 4 27 | 28 | Clamp = WrapMode.Clamp 29 | Repeat = WrapMode.Repeat 30 | ClampToBorder = WrapMode.ClampToBorder 31 | Mirror = WrapMode.Mirror 32 | 33 | class FilterMode(Enum): 34 | ModeNone = 0 35 | Nearest = 1 36 | Linear = 2 37 | SCNFilterModeNone = 0 38 | SCNFilterModeNearest = 1 39 | SCNFilterModeLinear = 2 40 | 41 | class ColorMask(Flag): 42 | MaskNone = 0 43 | Alpha = (0x1 << 0) 44 | Blue = (0x1 << 1) 45 | Green = (0x1 << 2) 46 | Red = (0x1 << 3) 47 | All = Alpha | Blue | Green | Red 48 | SCNColorMaskNone = 0 49 | SCNColorMaskAlpha = (0x1 << 0) 50 | SCNColorMaskBlue = (0x1 << 1) 51 | SCNColorMaskGreen = (0x1 << 2) 52 | SCNColorMaskRed = (0x1 << 3) 53 | SCNColorMaskAll = Alpha | Blue | Green | Red 54 | 55 | class TransparencyMode(Enum): 56 | AOne = 0 57 | Default = 0 58 | RGBZero = 1 59 | SingleLayer = 2 60 | DualLayer = 3 61 | SCNTransparencyModeAOne = 0 62 | SCNTransparencyModeDefault = 0 63 | SCNTransparencyModeRGBZero = 1 64 | SCNTransparencyModeSingleLayer = 2 65 | SCNTransparencyModeDualLayer = 3 66 | 67 | class BlendMode(Enum): 68 | Alpha = 0 69 | Add = 1 70 | Subtract = 2 71 | Multiply = 3 72 | Screen = 4 73 | Replace = 5 74 | Max = 6 75 | SCNBlendModeAlpha = 0 76 | SCNBlendModeAdd = 1 77 | SCNBlendModeSubtract = 2 78 | SCNBlendModeMultiply = 3 79 | SCNBlendModeScreen = 4 80 | SCNBlendModeReplace = 5 81 | SCNBlendModeMax = 6 82 | 83 | class CullMode(Enum): 84 | Back = 0 85 | Front = 1 86 | SCNCullModeBack = 0 87 | SCNCullModeFront = 1 88 | 89 | CullBack = CullMode.Back 90 | CullFront = CullMode.Front 91 | 92 | class FillMode(Enum): 93 | Fill = 0 94 | Lines = 1 95 | SCNFillModeFill = 0 96 | SCNFillModeLines = 1 97 | 98 | class Material(Animatable, CInst): 99 | def __init__(self, mdlMaterial=None, ID=None): 100 | if ID is not None: 101 | self.ID = ID 102 | elif mdlMaterial is not None: 103 | self.ID = SCNMaterial.materialWithMDLMaterial_(mdlMaterial) 104 | if self.ID is None: 105 | raise RuntimeError('materialWithMDLMaterial failed. Wrong MDL asset?') 106 | else: 107 | self.ID = SCNMaterial.material() 108 | 109 | @classmethod 110 | def material(cls): 111 | return cls() 112 | 113 | @classmethod 114 | def materialWithMDLMaterial(cls, mdlMaterial=None): 115 | return cls(mdlMaterial=mdlMaterial) 116 | 117 | def setName(self, aString): 118 | self.ID.setName_(aString) 119 | def getName(self): 120 | return str(self.ID.name()) 121 | name = property(getName, setName) 122 | 123 | def setContents(self, aContent): 124 | self.diffuse.contents = aContent 125 | def getContents(self): 126 | return self.diffuse.contents 127 | contents = property(getContents, setContents) 128 | 129 | def setLightingModelName(self, aName): 130 | if aName[:3] != 'SCN': aName = 'SCN'+aName 131 | self.ID.setLightingModelName_(aName) 132 | def getLightingModelName(self): 133 | return str(self.ID.lightingModelName())[3:] 134 | lightingModelName = property(getLightingModelName, setLightingModelName) 135 | 136 | def getDiffuse(self): 137 | return sceneKit.MaterialProperty.outof(self.ID.diffuse()) 138 | diffuse = property(getDiffuse, None) 139 | 140 | def getMetalness(self): 141 | return sceneKit.MaterialProperty.outof(self.ID.metalness()) 142 | metalness = property(getMetalness, None) 143 | 144 | def getRoughness(self): 145 | return sceneKit.MaterialProperty.outof(self.ID.roughness()) 146 | roughness = property(getRoughness, None) 147 | 148 | def getNormal(self): 149 | return sceneKit.MaterialProperty.outof(self.ID.normal()) 150 | normal = property(getNormal, None) 151 | 152 | def getDisplacement(self): 153 | return sceneKit.MaterialProperty.outof(self.ID.displacement()) 154 | displacement = property(getDisplacement, None) 155 | 156 | def getEmission(self): 157 | return sceneKit.MaterialProperty.outof(self.ID.emission()) 158 | emission = property(getEmission, None) 159 | 160 | def getSelfIllumination(self): 161 | return sceneKit.MaterialProperty.outof(self.ID.selfIllumination()) 162 | selfIllumination = property(getSelfIllumination, None) 163 | 164 | def getAmbientOcclusion(self): 165 | return sceneKit.MaterialProperty.outof(self.ID.ambientOcclusion()) 166 | ambientOcclusion = property(getAmbientOcclusion, None) 167 | 168 | def getDiffuse(self): 169 | return sceneKit.MaterialProperty.outof(self.ID.diffuse()) 170 | diffuse = property(getDiffuse, None) 171 | 172 | def getAmbient(self): 173 | return sceneKit.MaterialProperty.outof(self.ID.ambient()) 174 | ambient = property(getAmbient, None) 175 | 176 | def getSpecular(self): 177 | return sceneKit.MaterialProperty.outof(self.ID.specular()) 178 | specular = property(getSpecular, None) 179 | 180 | def getReflective(self): 181 | return scenekit.MaterialProperty.outof(self.ID.reflective()) 182 | reflective = property(getReflective, None) 183 | 184 | def getMultiply(self): 185 | return sceneKit.MaterialProperty.outof(self.ID.multiply()) 186 | multiply = property(getMultiply, None) 187 | 188 | def getTransparent(self): 189 | return sceneKit.MaterialProperty.outof(self.ID.transparent()) 190 | transparent = property(getTransparent, None) 191 | 192 | def setShininess(self, aShineniness): 193 | self.ID.setShininess_(aShineniness) 194 | def getShininess(self): 195 | return self.ID.shininess() 196 | shininess = property(getShininess, setShininess) 197 | 198 | def setFresnelExponent(self, anExponent): 199 | self.ID.setFresnelExponent_(anExponent) 200 | def getFresnelExponent(self): 201 | return self.ID.fresnelExponent() 202 | fresnelExponent = property(getFresnelExponent, setFresnelExponent) 203 | 204 | def setLocksAmbientWithDiffuse(self, aBool): 205 | self.ID.setLocksAmbientWithDiffuse_(aBool) 206 | def getLocksAmbientWithDiffuse(self): 207 | return self.ID.locksAmbientWithDiffuse() 208 | locksAmbientWithDiffuse = property(getLocksAmbientWithDiffuse, setLocksAmbientWithDiffuse) 209 | 210 | def setTransparency(self, aTransparency): 211 | self.ID.setTransparency_(aTransparency) 212 | def getTransparency(self): 213 | return self.ID.transparency() 214 | transparency = property(getTransparency, setTransparency) 215 | 216 | def setTransparencyMode(self, aTransparencyMode): 217 | self.ID.setTransparencyMode_(aTransparencyMode.value) 218 | def getTransparencyMode(self): 219 | return TransparencyMode(self.ID.transparencyMode()) 220 | transparencyMode = property(getTransparencyMode, setTransparencyMode) 221 | 222 | def setBlendMode(self, aBlendMode): 223 | self.ID.setBlendMode_(aBlendMode.value) 224 | def getBlendMode(self): 225 | return BlendMode(self.ID.blendMode()) 226 | blendMode = property(getBlendMode, setBlendMode) 227 | 228 | def setLitPerPixel(self, aBool): 229 | self.ID.setLitPerPixel_(aBool) 230 | def getLitPerPixel(self): 231 | return self.ID.litPerPixel() 232 | litPerPixel = property(getLitPerPixel, setLitPerPixel) 233 | 234 | def setDoubleSided(self, aBool): 235 | self.ID.setDoubleSided_(aBool) 236 | def isDoubleSided(self): 237 | return self.ID.doubleSided() 238 | doubleSided = property(isDoubleSided, setDoubleSided) 239 | 240 | def setCullMode(self, aCullMode): 241 | self.ID.setCullMode_(aCullMode.value) 242 | def getCullMode(self): 243 | return CullMode(self.ID.cullMode()) 244 | cullMode = property(getCullMode, setCullMode) 245 | 246 | def setFillMode(self, aFillMode): 247 | self.ID.setFillMode_(aFillMode.value) 248 | def getFillMode(self): 249 | return FillMode(self.ID.fillMode()) 250 | fillMode = property(getFillMode, setFillMode) 251 | 252 | def setWritesToDepthBuffer(self, aBool): 253 | self.ID.setWritesToDepthBuffer_(aBool) 254 | def getWritesToDepthBuffer(self): 255 | return self.ID.writesToDepthBuffer() 256 | writesToDepthBuffer = property(getWritesToDepthBuffer, setWritesToDepthBuffer) 257 | 258 | def setReadsFromDepthBuffer(self, aBool): 259 | self.ID.setReadsFromDepthBuffer_(aBool) 260 | def getReadsFromDepthBuffer(self): 261 | return self.ID.readsFromDepthBuffer() 262 | readsFromDepthBuffer = property(getReadsFromDepthBuffer, setReadsFromDepthBuffer) 263 | 264 | def setColorBufferWriteMask(self, aColorBufferWriteMask): 265 | self.ID.setColorBufferWriteMask_(_colorMasks.index(aColorBufferWriteMask)) 266 | def getColorBufferWriteMask(self): 267 | return _colorMasks[self.ID.colorBufferWriteMask()] 268 | colorBufferWriteMask = property(getColorBufferWriteMask, setColorBufferWriteMask) 269 | 270 | class MaterialProperty(Animatable, CInst): 271 | def __init__(self, contents=None, ID=None): 272 | if contents is not None: 273 | self.ID = SCNMaterialProperty.materialPropertyWithContents_(contents) 274 | elif ID is not None: 275 | self.ID = ID 276 | else: 277 | self.ID = None 278 | 279 | @classmethod 280 | def materialPropertyWithContents(cls, contents=None): 281 | return cls(contents=contents) 282 | 283 | def setContents(self, aContent): 284 | self.ID.setContents_(contentFromPy(aContent)) 285 | def getContents(self): 286 | contents = self.ID.contents() 287 | return contentFromC(contents) 288 | contents = property(getContents, setContents) 289 | 290 | def setIntensity(self, anIntensity): 291 | self.ID.setIntensity_(anIntensity) 292 | def getIntensity(self): 293 | return self.ID.intensity() 294 | intensity = property(getIntensity, setIntensity) 295 | 296 | def setContentsTransform(self, aTransform): 297 | self.ID.setContentsTransform_(matrix4Make(aTransform)) 298 | def getContentsTransform(self): 299 | trans = self.ID.contentsTransform() 300 | return matrix4Make(getCStructValuesAsList(trans)) 301 | contentsTransform = property(getContentsTransform, setContentsTransform) 302 | 303 | def setWrapS(self, aWrapMode): 304 | self.ID.setWrapS_(aWrapMode.value) 305 | def getWrapS(self): 306 | return WrapMode(self.ID.wrapS()) 307 | wrapS = property(getWrapS, setWrapS) 308 | 309 | def setWrapT(self, aWrapMode): 310 | self.ID.setWrapT_(aWrapMode.value) 311 | def getWrapT(self): 312 | return WrapMode(self.ID.wrapT()) 313 | wrapT = property(getWrapT, setWrapT) 314 | 315 | def setMinificationFilter(self, aFilter): 316 | self.ID.setMinificationFilter_(aFilter.value) 317 | def getMinificationFilter(self): 318 | return FilterMode(self.ID.minificationFilter()) 319 | minificationFilter = property(getMinificationFilter, setMinificationFilter) 320 | 321 | def setMagnificationFilter(self, aFilter): 322 | self.ID.setMagnificationFilter_(aFilter.value) 323 | def getMagnificationFilter(self): 324 | return FilterMode(self.ID.magnificationFilter()) 325 | magnificationFilter = property(getMagnificationFilter, setMagnificationFilter) 326 | 327 | def setMipFilter(self, aFilter): 328 | self.ID.setMipFilter_(aFilter.value) 329 | def getMipFilter(self): 330 | return FilterMode(self.ID.mipFilter()) 331 | mipFilter = property(getMipFilter, setMipFilter) 332 | 333 | def setMaxAnisotropy(self, anAnisotropy): 334 | self.ID.setMaxAnisotropy_(anAnisotropy) 335 | def getMaxAnisotropy(self): 336 | return self.ID.maxAnisotropy() 337 | maxAnisotropy = property(getMaxAnisotropy, setMaxAnisotropy) 338 | 339 | def setMappingChannel(self, aChannel): 340 | self.ID.setMappingChannel_(aChannel) 341 | def getMappingChannel(self): 342 | return self.ID.mappingChannel() 343 | mappingChannel = property(getMappingChannel, setMappingChannel) 344 | 345 | def setTextureComponents(self, flags): 346 | if not isinstance(flags, Iterable): 347 | flags = [flags] 348 | mask = 0 349 | for aFlag in flags: 350 | mask = mask | aFlag.value 351 | self.ID.setTextureComponents_(mask) 352 | def getTextureComponents(self): 353 | mask = self.ID.textureComponents() 354 | flags = [] 355 | for aFlag in ColorMask: 356 | if (mask & aFlag.value) != 0: flags.append(aFlag) 357 | if len(flags) == 5: flags = [ColorMask.All] 358 | elif len(flags) == 0: flags = [ColorMask.MaskNone] 359 | return tuple(flags) 360 | textureComponents = property(getTextureComponents, setTextureComponents) 361 | -------------------------------------------------------------------------------- /sceneKit/sceneKitGeometrySource.py: -------------------------------------------------------------------------------- 1 | ''' 2 | geometrySource module for sceneKit 3 | includes class GeometrySource and class GeometryElement 4 | ''' 5 | 6 | from ctypes import * 7 | from objc_util import * 8 | import sys 9 | 10 | import sceneKit 11 | from .sceneKitEnv import * 12 | 13 | class GeometryPrimitiveType(Enum): 14 | Triangles = 0 15 | TriangleStrip = 1 16 | Line = 2 17 | Point = 3 18 | Polygon = 4 19 | SCNGeometryPrimitiveTypeTriangles = 0 20 | SCNGeometryPrimitiveTypeTriangleStrip = 1 21 | SCNGeometryPrimitiveTypeLine = 2 22 | SCNGeometryPrimitiveTypePoint = 3 23 | SCNGeometryPrimitiveTypePolygon = 4 24 | 25 | _geometrySourceSemantics = [] 26 | for aSemantics in ['SCNGeometrySourceSemanticVertex', 'SCNGeometrySourceSemanticNormal', 'SCNGeometrySourceSemanticTexcoord', 'SCNGeometrySourceSemanticColor', 'SCNGeometrySourceSemanticTangent', 'SCNGeometrySourceSemanticEdgeCrease', 'SCNGeometrySourceSemanticVertexCrease', 'SCNGeometrySourceSemanticBoneIndices', 'SCNGeometrySourceSemanticBoneWeights']: 27 | _geometrySourceSemantics.append(str(ObjCInstance(c_void_p.in_dll(c, aSemantics)))) 28 | _geometrySourceSemantics = tuple(_geometrySourceSemantics) 29 | 30 | SCNGeometrySourceSemanticVertex, SCNGeometrySourceSemanticNormal, SCNGeometrySourceSemanticTexcoord, SCNGeometrySourceSemanticColor, SCNGeometrySourceSemanticTangent, SCNGeometrySourceSemanticEdgeCrease, SCNGeometrySourceSemanticVertexCrease, SCNGeometrySourceSemanticBoneIndices, SCNGeometrySourceSemanticBoneWeights = _geometrySourceSemantics 31 | GeometrySourceSemanticVertex, GeometrySourceSemanticNormal, GeometrySourceSemanticTexcoord, GeometrySourceSemanticColor, GeometrySourceSemanticTangent, GeometrySourceSemanticEdgeCrease, GeometrySourceSemanticVertexCrease, GeometrySourceSemanticBoneIndices, GeometrySourceSemanticBoneWeights = _geometrySourceSemantics 32 | 33 | class GeometrySource(CInst): 34 | def __init__(self, count=None, data=None, semantic=None, vectorCount=None, floatComponents=None, componentsPerVector=None, bytesPerComponent=None, dataOffset=None, dataStride=None, geometrySourceWithVertices=None, geometrySourceWithNormals=None, geometrySourceWithTextureCoordinates=None, geometrySourceWithData=None, geometrySourceWithCData=None, ID=None): 35 | if geometrySourceWithVertices is not None: 36 | try: 37 | iterator = iter(geometrySourceWithVertices) 38 | except TypeError: 39 | geometrySourceWithVertices = [geometrySourceWithVertices] 40 | SCNVector3ArrayN = SCNVector3 * len(geometrySourceWithVertices) 41 | verts = [SCNVector3(*aVertice) for aVertice in geometrySourceWithVertices] 42 | verts_array = SCNVector3ArrayN(*verts) 43 | self.ID = SCNGeometrySource.geometrySourceWithVertices_count_(byref(verts_array), len(verts), restype=c_void_p, argtypes=[POINTER(SCNVector3ArrayN), c_long]) 44 | elif geometrySourceWithNormals is not None: 45 | try: 46 | iterator = iter(geometrySourceWithNormals) 47 | except TypeError: 48 | geometrySourceWithNormals = [geometrySourceWithNormals] 49 | SCNVector3ArrayN = SCNVector3 * len(geometrySourceWithNormals) 50 | norms = [SCNVector3(*aNorm) for aNorm in geometrySourceWithNormals] 51 | norms_array = SCNVector3ArrayN(*norms) 52 | self.ID = SCNGeometrySource.geometrySourceWithNormals_count_(byref(norms_array), len(norms), restype=c_void_p, argtypes=[POINTER(SCNVector3ArrayN), c_long]) 53 | elif geometrySourceWithTextureCoordinates is not None: 54 | try: 55 | iterator = iter(geometrySourceWithTextureCoordinates) 56 | except TypeError: 57 | geometrySourceWithTextureCoordinates = [geometrySourceWithTextureCoordinates] 58 | CGPointArrayN = CGPoint * len(geometrySourceWithTextureCoordinates) 59 | coords = [CGPoint(*aCoord) for aCoord in geometrySourceWithTextureCoordinates] 60 | coords_array = CGPointArrayN(*coords) 61 | self.ID = SCNGeometrySource.geometrySourceWithTextureCoordinates_count_(byref(coords_array), len(coords), restype=c_void_p, argtypes=[POINTER(CGPointArrayN), c_long]) 62 | elif geometrySourceWithData is not None: 63 | dataC = ns(bytes(geometrySourceWithData)) 64 | self.ID = SCNGeometrySource.geometrySourceWithData_semantic_vectorCount_floatComponents_componentsPerVector_bytesPerComponent_dataOffset_dataStride_(dataC, semantic, vectorCount, floatComponents, componentsPerVector, bytesPerComponent, dataOffset, dataStride) 65 | elif ID is not None: 66 | self.ID = ID 67 | else: 68 | self.ID = None 69 | 70 | @classmethod 71 | def geometrySourceWithData(cls, data=None, semantic=None, vectorCount=None, floatComponents=None, componentsPerVector=None, bytesPerComponent=None, dataOffset=None, dataStride=None): 72 | return cls(geometrySourceWithData=data, semantic=semantic, vectorCount=vectorCount, floatComponents=floatComponents, componentsPerVector=componentsPerVector, bytesPerComponent=bytesPerComponent, dataOffset=dataOffset, dataStride=dataStride) 73 | 74 | @classmethod 75 | def geometrySourceWithVertices(cls, vertices=None, count=None): 76 | return cls(geometrySourceWithVertices=vertices, count=count) 77 | 78 | @classmethod 79 | def geometrySourceWithNormals(cls, normals=None, count=None): 80 | return cls(geometrySourceWithNormals=normals, count=count) 81 | 82 | @classmethod 83 | def geometrySourceWithTextureCoordinates(cls, textureCoordinates=None, count=None): 84 | return cls(geometrySourceWithTextureCoordinates=textureCoordinates, count=count) 85 | 86 | @classmethod 87 | def interleavedGeometrySourceWithVectors(cls, sourceList, semanticList): 88 | returnSources, sourceParams, vertexStructFields = [], [], [] 89 | vectorCount = len(sourceList[0]) 90 | dataOffset = 0 91 | for i in range(len(sourceList)): 92 | vectors = sourceList[i] 93 | floatComponents = isinstance(vectors[0][0], float) 94 | componentsPerVector = len(vectors[0]) 95 | bytesPerComponent = sizeof(c_float) if floatComponents else sizeof(c_int) 96 | sourceParams.append((floatComponents, componentsPerVector, bytesPerComponent, dataOffset)) 97 | for j in range(componentsPerVector): 98 | field = 'f'+str(i)+str(j) 99 | if floatComponents: 100 | vertexStructFields.append((field, c_float)) 101 | dataOffset += sizeof(c_float) 102 | else: 103 | vertexStructFields.append((field, c_int)) 104 | dataOffset += sizeof(c_int) 105 | dataStride = dataOffset 106 | class vertexStruct(Structure): 107 | _fields_ = vertexStructFields 108 | vertexStructArrayType = vertexStruct * vectorCount 109 | vertexStructArray = vertexStructArrayType() 110 | for i in range(vectorCount): 111 | element = [] 112 | for j in range(len(sourceList)): 113 | element += sourceList[j][i] 114 | vertexStructArray[i] = vertexStruct(*element) 115 | data = NSData.dataWithBytes_length_(byref(vertexStructArray), sizeof(vertexStructArray)) 116 | for i in range(len(sourceList)): 117 | returnSources.append( cls.outof( SCNGeometrySource.geometrySourceWithData_semantic_vectorCount_floatComponents_componentsPerVector_bytesPerComponent_dataOffset_dataStride_(data, semanticList[i], vectorCount, sourceParams[i][0], sourceParams[i][1], sourceParams[i][2], sourceParams[i][3], dataStride))) 118 | 119 | return tuple(returnSources) 120 | 121 | @classmethod 122 | def geometrySourceWithVectors(cls, source, semantic): 123 | return cls.interleavedGeometrySourceWithVectors([source], [semantic])[0] 124 | 125 | def getVectors(self): 126 | data = nsdata_to_bytes(self.ID.data()) 127 | dataRet = [] 128 | for i in range(self.vectorCount): 129 | components = [] 130 | for j in range(self.componentsPerVector): 131 | if self.floatComponents: 132 | dataC = create_string_buffer(data[(i*self.dataStride+self.dataOffset)+j*self.bytesPerComponent : (i*self.dataStride+self.dataOffset)+j*self.bytesPerComponent+self.bytesPerComponent], size=self.bytesPerComponent) 133 | components.append(c_float.from_buffer(dataC).value) 134 | else: 135 | components.append(int.from_bytes(data[(i*self.dataStride+self.dataOffset)+j*self.bytesPerComponent : (i*self.dataStride+self.dataOffset)+j*self.bytesPerComponent+self.bytesPerComponent], sys.byteorder)) 136 | if self.componentsPerVector == 2: 137 | dataRet.append(vector2Make(components)) 138 | elif self.componentsPerVector == 3: 139 | dataRet.append(vector3Make(components)) 140 | elif self.componentsPerVector == 4: 141 | dataRet.append(vector4Make(components)) 142 | else: 143 | dataRet.append(tuple(components)) 144 | return tuple(dataRet) 145 | vectors = property(getVectors, None) 146 | 147 | def getData(self): 148 | return nsdata_to_bytes(self.ID.data()) 149 | data = property(getData, None) 150 | 151 | def getSemantic(self): 152 | return self.ID.semantic() 153 | semantic = property(getSemantic, None) 154 | 155 | def getVectorCount(self): 156 | return self.ID.vectorCount() 157 | vectorCount = property(getVectorCount, None) 158 | 159 | def getFloatComponents(self): 160 | return self.ID.floatComponents() 161 | floatComponents = property(getFloatComponents, None) 162 | 163 | def getComponentsPerVector(self): 164 | return self.ID.componentsPerVector() 165 | componentsPerVector = property(getComponentsPerVector, None) 166 | 167 | def getBytesPerComponent(self): 168 | return self.ID.bytesPerComponent() 169 | bytesPerComponent = property(getBytesPerComponent, None) 170 | 171 | def getDataOffset(self): 172 | return self.ID.dataOffset() 173 | dataOffset = property(getDataOffset, None) 174 | 175 | def getDataStride(self): 176 | return self.ID.dataStride() 177 | dataStride = property(getDataStride, None) 178 | 179 | 180 | class GeometryElement(CInst): 181 | def __init__(self, geometryElementWithData=None, primitiveType=None, mdlSubMesh=None, ID=None): 182 | if geometryElementWithData is not None: 183 | if primitiveType == GeometryPrimitiveType.Triangles: 184 | count = len(geometryElementWithData) // 3 185 | elif primitiveType == GeometryPrimitiveType.TriangleStrip: 186 | count = len(geometryElementWithData) - 2 187 | elif primitiveType == GeometryPrimitiveType.Line: 188 | count = len(geometryElementWithData) // 2 189 | elif primitiveType == GeometryPrimitiveType.Point: 190 | count = len(geometryElementWithData) 191 | elif primitiveType == GeometryPrimitiveType.Polygon: 192 | count, remainder = 0, len(geometryElementWithData) 193 | for i in range(len(geometryElementWithData)): 194 | if remainder > 0: 195 | count += 1 196 | remainder -= (geometryElementWithData[i]+1) 197 | else: 198 | break 199 | else: count = 0 200 | bytesPerIndex = (max(geometryElementWithData).bit_length() // 8) + 1 201 | indexes_array = bytearray(len(geometryElementWithData) * bytesPerIndex) 202 | for i in range(len(geometryElementWithData)): 203 | indexes_array[i*bytesPerIndex : (i+1)*bytesPerIndex] = geometryElementWithData[i].to_bytes(bytesPerIndex, sys.byteorder) 204 | datIndexes = ns(bytes(indexes_array)) 205 | self.ID = SCNGeometryElement.geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes, primitiveType.value, count, bytesPerIndex) 206 | elif mdlSubMesh is not None: 207 | self.ID = SCNGeometryElement.geometryElementWithMDLSubmesh_(mdlSubMesh) 208 | if self.ID is None: 209 | raise RuntimeError('geometryElementWithMDLSubmesh failed. Wrong MDL asset?') 210 | elif ID is not None: 211 | self.ID = ID 212 | else: 213 | self.ID = None 214 | 215 | @classmethod 216 | def geometryElementWithData(cls, data=None, primitiveType=None, primitiveCount=None, bytesPerIndex=None): 217 | return cls(geometryElementWithData=data, primitiveType=primitiveType) 218 | 219 | @classmethod 220 | def geometryElementWithMDLSubmesh(cls, mdlSubMesh=None): 221 | return cls(mdlSubMesh=mdlSubMesh) 222 | 223 | def getData(self): 224 | data = nsdata_to_bytes(self.ID.data()) 225 | bytesPerIndex = self.bytesPerIndex 226 | dataRet = [] 227 | for i in range(0, len(data)//bytesPerIndex): 228 | dataRet.append(int.from_bytes(data[i*bytesPerIndex : (i+1)*bytesPerIndex], sys.byteorder)) 229 | return tuple(dataRet) 230 | data = property(getData, None) 231 | 232 | def getBytesPerIndex(self): 233 | return self.ID.bytesPerIndex() 234 | bytesPerIndex = property(getBytesPerIndex, None) 235 | 236 | def getPrimitiveType(self): 237 | return GeometryPrimitiveType(self.ID.primitiveType()) 238 | primitiveType = property(getPrimitiveType, None) 239 | 240 | def getPrimitiveCount(self): 241 | return self.ID.primitiveCount() 242 | primitiveCount = property(getPrimitiveCount, None) 243 | 244 | def setPrimitiveRange(self, aRange): 245 | anNSRange = NSRange(aRange[0], aRange[1]) 246 | self.ID.setPrimitiveRange_(anNSRange) 247 | def getPrimitiveRange(self): 248 | aRange = self.ID.primitiveRange() 249 | location, length = aRange.location, aRange.length 250 | if location > self.primitiveCount: 251 | location, length = 0, self.primitiveCount 252 | return (location, length) 253 | primitiveRange = property(getPrimitiveRange, setPrimitiveRange) 254 | 255 | def setPointSize(self, aSize): 256 | self.ID.setPointSize_(aSize) 257 | def getPointSize(self): 258 | return self.ID.pointSize() 259 | pointSize = property(getPointSize, setPointSize) 260 | 261 | def setMinimumPointScreenSpaceRadius(self, aRadius): 262 | self.ID.setMinimumPointScreenSpaceRadius_(aRadius) 263 | def getMinimumPointScreenSpaceRadius(self): 264 | return self.ID.minimumPointScreenSpaceRadius() 265 | minimumPointScreenSpaceRadius = property(getMinimumPointScreenSpaceRadius, setMinimumPointScreenSpaceRadius) 266 | 267 | def setMaximumPointScreenSpaceRadius(self, aRadius): 268 | self.ID.setMaximumPointScreenSpaceRadius_(aRadius) 269 | def getMaximumPointScreenSpaceRadius(self): 270 | return self.ID.maximumPointScreenSpaceRadius() 271 | maximumPointScreenSpaceRadius = property(getMaximumPointScreenSpaceRadius, setMaximumPointScreenSpaceRadius) 272 | -------------------------------------------------------------------------------- /sceneKit/sceneKitConstraints.py: -------------------------------------------------------------------------------- 1 | '''constraints modul, to be included in sceneKit''' 2 | 3 | from collections import OrderedDict, Iterable 4 | 5 | import sceneKit 6 | from .sceneKitEnv import * 7 | from .sceneKitNode import * 8 | 9 | class BillboardAxis(Flag): 10 | X = (0x1 << 0) 11 | Y = (0x1 << 1) 12 | Z = (0x1 << 2) 13 | All = X | Y | Z 14 | SCNBillboardAxisX = (0x1 << 0) 15 | SCNBillboardAxisY = (0x1 << 1) 16 | SCNBillboardAxisZ = (0x1 << 2) 17 | SCNBillboardAxisAll = X | Y | Z 18 | 19 | 20 | class Constraint(CInst): 21 | def __init__(self, ID=None): 22 | if ID is not None: 23 | self.ID = ID 24 | else: 25 | self.ID = None 26 | 27 | @classmethod 28 | def subclassOutof(cls, ID=None): 29 | desc = str(ID.description()) 30 | kind = desc[4:desc.index(':')] 31 | if kind == 'BillboardConstraint': 32 | return BillboardConstraint(ID=ID) 33 | elif kind == 'LookAtConstraint': 34 | return LookAtConstraint(ID=ID) 35 | elif kind == 'DistanceConstraint': 36 | return DistanceConstraint(ID=ID) 37 | elif kind == 'AvoidOccluderConstraint': 38 | return AvoidOccluderConstraint(ID=ID) 39 | elif kind == 'AccelerationConstraint': 40 | return AccelerationConstraint(ID=ID) 41 | elif kind == 'IKConstraint': 42 | return IKConstraint(ID=ID) 43 | elif kind == 'ReplicatorConstraint': 44 | return ReplicatorConstraint(ID=ID) 45 | else: 46 | return cls(ID=ID) 47 | 48 | def setInfluenceFactor(self, anInfluenceFactor): 49 | self.ID.setInfluenceFactor(anInfluenceFactor) 50 | def getInfluenceFactor(self): 51 | return self.ID.influenceFactor() 52 | influenceFactor = property(getInfluenceFactor, setInfluenceFactor) 53 | 54 | def setEnabled(self, aBool): 55 | self.ID.setEnabled(aBool) 56 | def getEnabled(self): 57 | return self.ID.enabled() 58 | enabled = property(getEnabled, setEnabled) 59 | 60 | def setIncremental(self, aBool): 61 | self.ID.setIncremental(aBool) 62 | def getIncremental(self): 63 | return self.ID.incremental() 64 | incremental = property(getIncremental, setIncremental) 65 | 66 | class _Target: 67 | def setTarget(self, aTargetNode): 68 | self.ID.setTarget(aTargetNode.ID) 69 | def getTarget(self): 70 | return sceneKit.Node.outof(self.ID.target()) 71 | target = property(getTarget, setTarget) 72 | 73 | class BillboardConstraint(Constraint): 74 | def __init__(self, ID=None): 75 | if ID is not None: 76 | self.ID = ID 77 | else: 78 | self.ID = SCNBillboardConstraint.billboardConstraint() 79 | 80 | @classmethod 81 | def billboardConstraint(cls): 82 | return cls() 83 | 84 | def setFreeAxes(self, axes): 85 | if not isinstance(axes, Iterable): 86 | axes = [axes] 87 | mask = 0 88 | for anAxis in axes: 89 | mask = mask | anAxis.value 90 | self.ID.setFreeAxes(mask) 91 | def getFreeAxes(self): 92 | mask = self.ID.freeAxes() 93 | axes = [] 94 | for anAxis in BillboardAxis: 95 | if (mask & anAxis.value) != 0: axes.append(anAxis) 96 | if len(axes) == 4: axes = [BillboardAxis.All] 97 | return tuple(axes) 98 | freeAxes = property(getFreeAxes, setFreeAxes) 99 | 100 | class LookAtConstraint(Constraint, _Target): 101 | def __init__(self, target=None, ID=None): 102 | if ID is not None: 103 | self.ID = ID 104 | else: 105 | self.ID = SCNLookAtConstraint.lookAtConstraintWithTarget_(target.ID) 106 | 107 | @classmethod 108 | def lookAtConstraintWithTarget(cls, target=None): 109 | return cls(target=target) 110 | 111 | def setGimbalLockEnabled(self, aBool): 112 | self.ID.setGimbalLockEnabled(aBool) 113 | def getGimbalLockEnabled(self): 114 | return self.ID.gimbalLockEnabled() 115 | gimbalLockEnabled = property(getGimbalLockEnabled, setGimbalLockEnabled) 116 | 117 | def setLocalFront(self, aLocalFront): 118 | self.ID.setLocalFront(vector3Make(aLocalFront)) 119 | def getLocalFront(self): 120 | front = self.ID.localFront() 121 | return Vector3(front.a, front.b, front.c) 122 | localFront = property(getLocalFront, setLocalFront) 123 | 124 | def setTargetOffset(self, aTargetOffset): 125 | self.ID.setTargetOffset(vector3Make(aTargetOffset)) 126 | def getTargetOffset(self): 127 | offset = self.ID.targetOffset() 128 | return Vector3(offset.a, offset.b, offset.c) 129 | targetOffset = property(getTargetOffset, setTargetOffset) 130 | 131 | def setWorldUp(self, aWorldUp): 132 | self.ID.setWorldUp(vector3Make(aWorldUp)) 133 | def getWorldUp(self): 134 | up = self.ID.worldUp() 135 | return Vector3(up.a, up.b, up.c) 136 | worldUp = property(getWorldUp, setWorldUp) 137 | 138 | class DistanceConstraint(Constraint, _Target): 139 | def __init__(self, target=None, ID=None): 140 | if ID is not None: 141 | self.ID = ID 142 | else: 143 | self.ID = SCNDistanceConstraint.distanceConstraintWithTarget_(target.ID) 144 | 145 | @classmethod 146 | def distanceConstraintWithTarget(cls, target=None): 147 | return cls(target=target) 148 | 149 | def setMaximumDistance(self, aMaximumDistance): 150 | self.ID.setMaximumDistance(aMaximumDistance) 151 | def getMaximumDistance(self): 152 | return self.ID.maximumDistance() 153 | maximumDistance = property(getMaximumDistance, setMaximumDistance) 154 | 155 | def setMinimumDistance(self, aMinimumDistance): 156 | self.ID.setMinimumDistance(aMinimumDistance) 157 | def getMinimumDistance(self): 158 | return self.ID.minimumDistance() 159 | minimumDistance = property(getMinimumDistance, setMinimumDistance) 160 | 161 | class AvoidOccluderConstraint(Constraint, _Target): 162 | def __init__(self, target=None, ID=None): 163 | if ID is not None: 164 | self.ID = ID 165 | else: 166 | self.ID = SCNAvoidOccluderConstraint.avoidOccluderConstraintWithTarget_(target.ID) 167 | 168 | @classmethod 169 | def avoidOccluderConstraintWithTarget(cls, target=None): 170 | return cls(target=target) 171 | 172 | def setBias(self, aBias): 173 | self.ID.setBias(aBias) 174 | def getBias(self): 175 | return self.ID.bias() 176 | bias = property(getBias, setBias) 177 | 178 | def setOccluderCategoryBitMask(self, aBitMask): 179 | self.ID.setOccluderCategoryBitMask(aBitMask) 180 | def getOccluderCategoryBitMask(self): 181 | return self.ID.occluderCategoryBitMask() 182 | occluderCategoryBitMask = property(getOccluderCategoryBitMask, setOccluderCategoryBitMask) 183 | 184 | def setDelegate(self, aDelegate): 185 | anAvoidOccluderConstraintDelegate = AvoidOccluderConstraintDelegate(aDelegate) 186 | self.ID.setDelegate_(anAvoidOccluderConstraintDelegate.ID) 187 | def getDelegate(self): 188 | anAvoidOccluderConstraintDelegate = sceneKit.AvoidOccluderConstraintDelegate.outof(self.ID.delegate()) 189 | return anAvoidOccluderConstraintDelegate.delegate if anAvoidOccluderConstraintDelegate is not None else None 190 | delegate = property(getDelegate, setDelegate) 191 | 192 | class AccelerationConstraint(Constraint): 193 | def __init__(self, ID=None): 194 | if ID is not None: 195 | self.ID = ID 196 | else: 197 | self.ID = SCNAccelerationConstraint.accelerationConstraint() 198 | 199 | @classmethod 200 | def accelerationConstraint(cls): 201 | return cls() 202 | 203 | def setDamping(self, aDamping): 204 | self.ID.setDamping(aDamping) 205 | def getDamping(self): 206 | return self.ID.damping() 207 | damping = property(getDamping, setDamping) 208 | 209 | def setDecelerationDistance(self, aDist): 210 | self.ID.setDecelerationDistance(aDist) 211 | def getDecelerationDistance(self): 212 | return self.ID.decelerationDistance() 213 | decelerationDistance = property(getDecelerationDistance, setDecelerationDistance) 214 | 215 | def setMaximumLinearAcceleration(self, aMaximumLinearAcceleration): 216 | self.ID.setMaximumLinearAcceleration(aMaximumLinearAcceleration) 217 | def getMaximumLinearAcceleration(self): 218 | return self.ID.maximumLinearAcceleration() 219 | maximumLinearAcceleration = property(getMaximumLinearAcceleration, setMaximumLinearAcceleration) 220 | 221 | def setMaximumLinearVelocity(self, aMaximumLinearVelocity): 222 | self.ID.setMaximumLinearVelocity(aMaximumLinearVelocity) 223 | def getMaximumLinearVelocity(self): 224 | return self.ID.maximumLinearVelocity() 225 | maximumLinearVelocity = property(getMaximumLinearVelocity, setMaximumLinearVelocity) 226 | 227 | class SliderConstraint(Constraint): 228 | def __init__(self, ID=None): 229 | if ID is not None: 230 | self.ID = ID 231 | else: 232 | self.ID = SCNSliderConstraint.sliderConstraint() 233 | 234 | @classmethod 235 | def sliderConstraint(cls): 236 | return cls() 237 | 238 | def setCollisionCategoryBitMask(self, aMask): 239 | self.ID.setCollisionCategoryBitMask(aMask) 240 | def getCollisionCategoryBitMask(self): 241 | return self.ID.collisionCategoryBitMask() 242 | collisionCategoryBitMask = property(getCollisionCategoryBitMask, setCollisionCategoryBitMask) 243 | 244 | def setOffset(self, anOffset): 245 | self.ID.setOffset(vector3Make(anOffset)) 246 | def getOffset(self): 247 | offset = self.ID.offset() 248 | return Vector3(offset.a, offset.b, offset.c) 249 | offset = property(getOffset, setOffset) 250 | 251 | def setRadius(self, aRadius): 252 | self.ID.setRadius(aRadius) 253 | def getRadius(self): 254 | return self.ID.radius() 255 | radius = property(getRadius, setRadius) 256 | 257 | class ReplicatorConstraint(Constraint, _Target): 258 | def __init__(self, target=None, ID=None): 259 | if ID is not None: 260 | self.ID = ID 261 | else: 262 | self.ID = SCNReplicatorConstraint.replicatorConstraintWithTarget_(target.ID) 263 | 264 | @classmethod 265 | def replicatorConstraintWithTarget(cls, target=None): 266 | return cls(target=target) 267 | 268 | def setOrientationOffset(self, anOrientationOffset): 269 | self.ID.setOrientationOffset(quaternionMake(anOrientationOffset)) 270 | def getOrientationOffset(self): 271 | anOffset = self.ID.orientationOffset() 272 | return Quaternion(anOffset.a, anOffset.b, anOffset.c, anOffset.d) 273 | orientationOffset = property(getOrientationOffset, setOrientationOffset) 274 | 275 | def setPositionOffset(self, anOffset): 276 | self.ID.setPositionOffset(vector3Make(anOffset)) 277 | def getPositionOffset(self): 278 | offset = self.ID.positionOffset() 279 | return Vector3(offset.a, offset.b, offset.c) 280 | positionOffset = property(getPositionOffset, setPositionOffset) 281 | 282 | def setReplicatesOrientation(self, aBool): 283 | self.ID.setReplicatesOrientation(aBool) 284 | def getReplicatesOrientation(self): 285 | return self.ID.replicatesOrientation() 286 | replicatesOrientation = property(getReplicatesOrientation, setReplicatesOrientation) 287 | 288 | def setReplicatesPosition(self, aBool): 289 | self.ID.setReplicatesPosition(aBool) 290 | def getReplicatesPosition(self): 291 | return self.ID.replicatesPosition() 292 | replicatesPosition = property(getReplicatesPosition, setReplicatesPosition) 293 | 294 | def setReplicatesScale(self, aBool): 295 | self.ID.setReplicatesScale(aBool) 296 | def getReplicatesScale(self): 297 | return self.ID.replicatesScale() 298 | replicatesScale = property(getReplicatesScale, setReplicatesScale) 299 | 300 | def setScaleOffset(self, aScaleOffset): 301 | self.ID.setScaleOffset(vector3Make(aScaleOffset)) 302 | def getScaleOffset(self): 303 | anOffset = self.ID.scaleOffset() 304 | return Vector3(offset.a, offset.b, offset.c) 305 | scaleOffset = property(getScaleOffset, setScaleOffset) 306 | 307 | class IKConstraint(Constraint): 308 | def __init__(self, chainRootNode=None, ID=None): 309 | if ID is not None: 310 | self.ID = ID 311 | elif chainRootNode is not None: 312 | self.ID = SCNIKConstraint.inverseKinematicsConstraintWithChainRootNode_(chainRootNode.ID) 313 | else: 314 | self.ID = SCNIKConstraint.alloc() 315 | 316 | @classmethod 317 | def inverseKinematicsConstraintWithChainRootNode(cls, chainRootNode=None): 318 | return cls(chainRootNode=chainRootNode) 319 | 320 | def initWithChainRootNode(self, chainRootNode=None): 321 | self.ID.initWithChainRootNode_(chainRootNode.ID) 322 | 323 | def setChainRootNode(self, aChainRootNode): 324 | self.ID.setChainRootNode(aChainRootNode.ID) 325 | def getChainRootNode(self): 326 | return sceneKit.Node.outof(self.ID.chainRootNode()) 327 | chainRootNode = property(getChainRootNode, setChainRootNode) 328 | 329 | def setMaxAllowedRotationAngleForJoint(self, aNode): 330 | self.ID.setMaxAllowedRotationAngleForJoint(aNode.ID) 331 | def getMaxAllowedRotationAngleForJoint(self): 332 | return self.ID.maxAllowedRotationAngleForJoint() 333 | maxAllowedRotationAngleForJoint = property(getMaxAllowedRotationAngleForJoint, setMaxAllowedRotationAngleForJoint) 334 | 335 | def setTargetPosition(self, aTargetPosition): 336 | self.ID.setTargetPosition(vector3Make(aTargetPosition)) 337 | def getTargetPosition(self): 338 | aPos = self.ID.targetPosition() 339 | return Vector3(aPos.a, aPos.b, aPos.c) 340 | targetPosition = property(getTargetPosition, setTargetPosition) 341 | 342 | def avoidOccluderConstraint_didAvoidOccluder_forNode_(_self, _cmd, constraint, occluder, node): 343 | anAvoidOccluderConstraintDelegate = sceneKit.AvoidOccluderConstraintDelegate.outof(ObjCInstance(_self)) 344 | if anAvoidOccluderConstraintDelegate.methods[0]: 345 | constraint = sceneKit.AvoidOccluderConstraint.outof(ObjCInstance(constraint)) 346 | occluder = sceneKit.Node.outof(ObjCInstance(occluder)) 347 | node = sceneKit.Node.outof(ObjCInstance(node)) 348 | anAvoidOccluderConstraintDelegate.delegate.didAvoidOccluder(constraint, occluder, node) 349 | 350 | def avoidOccluderConstraint_shouldAvoidOccluder_forNode_(_self, _cmd, constraint, occluder, node): 351 | anAvoidOccluderConstraintDelegate = sceneKit.AvoidOccluderConstraintDelegate.outof(ObjCInstance(_self)) 352 | if anAvoidOccluderConstraintDelegate.methods[1]: 353 | constraint = sceneKit.AvoidOccluderConstraint.outof(ObjCInstance(constraint)) 354 | occluder = sceneKit.Node.outof(ObjCInstance(occluder)) 355 | node = sceneKit.Node.outof(ObjCInstance(node)) 356 | ret = anAvoidOccluderConstraintDelegate.delegate.shouldAvoidOccluder(constraint, occluder, node) 357 | return ret if ret is not None else False 358 | 359 | class AvoidOccluderConstraintDelegate(CInst): 360 | _actions = ('didAvoidOccluder', 'shouldAvoidOccluder') 361 | _newCClassName = 'SCNPYAvoidOccluderConstraintDelegateClass' 362 | _cMethodList = [avoidOccluderConstraint_didAvoidOccluder_forNode_, avoidOccluderConstraint_shouldAvoidOccluder_forNode_] 363 | avoidOccluderConstraint_didAvoidOccluder_forNode_.restype = None 364 | avoidOccluderConstraint_didAvoidOccluder_forNode_.argtypes = [c_void_p, c_void_p, c_void_p] 365 | avoidOccluderConstraint_shouldAvoidOccluder_forNode_.restype = c_bool 366 | avoidOccluderConstraint_shouldAvoidOccluder_forNode_.argtypes = [c_void_p, c_void_p, c_void_p] 367 | _protocols =['SCNAvoidOccluderConstraintDelegate'] 368 | DelegateCClass = create_objc_class(_newCClassName, NSObject, methods=_cMethodList, protocols=_protocols) 369 | 370 | def __init__(self, aDelegate=None, ID=None): 371 | if ID is not None: 372 | self.ID = ID 373 | else: 374 | self.delegate = aDelegate 375 | self.methods = [callable(_a) for _a in [getattr(aDelegate, anAction, False) for anAction in AvoidOccluderConstraintDelegate._actions]] 376 | self.ID = AvoidOccluderConstraintDelegate.DelegateCClass.alloc().init().autorelease() 377 | 378 | -------------------------------------------------------------------------------- /sceneKit/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | sceneKit top module for the sceneKit wrapper under Pythonista 3 | 4 | (c) Peter Ulbrich, 2019, peter.ulbrich@gmail.com 5 | 6 | Use of this source code is governed by the MIT license: 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | ''' 15 | 16 | from objc_util import * 17 | import ctypes 18 | import ui 19 | from collections import namedtuple, OrderedDict 20 | import os 21 | 22 | from .sceneKitEnv import * 23 | from .sceneKitNode import * 24 | from .sceneKitMaterial import * 25 | from .sceneKitCamera import * 26 | from .sceneKitGeometry import * 27 | from .sceneKitLight import * 28 | from .sceneKitConstraints import * 29 | from .sceneKitAnimation import * 30 | from .sceneKitSceneRenderer import * 31 | from .sceneKitGeometrySource import * 32 | from .sceneKitPhysics import * 33 | from .sceneKitParticleSystem import * 34 | from .sceneKitAudio import * 35 | # from .sceneKitSceneSource import * #not implemented due to limited use under runtime environment 36 | 37 | 38 | class ViewAutoresizing(Flag): 39 | AutoresizingNone = 0 40 | FlexibleLeftMargin = (1 << 0) 41 | FlexibleWidth = (1 << 1) 42 | FlexibleRightMargin = (1 << 2) 43 | FlexibleTopMargin = (1 << 3) 44 | FlexibleHeight = (1 << 4) 45 | FlexibleBottomMargin = (1 << 5) 46 | 47 | UIViewAutoresizingNone = 0 48 | UIViewAutoresizingFlexibleLeftMargin = (1 << 0) 49 | UIViewAutoresizingFlexibleWidth = (1 << 1) 50 | UIViewAutoresizingFlexibleRightMargin = (1 << 2) 51 | UIViewAutoresizingFlexibleTopMargin = (1 << 3) 52 | UIViewAutoresizingFlexibleHeight = (1 << 4) 53 | UIViewAutoresizingFlexibleBottomMargin = (1 << 5) 54 | 55 | _sceneAttributes = [] 56 | for anAttribute in ['SCNSceneEndTimeAttributeKey', 'SCNSceneFrameRateAttributeKey', 'SCNSceneStartTimeAttributeKey', 'SCNSceneUpAxisAttributeKey']: 57 | _sceneAttributes.append(str(ObjCInstance(c_void_p.in_dll(c, anAttribute)))) 58 | _sceneAttributes = tuple(_sceneAttributes) 59 | 60 | SCNSceneEndTimeAttributeKey, SCNSceneFrameRateAttributeKey, SCNSceneStartTimeAttributeKey, \ 61 | SCNSceneUpAxisAttributeKey = _sceneAttributes 62 | SceneEndTimeAttributeKey, SceneFrameRateAttributeKey, SceneStartTimeAttributeKey, \ 63 | SceneUpAxisAttributeKey = _sceneAttributes 64 | 65 | class AntialiasingMode(Enum): 66 | ModeNone = 0 67 | Multisampling2X = 1 68 | Multisampling4X = 2 69 | Multisampling8X = 3 70 | Multisampling16X = 4 71 | SCNAntialiasingModeNone = 0 72 | SCNAntialiasingModeMultisampling2X = 1 73 | SCNAntialiasingModeMultisampling4X = 2 74 | SCNAntialiasingModeMultisampling8X = 3 75 | SCNAntialiasingModeMultisampling16X = 4 76 | 77 | 78 | class View(SceneRenderer, CInst): 79 | def __init__(self, frame=None, superView=None, ID=None): 80 | if frame is not None: 81 | try: 82 | if len(frame) == 4: 83 | x, y, w, h = frame 84 | elif len(frame) == 2: 85 | (x, y), (w, h) = frame 86 | frame = CGRect((x, y),(w, h)) 87 | except TypeError: 88 | pass 89 | self.ID = SCNView.alloc().initWithFrame_options_(frame, None).autorelease() 90 | if superView is not None: 91 | self.addToSuperview(superView) 92 | elif ID is not None: 93 | self.ID = ID 94 | else: 95 | self.ID = SCNView.alloc().init() 96 | if superView is not None: 97 | self.addToSuperview(superView) 98 | 99 | def addToSuperview(self, aUIView): 100 | aUIView.objc_instance.addSubview_(self.ID) 101 | 102 | def removeFromSuperview(self): 103 | self.ID.removeFromSuperview() 104 | 105 | def initWithFrame(self, frame=None): 106 | if frame is not None: 107 | try: 108 | if len(frame) == 4: 109 | x, y, w, h = frame 110 | elif len(frame) == 2: 111 | (x, y), (w, h) = frame 112 | frame = CGRect((x, y),(w, h)) 113 | except TypeError: 114 | pass 115 | self.ID.initWithFrame_options_(frame, None) 116 | 117 | def setBackgroundColor(self, color): 118 | r, g, b, a = parse_color(color) 119 | self.ID.setBackgroundColor_(UIColor.color(red=r, green=g, blue=b, alpha=a)) 120 | def getBackgroundColor(self): 121 | backgroundColor = self.ID.backgroundColor() 122 | return RGBA(backgroundColor.red(), backgroundColor.green(), backgroundColor.blue(), backgroundColor.alpha()) 123 | backgroundColor = property(getBackgroundColor, setBackgroundColor) 124 | background_color = property(getBackgroundColor, setBackgroundColor) 125 | 126 | def setPreferredFramesPerSecond(self, aRate): 127 | self.ID.setPreferredFramesPerSecond(aRate) 128 | def getPreferredFramesPerSecond(self): 129 | return self.ID.preferredFramesPerSecond() 130 | preferredFramesPerSecond = property(getPreferredFramesPerSecond, setPreferredFramesPerSecond) 131 | 132 | def setRendersContinuously(self, aBool): 133 | self.ID.setRendersContinuously(aBool) 134 | def getRendersContinuously(self): 135 | return self.ID.rendersContinuously() 136 | rendersContinuously = property(getRendersContinuously, setRendersContinuously) 137 | 138 | def setAntialiasingMode(self, anAntialiasingMode): 139 | self.ID.setAntialiasingMode(anAntialiasingMode.value) 140 | def getAntialiasingMode(self): 141 | return AntialiasingMode(self.ID.antialiasingMode()) 142 | antialiasingMode = property(getAntialiasingMode, setAntialiasingMode) 143 | 144 | def setAllowsCameraControl(self, control=True): 145 | self.ID.setAllowsCameraControl_(control) 146 | def getAllowsCameraControl(self): 147 | return self.ID.allowsCameraControl() 148 | allowsCameraControl = property(getAllowsCameraControl, setAllowsCameraControl) 149 | 150 | def getCameraControlConfiguration(self): 151 | return sceneKit.CameraControlConfiguration.outof(self.ID.cameraControlConfiguration()) 152 | cameraControlConfiguration = property(getCameraControlConfiguration, None) 153 | 154 | def getDefaultCameraController(self): 155 | return sceneKit.CameraController.outof(self.ID.defaultCameraController()) 156 | defaultCameraController = property(getDefaultCameraController, None) 157 | 158 | def pause(self, sender=None): 159 | self.ID.pause_(sender) 160 | 161 | def play(self, sender=None): 162 | self.ID.play_(sender) 163 | 164 | def stop(self, sender=None): 165 | self.ID.stop_(sender) 166 | 167 | def snapshot(self): 168 | uiImage = self.ID.snapshot() 169 | c.UIImagePNGRepresentation.argtypes = [c_void_p] 170 | c.UIImagePNGRepresentation.restype = c_void_p 171 | data = ObjCInstance(c.UIImagePNGRepresentation(uiImage.ptr)) 172 | return Image.from_data(nsdata_to_bytes(data),2.0) 173 | 174 | def setAutoresizingMask(self, flags): 175 | try: 176 | iterator = iter(flags) 177 | except TypeError: 178 | flags = [flags] 179 | mask = 0 180 | for aFlag in flags: 181 | mask = mask | aFlag.value 182 | self.ID.setAutoresizingMask_(mask) 183 | def getAutoresizingMask(self): 184 | mask = self.ID.autoresizingMask() 185 | flags = [] 186 | for aFlag in ViewAutoresizing: 187 | if (mask & aFlag.value) != 0: flags.append(aFlag) 188 | return tuple(flags) 189 | autoresizingMask = property(getAutoresizingMask, setAutoresizingMask) 190 | 191 | def setDrawableResizesAsynchronously(self, aBool): 192 | self.ID.setDrawableResizesAsynchronously_(aBool) 193 | def getDrawableResizesAsynchronously(self): 194 | return self.ID.drawableResizesAsynchronously() 195 | drawableResizesAsynchronously = property(getDrawableResizesAsynchronously, setDrawableResizesAsynchronously) 196 | 197 | def setFrame(self, aFrame): 198 | aFrame = CGRect(CGPoint(aFrame[0], aFrame[1]), CGSize(aFrame[2], aFrame[3])) 199 | self.ID.setFrame_(aFrame) 200 | def getFrame(self): 201 | ret = self.ID.frame() 202 | return (ret.origin.x, ret.origin.y, ret.size.width, ret.size.height) 203 | frame = property(getFrame, setFrame) 204 | 205 | 206 | class Scene(Animatable, CInst): 207 | def __init__(self, name=None, inDirectory= None, options=None, url=None, mdlAsset=None, ID=None): 208 | if name is not None and inDirectory is None: 209 | self.ID = SCNScene.sceneNamed_(name) 210 | elif name is not None and inDirectory is not None: 211 | self.ID = SCNScene.sceneNamed_inDirectory_options_(name, inDirectory, options) 212 | elif url is not None: 213 | error = c_void_p(0) 214 | ret = SCNScene.sceneWithURL_options_error_(nsurl(url), self.convertOptions(options), byref(error), restype=c_void_p, argtypes=[c_void_p, c_void_p, c_void_p]) 215 | if ret is None: 216 | self.ID = None 217 | try: 218 | error = ObjCInstance(error) 219 | message = "sceneWithURL failed. Error: " + str(int(error.code())) + ' ' + str(error.localizedDescription()) 220 | except AttributeError: 221 | message = "sceneWithURL failed. Wrong URL?" 222 | raise RuntimeError(message) 223 | else: 224 | self.ID = ret 225 | elif mdlAsset is not None: 226 | self.ID = SCNScene.sceneWithMDLAsset_(mdlAsset) 227 | if self.ID is None: 228 | raise RuntimeError('sceneWithMDLAsset failed. Wrong asset?') 229 | elif ID is not None: 230 | self.ID = ID 231 | else: 232 | self.ID = SCNScene.scene() 233 | 234 | @classmethod 235 | def scene(cls): 236 | return cls() 237 | 238 | @classmethod 239 | def sceneNamed(cls, name=None, inDiectory=None, options=None): 240 | return cls(name=name, inDiectory=inDiectory, options=options) 241 | 242 | @classmethod 243 | def sceneWithURL(cls, url=None, options=None): 244 | return cls(url=url, options=options) 245 | 246 | @classmethod 247 | def sceneWithMDLAsset(cls, mdlAsset=None): 248 | return cls(mdlAsset=mdlAsset) 249 | 250 | def convertOptions(self, options): 251 | if options is None: return None 252 | retOptions = {} 253 | for key, value in options.items(): 254 | i = _sceneSourceLoadingOptions.index(key) 255 | if key == SceneSourceAnimationImportPolicyKey: 256 | i = _sceneSourceAnimationImportPolicies.index(value) 257 | elif key == SCNSceneSourceAssetDirectoryURLsKey: 258 | value = [nsurl(aValue) for aValue in value] 259 | retOptions[key] = value 260 | return retOptions 261 | 262 | def setPaused(self, aBool): 263 | self.ID.setPaused(aBool) 264 | def getPaused(self): 265 | return self.ID.paused() 266 | paused = property(getPaused, setPaused) 267 | 268 | def getRootNode(self): 269 | return Node.outof(self.ID.rootNode()) 270 | rootNode = property(getRootNode, None) 271 | 272 | def getBackground(self): 273 | return MaterialProperty.outof(self.ID.background()) 274 | background = property(getBackground, None) 275 | 276 | def getLightingEnvironment(self): 277 | return MaterialProperty.outof(self.ID.lightingEnvironment()) 278 | lightingEnvironment = property(getLightingEnvironment, None) 279 | 280 | def attributeForKey(self, key): 281 | attribute = self.ID.attributeForKey_(key) 282 | if key == SCNSceneUpAxisAttributeKey and attribute is not None: 283 | attribute = attribute.SCNVector3Value() 284 | return Vector3(attribute.a, attribute.b, attribute.c) 285 | else: 286 | return attribute 287 | 288 | def setAttribute(self, attribute, key): 289 | if key == SCNSceneUpAxisAttributeKey: 290 | attribute = vector3Make(attribute) 291 | attribute = NSValue.valueWithSCNVector3_(attribute) 292 | self.ID.setAttribute_forKey_(attribute, key) 293 | def setAttributeForKey(self, attribute, key): 294 | self.setAttribute(attribute, key) 295 | 296 | def writeToURL(self, url=None, options=None, delegate=None, progressHandler=None): 297 | if delegate is not None: 298 | sceneWriteToUrlDelegate = SceneWriteToUrlDelegate(delegate) 299 | delegate = sceneWriteToUrlDelegate.ID 300 | if progressHandler is not None: 301 | self.sceneWriteToUrlProgressHandler = sceneWriteToUrlProgressHandlerBlock(progressHandler) 302 | progressHandler = self.sceneWriteToUrlProgressHandler.blockCode 303 | return self.ID.writeToURL_options_delegate_progressHandler_(nsurl(url), self.convertOptions(options), delegate, progressHandler) 304 | 305 | def setFogStartDistance(self, aFogStartDistance): 306 | self.ID.setFogStartDistance(aFogStartDistance) 307 | def getFogStartDistance(self): 308 | return self.ID.fogStartDistance() 309 | fogStartDistance = property(getFogStartDistance, setFogStartDistance) 310 | 311 | def setFogEndDistance(self, aFogEndDistance): 312 | self.ID.setFogEndDistance(aFogEndDistance) 313 | def getFogEndDistance(self): 314 | return self.ID.fogEndDistance() 315 | fogEndDistance = property(getFogEndDistance, setFogEndDistance) 316 | 317 | def setFogDensityExponent(self, aFogDensityExponent): 318 | self.ID.setFogDensityExponent(aFogDensityExponent) 319 | def getFogDensityExponent(self): 320 | return self.ID.fogDensityExponent() 321 | fogDensityExponent = property(getFogDensityExponent, setFogDensityExponent) 322 | 323 | def setFogColor(self, aFogColor): 324 | r, g, b, a = parse_color(aFogColor) 325 | self.ID.setFogColor_(ObjCClass('UIColor').color(red=r, green=g, blue=b, alpha=a)) 326 | def getFogColor(self): 327 | aFogColor = self.ID.fogColor() 328 | return RGBA(aFogColor.red(), aFogColor.green(), aFogColor.blue(), aFogColor.alpha()) 329 | fogColor = property(getFogColor, setFogColor) 330 | 331 | def getPhysicsWorld(self): 332 | return PhysicsWorld.outof(self.ID.physicsWorld()) 333 | physicsWorld = property(getPhysicsWorld, None) 334 | 335 | def addParticleSystem(self, system=None, transform=Matrix4Identity, withTransform=None): 336 | if withTransform is not None: transform = withTransform 337 | self.ID.addParticleSystem_withTransform_(system.ID, matrix4Make(transform)) 338 | 339 | def getParticleSystems(self): 340 | return tuple([sceneKit.ParticleSystem.outof(aSystem) for aSystem in self.ID.particleSystems()]) 341 | particleSystems = property(getParticleSystems, None) 342 | 343 | def removeParticleSystem(self, system=None): 344 | self.ID.removeParticleSystem_(system.ID) 345 | 346 | def removeAllParticleSystems(self): 347 | self.ID.removeAllParticleSystems() 348 | 349 | def writeImage_withSceneDocumentURL_originalImageURL_(_self, _cmd, ximage, xdocumentURL, xoriginalImageURL): 350 | aWriteToUrlDelegate = sceneKit.SceneWriteToUrlDelegate.outof(ObjCInstance(_self)) 351 | if aWriteToUrlDelegate.methods[0]: 352 | if ximage is not None: 353 | ximage = ObjCInstance(ximage) 354 | ximage = contentFromC(ximage) 355 | if xdocumentURL is not None: 356 | xdocumentURL = ObjCInstance(xdocumentURL) 357 | xdocumentURL = contentFromC(xdocumentURL) 358 | if xoriginalImageURL is not None: 359 | xoriginalImageURL = ObjCInstance(xoriginalImageURL) 360 | xoriginalImageURL = contentFromC(xoriginalImageURL) 361 | ret =aWriteToUrlDelegate.delegate.writeImage(ximage, xdocumentURL, xoriginalImageURL) 362 | return nsurl(ret) if ret is not None else None 363 | 364 | class SceneWriteToUrlDelegate(CInst): 365 | _actions = ('writeImage',) 366 | _newCClassName = 'SCNPYWriteToUrlDelegateClass' 367 | _cMethodList = [writeImage_withSceneDocumentURL_originalImageURL_] 368 | writeImage_withSceneDocumentURL_originalImageURL_.restype = c_void_p 369 | writeImage_withSceneDocumentURL_originalImageURL_.argtypes = [c_void_p, c_void_p, c_void_p] 370 | _protocols = ['SCNSceneExportDelegate'] 371 | DelegateCClass = create_objc_class(_newCClassName, NSObject, methods=_cMethodList, protocols=_protocols) 372 | 373 | def __init__(self, aDelegate=None, ID=None): 374 | if ID is not None: 375 | self.ID = ID 376 | else: 377 | self.delegate = aDelegate 378 | self.methods = [callable(_a) for _a in [getattr(aDelegate, anAction, False) for anAction in WriteToUrlDelegate._actions]] 379 | self.ID = WriteToUrlDelegate.DelegateCClass.alloc().init() 380 | 381 | class sceneWriteToUrlProgressHandlerBlock: 382 | def __init__(self, block): 383 | self.blockCode = ObjCBlock(self.blockInterface, restype=None, argtypes=[c_void_p, c_float, c_void_p, POINTER(c_bool)]) 384 | self.pCode = block 385 | 386 | def blockInterface(self, _cmd, totalProgress, xError, xStop): 387 | totalProgress = float(totalProgress) 388 | try: 389 | xError = ObjCInstance(xError) 390 | code = xError.code() 391 | except AttributeError: 392 | xError = None 393 | stop = self.pCode(totalProgress, xError) 394 | if stop is None: stop = False 395 | xStop[0] = stop 396 | --------------------------------------------------------------------------------