dwarvenmodeller 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. dwarvenmodeller-0.3.0/CHANGES.md +23 -0
  2. dwarvenmodeller-0.3.0/LICENSE +28 -0
  3. dwarvenmodeller-0.3.0/MANIFEST.in +7 -0
  4. dwarvenmodeller-0.3.0/PKG-INFO +135 -0
  5. dwarvenmodeller-0.3.0/README.md +113 -0
  6. dwarvenmodeller-0.3.0/assets/aldric-test.dms +261 -0
  7. dwarvenmodeller-0.3.0/assets/aldric.dms +261 -0
  8. dwarvenmodeller-0.3.0/assets/bench.dms +191 -0
  9. dwarvenmodeller-0.3.0/assets/benchmark-text.dms +62 -0
  10. dwarvenmodeller-0.3.0/assets/benchmark.dms +75 -0
  11. dwarvenmodeller-0.3.0/assets/box3.dms +182 -0
  12. dwarvenmodeller-0.3.0/dwarvenmodeller/LICENSE +28 -0
  13. dwarvenmodeller-0.3.0/dwarvenmodeller/README.md +113 -0
  14. dwarvenmodeller-0.3.0/dwarvenmodeller/__init__.py +15 -0
  15. dwarvenmodeller-0.3.0/dwarvenmodeller/__main__.py +4 -0
  16. dwarvenmodeller-0.3.0/dwarvenmodeller/cli.py +562 -0
  17. dwarvenmodeller-0.3.0/dwarvenmodeller/constants.py +41 -0
  18. dwarvenmodeller-0.3.0/dwarvenmodeller/exporters.py +2047 -0
  19. dwarvenmodeller-0.3.0/dwarvenmodeller/feedback.py +428 -0
  20. dwarvenmodeller-0.3.0/dwarvenmodeller/math_utils.py +195 -0
  21. dwarvenmodeller-0.3.0/dwarvenmodeller/ops.py +1698 -0
  22. dwarvenmodeller-0.3.0/dwarvenmodeller/primitives.py +303 -0
  23. dwarvenmodeller-0.3.0/dwarvenmodeller/scene.py +461 -0
  24. dwarvenmodeller-0.3.0/dwarvenmodeller.egg-info/PKG-INFO +135 -0
  25. dwarvenmodeller-0.3.0/dwarvenmodeller.egg-info/SOURCES.txt +36 -0
  26. dwarvenmodeller-0.3.0/dwarvenmodeller.egg-info/dependency_links.txt +1 -0
  27. dwarvenmodeller-0.3.0/dwarvenmodeller.egg-info/entry_points.txt +3 -0
  28. dwarvenmodeller-0.3.0/dwarvenmodeller.egg-info/requires.txt +5 -0
  29. dwarvenmodeller-0.3.0/dwarvenmodeller.egg-info/top_level.txt +1 -0
  30. dwarvenmodeller-0.3.0/pyproject.toml +39 -0
  31. dwarvenmodeller-0.3.0/setup.cfg +4 -0
  32. dwarvenmodeller-0.3.0/tests/test_dwarvenmodeller.py +164 -0
