Skip to content

Commit c03ea01

Browse files
authored
Merge branch 'main' into fix-planarsurface
2 parents 2ace77e + 8fc7828 commit c03ea01

File tree

12 files changed

+453
-9
lines changed

12 files changed

+453
-9
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
* Added `compas_rhino.install_with_pip` with corresponding command line utility `install_in_rhino`.
1213
* Added support for `.stp` file extension in addition to `.step` for `RhinoBrep.from_step()` and `RhinoBrep.to_step()` methods.
1314

1415
### Changed
1516

1617
* Updated minimum library version to `2.14.1` in Rhino8 GH components.
1718
* Changed name of YAK package from `bluejay` to `compas`.
19+
* Fixed broken `scaled()` method in `Sphere`, `Cylinder`, and `Capsule` classes by overriding to accept uniform scaling factor.
1820
* Fixed bug in `compas.geometry.PlanarSurface`.
1921

2022
### Removed

docs/userguide/cad.rhino8.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,52 @@ To create an editable install, you should update `pip` itself, first.
6868
$ ~/.rhinocode/py39-rh8/python3.9 -m pip install -e .
6969
7070
71+
Experimental Method
72+
===================
73+
74+
COMPAS now makes the `install_in_rhino` command line utility available to simplify the installation of Python packages in Rhino 8 CPython.
75+
This utility is available after installing the main `compas` package:
76+
77+
Install any Python package from PyPI
78+
79+
.. code-block:: bash
80+
81+
install_in_rhino requests numpy
82+
83+
Install from the current directory
84+
85+
.. code-block:: bash
86+
87+
install_in_rhino .
88+
89+
Install from a local path
90+
91+
.. code-block:: bash
92+
93+
install_in_rhino path/to/package
94+
95+
Install from a requirements file
96+
97+
.. code-block:: bash
98+
99+
install_in_rhino -r requirements.txt
100+
101+
Install in a specific site environment.
102+
103+
.. code-block:: bash
104+
105+
install_in_rhino compas --env compas-dev
106+
107+
Clear the site environment before installing
108+
109+
.. code-block:: bash
110+
111+
install_in_rhino compas --env compas-dev --clear
112+
113+
114+
For more information, see :func:`compas_rhino.install_with_pip.install_in_rhino_with_pip`.
115+
116+
71117
Verification
72118
============
73119

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Forum = "https://forum.compas-framework.org/"
3939

4040
[project.scripts]
4141
compas_rpc = "compas.rpc.__main__:main"
42+
install_in_rhino = "compas_rhino.install_with_pip:main"
4243

4344
# ============================================================================
4445
# setuptools config

src/compas/geometry/shapes/cone.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,22 @@ def to_brep(self):
300300
# Transformations
301301
# ==========================================================================
302302

303+
def scale(self, factor):
304+
"""Scale the cone by multiplying the radius and height by a factor.
305+
306+
Parameters
307+
----------
308+
factor : float
309+
The scaling factor.
310+
311+
Returns
312+
-------
313+
None
314+
315+
"""
316+
self.radius *= factor
317+
self.height *= factor
318+
303319
# ==========================================================================
304320
# Methods
305321
# ==========================================================================

src/compas/geometry/shapes/shape.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,13 +432,36 @@ def scale(self, scale):
432432
433433
See Also
434434
--------
435+
scaled
435436
translate
436437
rotate
437438
transform
438439
439440
"""
440441
raise NotImplementedError
441442

443+
def scaled(self, factor):
444+
"""Returns a scaled copy of the shape.
445+
446+
Parameters
447+
----------
448+
factor : float
449+
The scaling factor.
450+
451+
Returns
452+
-------
453+
:class:`compas.geometry.Shape`
454+
The scaled shape.
455+
456+
See Also
457+
--------
458+
scale
459+
460+
"""
461+
shape = self.copy()
462+
shape.scale(factor)
463+
return shape
464+
442465
# =============================================================================
443466
# Methods
444467
# =============================================================================

src/compas/geometry/shapes/torus.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,20 @@ def transform(self, transformation):
278278
279279
"""
280280
self.frame.transform(transformation)
281+
282+
def scale(self, factor):
283+
"""Scale the torus by multiplying the axis radius and pipe radius by a factor.
284+
285+
Parameters
286+
----------
287+
factor : float
288+
The scaling factor.
289+
290+
Returns
291+
-------
292+
None
293+
294+
"""
295+
self.radius_axis *= factor
296+
self.radius_pipe *= factor
297+
Lines changed: 158 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,169 @@
1+
import argparse
2+
import pathlib
3+
import random
4+
import shutil
5+
import string
16
import subprocess
7+
from typing import Optional
28

3-
import compas_rhino
9+
import compas
410

11+
executable = "python" if compas.WINDOWS else "python3.9"
12+
rhinocode = pathlib.Path().home() / ".rhinocode"
13+
rhinopython = rhinocode / "py39-rh8" / executable
14+
site_envs = rhinocode / "py39-rh8" / "site-envs"
515

6-
def install(args):
7-
subprocess.check_call([compas_rhino._get_default_rhino_cpython_path("8.0"), "-m", "pip", "install"] + args)
816

17+
def random_string(length=8) -> str:
18+
return "".join(random.choices(string.ascii_letters + string.digits, k=length))
919

10-
if __name__ == "__main__":
11-
import argparse
1220

13-
parser = argparse.ArgumentParser()
21+
def find_full_env_name(name: str) -> str:
22+
for filepath in site_envs.iterdir():
23+
if filepath.stem.startswith(name):
24+
return filepath.stem
25+
raise ValueError(f"No environment with this name exists: {name}")
26+
27+
28+
def ensure_site_env(name: str) -> str:
29+
try:
30+
fullname = find_full_env_name(name)
31+
except ValueError:
32+
fullname = f"{name}-{random_string()}"
33+
return fullname
34+
35+
36+
def install_in_rhino_with_pip(
37+
packages: list[str],
38+
requirements: Optional[str] = None,
39+
env: str = "default",
40+
upgrade: bool = False,
41+
deps: bool = True,
42+
clear: bool = False,
43+
):
44+
"""Install a package with Rhino's CPython pip.
45+
46+
Parameters
47+
----------
48+
packages : list of str
49+
The package(s) to install (PyPI names or local package paths).
50+
requirements : str, optional
51+
Path to a requirements file.
52+
env : str, optional
53+
Name of the site env, without the random suffix.
54+
upgrade : bool, optional
55+
Attempt to upgrade packages already installed.
56+
deps : bool, optional
57+
If False, do not install dependencies.
58+
clear : bool, optional
59+
If True, clear the environment before installing.
60+
61+
Returns
62+
-------
63+
int
64+
The return code from the pip install command.
65+
66+
Raises
67+
------
68+
ValueError
69+
If both packages and requirements are provided, or if neither are provided.
70+
71+
Examples
72+
--------
73+
>>> install_in_rhino_with_pip(packages=["requests"], env="myenv", upgrade=True)
74+
>>> install_in_rhino_with_pip(requirements="requirements.txt", env="myenv")
75+
>>> install_in_rhino_with_pip(packages=["."], env="myenv")
76+
>>> install_in_rhino_with_pip(packages=[".."], env="myenv")
77+
78+
Notes
79+
-----
80+
This function is made available as a command line script under the name `install_in_rhino`.
81+
On the command line you can use the following syntax
82+
83+
.. code-block:: bash
84+
85+
install_in_rhino requests numpy
86+
install_in_rhino -r requirements.txt --env myenv --upgrade
87+
install_in_rhino . --env myenv
88+
install_in_rhino .. --env myenv
89+
install_in_rhino -r requirements.txt --env myenv --no-deps
90+
install_in_rhino requests --env myenv --clear
91+
92+
"""
1493

15-
parser.add_argument("pipargs", help="Arguments to be passed on to pip as a string")
94+
if requirements and packages:
95+
raise ValueError("You must provide either packages or a requirements file, not both.")
1696