@@ -0,0 +1,23 @@
1
+ # DwarvenModeller Changelog
2
+
3
+ ## 0.3.0 (2026-06)
4
+ - Modular package split from monolithic script
5
+ - Tessellation-based native PNG renderer (cubes, spheres, all shapes)
6
+ - Per-face POV-Ray shading via native rotation convention
7
+ - Human-friendly op vocabulary: yaw/pitch/zoom/turn/tilt/place
8
+ - Colour name aliases (red, navy, coral...)
9
+ - --no-save is now a proper dry-run (auto-triggers feedback)
10
+ - --repl removed (anti-philosophy)
11
+ - New scene default yaw=180 (faces you)
12
+ - Sample scenes auto-installed to ~/.dwarvenmodeller/samples/
13
+ - File search path: current dir then ~/.dwarvenmodeller/samples/
14
+ - .dms extension auto-added if missing
15
+
16
+ ## 0.2.0
17
+ - POV-Ray export with per-face shading
18
+ - SVG export via vtracer
19
+ - look_at centred in all renderers
20
+ - Compass rule established
21
+
22
+ ## 0.1.0
23
+ - Initial release
@@ -0,0 +1,28 @@
1
+ DwarvenModeller
2
+ Copyright (c) 2026 thedwarf / gitdwarf
3
+
4
+ Licensed under CC BY-NC-ND 4.0 with the following commercial terms:
5
+ https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode
6
+
7
+ COMMERCIAL USE - USD $1,000 ONE-TIME FEE
8
+ Any company, business, corporation, LLC, partnership, sole trader, or other
9
+ legal entity must purchase a commercial licence to use DwarvenModeller for
10
+ any purpose, including internal use, tooling, automation, or embedding it
11
+ in a product or workflow.
12
+
13
+ This fee is one-time, per legal entity, and grants perpetual commercial use.
14
+
15
+ Payment: USD $1,000 via PayPal
16
+ https://www.paypal.com/paypalme/gitdwarf
17
+
18
+ Questions or support:
19
+ https://github.com/gitdwarf/DwarvenModeller/issues
20
+
21
+ INDIVIDUAL USE - FREE
22
+ Any natural person using DwarvenModeller as themselves -- not on behalf of
23
+ a company or other legal entity -- may use it free of charge for any
24
+ purpose, including creating and selling works produced with it (models,
25
+ images, artwork, prints, etc).
26
+
27
+ No payment required. Though donations are gratefully accepted:
28
+ https://www.paypal.com/paypalme/gitdwarf
@@ -0,0 +1,7 @@
1
+ include README.md
2
+ include CHANGES.md
3
+ include LICENSE
4
+ include pyproject.toml
5
+ recursive-include assets *.dms
6
+ recursive-include dwarvenmodeller *.py
7
+ recursive-include tests *.py
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.4
2
+ Name: dwarvenmodeller
3
+ Version: 0.3.0
4
+ Summary: Headless stateless 3D clay modeller. Text-first. No GUI. No viewport. No mouse required.
5
+ Author: thedwarf
6
+ Project-URL: Homepage, https://github.com/gitdwarf/DwarvenModeller
7
+ Project-URL: Issues, https://github.com/gitdwarf/DwarvenModeller/issues
8
+ Keywords: 3d,modeller,clay,headless,accessibility,blind,ai
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
13
+ Classifier: Programming Language :: Python :: 3
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: Pillow>=9.0
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest; extra == "dev"
20
+ Requires-Dist: pillow; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # DwarvenModeller
24
+
25
+ **Headless, stateless 3D clay modeller. Part of the [DwarvenSuite](https://github.com/gitdwarf).**
26
+
27
+ Text-first. No GUI. No viewport. No mouse required.
28
+ The blind human artist and the Claude AI instance use the same interface.
29
+ Accessibility is not a feature -- it is the architecture.
30
+
31
+ ## Philosophy
32
+
33
+ - **Digital clay, not CAD** -- think in shapes and relationships, not vertices and edge loops
34
+ - **Stateless** -- every operation is a single command; no persistent process, no session state
35
+ - **Text-first** -- all feedback is readable by screen readers, AI instances, and humans equally
36
+ - **Headless** -- runs anywhere: terminal, cron, AI tool calls, CI pipelines
37
+
38
+ ## What it does
39
+
40
+ Model in 3D using plain text operations. Each `--op` transforms the scene and saves.
41
+ `--feedback` tells you where everything is in human spatial language.
42
+ Export to POV-Ray, PNG, SVG, OBJ, STL, glTF, and more.
43
+
44
+ ## Installation
45
+
46
+ pip install dwarvenmodeller
47
+
48
+ Or from source:
49
+
50
+ git clone https://github.com/gitdwarf/DwarvenModeller.git
51
+ cd DwarvenModeller
52
+ pip install -e .
53
+
54
+ Requires Python 3.10+. POV-Ray is optional (for photorealistic renders).
55
+
56
+ ## Quick start
57
+
58
+ # Create a new scene
59
+ dwarvenmodeller --new mysculpt
60
+
61
+ # Add shapes
62
+ dwarvenmodeller --file mysculpt.dms --op "add type=sphere id=head radius=10"
63
+ dwarvenmodeller --file mysculpt.dms --op "add type=cube id=body width=6 height=8 depth=5 fill=coral"
64
+
65
+ # Check what you have
66
+ dwarvenmodeller --file mysculpt.dms --feedback
67
+
68
+ # Move things
69
+ dwarvenmodeller --file mysculpt.dms --op "move target=body down=5"
70
+
71
+ # Export
72
+ dwarvenmodeller --file mysculpt.dms --export "format=png out=mysculpt.png"
73
+
74
+ # Load a sample scene
75
+ dwarvenmodeller --file aldric --feedback
76
+
77
+ ## Sample scenes
78
+
79
+ Installed to `~/.dwarvenmodeller/samples/` on first run:
80
+
81
+ - `aldric.dms` -- 30-object face sculpture, all primitive types demonstrated
82
+ - `bench.dms` -- calibration scene, all 6 cardinal directions labelled
83
+ - `box3.dms` -- DwarvenArchive icon: open box with documents
84
+ - `benchmark.dms` -- export regression testing scene
85
+
86
+ ## Operations
87
+
88
+ dwarvenmodeller --help-ops
89
+
90
+ ## Shapes
91
+
92
+ sphere cube cylinder cone capsule torus plane
93
+ icosahedron tetrahedron octahedron dodecahedron text null
94
+
95
+ ## Exports
96
+
97
+ png png_native povray svg obj stl glb gltf x3d braille spatial txt
98
+
99
+ ## Python API
100
+
101
+ ```python
102
+ import dwarvenmodeller as dm
103
+
104
+ scene = dm.Scene.load('mysculpt.dms')
105
+ dm.op_add(scene, {'type': 'sphere', 'id': 'head', 'radius': '10'})
106
+ print(dm.generate_feedback(scene, tty=False))
107
+ dm.export_png_native(scene, 'preview.png', size=512)
108
+ scene.save('mysculpt.dms')
109
+ ```
110
+
111
+ ## Part of DwarvenSuite
112
+
113
+ All tools follow the same philosophy: small, fast, correct, as few dependencies as possible.
114
+
115
+ ## Author
116
+
117
+ thedwarf -- gitdwarf
118
+
119
+ ## Support / Tip Jar
120
+
121
+ If you find DwarvenModeller useful, you can support the project:
122
+
123
+ [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-blue?logo=paypal)](https://www.paypal.com/paypalme/gitdwarf)
124
+
125
+ ## Licence
126
+
127
+ **Individual use: free.** Use it, sell what you make with it, no payment needed.
128
+
129
+ **Business/entity use: USD $1,000 one-time fee.**
130
+ Any company, LLC, sole trader, or other legal entity must purchase a commercial licence.
131
+ Pay via PayPal: https://www.paypal.com/paypalme/gitdwarf
132
+
133
+ Questions: https://github.com/gitdwarf/DwarvenModeller/issues
134
+
135
+ Run `dwarvenmodeller --license` for full terms.
@@ -0,0 +1,113 @@
1
+ # DwarvenModeller
2
+
3
+ **Headless, stateless 3D clay modeller. Part of the [DwarvenSuite](https://github.com/gitdwarf).**
4
+
5
+ Text-first. No GUI. No viewport. No mouse required.
6
+ The blind human artist and the Claude AI instance use the same interface.
7
+ Accessibility is not a feature -- it is the architecture.
8
+
9
+ ## Philosophy
10
+
11
+ - **Digital clay, not CAD** -- think in shapes and relationships, not vertices and edge loops
12
+ - **Stateless** -- every operation is a single command; no persistent process, no session state
13
+ - **Text-first** -- all feedback is readable by screen readers, AI instances, and humans equally
14
+ - **Headless** -- runs anywhere: terminal, cron, AI tool calls, CI pipelines
15
+
16
+ ## What it does
17
+
18
+ Model in 3D using plain text operations. Each `--op` transforms the scene and saves.
19
+ `--feedback` tells you where everything is in human spatial language.
20
+ Export to POV-Ray, PNG, SVG, OBJ, STL, glTF, and more.
21
+
22
+ ## Installation
23
+
24
+ pip install dwarvenmodeller
25
+
26
+ Or from source:
27
+
28
+ git clone https://github.com/gitdwarf/DwarvenModeller.git
29
+ cd DwarvenModeller
30
+ pip install -e .
31
+
32
+ Requires Python 3.10+. POV-Ray is optional (for photorealistic renders).
33
+
34
+ ## Quick start
35
+
36
+ # Create a new scene
37
+ dwarvenmodeller --new mysculpt
38
+
39
+ # Add shapes
40
+ dwarvenmodeller --file mysculpt.dms --op "add type=sphere id=head radius=10"
41
+ dwarvenmodeller --file mysculpt.dms --op "add type=cube id=body width=6 height=8 depth=5 fill=coral"
42
+
43
+ # Check what you have
44
+ dwarvenmodeller --file mysculpt.dms --feedback
45
+
46
+ # Move things
47
+ dwarvenmodeller --file mysculpt.dms --op "move target=body down=5"
48
+
49
+ # Export
50
+ dwarvenmodeller --file mysculpt.dms --export "format=png out=mysculpt.png"
51
+
52
+ # Load a sample scene
53
+ dwarvenmodeller --file aldric --feedback
54
+
55
+ ## Sample scenes
56
+
57
+ Installed to `~/.dwarvenmodeller/samples/` on first run:
58
+
59
+ - `aldric.dms` -- 30-object face sculpture, all primitive types demonstrated
60
+ - `bench.dms` -- calibration scene, all 6 cardinal directions labelled
61
+ - `box3.dms` -- DwarvenArchive icon: open box with documents
62
+ - `benchmark.dms` -- export regression testing scene
63
+
64
+ ## Operations
65
+
66
+ dwarvenmodeller --help-ops
67
+
68
+ ## Shapes
69
+
70
+ sphere cube cylinder cone capsule torus plane
71
+ icosahedron tetrahedron octahedron dodecahedron text null
72
+
73
+ ## Exports
74
+
75
+ png png_native povray svg obj stl glb gltf x3d braille spatial txt
76
+
77
+ ## Python API
78
+
79
+ ```python
80
+ import dwarvenmodeller as dm
81
+
82
+ scene = dm.Scene.load('mysculpt.dms')
83
+ dm.op_add(scene, {'type': 'sphere', 'id': 'head', 'radius': '10'})
84
+ print(dm.generate_feedback(scene, tty=False))
85
+ dm.export_png_native(scene, 'preview.png', size=512)
86
+ scene.save('mysculpt.dms')
87
+ ```
88
+
89
+ ## Part of DwarvenSuite
90
+
91
+ All tools follow the same philosophy: small, fast, correct, as few dependencies as possible.
92
+
93
+ ## Author
94
+
95
+ thedwarf -- gitdwarf
96
+
97
+ ## Support / Tip Jar
98
+
99
+ If you find DwarvenModeller useful, you can support the project:
100
+
101
+ [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-blue?logo=paypal)](https://www.paypal.com/paypalme/gitdwarf)
102
+
103
+ ## Licence
104
+
105
+ **Individual use: free.** Use it, sell what you make with it, no payment needed.
106
+
107
+ **Business/entity use: USD $1,000 one-time fee.**
108
+ Any company, LLC, sole trader, or other legal entity must purchase a commercial licence.
109
+ Pay via PayPal: https://www.paypal.com/paypalme/gitdwarf
110
+
111
+ Questions: https://github.com/gitdwarf/DwarvenModeller/issues
112
+
113
+ Run `dwarvenmodeller --license` for full terms.
@@ -0,0 +1,261 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <dms version="1.0">
3
+ <metadata created="2026-03-21T20:43:25.554926+00:00" version="1.0"/>
4
+ <viewpoints>
5
+ <viewpoint name="default" az="150.0" el="15.0" scale="9.0"/>
6
+ </viewpoints>
7
+ <scene>
8
+ <object id="skull" type="icosahedron" tags="deformed_by=socket_right,deformed_by=socket_left,deformed_by=nose_bridge,deformed_by=nose_tip,deformed_by=nostril_right,deformed_by=nostril_left,deformed_by=ear_right,deformed_by=ear_left,deformed_by=cheek_right,deformed_by=cheek_left,deformed_by=jaw,deformed_by=chin,deformed_by=upper_lip,deformed_by=lower_lip,deformed_by=mouth_line,deformed_by=philtrum,deformed_by=brow_right,deformed_by=brow_left,deformed_by=eyebrow_right,deformed_by=eyebrow_left">
9
+ <params radius="10.0" subdivisions="3.0"/>
10
+ <transform translate="0.0,0.0,0.0" rotate="0.0,0.0,0.0" scale="1.0,1.15,0.88"/>
11
+ <material fill="#e8c49a" stroke="#c8a070" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
12
+ <object id="neck" type="cylinder" tags="null">
13
+ <params radius="4.5" height="8.0" segments="16"/>
14
+ <transform translate="0.0,-12.0,0.0" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
15
+ <material fill="#e0b896" stroke="#c8a070" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
16
+ <attach point="0.0,-12.0,0.0"/>
17
+ </object>
18
+ <object id="jaw" type="cube" tags="pressed_into=skull">
19
+ <params width="14.0" height="5.0" depth="7.0"/>
20
+ <transform translate="0.0,-7.0,1.0" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
21
+ <material fill="#e4c094" stroke="#c8a070" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
22
+ <attach point="0.0,-7.0,1.0"/>
23
+ </object>
24
+ <object id="brow_right" type="cube" tags="pressed_into=skull">
25
+ <params width="4.0" height="1.2" depth="1.5"/>
26
+ <transform translate="-3.2,3.5,9.2" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
27
+ <material fill="#4a7a38" stroke="#c8a060" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
28
+ <attach point="-3.2,3.5,9.2"/>
29
+ </object>
30
+ <object id="brow_left" type="cube" tags="pressed_into=skull">
31
+ <params width="4.0" height="1.2" depth="1.5"/>
32
+ <transform translate="3.2,3.5,9.2" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
33
+ <material fill="#ddb07a" stroke="#c8a060" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
34
+ <attach point="3.2,3.5,9.2"/>
35
+ </object>
36
+ <object id="socket_right" type="sphere" tags="deformed_by=white_right,pressed_into=skull">
37
+ <params radius="2.2" subdivisions="3"/>
38
+ <transform translate="-3.2,2.8,9.0" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
39
+ <material fill="#c8a07a" stroke="#a07040" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
40
+ <attach point="-3.2,2.8,9.0"/>
41
+ </object>
42
+ <object id="socket_left" type="sphere" tags="deformed_by=white_left,pressed_into=skull">
43
+ <params radius="2.2" subdivisions="3"/>
44
+ <transform translate="3.2,2.8,9.0" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
45
+ <material fill="#c8a07a" stroke="#a07040" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
46
+ <attach point="3.2,2.8,9.0"/>
47
+ </object>
48
+ <object id="white_right" type="sphere" tags="deformed_by=iris_right,pressed_into=socket_right">
49
+ <params radius="1.8" subdivisions="3"/>
50
+ <transform translate="-3.2,2.8,9.6" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
51
+ <material fill="#f4f0e8" stroke="#ccccbb" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
52
+ <attach point="-3.2,2.8,9.6"/>
53
+ </object>
54
+ <object id="white_left" type="sphere" tags="deformed_by=iris_left,pressed_into=socket_left">
55
+ <params radius="1.8" subdivisions="3"/>
56
+ <transform translate="3.2,2.8,9.6" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
57
+ <material fill="#f4f0e8" stroke="#ccccbb" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
58
+ <attach point="3.2,2.8,9.6"/>
59
+ </object>
60
+ <object id="iris_right" type="sphere" tags="deformed_by=pupil_right,pressed_into=white_right">
61
+ <params radius="1.1" subdivisions="3"/>
62
+ <transform translate="-3.2,2.8,10.3" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
63
+ <material fill="#4a7a38" stroke="#2a4a18" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
64
+ <attach point="-3.2,2.8,10.3"/>
65
+ </object>
66
+ <object id="iris_left" type="sphere" tags="deformed_by=pupil_left,pressed_into=white_left">
67
+ <params radius="1.1" subdivisions="3"/>
68
+ <transform translate="3.2,2.8,10.3" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
69
+ <material fill="#4a7a38" stroke="#2a4a18" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
70
+ <attach point="3.2,2.8,10.3"/>
71
+ </object>
72
+ <object id="pupil_right" type="sphere" tags="pressed_into=iris_right">
73
+ <params radius="0.6" subdivisions="3"/>
74
+ <transform translate="-3.2,2.8,10.9" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
75
+ <material fill="#0a0808" stroke="#050404" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
76
+ <attach point="-3.2,2.8,10.9"/>
77
+ </object>
78
+ <object id="pupil_left" type="sphere" tags="pressed_into=iris_left">
79
+ <params radius="0.6" subdivisions="3"/>
80
+ <transform translate="3.2,2.8,10.9" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
81
+ <material fill="#0a0808" stroke="#050404" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
82
+ <attach point="3.2,2.8,10.9"/>
83
+ </object>
84
+ <object id="nose_bridge" type="octahedron" tags="pressed_into=skull">
85
+ <params radius="2.0"/>
86
+ <transform translate="0.0,0.5,9.8" rotate="0.0,0.0,0.0" scale="0.45,1.4,0.7"/>
87
+ <material fill="#dba878" stroke="#c8906a" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
88
+ <attach point="0.0,0.5,9.8"/>
89
+ </object>
90
+ <object id="nose_tip" type="sphere" tags="pressed_into=skull">
91
+ <params radius="1.3" subdivisions="3"/>
92
+ <transform translate="0.0,-1.8,10.4" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
93
+ <material fill="#d4956a" stroke="#b87848" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
94
+ <attach point="0.0,-1.8,10.4"/>
95
+ </object>
96
+ <object id="nostril_right" type="sphere" tags="pressed_into=skull">
97
+ <params radius="0.7" subdivisions="3"/>
98
+ <transform translate="-1.4,-2.4,9.9" rotate="0.0,0.0,0.0" scale="1.2,0.7,0.8"/>
99
+ <material fill="#c07848" stroke="#904820" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
100
+ <attach point="-1.4,-2.4,9.9"/>
101
+ </object>
102
+ <object id="nostril_left" type="sphere" tags="pressed_into=skull">
103
+ <params radius="0.7" subdivisions="3"/>
104
+ <transform translate="1.4,-2.4,9.9" rotate="0.0,0.0,0.0" scale="1.2,0.7,0.8"/>
105
+ <material fill="#c07848" stroke="#904820" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
106
+ <attach point="1.4,-2.4,9.9"/>
107
+ </object>
108
+ <object id="upper_lip" type="cube" tags="pressed_into=skull">
109
+ <params width="5.5" height="1.4" depth="1.2"/>
110
+ <transform translate="0.0,-4.8,9.5" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
111
+ <material fill="#c06868" stroke="#904848" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
112
+ <attach point="0.0,-4.8,9.5"/>
113
+ </object>
114
+ <object id="lower_lip" type="cube" tags="pressed_into=skull">
115
+ <params width="5.0" height="1.6" depth="1.4"/>
116
+ <transform translate="0.0,-6.4,9.3" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
117
+ <material fill="#b85858" stroke="#883838" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
118
+ <attach point="0.0,-6.4,9.3"/>
119
+ </object>
120
+ <object id="mouth_line" type="cube" tags="pressed_into=skull">
121
+ <params width="5.2" height="0.35" depth="0.5"/>
122
+ <transform translate="0.0,-5.65,9.7" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
123
+ <material fill="#6a2828" stroke="#4a1818" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
124
+ <attach point="0.0,-5.65,9.7"/>
125
+ </object>
126
+ <object id="ear_right" type="sphere" tags="deformed_by=lobe_right,pressed_into=skull">
127
+ <params radius="2.8" subdivisions="3"/>
128
+ <transform translate="-10.2,0.5,1.5" rotate="0.0,0.0,0.0" scale="0.5,1.4,0.4"/>
129
+ <material fill="#4a7a38" stroke="#b88858" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
130
+ <attach point="-10.2,0.5,1.5"/>
131
+ </object>
132
+ <object id="ear_left" type="sphere" tags="deformed_by=lobe_left,pressed_into=skull">
133
+ <params radius="2.8" subdivisions="3"/>
134
+ <transform translate="10.2,0.5,1.5" rotate="0.0,0.0,0.0" scale="0.5,1.4,0.4"/>
135
+ <material fill="#daa878" stroke="#b88858" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
136
+ <attach point="10.2,0.5,1.5"/>
137
+ </object>
138
+ <object id="lobe_right" type="sphere" tags="pressed_into=ear_right">
139
+ <params radius="1.0" subdivisions="3"/>
140
+ <transform translate="-10.4,-3.2,1.2" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
141
+ <material fill="#4a7a38" stroke="#b08050" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
142
+ <attach point="-10.4,-3.2,1.2"/>
143
+ </object>
144
+ <object id="lobe_left" type="sphere" tags="pressed_into=ear_left">
145
+ <params radius="1.0" subdivisions="3"/>
146
+ <transform translate="10.4,-3.2,1.2" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
147
+ <material fill="#d4a070" stroke="#b08050" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
148
+ <attach point="10.4,-3.2,1.2"/>
149
+ </object>
150
+ <object id="eyebrow_right" type="cube" tags="pressed_into=skull">
151
+ <params width="4.2" height="0.8" depth="0.9"/>
152
+ <transform translate="-3.2,5.1,9.5" rotate="0.0,0.0,8.0" scale="1.0,1.0,1.0"/>
153
+ <material fill="#4a7a38" stroke="#1a0a00" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
154
+ <attach point="-3.2,5.1,9.5"/>
155
+ </object>
156
+ <object id="eyebrow_left" type="cube" tags="pressed_into=skull">
157
+ <params width="4.2" height="0.8" depth="0.9"/>
158
+ <transform translate="3.2,5.1,9.5" rotate="0.0,0.0,-8.0" scale="1.0,1.0,1.0"/>
159
+ <material fill="#3d2010" stroke="#1a0a00" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
160
+ <attach point="3.2,5.1,9.5"/>
161
+ </object>
162
+ <object id="cheek_right" type="sphere" tags="pressed_into=skull">
163
+ <params radius="2.5" subdivisions="3"/>
164
+ <transform translate="-6.0,-0.5,8.2" rotate="0.0,0.0,0.0" scale="1.3,0.7,0.6"/>
165
+ <material fill="#4a7a38" stroke="#c89060" stroke_width="0.5" opacity="0.7" shininess="0.0"/>
166
+ <attach point="-6.0,-0.5,8.2"/>
167
+ </object>
168
+ <object id="cheek_left" type="sphere" tags="pressed_into=skull">
169
+ <params radius="2.5" subdivisions="3"/>
170
+ <transform translate="6.0,-0.5,8.2" rotate="0.0,0.0,0.0" scale="1.3,0.7,0.6"/>
171
+ <material fill="#e8b888" stroke="#c89060" stroke_width="0.5" opacity="0.7" shininess="0.0"/>
172
+ <attach point="6.0,-0.5,8.2"/>
173
+ </object>
174
+ <object id="philtrum" type="cube" tags="pressed_into=skull">
175
+ <params width="1.8" height="2.2" depth="0.4"/>
176
+ <transform translate="0.0,-3.4,10.0" rotate="0.0,0.0,0.0" scale="1.0,1.0,1.0"/>
177
+ <material fill="#d8a878" stroke="#b88858" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
178
+ <attach point="0.0,-3.4,10.0"/>
179
+ </object>
180
+ <object id="chin" type="sphere" tags="pressed_into=skull">
181
+ <params radius="2.2" subdivisions="3"/>
182
+ <transform translate="0.0,-9.5,7.5" rotate="0.0,0.0,0.0" scale="1.1,0.6,0.7"/>
183
+ <material fill="#e0b890" stroke="#c09060" stroke_width="0.5" opacity="1.0" shininess="0.0"/>
184
+ <attach point="0.0,-9.5,7.5"/>
185
+ </object>
186
+ </object>
187
+ </scene>
188
+ <history>
189
+ <op cmd="add type=cylinder id=neck radius=4.5 height=8 fill=#e0b896 stroke=#c8a070 at=0,-14,0" timestamp="2026-03-21T20:43:25.729201+00:00"/>
190
+ <op cmd="add type=icosahedron id=skull radius=10 subdivisions=3 fill=#e8c49a stroke=#c8a070" timestamp="2026-03-21T20:43:25.868099+00:00"/>
191
+ <op cmd="scale target=skull x=1.0 y=1.15 z=0.88" timestamp="2026-03-21T20:43:26.032872+00:00"/>
192
+ <op cmd="attach child=neck to=skull at=0,-12,0" timestamp="2026-03-21T20:43:26.171717+00:00"/>
193
+ <op cmd="add type=cube id=jaw width=14 height=5 depth=7 fill=#e4c094 stroke=#c8a070" timestamp="2026-03-21T20:43:26.327027+00:00"/>
194
+ <op cmd="attach child=jaw to=skull at=0,-7,1" timestamp="2026-03-21T20:43:26.458926+00:00"/>
195
+ <op cmd="add type=cube id=brow_right width=4 height=1.2 depth=1.5 fill=#ddb07a stroke=#c8a060" timestamp="2026-03-21T20:43:26.617891+00:00"/>
196
+ <op cmd="attach child=brow_right to=skull at=-3.2,3.5,9.2" timestamp="2026-03-21T20:43:26.749270+00:00"/>
197
+ <op cmd="add type=cube id=brow_left width=4 height=1.2 depth=1.5 fill=#ddb07a stroke=#c8a060" timestamp="2026-03-21T20:43:26.907865+00:00"/>
198
+ <op cmd="attach child=brow_left to=skull at=3.2,3.5,9.2" timestamp="2026-03-21T20:43:27.037530+00:00"/>
199
+ <op cmd="add type=sphere id=socket_right radius=2.2 fill=#c8a07a stroke=#a07040" timestamp="2026-03-21T20:43:27.173839+00:00"/>
200
+ <op cmd="attach child=socket_right to=skull at=-3.2,2.8,9.0" timestamp="2026-03-21T20:43:27.327877+00:00"/>
201
+ <op cmd="add type=sphere id=socket_left radius=2.2 fill=#c8a07a stroke=#a07040" timestamp="2026-03-21T20:43:27.460230+00:00"/>
202
+ <op cmd="attach child=socket_left to=skull at=3.2,2.8,9.0" timestamp="2026-03-21T20:43:27.613216+00:00"/>
203
+ <op cmd="add type=sphere id=white_right radius=1.8 fill=#f4f0e8 stroke=#ccccbb" timestamp="2026-03-21T20:43:27.745112+00:00"/>
204
+ <op cmd="attach child=white_right to=skull at=-3.2,2.8,9.6" timestamp="2026-03-21T20:43:27.878115+00:00"/>
205
+ <op cmd="add type=sphere id=white_left radius=1.8 fill=#f4f0e8 stroke=#ccccbb" timestamp="2026-03-21T20:43:28.033047+00:00"/>
206
+ <op cmd="attach child=white_left to=skull at=3.2,2.8,9.6" timestamp="2026-03-21T20:43:28.167388+00:00"/>
207
+ <op cmd="add type=sphere id=iris_right radius=1.1 fill=#4a7a38 stroke=#2a4a18" timestamp="2026-03-21T20:43:28.321539+00:00"/>
208
+ <op cmd="attach child=iris_right to=skull at=-3.2,2.8,10.3" timestamp="2026-03-21T20:43:28.442944+00:00"/>
209
+ <op cmd="add type=sphere id=iris_left radius=1.1 fill=#4a7a38 stroke=#2a4a18" timestamp="2026-03-21T20:43:28.578397+00:00"/>
210
+ <op cmd="attach child=iris_left to=skull at=3.2,2.8,10.3" timestamp="2026-03-21T20:43:28.725483+00:00"/>
211
+ <op cmd="add type=sphere id=pupil_right radius=0.6 fill=#0a0808 stroke=#050404" timestamp="2026-03-21T20:43:28.851323+00:00"/>
212
+ <op cmd="attach child=pupil_right to=skull at=-3.2,2.8,10.9" timestamp="2026-03-21T20:43:29.006459+00:00"/>
213
+ <op cmd="add type=sphere id=pupil_left radius=0.6 fill=#0a0808 stroke=#050404" timestamp="2026-03-21T20:43:29.140641+00:00"/>
214
+ <op cmd="attach child=pupil_left to=skull at=3.2,2.8,10.9" timestamp="2026-03-21T20:43:29.273131+00:00"/>
215
+ <op cmd="add type=octahedron id=nose_bridge radius=2.0 fill=#dba878 stroke=#c8906a" timestamp="2026-03-21T20:43:29.424103+00:00"/>
216
+ <op cmd="scale target=nose_bridge x=0.45 y=1.4 z=0.7" timestamp="2026-03-21T20:43:29.549372+00:00"/>
217
+ <op cmd="attach child=nose_bridge to=skull at=0,0.5,9.8" timestamp="2026-03-21T20:43:29.706755+00:00"/>
218
+ <op cmd="add type=sphere id=nose_tip radius=1.3 fill=#d4956a stroke=#b87848" timestamp="2026-03-21T20:43:29.829378+00:00"/>
219
+ <op cmd="attach child=nose_tip to=skull at=0,-1.8,10.4" timestamp="2026-03-21T20:43:29.954270+00:00"/>
220
+ <op cmd="add type=sphere id=nostril_right radius=0.7 fill=#c07848 stroke=#904820" timestamp="2026-03-21T20:43:30.107003+00:00"/>
221
+ <op cmd="scale target=nostril_right x=1.2 y=0.7 z=0.8" timestamp="2026-03-21T20:43:30.231819+00:00"/>
222
+ <op cmd="attach child=nostril_right to=skull at=-1.4,-2.4,9.9" timestamp="2026-03-21T20:43:30.362245+00:00"/>
223
+ <op cmd="add type=sphere id=nostril_left radius=0.7 fill=#c07848 stroke=#904820" timestamp="2026-03-21T20:43:30.512969+00:00"/>
224
+ <op cmd="scale target=nostril_left x=1.2 y=0.7 z=0.8" timestamp="2026-03-21T20:43:30.641185+00:00"/>
225
+ <op cmd="attach child=nostril_left to=skull at=1.4,-2.4,9.9" timestamp="2026-03-21T20:43:30.770367+00:00"/>
226
+ <op cmd="add type=cube id=upper_lip width=5.5 height=1.4 depth=1.2 fill=#c06868 stroke=#904848" timestamp="2026-03-21T20:43:30.921910+00:00"/>
227
+ <op cmd="attach child=upper_lip to=skull at=0,-4.8,9.5" timestamp="2026-03-21T20:43:31.053018+00:00"/>
228
+ <op cmd="add type=cube id=lower_lip width=5.0 height=1.6 depth=1.4 fill=#b85858 stroke=#883838" timestamp="2026-03-21T20:43:31.212441+00:00"/>
229
+ <op cmd="attach child=lower_lip to=skull at=0,-6.4,9.3" timestamp="2026-03-21T20:43:31.344756+00:00"/>
230
+ <op cmd="add type=cube id=mouth_line width=5.2 height=0.35 depth=0.5 fill=#6a2828 stroke=#4a1818" timestamp="2026-03-21T20:43:31.477269+00:00"/>
231
+ <op cmd="attach child=mouth_line to=skull at=0,-5.65,9.7" timestamp="2026-03-21T20:43:31.633561+00:00"/>
232
+ <op cmd="add type=sphere id=ear_right radius=2.8 fill=#daa878 stroke=#b88858" timestamp="2026-03-21T20:43:31.766415+00:00"/>
233
+ <op cmd="scale target=ear_right x=0.5 y=1.4 z=0.4" timestamp="2026-03-21T20:43:31.926055+00:00"/>
234
+ <op cmd="attach child=ear_right to=skull at=-10.2,0.5,1.5" timestamp="2026-03-21T20:43:32.058180+00:00"/>
235
+ <op cmd="add type=sphere id=ear_left radius=2.8 fill=#daa878 stroke=#b88858" timestamp="2026-03-21T20:43:32.216553+00:00"/>
236
+ <op cmd="scale target=ear_left x=0.5 y=1.4 z=0.4" timestamp="2026-03-21T20:43:32.354671+00:00"/>
237
+ <op cmd="attach child=ear_left to=skull at=10.2,0.5,1.5" timestamp="2026-03-21T20:43:32.511607+00:00"/>
238
+ <op cmd="add type=sphere id=lobe_right radius=1.0 fill=#d4a070 stroke=#b08050" timestamp="2026-03-21T20:43:32.639167+00:00"/>
239
+ <op cmd="attach child=lobe_right to=skull at=-10.4,-3.2,1.2" timestamp="2026-03-21T20:43:32.776468+00:00"/>
240
+ <op cmd="add type=sphere id=lobe_left radius=1.0 fill=#d4a070 stroke=#b08050" timestamp="2026-03-21T20:43:32.929112+00:00"/>
241
+ <op cmd="attach child=lobe_left to=skull at=10.4,-3.2,1.2" timestamp="2026-03-21T20:43:33.064354+00:00"/>
242
+ <op cmd="add type=cube id=eyebrow_right width=4.2 height=0.8 depth=0.9 fill=#3d2010 stroke=#1a0a00" timestamp="2026-03-21T20:43:33.221735+00:00"/>
243
+ <op cmd="rotate target=eyebrow_right z=8" timestamp="2026-03-21T20:43:33.353855+00:00"/>
244
+ <op cmd="attach child=eyebrow_right to=skull at=-3.2,5.1,9.5" timestamp="2026-03-21T20:43:33.509323+00:00"/>
245
+ <op cmd="add type=cube id=eyebrow_left width=4.2 height=0.8 depth=0.9 fill=#3d2010 stroke=#1a0a00" timestamp="2026-03-21T20:43:33.638035+00:00"/>
246
+ <op cmd="rotate target=eyebrow_left z=-8" timestamp="2026-03-21T20:43:33.769183+00:00"/>
247
+ <op cmd="attach child=eyebrow_left to=skull at=3.2,5.1,9.5" timestamp="2026-03-21T20:43:33.920196+00:00"/>
248
+ <op cmd="add type=sphere id=cheek_right radius=2.5 fill=#e8b888 stroke=#c89060 opacity=0.7" timestamp="2026-03-21T20:43:34.048229+00:00"/>
249
+ <op cmd="scale target=cheek_right x=1.3 y=0.7 z=0.6" timestamp="2026-03-21T20:43:34.208976+00:00"/>
250
+ <op cmd="attach child=cheek_right to=skull at=-6.0,-0.5,8.2" timestamp="2026-03-21T20:43:34.338215+00:00"/>
251
+ <op cmd="add type=sphere id=cheek_left radius=2.5 fill=#e8b888 stroke=#c89060 opacity=0.7" timestamp="2026-03-21T20:43:34.474171+00:00"/>
252
+ <op cmd="scale target=cheek_left x=1.3 y=0.7 z=0.6" timestamp="2026-03-21T20:43:34.628398+00:00"/>
253
+ <op cmd="attach child=cheek_left to=skull at=6.0,-0.5,8.2" timestamp="2026-03-21T20:43:34.766552+00:00"/>
254
+ <op cmd="add type=cube id=philtrum width=1.8 height=2.2 depth=0.4 fill=#d8a878 stroke=#b88858" timestamp="2026-03-21T20:43:34.924386+00:00"/>
255
+ <op cmd="attach child=philtrum to=skull at=0,-3.4,10.0" timestamp="2026-03-21T20:43:35.054281+00:00"/>
256
+ <op cmd="add type=sphere id=chin radius=2.2 fill=#e0b890 stroke=#c09060" timestamp="2026-03-21T20:43:35.211062+00:00"/>
257
+ <op cmd="scale target=chin x=1.1 y=0.6 z=0.7" timestamp="2026-03-21T20:43:35.345332+00:00"/>
258
+ <op cmd="attach child=chin to=skull at=0,-9.5,7.5" timestamp="2026-03-21T20:43:35.482052+00:00"/>
259
+ <op cmd="viewpoint az=150 el=15 scale=9" timestamp="2026-03-21T20:43:35.634446+00:00"/>
260
+ </history>
261
+ </dms>