97+
if requirements:
98+
params = ["-r", str(pathlib.Path(requirements).resolve())]
99+
elif packages:
100+
params = []
101+
for p in packages:
102+
if p == ".":
103+
p = str(pathlib.Path().cwd())
104+
elif p == "..":
105+
p = str(pathlib.Path().cwd().parent)
106+
params.append(p)
107+
else:
108+
raise ValueError("You must provide either packages or a requirements file.")
109+
110+
env = env or "default"
111+
112+
if clear:
113+
try:
114+
fullname = find_full_env_name(env)
115+
except ValueError:
116+
pass
117+
else:
118+
target = site_envs / fullname
119+
shutil.rmtree(target, ignore_errors=True)
120+
121+
target = site_envs / ensure_site_env(env)
122+
target.mkdir(exist_ok=True)
123+
124+
args = [
125+
str(rhinopython),
126+
"-m",
127+
"pip",
128+
"install",
129+
*params,
130+
"--target",
131+
str(target),
132+
"--no-warn-script-location",
133+
]
134+
135+
if upgrade:
136+
args.append("--upgrade")
137+
if not deps:
138+
args.append("--no-deps")
139+
140+
return subprocess.check_call(args)
141+
142+
143+
def main():
144+
parser = argparse.ArgumentParser(description="Install a package with Rhino's CPython pip.")
145+
parser.add_argument("packages", help="The package(s) to install (PyPI names or local package paths)", nargs="*")
146+
parser.add_argument("-r", "--requirements", help="Path to a requirements file")
147+
parser.add_argument("--env", default="default", help="Name of the site env, without the random suffix")
148+
parser.add_argument("--upgrade", action="store_true", help="Attempt to upgrade packages already installed")
149+
parser.add_argument("--no-deps", dest="deps", action="store_false", help="Do not install dependencies")
150+
parser.add_argument("--clear", action="store_true", help="Clear the environment before installing")
151+
parser.set_defaults(deps=True)
17152
args = parser.parse_args()
18-
pipargs = args.pipargs.split()
19153

20-
install(pipargs)
154+
install_in_rhino_with_pip(
155+
packages=args.packages,
156+
requirements=args.requirements,
157+
env=args.env,
158+
upgrade=args.upgrade,
159+
deps=args.deps,
160+
clear=args.clear,
161+
)
162+
163+
164+
# =============================================================================
165+
# Main
166+
# =============================================================================
167+
168+
if __name__ == "__main__":
169+
main()

tests/compas/geometry/test_capsule.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,39 @@ def test_capsule_discretization(capsule):
1414
assert capsule.edges
1515
assert capsule.faces
1616
assert capsule.vertices
17+
18+
19+
def test_capsule_scaled():
20+
"""Test that Capsule.scaled() returns a scaled copy without modifying the original."""
21+
capsule = Capsule(radius=5.0, height=10.0)
22+
23+
# Test uniform scaling
24+
scaled_capsule = capsule.scaled(0.5)
25+
26+
# Original should be unchanged
27+
assert capsule.radius == 5.0
28+
assert capsule.height == 10.0
29+
30+
# Scaled copy should have scaled dimensions
31+
assert scaled_capsule.radius == 2.5
32+
assert scaled_capsule.height == 5.0
33+
34+
# Test scaling with factor > 1
35+
scaled_capsule_2 = capsule.scaled(2.0)
36+
assert scaled_capsule_2.radius == 10.0
37+
assert scaled_capsule_2.height == 20.0
38+
assert capsule.radius == 5.0 # Original still unchanged
39+
assert capsule.height == 10.0
40+
41+
42+
def test_capsule_scale():
43+
"""Test that Capsule.scale() modifies the capsule in place."""
44+
capsule = Capsule(radius=5.0, height=10.0)
45+
46+
# Test uniform scaling
47+
capsule.scale(0.5)
48+
49+
# Capsule should be modified
50+
assert capsule.radius == 2.5
51+
assert capsule.height == 5.0
52+

0 commit comments

Comments
 (0)