Merge branch 'master' into OCP
This commit is contained in:
@ -1,35 +0,0 @@
|
|||||||
version: 2
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
runtests:
|
|
||||||
|
|
||||||
macos:
|
|
||||||
xcode: "9.0"
|
|
||||||
|
|
||||||
environment:
|
|
||||||
PYTEST_QT_API: pyqt5
|
|
||||||
PYTHON_VERSION: 3.6
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- run: HOMEBREW_NO_AUTO_UPDATE=1 brew install wget
|
|
||||||
- run: cd && rm -rf ~/.pyenv && rm -rf ~/virtualenvs
|
|
||||||
- run: wget https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O ~/miniconda.sh
|
|
||||||
- run: chmod +x ~/miniconda.sh && ~/miniconda.sh -b
|
|
||||||
- run: echo "export PATH=~/miniconda3/bin:$PATH" >> $BASH_ENV
|
|
||||||
- run: conda config --set always_yes yes
|
|
||||||
- run: conda create --quiet --name cqtest -c cadquery -c conda-forge -c dlr-sc pythonocc-core=0.18.2 oce=0.18.2 python=$PYTHON_VERSION pyparsing mock lldb
|
|
||||||
- run: |
|
|
||||||
source activate cqtest
|
|
||||||
pip install coverage
|
|
||||||
python setup.py install
|
|
||||||
conda env list
|
|
||||||
conda list
|
|
||||||
coverage run runtests.py
|
|
||||||
|
|
||||||
workflows:
|
|
||||||
version: 2
|
|
||||||
|
|
||||||
workflow:
|
|
||||||
jobs:
|
|
||||||
- runtests
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ dist/*
|
|||||||
.idea/*
|
.idea/*
|
||||||
cadquery.egg-info
|
cadquery.egg-info
|
||||||
target/*
|
target/*
|
||||||
|
.vscode
|
||||||
|
50
.travis.yml
50
.travis.yml
@ -5,7 +5,6 @@ dist: trusty
|
|||||||
|
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- adam-urbanczyk-OCC-version-update
|
|
||||||
- master
|
- master
|
||||||
- "/\\d+\\.\\d+\\.?d*\\-*[a-z]*/"
|
- "/\\d+\\.\\d+\\.?d*\\-*[a-z]*/"
|
||||||
|
|
||||||
@ -25,36 +24,37 @@ env:
|
|||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- env: TRAVIS_PYTHON_VERSION=3.6
|
- name: "Python 3.6 - osx"
|
||||||
|
env: PYTHON_VERSION=3.6
|
||||||
os: osx
|
os: osx
|
||||||
- env: TRAVIS_PYTHON_VERSION=3.6
|
- name: "Python 3.6 - linux"
|
||||||
|
env: PYTHON_VERSION=3.6
|
||||||
os: linux
|
os: linux
|
||||||
- env: TRAVIS_PYTHON_VERSION=3.7
|
- name: "Python 3.7 - osx"
|
||||||
|
env: PYTHON_VERSION=3.7
|
||||||
os: osx
|
os: osx
|
||||||
- env: TRAVIS_PYTHON_VERSION=3.7
|
- name: "Python 3.7 - linux"
|
||||||
|
env: PYTHON_VERSION=3.7
|
||||||
os: linux
|
os: linux
|
||||||
|
- name: "Lint"
|
||||||
|
env: PYTHON_VERSION=3.7
|
||||||
|
os: linux
|
||||||
|
script:
|
||||||
|
- black . --diff --check
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||||
PY_MAJOR=2 ;
|
|
||||||
else
|
|
||||||
PY_MAJOR=3 ;
|
|
||||||
fi ;
|
|
||||||
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
|
||||||
OS=Linux ;
|
OS=Linux ;
|
||||||
else
|
else
|
||||||
OS=MacOSX ;
|
OS=MacOSX ;
|
||||||
fi ;
|
fi ;
|
||||||
wget https://repo.continuum.io/miniconda/Miniconda$PY_MAJOR-latest-$OS-x86_64.sh -O miniconda.sh
|
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-$OS-x86_64.sh -O miniconda.sh
|
||||||
- bash miniconda.sh -b -p $HOME/miniconda;
|
- bash miniconda.sh -b -p $HOME/miniconda;
|
||||||
- export PATH="$HOME/miniconda/bin:$HOME/miniconda/lib:$PATH";
|
- export PATH="$HOME/miniconda/bin:$HOME/miniconda/lib:$PATH";
|
||||||
- hash -r;
|
|
||||||
- conda config --set always_yes yes --set changeps1 no;
|
- conda config --set always_yes yes --set changeps1 no;
|
||||||
- conda create -y -q -n test_cq -c cadquery -c conda-forge pythonocc-core=0.18.2 oce=0.18.2 python=$TRAVIS_PYTHON_VERSION
|
- conda env create -f environment.yml
|
||||||
pyparsing mock;
|
- source ~/miniconda/bin/activate cadquery
|
||||||
- source ~/miniconda/bin/activate test_cq;
|
- conda install -c conda-forge -c defaults -c cadquery python=$PYTHON_VERSION
|
||||||
- python -c 'import OCC.gp as gp; print(gp.gp_Vec())'
|
|
||||||
- pip install codecov
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- python setup.py install
|
- python setup.py install
|
||||||
@ -64,7 +64,7 @@ before_script:
|
|||||||
- sudo rm -f /cores/core.*
|
- sudo rm -f /cores/core.*
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- coverage run runtests.py
|
- pytest -v --cov
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- codecov
|
- codecov
|
||||||
@ -72,15 +72,3 @@ after_success:
|
|||||||
after_failure:
|
after_failure:
|
||||||
- ls /cores/core.*
|
- ls /cores/core.*
|
||||||
- lldb --core `ls /cores/core.*` --batch --one-line "bt"
|
- lldb --core `ls /cores/core.*` --batch --one-line "bt"
|
||||||
|
|
||||||
|
|
||||||
before_deploy:
|
|
||||||
- conda install anaconda-client conda-build
|
|
||||||
- sh conda_build.sh
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
- provider: script
|
|
||||||
skip_cleanup: true
|
|
||||||
script: anaconda -v -t $ANACONDA_TOKEN upload --force --user cadquery /tmp/cbld/**/cadquery-*.tar.bz2
|
|
||||||
on:
|
|
||||||
tags: true
|
|
||||||
|
32
README.md
32
README.md
@ -123,6 +123,38 @@ You do not need to be a software developer to have a big impact on this project.
|
|||||||
|
|
||||||
It is asked that all contributions to this project be made in a respectful and considerate way. Please use the [Python Community Code of Conduct's](https://www.python.org/psf/codeofconduct/) guidelines as a reference.
|
It is asked that all contributions to this project be made in a respectful and considerate way. Please use the [Python Community Code of Conduct's](https://www.python.org/psf/codeofconduct/) guidelines as a reference.
|
||||||
|
|
||||||
|
### Contributing code
|
||||||
|
|
||||||
|
If you are going to contribute code, make sure to follow this steps:
|
||||||
|
|
||||||
|
- Consider opening an issue first to discuss what you have in mind
|
||||||
|
- Try to keep it as short and simple as possible (if you want to change several
|
||||||
|
things, start with just one!)
|
||||||
|
- Fork the CadQuery repository, clone your fork and create a new branch to
|
||||||
|
start working on your changes
|
||||||
|
- Start with the tests! How should CadQuery behave after your changes? Make
|
||||||
|
sure to add some tests to the test suite to ensure proper behavior
|
||||||
|
- Make sure your tests have assertions checking all the expected results
|
||||||
|
- Add a nice docstring to the test indicating what the test is doing; if there
|
||||||
|
is too much to explain, consider splitting the test in two!
|
||||||
|
- Go ahead and implement the changes
|
||||||
|
- Add a nice docstring to the functions/methods/classes you implement
|
||||||
|
describing what they do, what the expected parameters are and what it returns
|
||||||
|
(if anything)
|
||||||
|
- Update the documentation if there is any change to the public API
|
||||||
|
- Consider adding an example to the documentation showing your cool new
|
||||||
|
feature!
|
||||||
|
- Make sure nothing is broken (run the complete test suite with `pytest`)
|
||||||
|
- Run `black` to autoformat your code and make sure your code style complies
|
||||||
|
with CadQuery's
|
||||||
|
- Push the changes to your fork and open a pull-request upstream
|
||||||
|
- Keep an eye on the automated feedback you will receive from the CI pipelines;
|
||||||
|
if there is a test failing or some code is not properly formatted, you will
|
||||||
|
be notified without human intervention
|
||||||
|
- Be prepared for constructive feedback and criticism!
|
||||||
|
- Be patient and respectful, remember that those reviewing your code are also
|
||||||
|
working hard (sometimes reviewing changes is harder than implementing them!)
|
||||||
|
|
||||||
### How to Report a Bug
|
### How to Report a Bug
|
||||||
When filing a bug report [issue](https://github.com/CadQuery/cadquery/issues), please be sure to answer these questions:
|
When filing a bug report [issue](https://github.com/CadQuery/cadquery/issues), please be sure to answer these questions:
|
||||||
|
|
||||||
|
28
appveyor.yml
28
appveyor.yml
@ -17,34 +17,14 @@ install:
|
|||||||
- set "PATH=%MINICONDA_DIRNAME%;%MINICONDA_DIRNAME%\\Scripts;%PATH%"
|
- set "PATH=%MINICONDA_DIRNAME%;%MINICONDA_DIRNAME%\\Scripts;%PATH%"
|
||||||
- conda config --set always_yes yes
|
- conda config --set always_yes yes
|
||||||
- conda update -q conda
|
- conda update -q conda
|
||||||
- conda create --quiet --name cqtest -c cadquery -c conda-forge -c dlr-sc pythonocc-core=0.18.2 python=%PYTHON_VERSION% pyparsing mock coverage codecov
|
- conda env create -f environment.yml
|
||||||
- activate cqtest
|
- activate cadquery
|
||||||
- pip install codecov
|
- conda install -c conda-forge -c defaults -c cadquery python=%PYTHON_VERSION%
|
||||||
- python setup.py install
|
|
||||||
|
|
||||||
build: false
|
build: false
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- coverage run runtests.py
|
- pytest -v --cov
|
||||||
|
|
||||||
on_success:
|
on_success:
|
||||||
- codecov
|
- codecov
|
||||||
|
|
||||||
#deploy:
|
|
||||||
#- provider: Script
|
|
||||||
# on:
|
|
||||||
# APPVEYOR_REPO_TAG: true
|
|
||||||
|
|
||||||
#before_deploy:
|
|
||||||
# - set "PATH=%MINICONDA_DIRNAME%;%MINICONDA_DIRNAME%\\Scripts;%PATH%"
|
|
||||||
# - set "TRAVIS_TAG=%APPVEYOR_REPO_TAG_NAME%"
|
|
||||||
# - set "TRAVIS_COMMIT=%APPVEYOR_REPO_COMMIT%"
|
|
||||||
# - set "TRAVIS_PYTHON_VERSION=%PYTHON_VERSION%"
|
|
||||||
# - conda config --set always_yes yes
|
|
||||||
# - activate cqtest
|
|
||||||
# - conda install anaconda-client conda-build
|
|
||||||
# - conda info --envs
|
|
||||||
# - call conda_build.bat
|
|
||||||
|
|
||||||
#deploy_script:
|
|
||||||
# - anaconda -v -t %ANACONDA_TOKEN% upload --force --user cadquery ./win-64/cadquery-*.tar.bz2
|
|
||||||
|
@ -1,25 +1,65 @@
|
|||||||
# these items point to the OCC implementation
|
# these items point to the OCC implementation
|
||||||
from .occ_impl.geom import Plane, BoundBox, Vector, Matrix
|
from .occ_impl.geom import Plane, BoundBox, Vector, Matrix
|
||||||
from .occ_impl.shapes import (Shape, Vertex, Edge, Face, Wire, Solid, Shell,
|
from .occ_impl.shapes import (
|
||||||
Compound, sortWiresByBuildOrder)
|
Shape,
|
||||||
|
Vertex,
|
||||||
|
Edge,
|
||||||
|
Face,
|
||||||
|
Wire,
|
||||||
|
Solid,
|
||||||
|
Shell,
|
||||||
|
Compound,
|
||||||
|
sortWiresByBuildOrder,
|
||||||
|
)
|
||||||
from .occ_impl import exporters
|
from .occ_impl import exporters
|
||||||
from .occ_impl import importers
|
from .occ_impl import importers
|
||||||
|
|
||||||
# these items are the common implementation
|
# these items are the common implementation
|
||||||
|
|
||||||
# the order of these matter
|
# the order of these matter
|
||||||
from .selectors import (NearestToPointSelector, ParallelDirSelector,
|
from .selectors import (
|
||||||
DirectionSelector, PerpendicularDirSelector, TypeSelector,
|
NearestToPointSelector,
|
||||||
DirectionMinMaxSelector, StringSyntaxSelector, Selector)
|
ParallelDirSelector,
|
||||||
|
DirectionSelector,
|
||||||
|
PerpendicularDirSelector,
|
||||||
|
TypeSelector,
|
||||||
|
DirectionMinMaxSelector,
|
||||||
|
StringSyntaxSelector,
|
||||||
|
Selector,
|
||||||
|
)
|
||||||
from .cq import CQ, Workplane, selectors
|
from .cq import CQ, Workplane, selectors
|
||||||
from . import plugins
|
from . import plugins
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'CQ', 'Workplane', 'plugins', 'selectors', 'Plane', 'BoundBox', 'Matrix', 'Vector', 'sortWiresByBuildOrder',
|
"CQ",
|
||||||
'Shape', 'Vertex', 'Edge', 'Wire', 'Face', 'Solid', 'Shell', 'Compound', 'exporters', 'importers',
|
"Workplane",
|
||||||
'NearestToPointSelector', 'ParallelDirSelector', 'DirectionSelector', 'PerpendicularDirSelector',
|
"plugins",
|
||||||
'TypeSelector', 'DirectionMinMaxSelector', 'StringSyntaxSelector', 'Selector', 'plugins'
|
"selectors",
|
||||||
|
"Plane",
|
||||||
|
"BoundBox",
|
||||||
|
"Matrix",
|
||||||
|
"Vector",
|
||||||
|
"sortWiresByBuildOrder",
|
||||||
|
"Shape",
|
||||||
|
"Vertex",
|
||||||
|
"Edge",
|
||||||
|
"Wire",
|
||||||
|
"Face",
|
||||||
|
"Solid",
|
||||||
|
"Shell",
|
||||||
|
"Compound",
|
||||||
|
"exporters",
|
||||||
|
"importers",
|
||||||
|
"NearestToPointSelector",
|
||||||
|
"ParallelDirSelector",
|
||||||
|
"DirectionSelector",
|
||||||
|
"PerpendicularDirSelector",
|
||||||
|
"TypeSelector",
|
||||||
|
"DirectionMinMaxSelector",
|
||||||
|
"StringSyntaxSelector",
|
||||||
|
"Selector",
|
||||||
|
"plugins",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "2.0.0dev"
|
__version__ = "2.0RC1"
|
||||||
|
734
cadquery/cq.py
734
cadquery/cq.py
File diff suppressed because it is too large
Load Diff
@ -20,13 +20,22 @@ template = """
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
template_content_indent = ' '
|
template_content_indent = " "
|
||||||
|
|
||||||
|
|
||||||
def cq_directive(name, arguments, options, content, lineno,
|
def cq_directive(
|
||||||
content_offset, block_text, state, state_machine):
|
name,
|
||||||
|
arguments,
|
||||||
|
options,
|
||||||
|
content,
|
||||||
|
lineno,
|
||||||
|
content_offset,
|
||||||
|
block_text,
|
||||||
|
state,
|
||||||
|
state_machine,
|
||||||
|
):
|
||||||
# only consider inline snippets
|
# only consider inline snippets
|
||||||
plot_code = '\n'.join(content)
|
plot_code = "\n".join(content)
|
||||||
|
|
||||||
# Since we don't have a filename, use a hash based on the content
|
# Since we don't have a filename, use a hash based on the content
|
||||||
# the script must define a variable called 'out', which is expected to
|
# the script must define a variable called 'out', which is expected to
|
||||||
@ -52,22 +61,20 @@ def cq_directive(name, arguments, options, content, lineno,
|
|||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
# get rid of new lines
|
# get rid of new lines
|
||||||
out_svg = out_svg.replace('\n', '')
|
out_svg = out_svg.replace("\n", "")
|
||||||
|
|
||||||
txt_align = "left"
|
txt_align = "left"
|
||||||
if "align" in options:
|
if "align" in options:
|
||||||
txt_align = options['align']
|
txt_align = options["align"]
|
||||||
|
|
||||||
lines.extend((template % locals()).split('\n'))
|
lines.extend((template % locals()).split("\n"))
|
||||||
|
|
||||||
lines.extend(['::', ''])
|
lines.extend(["::", ""])
|
||||||
lines.extend([' %s' % row.rstrip()
|
lines.extend([" %s" % row.rstrip() for row in plot_code.split("\n")])
|
||||||
for row in plot_code.split('\n')])
|
lines.append("")
|
||||||
lines.append('')
|
|
||||||
|
|
||||||
if len(lines):
|
if len(lines):
|
||||||
state_machine.insert_input(
|
state_machine.insert_input(lines, state_machine.input_lines.source(0))
|
||||||
lines, state_machine.input_lines.source(0))
|
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@ -77,9 +84,10 @@ def setup(app):
|
|||||||
setup.config = app.config
|
setup.config = app.config
|
||||||
setup.confdir = app.confdir
|
setup.confdir = app.confdir
|
||||||
|
|
||||||
options = {'height': directives.length_or_unitless,
|
options = {
|
||||||
'width': directives.length_or_percentage_or_unitless,
|
"height": directives.length_or_unitless,
|
||||||
'align': directives.unchanged
|
"width": directives.length_or_percentage_or_unitless,
|
||||||
|
"align": directives.unchanged,
|
||||||
}
|
}
|
||||||
|
|
||||||
app.add_directive('cq_plot', cq_directive, True, (0, 2, 0), **options)
|
app.add_directive("cq_plot", cq_directive, True, (0, 2, 0), **options)
|
||||||
|
110
cadquery/cqgi.py
110
cadquery/cqgi.py
@ -9,6 +9,7 @@ import cadquery
|
|||||||
|
|
||||||
CQSCRIPT = "<cqscript>"
|
CQSCRIPT = "<cqscript>"
|
||||||
|
|
||||||
|
|
||||||
def parse(script_source):
|
def parse(script_source):
|
||||||
"""
|
"""
|
||||||
Parses the script as a model, and returns a model.
|
Parses the script as a model, and returns a model.
|
||||||
@ -34,6 +35,7 @@ class CQModel(object):
|
|||||||
|
|
||||||
the build method can be used to generate a 3d model
|
the build method can be used to generate a 3d model
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, script_source):
|
def __init__(self, script_source):
|
||||||
"""
|
"""
|
||||||
Create an object by parsing the supplied python script.
|
Create an object by parsing the supplied python script.
|
||||||
@ -100,14 +102,18 @@ class CQModel(object):
|
|||||||
try:
|
try:
|
||||||
self.set_param_values(build_parameters)
|
self.set_param_values(build_parameters)
|
||||||
collector = ScriptCallback()
|
collector = ScriptCallback()
|
||||||
env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \
|
env = (
|
||||||
.add_entry("__name__", "__cqgi__") \
|
EnvironmentBuilder()
|
||||||
.add_entry("show_object", collector.show_object) \
|
.with_real_builtins()
|
||||||
.add_entry("debug", collector.debug) \
|
.with_cadquery_objects()
|
||||||
.add_entry("describe_parameter",collector.describe_parameter) \
|
.add_entry("__name__", "__cqgi__")
|
||||||
|
.add_entry("show_object", collector.show_object)
|
||||||
|
.add_entry("debug", collector.debug)
|
||||||
|
.add_entry("describe_parameter", collector.describe_parameter)
|
||||||
.build()
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
c = compile(self.ast_tree, CQSCRIPT, 'exec')
|
c = compile(self.ast_tree, CQSCRIPT, "exec")
|
||||||
exec(c, env)
|
exec(c, env)
|
||||||
result.set_debug(collector.debugObjects)
|
result.set_debug(collector.debugObjects)
|
||||||
result.set_success_result(collector.outputObjects)
|
result.set_success_result(collector.outputObjects)
|
||||||
@ -124,7 +130,9 @@ class CQModel(object):
|
|||||||
|
|
||||||
for k, v in params.items():
|
for k, v in params.items():
|
||||||
if k not in model_parameters:
|
if k not in model_parameters:
|
||||||
raise InvalidParameterError("Cannot set value '%s': not a parameter of the model." % k)
|
raise InvalidParameterError(
|
||||||
|
"Cannot set value '%s': not a parameter of the model." % k
|
||||||
|
)
|
||||||
|
|
||||||
p = model_parameters[k]
|
p = model_parameters[k]
|
||||||
p.set_value(v)
|
p.set_value(v)
|
||||||
@ -134,10 +142,12 @@ class ShapeResult(object):
|
|||||||
"""
|
"""
|
||||||
An object created by a build, including the user parameters provided
|
An object created by a build, including the user parameters provided
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.shape = None
|
self.shape = None
|
||||||
self.options = None
|
self.options = None
|
||||||
|
|
||||||
|
|
||||||
class BuildResult(object):
|
class BuildResult(object):
|
||||||
"""
|
"""
|
||||||
The result of executing a CadQuery script.
|
The result of executing a CadQuery script.
|
||||||
@ -149,6 +159,7 @@ class BuildResult(object):
|
|||||||
If unsuccessful, the exception property contains a reference to
|
If unsuccessful, the exception property contains a reference to
|
||||||
the stack trace that occurred.
|
the stack trace that occurred.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.buildTime = None
|
self.buildTime = None
|
||||||
self.results = [] # list of ShapeResult
|
self.results = [] # list of ShapeResult
|
||||||
@ -176,6 +187,7 @@ class ScriptMetadata(object):
|
|||||||
Defines the metadata for a parsed CQ Script.
|
Defines the metadata for a parsed CQ Script.
|
||||||
the parameters property is a dict of InputParameter objects.
|
the parameters property is a dict of InputParameter objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.parameters = {}
|
self.parameters = {}
|
||||||
|
|
||||||
@ -214,6 +226,7 @@ class InputParameter:
|
|||||||
provide additional metadata
|
provide additional metadata
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
#: the default value for the variable.
|
#: the default value for the variable.
|
||||||
@ -234,7 +247,9 @@ class InputParameter:
|
|||||||
self.ast_node = None
|
self.ast_node = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create(ast_node, var_name, var_type, default_value, valid_values=None, desc=None):
|
def create(
|
||||||
|
ast_node, var_name, var_type, default_value, valid_values=None, desc=None
|
||||||
|
):
|
||||||
|
|
||||||
if valid_values is None:
|
if valid_values is None:
|
||||||
valid_values = []
|
valid_values = []
|
||||||
@ -251,8 +266,10 @@ class InputParameter:
|
|||||||
def set_value(self, new_value):
|
def set_value(self, new_value):
|
||||||
if len(self.valid_values) > 0 and new_value not in self.valid_values:
|
if len(self.valid_values) > 0 and new_value not in self.valid_values:
|
||||||
raise InvalidParameterError(
|
raise InvalidParameterError(
|
||||||
"Cannot set value '{0:s}' for parameter '{1:s}': not a valid value. Valid values are {2:s} "
|
"Cannot set value '{0:s}' for parameter '{1:s}': not a valid value. Valid values are {2:s} ".format(
|
||||||
.format(str(new_value), self.name, str(self.valid_values)))
|
str(new_value), self.name, str(self.valid_values)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if self.varType == NumberParameterType:
|
if self.varType == NumberParameterType:
|
||||||
try:
|
try:
|
||||||
@ -265,28 +282,33 @@ class InputParameter:
|
|||||||
self.ast_node.n = f
|
self.ast_node.n = f
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise InvalidParameterError(
|
raise InvalidParameterError(
|
||||||
"Cannot set value '{0:s}' for parameter '{1:s}': parameter must be numeric."
|
"Cannot set value '{0:s}' for parameter '{1:s}': parameter must be numeric.".format(
|
||||||
.format(str(new_value), self.name))
|
str(new_value), self.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
elif self.varType == StringParameterType:
|
elif self.varType == StringParameterType:
|
||||||
self.ast_node.s = str(new_value)
|
self.ast_node.s = str(new_value)
|
||||||
elif self.varType == BooleanParameterType:
|
elif self.varType == BooleanParameterType:
|
||||||
if new_value:
|
if new_value:
|
||||||
if hasattr(ast, 'NameConstant'):
|
if hasattr(ast, "NameConstant"):
|
||||||
self.ast_node.value = True
|
self.ast_node.value = True
|
||||||
else:
|
else:
|
||||||
self.ast_node.id = 'True'
|
self.ast_node.id = "True"
|
||||||
else:
|
else:
|
||||||
if hasattr(ast, 'NameConstant'):
|
if hasattr(ast, "NameConstant"):
|
||||||
self.ast_node.value = False
|
self.ast_node.value = False
|
||||||
else:
|
else:
|
||||||
self.ast_node.id = 'False'
|
self.ast_node.id = "False"
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown Type of var: ", str(self.varType))
|
raise ValueError("Unknown Type of var: ", str(self.varType))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "InputParameter: {name=%s, type=%s, defaultValue=%s" % (
|
return "InputParameter: {name=%s, type=%s, defaultValue=%s" % (
|
||||||
self.name, str(self.varType), str(self.default_value))
|
self.name,
|
||||||
|
str(self.varType),
|
||||||
|
str(self.default_value),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ScriptCallback(object):
|
class ScriptCallback(object):
|
||||||
@ -295,6 +317,7 @@ class ScriptCallback(object):
|
|||||||
the show_object() method is exposed to CQ scripts, to allow them
|
the show_object() method is exposed to CQ scripts, to allow them
|
||||||
to return objects to the execution environment
|
to return objects to the execution environment
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.outputObjects = []
|
self.outputObjects = []
|
||||||
self.debugObjects = []
|
self.debugObjects = []
|
||||||
@ -335,12 +358,12 @@ class ScriptCallback(object):
|
|||||||
return len(self.outputObjects) > 0
|
return len(self.outputObjects) > 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidParameterError(Exception):
|
class InvalidParameterError(Exception):
|
||||||
"""
|
"""
|
||||||
Raised when an attempt is made to provide a new parameter value
|
Raised when an attempt is made to provide a new parameter value
|
||||||
that cannot be assigned to the model
|
that cannot be assigned to the model
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -349,6 +372,7 @@ class NoOutputError(Exception):
|
|||||||
Raised when the script does not execute the show_object() method to
|
Raised when the script does not execute the show_object() method to
|
||||||
return a solid
|
return a solid
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -386,6 +410,7 @@ class EnvironmentBuilder(object):
|
|||||||
The environment includes the builtins, as well as
|
The environment includes the builtins, as well as
|
||||||
the other methods the script will need.
|
the other methods the script will need.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.env = {}
|
self.env = {}
|
||||||
|
|
||||||
@ -393,12 +418,12 @@ class EnvironmentBuilder(object):
|
|||||||
return self.with_builtins(__builtins__)
|
return self.with_builtins(__builtins__)
|
||||||
|
|
||||||
def with_builtins(self, env_dict):
|
def with_builtins(self, env_dict):
|
||||||
self.env['__builtins__'] = env_dict
|
self.env["__builtins__"] = env_dict
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def with_cadquery_objects(self):
|
def with_cadquery_objects(self):
|
||||||
self.env['cadquery'] = cadquery
|
self.env["cadquery"] = cadquery
|
||||||
self.env['cq'] = cadquery
|
self.env["cq"] = cadquery
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_entry(self, name, value):
|
def add_entry(self, name, value):
|
||||||
@ -408,10 +433,12 @@ class EnvironmentBuilder(object):
|
|||||||
def build(self):
|
def build(self):
|
||||||
return self.env
|
return self.env
|
||||||
|
|
||||||
|
|
||||||
class ParameterDescriptionFinder(ast.NodeTransformer):
|
class ParameterDescriptionFinder(ast.NodeTransformer):
|
||||||
"""
|
"""
|
||||||
Visits a parse tree, looking for function calls to describe_parameter(var, description )
|
Visits a parse tree, looking for function calls to describe_parameter(var, description )
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cq_model):
|
def __init__(self, cq_model):
|
||||||
self.cqModel = cq_model
|
self.cqModel = cq_model
|
||||||
|
|
||||||
@ -420,7 +447,7 @@ class ParameterDescriptionFinder(ast.NodeTransformer):
|
|||||||
Called when we see a function call. Is it describe_parameter?
|
Called when we see a function call. Is it describe_parameter?
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if node.func.id == 'describe_parameter':
|
if node.func.id == "describe_parameter":
|
||||||
# looks like we have a call to our function.
|
# looks like we have a call to our function.
|
||||||
# first parameter is the variable,
|
# first parameter is the variable,
|
||||||
# second is the description
|
# second is the description
|
||||||
@ -433,6 +460,7 @@ class ParameterDescriptionFinder(ast.NodeTransformer):
|
|||||||
pass
|
pass
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
class ConstantAssignmentFinder(ast.NodeTransformer):
|
class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||||
"""
|
"""
|
||||||
Visits a parse tree, and adds script parameters to the cqModel
|
Visits a parse tree, and adds script parameters to the cqModel
|
||||||
@ -446,24 +474,42 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
|||||||
|
|
||||||
if type(value_node) == ast.Num:
|
if type(value_node) == ast.Num:
|
||||||
self.cqModel.add_script_parameter(
|
self.cqModel.add_script_parameter(
|
||||||
InputParameter.create(value_node, var_name, NumberParameterType, value_node.n))
|
InputParameter.create(
|
||||||
|
value_node, var_name, NumberParameterType, value_node.n
|
||||||
|
)
|
||||||
|
)
|
||||||
elif type(value_node) == ast.Str:
|
elif type(value_node) == ast.Str:
|
||||||
self.cqModel.add_script_parameter(
|
self.cqModel.add_script_parameter(
|
||||||
InputParameter.create(value_node, var_name, StringParameterType, value_node.s))
|
InputParameter.create(
|
||||||
|
value_node, var_name, StringParameterType, value_node.s
|
||||||
|
)
|
||||||
|
)
|
||||||
elif type(value_node) == ast.Name:
|
elif type(value_node) == ast.Name:
|
||||||
if value_node.id == 'True':
|
if value_node.id == "True":
|
||||||
self.cqModel.add_script_parameter(
|
self.cqModel.add_script_parameter(
|
||||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
InputParameter.create(
|
||||||
elif value_node.id == 'False':
|
value_node, var_name, BooleanParameterType, True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif value_node.id == "False":
|
||||||
self.cqModel.add_script_parameter(
|
self.cqModel.add_script_parameter(
|
||||||
InputParameter.create(value_node, var_name, BooleanParameterType, False))
|
InputParameter.create(
|
||||||
elif hasattr(ast, 'NameConstant') and type(value_node) == ast.NameConstant:
|
value_node, var_name, BooleanParameterType, False
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif hasattr(ast, "NameConstant") and type(value_node) == ast.NameConstant:
|
||||||
if value_node.value == True:
|
if value_node.value == True:
|
||||||
self.cqModel.add_script_parameter(
|
self.cqModel.add_script_parameter(
|
||||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
InputParameter.create(
|
||||||
|
value_node, var_name, BooleanParameterType, True
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.cqModel.add_script_parameter(
|
self.cqModel.add_script_parameter(
|
||||||
InputParameter.create(value_node, var_name, BooleanParameterType, False))
|
InputParameter.create(
|
||||||
|
value_node, var_name, BooleanParameterType, False
|
||||||
|
)
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
print("Unable to handle assignment for variable '%s'" % var_name)
|
print("Unable to handle assignment for variable '%s'" % var_name)
|
||||||
pass
|
pass
|
||||||
@ -479,7 +525,7 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
|||||||
|
|
||||||
# Handle the NamedConstant type that is only present in Python 3
|
# Handle the NamedConstant type that is only present in Python 3
|
||||||
astTypes = [ast.Num, ast.Str, ast.Name]
|
astTypes = [ast.Num, ast.Str, ast.Name]
|
||||||
if hasattr(ast, 'NameConstant'):
|
if hasattr(ast, "NameConstant"):
|
||||||
astTypes.append(ast.NameConstant)
|
astTypes.append(ast.NameConstant)
|
||||||
|
|
||||||
if type(node.value) in astTypes:
|
if type(node.value) in astTypes:
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
# from OCP.Visualization import Tesselator
|
# from OCP.Visualization import Tesselator
|
||||||
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if sys.version_info.major == 2:
|
if sys.version_info.major == 2:
|
||||||
import cStringIO as StringIO
|
import cStringIO as StringIO
|
||||||
else:
|
else:
|
||||||
@ -112,7 +114,7 @@ def readAndDeleteFile(fileName):
|
|||||||
return the contents as a string
|
return the contents as a string
|
||||||
"""
|
"""
|
||||||
res = ""
|
res = ""
|
||||||
with open(fileName, 'r') as f:
|
with open(fileName, "r") as f:
|
||||||
res = "{}".format(f.read())
|
res = "{}".format(f.read())
|
||||||
|
|
||||||
os.remove(fileName)
|
os.remove(fileName)
|
||||||
@ -148,32 +150,32 @@ class AmfWriter(object):
|
|||||||
self.tessellation = tessellation
|
self.tessellation = tessellation
|
||||||
|
|
||||||
def writeAmf(self, outFile):
|
def writeAmf(self, outFile):
|
||||||
amf = ET.Element('amf', units=self.units)
|
amf = ET.Element("amf", units=self.units)
|
||||||
# TODO: if result is a compound, we need to loop through them
|
# TODO: if result is a compound, we need to loop through them
|
||||||
object = ET.SubElement(amf, 'object', id="0")
|
object = ET.SubElement(amf, "object", id="0")
|
||||||
mesh = ET.SubElement(object, 'mesh')
|
mesh = ET.SubElement(object, "mesh")
|
||||||
vertices = ET.SubElement(mesh, 'vertices')
|
vertices = ET.SubElement(mesh, "vertices")
|
||||||
volume = ET.SubElement(mesh, 'volume')
|
volume = ET.SubElement(mesh, "volume")
|
||||||
|
|
||||||
# add vertices
|
# add vertices
|
||||||
for v in self.tessellation[0]:
|
for v in self.tessellation[0]:
|
||||||
vtx = ET.SubElement(vertices, 'vertex')
|
vtx = ET.SubElement(vertices, "vertex")
|
||||||
coord = ET.SubElement(vtx, 'coordinates')
|
coord = ET.SubElement(vtx, "coordinates")
|
||||||
x = ET.SubElement(coord, 'x')
|
x = ET.SubElement(coord, "x")
|
||||||
x.text = str(v.x)
|
x.text = str(v.x)
|
||||||
y = ET.SubElement(coord, 'y')
|
y = ET.SubElement(coord, "y")
|
||||||
y.text = str(v.y)
|
y.text = str(v.y)
|
||||||
z = ET.SubElement(coord, 'z')
|
z = ET.SubElement(coord, "z")
|
||||||
z.text = str(v.z)
|
z.text = str(v.z)
|
||||||
|
|
||||||
# add triangles
|
# add triangles
|
||||||
for t in self.tessellation[1]:
|
for t in self.tessellation[1]:
|
||||||
triangle = ET.SubElement(volume, 'triangle')
|
triangle = ET.SubElement(volume, "triangle")
|
||||||
v1 = ET.SubElement(triangle, 'v1')
|
v1 = ET.SubElement(triangle, "v1")
|
||||||
v1.text = str(t[0])
|
v1.text = str(t[0])
|
||||||
v2 = ET.SubElement(triangle, 'v2')
|
v2 = ET.SubElement(triangle, "v2")
|
||||||
v2.text = str(t[1])
|
v2.text = str(t[1])
|
||||||
v3 = ET.SubElement(triangle, 'v3')
|
v3 = ET.SubElement(triangle, "v3")
|
||||||
v3.text = str(t[2])
|
v3.text = str(t[2])
|
||||||
|
|
||||||
amf = ET.ElementTree(amf).write(outFile, xml_declaration=True)
|
amf = ET.ElementTree(amf).write(outFile, xml_declaration=True)
|
||||||
@ -211,11 +213,11 @@ class JsonMesh(object):
|
|||||||
|
|
||||||
def toJson(self):
|
def toJson(self):
|
||||||
return JSON_TEMPLATE % {
|
return JSON_TEMPLATE % {
|
||||||
'vertices': str(self.vertices),
|
"vertices": str(self.vertices),
|
||||||
'faces': str(self.faces),
|
"faces": str(self.faces),
|
||||||
'nVertices': self.nVertices,
|
"nVertices": self.nVertices,
|
||||||
'nFaces': self.nFaces
|
"nFaces": self.nFaces,
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
def makeSVGedge(e):
|
def makeSVGedge(e):
|
||||||
@ -229,20 +231,16 @@ def makeSVGedge(e):
|
|||||||
start = curve.FirstParameter()
|
start = curve.FirstParameter()
|
||||||
end = curve.LastParameter()
|
end = curve.LastParameter()
|
||||||
|
|
||||||
points = GCPnts_QuasiUniformDeflection(curve,
|
points = GCPnts_QuasiUniformDeflection(curve, DISCRETIZATION_TOLERANCE, start, end)
|
||||||
DISCRETIZATION_TOLERANCE,
|
|
||||||
start,
|
|
||||||
end)
|
|
||||||
|
|
||||||
if points.IsDone():
|
if points.IsDone():
|
||||||
point_it = (points.Value(i + 1) for i in
|
point_it = (points.Value(i + 1) for i in range(points.NbPoints()))
|
||||||
range(points.NbPoints()))
|
|
||||||
|
|
||||||
p = next(point_it)
|
p = next(point_it)
|
||||||
cs.write('M{},{} '.format(p.X(), p.Y()))
|
cs.write("M{},{} ".format(p.X(), p.Y()))
|
||||||
|
|
||||||
for p in point_it:
|
for p in point_it:
|
||||||
cs.write('L{},{} '.format(p.X(), p.Y()))
|
cs.write("L{},{} ".format(p.X(), p.Y()))
|
||||||
|
|
||||||
return cs.getvalue()
|
return cs.getvalue()
|
||||||
|
|
||||||
@ -271,7 +269,7 @@ def getSVG(shape, opts=None):
|
|||||||
Export a shape to SVG
|
Export a shape to SVG
|
||||||
"""
|
"""
|
||||||
|
|
||||||
d = {'width': 800, 'height': 240, 'marginLeft': 200, 'marginTop': 20}
|
d = {"width": 800, "height": 240, "marginLeft": 200, "marginTop": 20}
|
||||||
|
|
||||||
if opts:
|
if opts:
|
||||||
d.update(opts)
|
d.update(opts)
|
||||||
@ -279,17 +277,15 @@ def getSVG(shape, opts=None):
|
|||||||
# need to guess the scale and the coordinate center
|
# need to guess the scale and the coordinate center
|
||||||
uom = guessUnitOfMeasure(shape)
|
uom = guessUnitOfMeasure(shape)
|
||||||
|
|
||||||
width = float(d['width'])
|
width = float(d["width"])
|
||||||
height = float(d['height'])
|
height = float(d["height"])
|
||||||
marginLeft = float(d['marginLeft'])
|
marginLeft = float(d["marginLeft"])
|
||||||
marginTop = float(d['marginTop'])
|
marginTop = float(d["marginTop"])
|
||||||
|
|
||||||
hlr = HLRBRep_Algo()
|
hlr = HLRBRep_Algo()
|
||||||
hlr.Add(shape.wrapped)
|
hlr.Add(shape.wrapped)
|
||||||
|
|
||||||
projector = HLRAlgo_Projector(gp_Ax2(gp_Pnt(),
|
projector = HLRAlgo_Projector(gp_Ax2(gp_Pnt(), DEFAULT_DIR))
|
||||||
DEFAULT_DIR)
|
|
||||||
)
|
|
||||||
|
|
||||||
hlr.Projector(projector)
|
hlr.Projector(projector)
|
||||||
hlr.Update()
|
hlr.Update()
|
||||||
@ -330,8 +326,7 @@ def getSVG(shape, opts=None):
|
|||||||
# convert to native CQ objects
|
# convert to native CQ objects
|
||||||
visible = list(map(Shape, visible))
|
visible = list(map(Shape, visible))
|
||||||
hidden = list(map(Shape, hidden))
|
hidden = list(map(Shape, hidden))
|
||||||
(hiddenPaths, visiblePaths) = getPaths(visible,
|
(hiddenPaths, visiblePaths) = getPaths(visible, hidden)
|
||||||
hidden)
|
|
||||||
|
|
||||||
# get bounding box -- these are all in 2-d space
|
# get bounding box -- these are all in 2-d space
|
||||||
bb = Compound.makeCompound(hidden + visible).BoundingBox()
|
bb = Compound.makeCompound(hidden + visible).BoundingBox()
|
||||||
@ -340,8 +335,10 @@ def getSVG(shape, opts=None):
|
|||||||
unitScale = min(width / bb.xlen * 0.75, height / bb.ylen * 0.75)
|
unitScale = min(width / bb.xlen * 0.75, height / bb.ylen * 0.75)
|
||||||
|
|
||||||
# compute amount to translate-- move the top left into view
|
# compute amount to translate-- move the top left into view
|
||||||
(xTranslate, yTranslate) = ((0 - bb.xmin) + marginLeft /
|
(xTranslate, yTranslate) = (
|
||||||
unitScale, (0 - bb.ymax) - marginTop / unitScale)
|
(0 - bb.xmin) + marginLeft / unitScale,
|
||||||
|
(0 - bb.ymax) - marginTop / unitScale,
|
||||||
|
)
|
||||||
|
|
||||||
# compute paths ( again -- had to strip out freecad crap )
|
# compute paths ( again -- had to strip out freecad crap )
|
||||||
hiddenContent = ""
|
hiddenContent = ""
|
||||||
@ -363,7 +360,7 @@ def getSVG(shape, opts=None):
|
|||||||
"width": str(width),
|
"width": str(width),
|
||||||
"height": str(height),
|
"height": str(height),
|
||||||
"textboxY": str(height - 30),
|
"textboxY": str(height - 30),
|
||||||
"uom": str(uom)
|
"uom": str(uom),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# svg = SVG_TEMPLATE % (
|
# svg = SVG_TEMPLATE % (
|
||||||
@ -380,7 +377,7 @@ def exportSVG(shape, fileName):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
svg = getSVG(shape.val())
|
svg = getSVG(shape.val())
|
||||||
f = open(fileName, 'w')
|
f = open(fileName, "w")
|
||||||
f.write(svg)
|
f.write(svg)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
@ -465,4 +462,4 @@ SVG_TEMPLATE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|||||||
</svg>
|
</svg>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PATHTEMPLATE = "\t\t\t<path d=\"%s\" />\n"
|
PATHTEMPLATE = '\t\t\t<path d="%s" />\n'
|
||||||
|
@ -132,16 +132,13 @@ class Vector(object):
|
|||||||
return self.wrapped.Angle(v.wrapped)
|
return self.wrapped.Angle(v.wrapped)
|
||||||
|
|
||||||
def distanceToLine(self):
|
def distanceToLine(self):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError("Have not needed this yet, but OCCT supports it!")
|
||||||
"Have not needed this yet, but FreeCAD supports it!")
|
|
||||||
|
|
||||||
def projectToLine(self):
|
def projectToLine(self):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError("Have not needed this yet, but OCCT supports it!")
|
||||||
"Have not needed this yet, but FreeCAD supports it!")
|
|
||||||
|
|
||||||
def distanceToPlane(self):
|
def distanceToPlane(self):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError("Have not needed this yet, but OCCT supports it!")
|
||||||
"Have not needed this yet, but FreeCAD supports it!")
|
|
||||||
|
|
||||||
def projectToPlane(self, plane):
|
def projectToPlane(self, plane):
|
||||||
"""
|
"""
|
||||||
@ -163,18 +160,19 @@ class Vector(object):
|
|||||||
return self.Length
|
return self.Length
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Vector: ' + str((self.x, self.y, self.z))
|
return "Vector: " + str((self.x, self.y, self.z))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'Vector: ' + str((self.x, self.y, self.z))
|
return "Vector: " + str((self.x, self.y, self.z))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.wrapped.IsEqual(other.wrapped, 0.00001, 0.00001)
|
return self.wrapped.IsEqual(other.wrapped, 0.00001, 0.00001)
|
||||||
'''
|
|
||||||
|
"""
|
||||||
is not implemented in OCC
|
is not implemented in OCC
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return self.wrapped.__ne__(other)
|
return self.wrapped.__ne__(other)
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def toPnt(self):
|
def toPnt(self):
|
||||||
|
|
||||||
@ -222,44 +220,48 @@ class Matrix:
|
|||||||
elif isinstance(matrix, (list, tuple)):
|
elif isinstance(matrix, (list, tuple)):
|
||||||
# Validate matrix size & 4x4 last row value
|
# Validate matrix size & 4x4 last row value
|
||||||
valid_sizes = all(
|
valid_sizes = all(
|
||||||
(isinstance(row, (list, tuple)) and (len(row) == 4))
|
(isinstance(row, (list, tuple)) and (len(row) == 4)) for row in matrix
|
||||||
for row in matrix
|
|
||||||
) and len(matrix) in (3, 4)
|
) and len(matrix) in (3, 4)
|
||||||
if not valid_sizes:
|
if not valid_sizes:
|
||||||
raise TypeError("Matrix constructor requires 2d list of 4x3 or 4x4, but got: {!r}".format(matrix))
|
raise TypeError(
|
||||||
|
"Matrix constructor requires 2d list of 4x3 or 4x4, but got: {!r}".format(
|
||||||
|
matrix
|
||||||
|
)
|
||||||
|
)
|
||||||
elif (len(matrix) == 4) and (tuple(matrix[3]) != (0, 0, 0, 1)):
|
elif (len(matrix) == 4) and (tuple(matrix[3]) != (0, 0, 0, 1)):
|
||||||
raise ValueError("Expected the last row to be [0,0,0,1], but got: {!r}".format(matrix[3]))
|
raise ValueError(
|
||||||
|
"Expected the last row to be [0,0,0,1], but got: {!r}".format(
|
||||||
|
matrix[3]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Assign values to matrix
|
# Assign values to matrix
|
||||||
self.wrapped = gp_GTrsf()
|
self.wrapped = gp_GTrsf()
|
||||||
[self.wrapped.SetValue(i+1,j+1,e)
|
[
|
||||||
|
self.wrapped.SetValue(i + 1, j + 1, e)
|
||||||
for i, row in enumerate(matrix[:3])
|
for i, row in enumerate(matrix[:3])
|
||||||
for j,e in enumerate(row)]
|
for j, e in enumerate(row)
|
||||||
|
]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError("Invalid param to matrix constructor: {}".format(matrix))
|
||||||
"Invalid param to matrix constructor: {}".format(matrix))
|
|
||||||
|
|
||||||
def rotateX(self, angle):
|
def rotateX(self, angle):
|
||||||
|
|
||||||
self._rotate(gp.OX_s(),
|
self._rotate(gp.OX_s(), angle)
|
||||||
angle)
|
|
||||||
|
|
||||||
def rotateY(self, angle):
|
def rotateY(self, angle):
|
||||||
|
|
||||||
self._rotate(gp.OY_s(),
|
self._rotate(gp.OY_s(), angle)
|
||||||
angle)
|
|
||||||
|
|
||||||
def rotateZ(self, angle):
|
def rotateZ(self, angle):
|
||||||
|
|
||||||
self._rotate(gp.OZ_s(),
|
self._rotate(gp.OZ_s(), angle)
|
||||||
angle)
|
|
||||||
|
|
||||||
def _rotate(self, direction, angle):
|
def _rotate(self, direction, angle):
|
||||||
|
|
||||||
new = gp_Trsf()
|
new = gp_Trsf()
|
||||||
new.SetRotation(direction,
|
new.SetRotation(direction, angle)
|
||||||
angle)
|
|
||||||
|
|
||||||
self.wrapped = self.wrapped * gp_GTrsf(new)
|
self.wrapped = self.wrapped * gp_GTrsf(new)
|
||||||
|
|
||||||
@ -279,8 +281,9 @@ class Matrix:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
trsf = self.wrapped
|
trsf = self.wrapped
|
||||||
data = [[trsf.Value(i,j) for j in range(1,5)] for i in range(1,4)] + \
|
data = [[trsf.Value(i, j) for j in range(1, 5)] for i in range(1, 4)] + [
|
||||||
[[0.,0.,0.,1.]]
|
[0.0, 0.0, 0.0, 1.0]
|
||||||
|
]
|
||||||
|
|
||||||
return [data[j][i] for i in range(4) for j in range(4)]
|
return [data[j][i] for i in range(4) for j in range(4)]
|
||||||
|
|
||||||
@ -298,7 +301,7 @@ class Matrix:
|
|||||||
else:
|
else:
|
||||||
# gp_GTrsf doesn't provide access to the 4th row because it has
|
# gp_GTrsf doesn't provide access to the 4th row because it has
|
||||||
# an implied value as below:
|
# an implied value as below:
|
||||||
return [0., 0., 0., 1.][c]
|
return [0.0, 0.0, 0.0, 1.0][c]
|
||||||
else:
|
else:
|
||||||
raise IndexError("Out of bounds access into 4x4 matrix: {!r}".format(rc))
|
raise IndexError("Out of bounds access into 4x4 matrix: {!r}".format(rc))
|
||||||
|
|
||||||
@ -352,95 +355,94 @@ class Plane(object):
|
|||||||
|
|
||||||
namedPlanes = {
|
namedPlanes = {
|
||||||
# origin, xDir, normal
|
# origin, xDir, normal
|
||||||
'XY': Plane(origin, (1, 0, 0), (0, 0, 1)),
|
"XY": Plane(origin, (1, 0, 0), (0, 0, 1)),
|
||||||
'YZ': Plane(origin, (0, 1, 0), (1, 0, 0)),
|
"YZ": Plane(origin, (0, 1, 0), (1, 0, 0)),
|
||||||
'ZX': Plane(origin, (0, 0, 1), (0, 1, 0)),
|
"ZX": Plane(origin, (0, 0, 1), (0, 1, 0)),
|
||||||
'XZ': Plane(origin, (1, 0, 0), (0, -1, 0)),
|
"XZ": Plane(origin, (1, 0, 0), (0, -1, 0)),
|
||||||
'YX': Plane(origin, (0, 1, 0), (0, 0, -1)),
|
"YX": Plane(origin, (0, 1, 0), (0, 0, -1)),
|
||||||
'ZY': Plane(origin, (0, 0, 1), (-1, 0, 0)),
|
"ZY": Plane(origin, (0, 0, 1), (-1, 0, 0)),
|
||||||
'front': Plane(origin, (1, 0, 0), (0, 0, 1)),
|
"front": Plane(origin, (1, 0, 0), (0, 0, 1)),
|
||||||
'back': Plane(origin, (-1, 0, 0), (0, 0, -1)),
|
"back": Plane(origin, (-1, 0, 0), (0, 0, -1)),
|
||||||
'left': Plane(origin, (0, 0, 1), (-1, 0, 0)),
|
"left": Plane(origin, (0, 0, 1), (-1, 0, 0)),
|
||||||
'right': Plane(origin, (0, 0, -1), (1, 0, 0)),
|
"right": Plane(origin, (0, 0, -1), (1, 0, 0)),
|
||||||
'top': Plane(origin, (1, 0, 0), (0, 1, 0)),
|
"top": Plane(origin, (1, 0, 0), (0, 1, 0)),
|
||||||
'bottom': Plane(origin, (1, 0, 0), (0, -1, 0))
|
"bottom": Plane(origin, (1, 0, 0), (0, -1, 0)),
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return namedPlanes[stdName]
|
return namedPlanes[stdName]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError('Supported names are {}'.format(
|
raise ValueError("Supported names are {}".format(list(namedPlanes.keys())))
|
||||||
list(namedPlanes.keys())))
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def XY(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
def XY(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||||
plane = Plane.named('XY', origin)
|
plane = Plane.named("XY", origin)
|
||||||
plane._setPlaneDir(xDir)
|
plane._setPlaneDir(xDir)
|
||||||
return plane
|
return plane
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def YZ(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
|
def YZ(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
|
||||||
plane = Plane.named('YZ', origin)
|
plane = Plane.named("YZ", origin)
|
||||||
plane._setPlaneDir(xDir)
|
plane._setPlaneDir(xDir)
|
||||||
return plane
|
return plane
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ZX(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
|
def ZX(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
|
||||||
plane = Plane.named('ZX', origin)
|
plane = Plane.named("ZX", origin)
|
||||||
plane._setPlaneDir(xDir)
|
plane._setPlaneDir(xDir)
|
||||||
return plane
|
return plane
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def XZ(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
def XZ(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||||
plane = Plane.named('XZ', origin)
|
plane = Plane.named("XZ", origin)
|
||||||
plane._setPlaneDir(xDir)
|
plane._setPlaneDir(xDir)
|
||||||
return plane
|
return plane
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def YX(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
|
def YX(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
|
||||||
plane = Plane.named('YX', origin)
|
plane = Plane.named("YX", origin)
|
||||||
plane._setPlaneDir(xDir)
|
plane._setPlaneDir(xDir)
|
||||||
return plane
|
return plane
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ZY(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
|
def ZY(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
|
||||||
plane = Plane.named('ZY', origin)
|
plane = Plane.named("ZY", origin)
|
||||||
plane._setPlaneDir(xDir)
|
plane._setPlaneDir(xDir)
|
||||||
return plane
|
return plane
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def front(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
def front(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||||
plane = Plane.named('front', origin)
|
plane = Plane.named("front", origin)
|
||||||
plane._setPlaneDir(xDir)
|
plane._setPlaneDir(xDir)
|
||||||
return plane
|
return plane
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def back(cls, origin=(0, 0, 0), xDir=Vector(-1, 0, 0)):
|
def back(cls, origin=(0, 0, 0), xDir=Vector(-1, 0, 0)):
|
||||||
plane = Plane.named('back', origin)
|
plane = Plane.named("back", origin)
|
||||||
plane._setPlaneDir(xDir)
|
plane._setPlaneDir(xDir)
|
||||||
return plane
|
return plane
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def left(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
|
def left(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
|
||||||
plane = Plane.named('left', origin)
|
plane = Plane.named("left", origin)
|
||||||
plane._setPlaneDir(xDir)
|
plane._setPlaneDir(xDir)
|
||||||
return plane
|
return plane
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def right(cls, origin=(0, 0, 0), xDir=Vector(0, 0, -1)):
|
def right(cls, origin=(0, 0, 0), xDir=Vector(0, 0, -1)):
|
||||||
plane = Plane.named('right', origin)
|
plane = Plane.named("right", origin)
|
||||||
plane._setPlaneDir(xDir)
|
plane._setPlaneDir(xDir)
|
||||||
return plane
|
return plane
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def top(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
def top(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||||
plane = Plane.named('top', origin)
|
plane = Plane.named("top", origin)
|
||||||
plane._setPlaneDir(xDir)
|
plane._setPlaneDir(xDir)
|
||||||
return plane
|
return plane
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def bottom(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
def bottom(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||||
plane = Plane.named('bottom', origin)
|
plane = Plane.named("bottom", origin)
|
||||||
plane._setPlaneDir(xDir)
|
plane._setPlaneDir(xDir)
|
||||||
return plane
|
return plane
|
||||||
|
|
||||||
@ -458,12 +460,12 @@ class Plane(object):
|
|||||||
:return: a plane in the global space, with the xDirection of the plane in the specified direction.
|
:return: a plane in the global space, with the xDirection of the plane in the specified direction.
|
||||||
"""
|
"""
|
||||||
zDir = Vector(normal)
|
zDir = Vector(normal)
|
||||||
if (zDir.Length == 0.0):
|
if zDir.Length == 0.0:
|
||||||
raise ValueError('normal should be non null')
|
raise ValueError("normal should be non null")
|
||||||
|
|
||||||
xDir = Vector(xDir)
|
xDir = Vector(xDir)
|
||||||
if (xDir.Length == 0.0):
|
if xDir.Length == 0.0:
|
||||||
raise ValueError('xDir should be non null')
|
raise ValueError("xDir should be non null")
|
||||||
|
|
||||||
self.zDir = zDir.normalized()
|
self.zDir = zDir.normalized()
|
||||||
self._setPlaneDir(xDir)
|
self._setPlaneDir(xDir)
|
||||||
@ -489,6 +491,7 @@ class Plane(object):
|
|||||||
@property
|
@property
|
||||||
def origin(self):
|
def origin(self):
|
||||||
return self._origin
|
return self._origin
|
||||||
|
|
||||||
# TODO is this property rly needed -- why not handle this in the constructor
|
# TODO is this property rly needed -- why not handle this in the constructor
|
||||||
|
|
||||||
@origin.setter
|
@origin.setter
|
||||||
@ -545,7 +548,7 @@ class Plane(object):
|
|||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
'''
|
"""
|
||||||
# TODO: also use a set of points along the wire to test as well.
|
# TODO: also use a set of points along the wire to test as well.
|
||||||
# TODO: would it be more efficient to create objects in the local
|
# TODO: would it be more efficient to create objects in the local
|
||||||
# coordinate system, and then transform to global
|
# coordinate system, and then transform to global
|
||||||
@ -562,7 +565,7 @@ class Plane(object):
|
|||||||
# findOutsideBox actually inspects both ways, here we only want to
|
# findOutsideBox actually inspects both ways, here we only want to
|
||||||
# know if one is inside the other
|
# know if one is inside the other
|
||||||
return bb == BoundBox.findOutsideBox2D(bb, tb)
|
return bb == BoundBox.findOutsideBox2D(bb, tb)
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def toLocalCoords(self, obj):
|
def toLocalCoords(self, obj):
|
||||||
"""Project the provided coordinates onto this plane
|
"""Project the provided coordinates onto this plane
|
||||||
@ -588,7 +591,9 @@ class Plane(object):
|
|||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Don't know how to convert type {} to local coordinates".format(
|
"Don't know how to convert type {} to local coordinates".format(
|
||||||
type(obj)))
|
type(obj)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def toWorldCoords(self, tuplePoint):
|
def toWorldCoords(self, tuplePoint):
|
||||||
"""Convert a point in local coordinates to global coordinates
|
"""Convert a point in local coordinates to global coordinates
|
||||||
@ -619,19 +624,29 @@ class Plane(object):
|
|||||||
:param rotate: Vector [xDegrees, yDegrees, zDegrees]
|
:param rotate: Vector [xDegrees, yDegrees, zDegrees]
|
||||||
:return: a copy of this plane rotated as requested.
|
:return: a copy of this plane rotated as requested.
|
||||||
"""
|
"""
|
||||||
|
# NB: this is not a geometric Vector
|
||||||
rotate = Vector(rotate)
|
rotate = Vector(rotate)
|
||||||
# Convert to radians.
|
# Convert to radians.
|
||||||
rotate = rotate.multiply(math.pi / 180.0)
|
rotate = rotate.multiply(math.pi / 180.0)
|
||||||
|
|
||||||
# Compute rotation matrix.
|
# Compute rotation matrix.
|
||||||
m = Matrix()
|
T1 = gp_Trsf()
|
||||||
m.rotateX(rotate.x)
|
T1.SetRotation(
|
||||||
m.rotateY(rotate.y)
|
gp_Ax1(gp_Pnt(*(0, 0, 0)), gp_Dir(*self.xDir.toTuple())), rotate.x
|
||||||
m.rotateZ(rotate.z)
|
)
|
||||||
|
T2 = gp_Trsf()
|
||||||
|
T2.SetRotation(
|
||||||
|
gp_Ax1(gp_Pnt(*(0, 0, 0)), gp_Dir(*self.yDir.toTuple())), rotate.y
|
||||||
|
)
|
||||||
|
T3 = gp_Trsf()
|
||||||
|
T3.SetRotation(
|
||||||
|
gp_Ax1(gp_Pnt(*(0, 0, 0)), gp_Dir(*self.zDir.toTuple())), rotate.z
|
||||||
|
)
|
||||||
|
T = Matrix(gp_GTrsf(T1 * T2 * T3))
|
||||||
|
|
||||||
# Compute the new plane.
|
# Compute the new plane.
|
||||||
newXdir = self.xDir.transform(m)
|
newXdir = self.xDir.transform(T)
|
||||||
newZdir = self.zDir.transform(m)
|
newZdir = self.zDir.transform(T)
|
||||||
|
|
||||||
return Plane(self.origin, newXdir, newZdir)
|
return Plane(self.origin, newXdir, newZdir)
|
||||||
|
|
||||||
@ -655,7 +670,7 @@ class Plane(object):
|
|||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
'''
|
"""
|
||||||
resultWires = []
|
resultWires = []
|
||||||
for w in listOfShapes:
|
for w in listOfShapes:
|
||||||
mirrored = w.transformGeometry(rotationMatrix.wrapped)
|
mirrored = w.transformGeometry(rotationMatrix.wrapped)
|
||||||
@ -681,21 +696,19 @@ class Plane(object):
|
|||||||
|
|
||||||
resultWires.append(cadquery.Shape.cast(mirroredWire))
|
resultWires.append(cadquery.Shape.cast(mirroredWire))
|
||||||
|
|
||||||
return resultWires'''
|
return resultWires"""
|
||||||
|
|
||||||
def mirrorInPlane(self, listOfShapes, axis='X'):
|
def mirrorInPlane(self, listOfShapes, axis="X"):
|
||||||
|
|
||||||
local_coord_system = gp_Ax3(self.origin.toPnt(),
|
local_coord_system = gp_Ax3(
|
||||||
self.zDir.toDir(),
|
self.origin.toPnt(), self.zDir.toDir(), self.xDir.toDir()
|
||||||
self.xDir.toDir())
|
)
|
||||||
T = gp_Trsf()
|
T = gp_Trsf()
|
||||||
|
|
||||||
if axis == 'X':
|
if axis == "X":
|
||||||
T.SetMirror(gp_Ax1(self.origin.toPnt(),
|
T.SetMirror(gp_Ax1(self.origin.toPnt(), local_coord_system.XDirection()))
|
||||||
local_coord_system.XDirection()))
|
elif axis == "Y":
|
||||||
elif axis == 'Y':
|
T.SetMirror(gp_Ax1(self.origin.toPnt(), local_coord_system.YDirection()))
|
||||||
T.SetMirror(gp_Ax1(self.origin.toPnt(),
|
|
||||||
local_coord_system.YDirection()))
|
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@ -731,17 +744,16 @@ class Plane(object):
|
|||||||
inverseT = gp_Trsf()
|
inverseT = gp_Trsf()
|
||||||
|
|
||||||
global_coord_system = gp_Ax3()
|
global_coord_system = gp_Ax3()
|
||||||
local_coord_system = gp_Ax3(gp_Pnt(*self.origin.toTuple()),
|
local_coord_system = gp_Ax3(
|
||||||
|
gp_Pnt(*self.origin.toTuple()),
|
||||||
gp_Dir(*self.zDir.toTuple()),
|
gp_Dir(*self.zDir.toTuple()),
|
||||||
gp_Dir(*self.xDir.toTuple())
|
gp_Dir(*self.xDir.toTuple()),
|
||||||
)
|
)
|
||||||
|
|
||||||
forwardT.SetTransformation(global_coord_system,
|
forwardT.SetTransformation(global_coord_system, local_coord_system)
|
||||||
local_coord_system)
|
|
||||||
forward.wrapped = gp_GTrsf(forwardT)
|
forward.wrapped = gp_GTrsf(forwardT)
|
||||||
|
|
||||||
inverseT.SetTransformation(local_coord_system,
|
inverseT.SetTransformation(local_coord_system, global_coord_system)
|
||||||
global_coord_system)
|
|
||||||
inverse.wrapped = gp_GTrsf(inverseT)
|
inverse.wrapped = gp_GTrsf(inverseT)
|
||||||
|
|
||||||
# TODO verify if this is OK
|
# TODO verify if this is OK
|
||||||
@ -751,7 +763,7 @@ class Plane(object):
|
|||||||
|
|
||||||
|
|
||||||
class BoundBox(object):
|
class BoundBox(object):
|
||||||
"""A BoundingBox for an object or set of objects. Wraps the OCP.one"""
|
"""A BoundingBox for an object or set of objects. Wraps the OCP one"""
|
||||||
|
|
||||||
def __init__(self, bb):
|
def __init__(self, bb):
|
||||||
self.wrapped = bb
|
self.wrapped = bb
|
||||||
@ -767,9 +779,7 @@ class BoundBox(object):
|
|||||||
self.zmax = ZMax
|
self.zmax = ZMax
|
||||||
self.zlen = ZMax - ZMin
|
self.zlen = ZMax - ZMin
|
||||||
|
|
||||||
self.center = Vector((XMax + XMin) / 2,
|
self.center = Vector((XMax + XMin) / 2, (YMax + YMin) / 2, (ZMax + ZMin) / 2)
|
||||||
(YMax + YMin) / 2,
|
|
||||||
(ZMax + ZMin) / 2)
|
|
||||||
|
|
||||||
self.DiagonalLength = self.wrapped.SquareExtent() ** 0.5
|
self.DiagonalLength = self.wrapped.SquareExtent() ** 0.5
|
||||||
|
|
||||||
@ -810,30 +820,36 @@ class BoundBox(object):
|
|||||||
the built-in implementation i do not understand.
|
the built-in implementation i do not understand.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if (bb1.XMin < bb2.XMin and
|
if (
|
||||||
bb1.XMax > bb2.XMax and
|
bb1.XMin < bb2.XMin
|
||||||
bb1.YMin < bb2.YMin and
|
and bb1.XMax > bb2.XMax
|
||||||
bb1.YMax > bb2.YMax):
|
and bb1.YMin < bb2.YMin
|
||||||
|
and bb1.YMax > bb2.YMax
|
||||||
|
):
|
||||||
return bb1
|
return bb1
|
||||||
|
|
||||||
if (bb2.XMin < bb1.XMin and
|
if (
|
||||||
bb2.XMax > bb1.XMax and
|
bb2.XMin < bb1.XMin
|
||||||
bb2.YMin < bb1.YMin and
|
and bb2.XMax > bb1.XMax
|
||||||
bb2.YMax > bb1.YMax):
|
and bb2.YMin < bb1.YMin
|
||||||
|
and bb2.YMax > bb1.YMax
|
||||||
|
):
|
||||||
return bb2
|
return bb2
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _fromTopoDS(cls, shape, tol=None, optimal=True):
|
def _fromTopoDS(cls, shape, tol=None, optimal=True):
|
||||||
'''
|
"""
|
||||||
Constructs a bounding box from a TopoDS_Shape
|
Constructs a bounding box from a TopoDS_Shape
|
||||||
'''
|
"""
|
||||||
tol = TOL if tol is None else tol # tol = TOL (by default)
|
tol = TOL if tol is None else tol # tol = TOL (by default)
|
||||||
bbox = Bnd_Box()
|
bbox = Bnd_Box()
|
||||||
|
|
||||||
if optimal:
|
if optimal:
|
||||||
BRepBndLib.AddOptimal_s(shape, bbox) #this is 'exact' but expensive - not yet wrapped by PythonOCC
|
BRepBndLib.AddOptimal_s(
|
||||||
|
shape, bbox
|
||||||
|
) # this is 'exact' but expensive - not yet wrapped by PythonOCC
|
||||||
else:
|
else:
|
||||||
mesh = BRepMesh_IncrementalMesh(shape, tol, True)
|
mesh = BRepMesh_IncrementalMesh(shape, tol, True)
|
||||||
mesh.Perform()
|
mesh.Perform()
|
||||||
@ -844,12 +860,14 @@ class BoundBox(object):
|
|||||||
|
|
||||||
def isInside(self, b2):
|
def isInside(self, b2):
|
||||||
"""Is the provided bounding box inside this one?"""
|
"""Is the provided bounding box inside this one?"""
|
||||||
if (b2.xmin > self.xmin and
|
if (
|
||||||
b2.ymin > self.ymin and
|
b2.xmin > self.xmin
|
||||||
b2.zmin > self.zmin and
|
and b2.ymin > self.ymin
|
||||||
b2.xmax < self.xmax and
|
and b2.zmin > self.zmin
|
||||||
b2.ymax < self.ymax and
|
and b2.xmax < self.xmax
|
||||||
b2.zmax < self.zmax):
|
and b2.ymax < self.ymax
|
||||||
|
and b2.zmax < self.zmax
|
||||||
|
):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -1,103 +1,8 @@
|
|||||||
from OCC.Display.WebGl.x3dom_renderer import X3DExporter
|
from IPython.display import SVG
|
||||||
from OCC.Core.gp import gp_Quaternion, gp_Vec
|
|
||||||
from uuid import uuid4
|
|
||||||
from math import tan
|
|
||||||
from xml.etree import ElementTree
|
|
||||||
|
|
||||||
from .geom import BoundBox
|
from .exporters import toString, ExportTypes
|
||||||
|
|
||||||
BOILERPLATE = \
|
|
||||||
'''
|
|
||||||
<link rel='stylesheet' type='text/css' href='http://www.x3dom.org/download/x3dom.css'></link>
|
|
||||||
<div style='height: {height}px; width: 100%;' width='100%' height='{height}px'>
|
|
||||||
<x3d style='height: {height}px; width: 100%;' id='{id}' width='100%' height='{height}px'>
|
|
||||||
<scene>
|
|
||||||
<Viewpoint position='{x},{y},{z}' centerOfRotation='{x0} {y0} {z0}' orientation='{rot}' fieldOfView='{fov}'></Viewpoint>
|
|
||||||
{src}
|
|
||||||
</scene>
|
|
||||||
</x3d>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
if (document.getElementById('X3DOM_JS_MODULE') == null){{
|
|
||||||
var scr = document.createElement('script');
|
|
||||||
head = document.head || document.getElementsByTagName('head')[0];
|
|
||||||
scr.src = 'http://www.x3dom.org/download/x3dom.js';
|
|
||||||
scr.async = false;
|
|
||||||
scr.id = 'X3DOM_JS_MODULE';
|
|
||||||
scr.onload = function () {{
|
|
||||||
x3dom.reload();
|
|
||||||
}}
|
|
||||||
head.insertBefore(scr, head.lastChild);
|
|
||||||
}}
|
|
||||||
else if (typeof x3dom != 'undefined') {{ //call reload only if x3dom already loaded
|
|
||||||
x3dom.reload();
|
|
||||||
}}
|
|
||||||
|
|
||||||
//document.getElementById('{id}').runtime.fitAll()
|
def display(shape):
|
||||||
</script>
|
|
||||||
'''
|
|
||||||
|
|
||||||
#https://stackoverflow.com/questions/950087/how-do-i-include-a-javascript-file-in-another-javascript-file
|
return SVG(toString(shape, ExportTypes.SVG))
|
||||||
#better if else
|
|
||||||
|
|
||||||
ROT = (0.77,0.3,0.55,1.28)
|
|
||||||
ROT = (0.,0,0,1.)
|
|
||||||
FOV = 0.2
|
|
||||||
|
|
||||||
def add_x3d_boilerplate(src, height=400, center=(0,0,0), d=(0,0,15), fov=FOV, rot='{} {} {} {} '.format(*ROT)):
|
|
||||||
|
|
||||||
return BOILERPLATE.format(src=src,
|
|
||||||
id=uuid4(),
|
|
||||||
height=height,
|
|
||||||
x=d[0],
|
|
||||||
y=d[1],
|
|
||||||
z=d[2],
|
|
||||||
x0=center[0],
|
|
||||||
y0=center[1],
|
|
||||||
z0=center[2],
|
|
||||||
fov=fov,
|
|
||||||
rot=rot)
|
|
||||||
|
|
||||||
def x3d_display(shape,
|
|
||||||
vertex_shader=None,
|
|
||||||
fragment_shader=None,
|
|
||||||
export_edges=True,
|
|
||||||
color=(1,1,0),
|
|
||||||
specular_color=(1,1,1),
|
|
||||||
shininess=0.4,
|
|
||||||
transparency=0.4,
|
|
||||||
line_color=(0,0,0),
|
|
||||||
line_width=2.,
|
|
||||||
mesh_quality=.3):
|
|
||||||
|
|
||||||
# Export to XML <Scene> tag
|
|
||||||
exporter = X3DExporter(shape,
|
|
||||||
vertex_shader,
|
|
||||||
fragment_shader,
|
|
||||||
export_edges,
|
|
||||||
color,
|
|
||||||
specular_color,
|
|
||||||
shininess,
|
|
||||||
transparency,
|
|
||||||
line_color,
|
|
||||||
line_width,
|
|
||||||
mesh_quality)
|
|
||||||
|
|
||||||
exporter.compute()
|
|
||||||
x3d_str = exporter.to_x3dfile_string(shape_id=0)
|
|
||||||
xml_et = ElementTree.fromstring(x3d_str)
|
|
||||||
scene_tag = xml_et.find('./Scene')
|
|
||||||
|
|
||||||
# Viewport Parameters
|
|
||||||
bb = BoundBox._fromTopoDS(shape)
|
|
||||||
d = max(bb.xlen,bb.ylen,bb.zlen)
|
|
||||||
c = bb.center
|
|
||||||
|
|
||||||
vec = gp_Vec(0,0,d/1.5/tan(FOV/2))
|
|
||||||
quat = gp_Quaternion(*ROT)
|
|
||||||
vec = quat*(vec) + c.wrapped
|
|
||||||
|
|
||||||
# return boilerplate + Scene
|
|
||||||
return add_x3d_boilerplate(ElementTree.tostring(scene_tag).decode('utf-8'),
|
|
||||||
d=(vec.X(),vec.Y(),vec.Z()),
|
|
||||||
center=(c.x,c.y,c.z))
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -21,9 +21,22 @@ import re
|
|||||||
import math
|
import math
|
||||||
from cadquery import Vector, Edge, Vertex, Face, Solid, Shell, Compound
|
from cadquery import Vector, Edge, Vertex, Face, Solid, Shell, Compound
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from pyparsing import Literal, Word, nums, Optional, Combine, oneOf, upcaseTokens,\
|
from pyparsing import (
|
||||||
CaselessLiteral, Group, infixNotation, opAssoc, Forward,\
|
Literal,
|
||||||
ZeroOrMore, Keyword
|
Word,
|
||||||
|
nums,
|
||||||
|
Optional,
|
||||||
|
Combine,
|
||||||
|
oneOf,
|
||||||
|
upcaseTokens,
|
||||||
|
CaselessLiteral,
|
||||||
|
Group,
|
||||||
|
infixNotation,
|
||||||
|
opAssoc,
|
||||||
|
Forward,
|
||||||
|
ZeroOrMore,
|
||||||
|
Keyword,
|
||||||
|
)
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
|
|
||||||
@ -81,7 +94,6 @@ class NearestToPointSelector(Selector):
|
|||||||
self.pnt = pnt
|
self.pnt = pnt
|
||||||
|
|
||||||
def filter(self, objectList):
|
def filter(self, objectList):
|
||||||
|
|
||||||
def dist(tShape):
|
def dist(tShape):
|
||||||
return tShape.Center().sub(Vector(*self.pnt)).Length
|
return tShape.Center().sub(Vector(*self.pnt)).Length
|
||||||
# if tShape.ShapeType == 'Vertex':
|
# if tShape.ShapeType == 'Vertex':
|
||||||
@ -121,15 +133,18 @@ class BoxSelector(Selector):
|
|||||||
def isInsideBox(p):
|
def isInsideBox(p):
|
||||||
# using XOR for checking if x/y/z is in between regardless
|
# using XOR for checking if x/y/z is in between regardless
|
||||||
# of order of x/y/z0 and x/y/z1
|
# of order of x/y/z0 and x/y/z1
|
||||||
return ((p.x < x0) ^ (p.x < x1)) and \
|
return (
|
||||||
((p.y < y0) ^ (p.y < y1)) and \
|
((p.x < x0) ^ (p.x < x1))
|
||||||
((p.z < z0) ^ (p.z < z1))
|
and ((p.y < y0) ^ (p.y < y1))
|
||||||
|
and ((p.z < z0) ^ (p.z < z1))
|
||||||
|
)
|
||||||
|
|
||||||
for o in objectList:
|
for o in objectList:
|
||||||
if self.test_boundingbox:
|
if self.test_boundingbox:
|
||||||
bb = o.BoundingBox()
|
bb = o.BoundingBox()
|
||||||
if isInsideBox(Vector(bb.xmin, bb.ymin, bb.zmin)) and \
|
if isInsideBox(Vector(bb.xmin, bb.ymin, bb.zmin)) and isInsideBox(
|
||||||
isInsideBox(Vector(bb.xmax, bb.ymax, bb.zmax)):
|
Vector(bb.xmax, bb.ymax, bb.zmax)
|
||||||
|
):
|
||||||
result.append(o)
|
result.append(o)
|
||||||
else:
|
else:
|
||||||
if isInsideBox(o.Center()):
|
if isInsideBox(o.Center()):
|
||||||
@ -168,7 +183,9 @@ class BaseDirSelector(Selector):
|
|||||||
|
|
||||||
if self.test(normal):
|
if self.test(normal):
|
||||||
r.append(o)
|
r.append(o)
|
||||||
elif type(o) == Edge and (o.geomType() == 'LINE' or o.geomType() == 'PLANE'):
|
elif type(o) == Edge and (
|
||||||
|
o.geomType() == "LINE" or o.geomType() == "PLANE"
|
||||||
|
):
|
||||||
# an edge is parallel to a direction if its underlying geometry is plane or line
|
# an edge is parallel to a direction if its underlying geometry is plane or line
|
||||||
tangent = o.tangentAt()
|
tangent = o.tangentAt()
|
||||||
if self.test(tangent):
|
if self.test(tangent):
|
||||||
@ -247,8 +264,7 @@ class PerpendicularDirSelector(BaseDirSelector):
|
|||||||
|
|
||||||
def test(self, vec):
|
def test(self, vec):
|
||||||
angle = self.direction.getAngle(vec)
|
angle = self.direction.getAngle(vec)
|
||||||
r = (abs(angle) < self.TOLERANCE) or (
|
r = (abs(angle) < self.TOLERANCE) or (abs(angle - math.pi) < self.TOLERANCE)
|
||||||
abs(angle - math.pi) < self.TOLERANCE)
|
|
||||||
return not r
|
return not r
|
||||||
|
|
||||||
|
|
||||||
@ -314,17 +330,16 @@ class DirectionMinMaxSelector(Selector):
|
|||||||
self.TOLERANCE = tolerance
|
self.TOLERANCE = tolerance
|
||||||
|
|
||||||
def filter(self, objectList):
|
def filter(self, objectList):
|
||||||
|
|
||||||
def distance(tShape):
|
def distance(tShape):
|
||||||
return tShape.Center().dot(self.vector)
|
return tShape.Center().dot(self.vector)
|
||||||
|
|
||||||
# import OrderedDict
|
# import OrderedDict
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
# make and distance to object dict
|
# make and distance to object dict
|
||||||
objectDict = {distance(el): el for el in objectList}
|
objectDict = {distance(el): el for el in objectList}
|
||||||
# transform it into an ordered dict
|
# transform it into an ordered dict
|
||||||
objectDict = OrderedDict(sorted(list(objectDict.items()),
|
objectDict = OrderedDict(sorted(list(objectDict.items()), key=lambda x: x[0]))
|
||||||
key=lambda x: x[0]))
|
|
||||||
|
|
||||||
# find out the max/min distance
|
# find out the max/min distance
|
||||||
if self.directionMax:
|
if self.directionMax:
|
||||||
@ -370,8 +385,9 @@ class DirectionNthSelector(ParallelDirSelector):
|
|||||||
objectDict[round(distance(el), digits)].append(el)
|
objectDict[round(distance(el), digits)].append(el)
|
||||||
|
|
||||||
# choose the Nth unique rounded distance
|
# choose the Nth unique rounded distance
|
||||||
nth_distance = sorted(list(objectDict.keys()),
|
nth_distance = sorted(list(objectDict.keys()), reverse=not self.directionMax)[
|
||||||
reverse=not self.directionMax)[self.N]
|
self.N
|
||||||
|
]
|
||||||
|
|
||||||
# map back to original objects and return
|
# map back to original objects and return
|
||||||
return objectDict[nth_distance]
|
return objectDict[nth_distance]
|
||||||
@ -388,8 +404,9 @@ class BinarySelector(Selector):
|
|||||||
self.right = right
|
self.right = right
|
||||||
|
|
||||||
def filter(self, objectList):
|
def filter(self, objectList):
|
||||||
return self.filterResults(self.left.filter(objectList),
|
return self.filterResults(
|
||||||
self.right.filter(objectList))
|
self.left.filter(objectList), self.right.filter(objectList)
|
||||||
|
)
|
||||||
|
|
||||||
def filterResults(self, r_left, r_right):
|
def filterResults(self, r_left, r_right):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -445,52 +462,56 @@ def _makeGrammar():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# float definition
|
# float definition
|
||||||
point = Literal('.')
|
point = Literal(".")
|
||||||
plusmin = Literal('+') | Literal('-')
|
plusmin = Literal("+") | Literal("-")
|
||||||
number = Word(nums)
|
number = Word(nums)
|
||||||
integer = Combine(Optional(plusmin) + number)
|
integer = Combine(Optional(plusmin) + number)
|
||||||
floatn = Combine(integer + Optional(point + Optional(number)))
|
floatn = Combine(integer + Optional(point + Optional(number)))
|
||||||
|
|
||||||
# vector definition
|
# vector definition
|
||||||
lbracket = Literal('(')
|
lbracket = Literal("(")
|
||||||
rbracket = Literal(')')
|
rbracket = Literal(")")
|
||||||
comma = Literal(',')
|
comma = Literal(",")
|
||||||
vector = Combine(lbracket + floatn('x') + comma +
|
vector = Combine(
|
||||||
floatn('y') + comma + floatn('z') + rbracket)
|
lbracket + floatn("x") + comma + floatn("y") + comma + floatn("z") + rbracket
|
||||||
|
)
|
||||||
|
|
||||||
# direction definition
|
# direction definition
|
||||||
simple_dir = oneOf(['X', 'Y', 'Z', 'XY', 'XZ', 'YZ'])
|
simple_dir = oneOf(["X", "Y", "Z", "XY", "XZ", "YZ"])
|
||||||
direction = simple_dir('simple_dir') | vector('vector_dir')
|
direction = simple_dir("simple_dir") | vector("vector_dir")
|
||||||
|
|
||||||
# CQ type definition
|
# CQ type definition
|
||||||
cqtype = oneOf(['Plane', 'Cylinder', 'Sphere', 'Cone', 'Line', 'Circle', 'Arc'],
|
cqtype = oneOf(
|
||||||
caseless=True)
|
["Plane", "Cylinder", "Sphere", "Cone", "Line", "Circle", "Arc"], caseless=True
|
||||||
|
)
|
||||||
cqtype = cqtype.setParseAction(upcaseTokens)
|
cqtype = cqtype.setParseAction(upcaseTokens)
|
||||||
|
|
||||||
# type operator
|
# type operator
|
||||||
type_op = Literal('%')
|
type_op = Literal("%")
|
||||||
|
|
||||||
# direction operator
|
# direction operator
|
||||||
direction_op = oneOf(['>', '<'])
|
direction_op = oneOf([">", "<"])
|
||||||
|
|
||||||
# index definition
|
# index definition
|
||||||
ix_number = Group(Optional('-') + Word(nums))
|
ix_number = Group(Optional("-") + Word(nums))
|
||||||
lsqbracket = Literal('[').suppress()
|
lsqbracket = Literal("[").suppress()
|
||||||
rsqbracket = Literal(']').suppress()
|
rsqbracket = Literal("]").suppress()
|
||||||
|
|
||||||
index = lsqbracket + ix_number('index') + rsqbracket
|
index = lsqbracket + ix_number("index") + rsqbracket
|
||||||
|
|
||||||
# other operators
|
# other operators
|
||||||
other_op = oneOf(['|', '#', '+', '-'])
|
other_op = oneOf(["|", "#", "+", "-"])
|
||||||
|
|
||||||
# named view
|
# named view
|
||||||
named_view = oneOf(['front', 'back', 'left', 'right', 'top', 'bottom'])
|
named_view = oneOf(["front", "back", "left", "right", "top", "bottom"])
|
||||||
|
|
||||||
return direction('only_dir') | \
|
return (
|
||||||
(type_op('type_op') + cqtype('cq_type')) | \
|
direction("only_dir")
|
||||||
(direction_op('dir_op') + direction('dir') + Optional(index)) | \
|
| (type_op("type_op") + cqtype("cq_type"))
|
||||||
(other_op('other_op') + direction('dir')) | \
|
| (direction_op("dir_op") + direction("dir") + Optional(index))
|
||||||
named_view('named_view')
|
| (other_op("other_op") + direction("dir"))
|
||||||
|
| named_view("named_view")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
_grammar = _makeGrammar() # make a grammar instance
|
_grammar = _makeGrammar() # make a grammar instance
|
||||||
@ -506,33 +527,34 @@ class _SimpleStringSyntaxSelector(Selector):
|
|||||||
|
|
||||||
# define all token to object mappings
|
# define all token to object mappings
|
||||||
self.axes = {
|
self.axes = {
|
||||||
'X': Vector(1, 0, 0),
|
"X": Vector(1, 0, 0),
|
||||||
'Y': Vector(0, 1, 0),
|
"Y": Vector(0, 1, 0),
|
||||||
'Z': Vector(0, 0, 1),
|
"Z": Vector(0, 0, 1),
|
||||||
'XY': Vector(1, 1, 0),
|
"XY": Vector(1, 1, 0),
|
||||||
'YZ': Vector(0, 1, 1),
|
"YZ": Vector(0, 1, 1),
|
||||||
'XZ': Vector(1, 0, 1)
|
"XZ": Vector(1, 0, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.namedViews = {
|
self.namedViews = {
|
||||||
'front': (Vector(0, 0, 1), True),
|
"front": (Vector(0, 0, 1), True),
|
||||||
'back': (Vector(0, 0, 1), False),
|
"back": (Vector(0, 0, 1), False),
|
||||||
'left': (Vector(1, 0, 0), False),
|
"left": (Vector(1, 0, 0), False),
|
||||||
'right': (Vector(1, 0, 0), True),
|
"right": (Vector(1, 0, 0), True),
|
||||||
'top': (Vector(0, 1, 0), True),
|
"top": (Vector(0, 1, 0), True),
|
||||||
'bottom': (Vector(0, 1, 0), False)
|
"bottom": (Vector(0, 1, 0), False),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.operatorMinMax = {
|
self.operatorMinMax = {
|
||||||
'>': True,
|
">": True,
|
||||||
'<': False,
|
"<": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.operator = {
|
self.operator = {
|
||||||
'+': DirectionSelector,
|
"+": DirectionSelector,
|
||||||
'-': lambda v: DirectionSelector(-v),
|
"-": lambda v: DirectionSelector(-v),
|
||||||
'#': PerpendicularDirSelector,
|
"#": PerpendicularDirSelector,
|
||||||
'|': ParallelDirSelector}
|
"|": ParallelDirSelector,
|
||||||
|
}
|
||||||
|
|
||||||
self.parseResults = parseResults
|
self.parseResults = parseResults
|
||||||
self.mySelector = self._chooseSelector(parseResults)
|
self.mySelector = self._chooseSelector(parseResults)
|
||||||
@ -541,23 +563,25 @@ class _SimpleStringSyntaxSelector(Selector):
|
|||||||
"""
|
"""
|
||||||
Sets up the underlying filters accordingly
|
Sets up the underlying filters accordingly
|
||||||
"""
|
"""
|
||||||
if 'only_dir' in pr:
|
if "only_dir" in pr:
|
||||||
vec = self._getVector(pr)
|
vec = self._getVector(pr)
|
||||||
return DirectionSelector(vec)
|
return DirectionSelector(vec)
|
||||||
|
|
||||||
elif 'type_op' in pr:
|
elif "type_op" in pr:
|
||||||
return TypeSelector(pr.cq_type)
|
return TypeSelector(pr.cq_type)
|
||||||
|
|
||||||
elif 'dir_op' in pr:
|
elif "dir_op" in pr:
|
||||||
vec = self._getVector(pr)
|
vec = self._getVector(pr)
|
||||||
minmax = self.operatorMinMax[pr.dir_op]
|
minmax = self.operatorMinMax[pr.dir_op]
|
||||||
|
|
||||||
if 'index' in pr:
|
if "index" in pr:
|
||||||
return DirectionNthSelector(vec, int(''.join(pr.index.asList())), minmax)
|
return DirectionNthSelector(
|
||||||
|
vec, int("".join(pr.index.asList())), minmax
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return DirectionMinMaxSelector(vec, minmax)
|
return DirectionMinMaxSelector(vec, minmax)
|
||||||
|
|
||||||
elif 'other_op' in pr:
|
elif "other_op" in pr:
|
||||||
vec = self._getVector(pr)
|
vec = self._getVector(pr)
|
||||||
return self.operator[pr.other_op](vec)
|
return self.operator[pr.other_op](vec)
|
||||||
|
|
||||||
@ -569,7 +593,7 @@ class _SimpleStringSyntaxSelector(Selector):
|
|||||||
"""
|
"""
|
||||||
Translate parsed vector string into a CQ Vector
|
Translate parsed vector string into a CQ Vector
|
||||||
"""
|
"""
|
||||||
if 'vector_dir' in pr:
|
if "vector_dir" in pr:
|
||||||
vec = pr.vector_dir
|
vec = pr.vector_dir
|
||||||
return Vector(float(vec.x), float(vec.y), float(vec.z))
|
return Vector(float(vec.x), float(vec.y), float(vec.z))
|
||||||
else:
|
else:
|
||||||
@ -590,10 +614,10 @@ def _makeExpressionGrammar(atom):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# define operators
|
# define operators
|
||||||
and_op = Literal('and')
|
and_op = Literal("and")
|
||||||
or_op = Literal('or')
|
or_op = Literal("or")
|
||||||
delta_op = oneOf(['exc', 'except'])
|
delta_op = oneOf(["exc", "except"])
|
||||||
not_op = Literal('not')
|
not_op = Literal("not")
|
||||||
|
|
||||||
def atom_callback(res):
|
def atom_callback(res):
|
||||||
return _SimpleStringSyntaxSelector(res)
|
return _SimpleStringSyntaxSelector(res)
|
||||||
@ -622,11 +646,15 @@ def _makeExpressionGrammar(atom):
|
|||||||
return InverseSelector(right)
|
return InverseSelector(right)
|
||||||
|
|
||||||
# construct the final grammar and set all the callbacks
|
# construct the final grammar and set all the callbacks
|
||||||
expr = infixNotation(atom,
|
expr = infixNotation(
|
||||||
[(and_op, 2, opAssoc.LEFT, and_callback),
|
atom,
|
||||||
|
[
|
||||||
|
(and_op, 2, opAssoc.LEFT, and_callback),
|
||||||
(or_op, 2, opAssoc.LEFT, or_callback),
|
(or_op, 2, opAssoc.LEFT, or_callback),
|
||||||
(delta_op, 2, opAssoc.LEFT, exc_callback),
|
(delta_op, 2, opAssoc.LEFT, exc_callback),
|
||||||
(not_op, 1, opAssoc.RIGHT, not_callback)])
|
(not_op, 1, opAssoc.RIGHT, not_callback),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
return expr
|
return expr
|
||||||
|
|
||||||
@ -690,8 +718,7 @@ class StringSyntaxSelector(Selector):
|
|||||||
Feed the input string through the parser and construct an relevant complex selector object
|
Feed the input string through the parser and construct an relevant complex selector object
|
||||||
"""
|
"""
|
||||||
self.selectorString = selectorString
|
self.selectorString = selectorString
|
||||||
parse_result = _expression_grammar.parseString(selectorString,
|
parse_result = _expression_grammar.parseString(selectorString, parseAll=True)
|
||||||
parseAll=True)
|
|
||||||
self.mySelector = parse_result.asList()[0]
|
self.mySelector = parse_result.asList()[0]
|
||||||
|
|
||||||
def filter(self, objectList):
|
def filter(self, objectList):
|
||||||
|
@ -20,11 +20,12 @@ requirements:
|
|||||||
- pyparsing 2.*
|
- pyparsing 2.*
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
requires:
|
||||||
|
- pytest
|
||||||
source_files:
|
source_files:
|
||||||
- runtests.py
|
|
||||||
- tests/
|
- tests/
|
||||||
commands:
|
commands:
|
||||||
- python runtests.py
|
- pytest -v
|
||||||
|
|
||||||
about:
|
about:
|
||||||
summary: CadQuery fork based on PythonOCC
|
summary: CadQuery fork based on PythonOCC
|
||||||
|
@ -54,6 +54,7 @@ All 2-d operations require a **Workplane** object to be created.
|
|||||||
Workplane.threePointArc
|
Workplane.threePointArc
|
||||||
Workplane.sagittaArc
|
Workplane.sagittaArc
|
||||||
Workplane.radiusArc
|
Workplane.radiusArc
|
||||||
|
Workplane.tangentArcPoint
|
||||||
Workplane.rotateAndCopy
|
Workplane.rotateAndCopy
|
||||||
Workplane.mirrorY
|
Workplane.mirrorY
|
||||||
Workplane.mirrorX
|
Workplane.mirrorX
|
||||||
|
54
doc/conf.py
54
doc/conf.py
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
import sys, os
|
import sys, os
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
# print "working path is %s" % os.getcwd()
|
# print "working path is %s" % os.getcwd()
|
||||||
# sys.path.append("../cadquery")
|
# sys.path.append("../cadquery")
|
||||||
import cadquery
|
import cadquery
|
||||||
@ -32,32 +33,37 @@ import cadquery
|
|||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.autosummary','cadquery.cq_directive']
|
extensions = [
|
||||||
|
"sphinx.ext.autodoc",
|
||||||
|
"sphinx.ext.viewcode",
|
||||||
|
"sphinx.ext.autosummary",
|
||||||
|
"cadquery.cq_directive",
|
||||||
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ["_templates"]
|
||||||
|
|
||||||
# The suffix of source filenames.
|
# The suffix of source filenames.
|
||||||
source_suffix = '.rst'
|
source_suffix = ".rst"
|
||||||
|
|
||||||
# The encoding of source files.
|
# The encoding of source files.
|
||||||
# source_encoding = 'utf-8-sig'
|
# source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = "index"
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'CadQuery'
|
project = u"CadQuery"
|
||||||
copyright = u'Parametric Products Intellectual Holdings LLC, All Rights Reserved'
|
copyright = u"Parametric Products Intellectual Holdings LLC, All Rights Reserved"
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '1.0'
|
version = "1.0"
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '1.0.0'
|
release = "1.0.0"
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
@ -71,7 +77,7 @@ release = '1.0.0'
|
|||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
exclude_patterns = ['_build']
|
exclude_patterns = ["_build"]
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||||
# default_role = None
|
# default_role = None
|
||||||
@ -88,7 +94,7 @@ add_module_names = True
|
|||||||
# show_authors = False
|
# show_authors = False
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = 'sphinx'
|
pygments_style = "sphinx"
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
# A list of ignored prefixes for module index sorting.
|
||||||
# modindex_common_prefix = []
|
# modindex_common_prefix = []
|
||||||
@ -99,7 +105,7 @@ pygments_style = 'sphinx'
|
|||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
# html_theme = 'timlinux-linfiniti-sphinx'
|
# html_theme = 'timlinux-linfiniti-sphinx'
|
||||||
html_theme = 'sphinx_rtd_theme'
|
html_theme = "sphinx_rtd_theme"
|
||||||
|
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
@ -154,7 +160,7 @@ html_logo = "_static/cqlogo.png"
|
|||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
html_static_path = ["_static"]
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
# using the given strftime format.
|
# using the given strftime format.
|
||||||
@ -198,7 +204,7 @@ html_show_sphinx = False
|
|||||||
# html_file_suffix = None
|
# html_file_suffix = None
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'CadQuerydoc'
|
htmlhelp_basename = "CadQuerydoc"
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
@ -206,10 +212,8 @@ htmlhelp_basename = 'CadQuerydoc'
|
|||||||
latex_elements = {
|
latex_elements = {
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
#'papersize': 'letterpaper',
|
#'papersize': 'letterpaper',
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
#'pointsize': '10pt',
|
#'pointsize': '10pt',
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
#'preamble': '',
|
#'preamble': '',
|
||||||
}
|
}
|
||||||
@ -217,8 +221,7 @@ latex_elements = {
|
|||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'CadQuery.tex', u'CadQuery Documentation',
|
("index", "CadQuery.tex", u"CadQuery Documentation", u"David Cowden", "manual"),
|
||||||
u'David Cowden', 'manual'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
@ -246,10 +249,7 @@ latex_documents = [
|
|||||||
|
|
||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [("index", "cadquery", u"CadQuery Documentation", [u"David Cowden"], 1)]
|
||||||
('index', 'cadquery', u'CadQuery Documentation',
|
|
||||||
[u'David Cowden'], 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
# man_show_urls = False
|
# man_show_urls = False
|
||||||
@ -261,9 +261,15 @@ man_pages = [
|
|||||||
# (source start file, target name, title, author,
|
# (source start file, target name, title, author,
|
||||||
# dir menu entry, description, category)
|
# dir menu entry, description, category)
|
||||||
texinfo_documents = [
|
texinfo_documents = [
|
||||||
('index', 'CadQuery', u'CadQuery Documentation',
|
(
|
||||||
u'David Cowden', 'CadQuery', 'A Fluent CAD api',
|
"index",
|
||||||
'Miscellaneous'),
|
"CadQuery",
|
||||||
|
u"CadQuery Documentation",
|
||||||
|
u"David Cowden",
|
||||||
|
"CadQuery",
|
||||||
|
"A Fluent CAD api",
|
||||||
|
"Miscellaneous",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
|
168
doc/examples.rst
168
doc/examples.rst
@ -71,8 +71,8 @@ of a working plane is at the center of the face. The default hole depth is thro
|
|||||||
center_hole_dia = 22.0
|
center_hole_dia = 22.0
|
||||||
|
|
||||||
# Create a box based on the dimensions above and add a 22mm center hole
|
# Create a box based on the dimensions above and add a 22mm center hole
|
||||||
result = cq.Workplane("XY").box(length, height, thickness) \
|
result = (cq.Workplane("XY").box(length, height, thickness)
|
||||||
.faces(">Z").workplane().hole(center_hole_dia)
|
.faces(">Z").workplane().hole(center_hole_dia))
|
||||||
|
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
|
||||||
@ -121,8 +121,8 @@ closed curve.
|
|||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = cq.Workplane("front").lineTo(2.0, 0).lineTo(2.0, 1.0).threePointArc((1.0, 1.5),(0.0, 1.0))\
|
result = (cq.Workplane("front").lineTo(2.0, 0).lineTo(2.0, 1.0).threePointArc((1.0, 1.5),(0.0, 1.0))
|
||||||
.close().extrude(0.25)
|
.close().extrude(0.25))
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
|
||||||
|
|
||||||
@ -152,7 +152,7 @@ A new work plane center can be established at any point.
|
|||||||
result = result.center(1.5, 0.0).rect(0.5, 0.5) # new work center is (1.5, 0.0)
|
result = result.center(1.5, 0.0).rect(0.5, 0.5) # new work center is (1.5, 0.0)
|
||||||
|
|
||||||
result = result.center(-1.5, 1.5).circle(0.25) # new work center is ( 0.0, 1.5).
|
result = result.center(-1.5, 1.5).circle(0.25) # new work center is ( 0.0, 1.5).
|
||||||
#the new center is specified relative to the previous center, not global coordinates!
|
# The new center is specified relative to the previous center, not global coordinates!
|
||||||
|
|
||||||
result = result.extrude(0.25)
|
result = result.extrude(0.25)
|
||||||
show_object(result)
|
show_object(result)
|
||||||
@ -204,8 +204,8 @@ correct for small hole sizes.
|
|||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = cq.Workplane("front").box(3.0, 4.0, 0.25).pushPoints ( [ ( 0,0.75 ),(0, -0.75) ]) \
|
result = (cq.Workplane("front").box(3.0, 4.0, 0.25).pushPoints ( [ ( 0,0.75 ),(0, -0.75) ])
|
||||||
.polygon(6, 1.0).cutThruAll()
|
.polygon(6, 1.0).cutThruAll())
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
@ -460,6 +460,32 @@ This example uses an offset workplane to make a compound object, which is perfec
|
|||||||
* :py:meth:`Workplane.box`
|
* :py:meth:`Workplane.box`
|
||||||
* :py:meth:`Workplane`
|
* :py:meth:`Workplane`
|
||||||
|
|
||||||
|
Copying Workplanes
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
An existing CQ object can copy a workplane from another CQ object.
|
||||||
|
|
||||||
|
.. cq_plot::
|
||||||
|
|
||||||
|
result = (cq.Workplane("front").circle(1).extrude(10) # make a cylinder
|
||||||
|
# We want to make a second cylinder perpendicular to the first,
|
||||||
|
# but we have no face to base the workplane off
|
||||||
|
.copyWorkplane(
|
||||||
|
# create a temporary object with the required workplane
|
||||||
|
cq.Workplane("right", origin=(-5, 0, 0))
|
||||||
|
).circle(1).extrude(10))
|
||||||
|
show_object(result)
|
||||||
|
|
||||||
|
.. topic:: API References
|
||||||
|
|
||||||
|
.. hlist:
|
||||||
|
:columns: 2
|
||||||
|
|
||||||
|
* :py:meth:`CQ.copyWorkplane` **!**
|
||||||
|
* :py:meth:`Workplane.circle`
|
||||||
|
* :py:meth:`Workplane.extrude`
|
||||||
|
* :py:meth:`Workplane`
|
||||||
|
|
||||||
Rotated Workplanes
|
Rotated Workplanes
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
@ -467,9 +493,9 @@ You can create a rotated work plane by specifying angles of rotation relative to
|
|||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z").workplane() \
|
result = (cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z").workplane()
|
||||||
.transformed(offset=cq.Vector(0, -1.5, 1.0),rotate=cq.Vector(60, 0, 0)) \
|
.transformed(offset=cq.Vector(0, -1.5, 1.0),rotate=cq.Vector(60, 0, 0))
|
||||||
.rect(1.5,1.5,forConstruction=True).vertices().hole(0.25)
|
.rect(1.5,1.5,forConstruction=True).vertices().hole(0.25))
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
@ -492,8 +518,8 @@ In the example below, a rectangle is drawn, and its vertices are used to locate
|
|||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = cq.Workplane("front").box(2, 2, 0.5).faces(">Z").workplane() \
|
result = (cq.Workplane("front").box(2, 2, 0.5).faces(">Z").workplane()
|
||||||
.rect(1.5, 1.5, forConstruction=True).vertices().hole(0.125 )
|
.rect(1.5, 1.5, forConstruction=True).vertices().hole(0.125 ))
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
@ -538,8 +564,8 @@ and a circular section.
|
|||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z").circle(1.5) \
|
result = (cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z").circle(1.5)
|
||||||
.workplane(offset=3.0).rect(0.75, 0.5).loft(combine=True)
|
.workplane(offset=3.0).rect(0.75, 0.5).loft(combine=True))
|
||||||
|
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
|
||||||
@ -563,8 +589,8 @@ Similar to :py:meth:`Workplane.hole` , these functions operate on a list of poin
|
|||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = cq.Workplane(cq.Plane.XY()).box(4,2, 0.5).faces(">Z").workplane().rect(3.5, 1.5, forConstruction=True)\
|
result = (cq.Workplane(cq.Plane.XY()).box(4,2, 0.5).faces(">Z").workplane().rect(3.5, 1.5, forConstruction=True)
|
||||||
.vertices().cboreHole(0.125, 0.25, 0.125, depth=None)
|
.vertices().cboreHole(0.125, 0.25, 0.125, depth=None))
|
||||||
|
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
|
||||||
@ -604,6 +630,55 @@ Here we fillet all of the edges of a simple plate.
|
|||||||
* :py:meth:`Workplane.edges`
|
* :py:meth:`Workplane.edges`
|
||||||
* :py:meth:`Workplane`
|
* :py:meth:`Workplane`
|
||||||
|
|
||||||
|
Tagging objects
|
||||||
|
----------------
|
||||||
|
|
||||||
|
The :py:meth:`CQ.tag` method can be used to tag a particular object in the chain with a string, so that it can be refered to later in the chain.
|
||||||
|
|
||||||
|
The :py:meth:`CQ.workplaneFromTagged` method applies :py:meth:`CQ.copyWorkplane` to a tagged object. For example, when extruding two different solids from a surface, after the first solid is extruded it can become difficult to reselect the original surface with CadQuery's other selectors.
|
||||||
|
|
||||||
|
.. cq_plot::
|
||||||
|
|
||||||
|
result = (cq.Workplane("XY")
|
||||||
|
# create and tag the base workplane
|
||||||
|
.box(10, 10, 10).faces(">Z").workplane().tag("baseplane")
|
||||||
|
# extrude a cylinder
|
||||||
|
.center(-3, 0).circle(1).extrude(3)
|
||||||
|
# to reselect the base workplane, simply
|
||||||
|
.workplaneFromTagged("baseplane")
|
||||||
|
# extrude a second cylinder
|
||||||
|
.center(3, 0).circle(1).extrude(2))
|
||||||
|
show_object(result)
|
||||||
|
|
||||||
|
|
||||||
|
Tags can also be used with most selectors, including :py:meth:`CQ.vertices`, :py:meth:`CQ.faces`, :py:meth:`CQ.edges`, :py:meth:`CQ.wires`, :py:meth:`CQ.shells`, :py:meth:`CQ.solids` and :py:meth:`CQ.compounds`.
|
||||||
|
|
||||||
|
.. cq_plot::
|
||||||
|
|
||||||
|
result = (cq.Workplane("XY")
|
||||||
|
# create a triangular prism and tag it
|
||||||
|
.polygon(3, 5).extrude(4).tag("prism")
|
||||||
|
# create a sphere that obscures the prism
|
||||||
|
.sphere(10)
|
||||||
|
# create features based on the prism's faces
|
||||||
|
.faces("<X", tag="prism").workplane().circle(1).cutThruAll()
|
||||||
|
.faces(">X", tag="prism").faces(">Y").workplane().circle(1).cutThruAll())
|
||||||
|
show_object(result)
|
||||||
|
|
||||||
|
.. topic:: Api References
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 2
|
||||||
|
|
||||||
|
* :py:meth:`CQ.tag` **!**
|
||||||
|
* :py:meth:`CQ.getTagged` **!**
|
||||||
|
* :py:meth:`CQ.workplaneFromTagged` **!**
|
||||||
|
* :py:meth:`Workplane.extrude`
|
||||||
|
* :py:meth:`Workplane.cutThruAll`
|
||||||
|
* :py:meth:`Workplane.circle`
|
||||||
|
* :py:meth:`Workplane.faces`
|
||||||
|
* :py:meth:`Workplane`
|
||||||
|
|
||||||
A Parametric Bearing Pillow Block
|
A Parametric Bearing Pillow Block
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
@ -614,10 +689,10 @@ with just a few lines of code.
|
|||||||
|
|
||||||
(length,height,bearing_diam, thickness,padding) = ( 30.0, 40.0, 22.0, 10.0, 8.0)
|
(length,height,bearing_diam, thickness,padding) = ( 30.0, 40.0, 22.0, 10.0, 8.0)
|
||||||
|
|
||||||
result = cq.Workplane("XY").box(length,height,thickness).faces(">Z").workplane().hole(bearing_diam) \
|
result = (cq.Workplane("XY").box(length,height,thickness).faces(">Z").workplane().hole(bearing_diam)
|
||||||
.faces(">Z").workplane() \
|
.faces(">Z").workplane()
|
||||||
.rect(length-padding,height-padding,forConstruction=True) \
|
.rect(length-padding,height-padding,forConstruction=True)
|
||||||
.vertices().cboreHole(2.4, 4.4, 2.1)
|
.vertices().cboreHole(2.4, 4.4, 2.1))
|
||||||
|
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
|
||||||
@ -665,10 +740,10 @@ ones at 13 lines, but that's very short compared to the pythonOCC version, which
|
|||||||
(L,w,t) = (20.0, 6.0, 3.0)
|
(L,w,t) = (20.0, 6.0, 3.0)
|
||||||
s = cq.Workplane("XY")
|
s = cq.Workplane("XY")
|
||||||
|
|
||||||
#draw half the profile of the bottle and extrude it
|
# Draw half the profile of the bottle and extrude it
|
||||||
p = s.center(-L/2.0, 0).vLine(w/2.0) \
|
p = (s.center(-L/2.0, 0).vLine(w/2.0)
|
||||||
.threePointArc((L/2.0, w/2.0 + t),(L, w/2.0)).vLine(-w/2.0) \
|
.threePointArc((L/2.0, w/2.0 + t),(L, w/2.0)).vLine(-w/2.0)
|
||||||
.mirrorX().extrude(30.0,True)
|
.mirrorX().extrude(30.0,True))
|
||||||
|
|
||||||
#make the neck
|
#make the neck
|
||||||
p = p.faces(">Z").workplane().circle(3.0).extrude(2.0,True)
|
p = p.faces(">Z").workplane().circle(3.0).extrude(2.0,True)
|
||||||
@ -729,9 +804,10 @@ A Parametric Enclosure
|
|||||||
oshell = oshell.edges("|Z").fillet(p_sideRadius)
|
oshell = oshell.edges("|Z").fillet(p_sideRadius)
|
||||||
|
|
||||||
#inner shell
|
#inner shell
|
||||||
ishell = oshell.faces("<Z").workplane(p_thickness,True)\
|
ishell = (oshell.faces("<Z").workplane(p_thickness,True)
|
||||||
.rect((p_outerWidth - 2.0* p_thickness),(p_outerLength - 2.0*p_thickness))\
|
.rect((p_outerWidth - 2.0* p_thickness),(p_outerLength - 2.0*p_thickness))
|
||||||
.extrude((p_outerHeight - 2.0*p_thickness),False) #set combine false to produce just the new boss
|
.extrude((p_outerHeight - 2.0*p_thickness),False) #set combine false to produce just the new boss
|
||||||
|
)
|
||||||
ishell = ishell.edges("|Z").fillet(p_sideRadius - p_thickness)
|
ishell = ishell.edges("|Z").fillet(p_sideRadius - p_thickness)
|
||||||
|
|
||||||
#make the box outer box
|
#make the box outer box
|
||||||
@ -741,10 +817,10 @@ A Parametric Enclosure
|
|||||||
POSTWIDTH = (p_outerWidth - 2.0*p_screwpostInset)
|
POSTWIDTH = (p_outerWidth - 2.0*p_screwpostInset)
|
||||||
POSTLENGTH = (p_outerLength -2.0*p_screwpostInset)
|
POSTLENGTH = (p_outerLength -2.0*p_screwpostInset)
|
||||||
|
|
||||||
box = box.faces(">Z").workplane(-p_thickness)\
|
box = (box.faces(">Z").workplane(-p_thickness)
|
||||||
.rect(POSTWIDTH,POSTLENGTH,forConstruction=True)\
|
.rect(POSTWIDTH,POSTLENGTH,forConstruction=True)
|
||||||
.vertices().circle(p_screwpostOD/2.0).circle(p_screwpostID/2.0)\
|
.vertices().circle(p_screwpostOD/2.0).circle(p_screwpostID/2.0)
|
||||||
.extrude((-1.0)*(p_outerHeight + p_lipHeight -p_thickness ),True)
|
.extrude((-1.0)*(p_outerHeight + p_lipHeight -p_thickness ),True))
|
||||||
|
|
||||||
#split lid into top and bottom parts
|
#split lid into top and bottom parts
|
||||||
(lid,bottom) = box.faces(">Z").workplane(-p_thickness -p_lipHeight ).split(keepTop=True,keepBottom=True).all() #splits into two solids
|
(lid,bottom) = box.faces(">Z").workplane(-p_thickness -p_lipHeight ).split(keepTop=True,keepBottom=True).all() #splits into two solids
|
||||||
@ -835,23 +911,23 @@ regarding the underside of the brick.
|
|||||||
s = s.faces("<Z").shell(-1.0 * t)
|
s = s.faces("<Z").shell(-1.0 * t)
|
||||||
|
|
||||||
# make the bumps on the top
|
# make the bumps on the top
|
||||||
s = s.faces(">Z").workplane(). \
|
s = (s.faces(">Z").workplane().
|
||||||
rarray(pitch, pitch, lbumps, wbumps, True).circle(bumpDiam / 2.0) \
|
rarray(pitch, pitch, lbumps, wbumps, True).circle(bumpDiam / 2.0)
|
||||||
.extrude(bumpHeight)
|
.extrude(bumpHeight))
|
||||||
|
|
||||||
# add posts on the bottom. posts are different diameter depending on geometry
|
# add posts on the bottom. posts are different diameter depending on geometry
|
||||||
# solid studs for 1 bump, tubes for multiple, none for 1x1
|
# solid studs for 1 bump, tubes for multiple, none for 1x1
|
||||||
tmp = s.faces("<Z").workplane(invert=True)
|
tmp = s.faces("<Z").workplane(invert=True)
|
||||||
|
|
||||||
if lbumps > 1 and wbumps > 1:
|
if lbumps > 1 and wbumps > 1:
|
||||||
tmp = tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True). \
|
tmp = (tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True).
|
||||||
circle(postDiam / 2.0).circle(bumpDiam / 2.0).extrude(height - t)
|
circle(postDiam / 2.0).circle(bumpDiam / 2.0).extrude(height - t))
|
||||||
elif lbumps > 1:
|
elif lbumps > 1:
|
||||||
tmp = tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True). \
|
tmp = (tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True).
|
||||||
circle(t).extrude(height - t)
|
circle(t).extrude(height - t))
|
||||||
elif wbumps > 1:
|
elif wbumps > 1:
|
||||||
tmp = tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True). \
|
tmp = (tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True).
|
||||||
circle(t).extrude(height - t)
|
circle(t).extrude(height - t))
|
||||||
else:
|
else:
|
||||||
tmp = s
|
tmp = s
|
||||||
|
|
||||||
@ -1018,12 +1094,12 @@ Braille Example
|
|||||||
line_start_pos += Point(0, -cell_geometry.interline)
|
line_start_pos += Point(0, -cell_geometry.interline)
|
||||||
|
|
||||||
r = get_cylinder_radius(cell_geometry)
|
r = get_cylinder_radius(cell_geometry)
|
||||||
base = base.faces('>Z').vertices('<XY').workplane() \
|
base = (base.faces('>Z').vertices('<XY').workplane()
|
||||||
.pushPoints(dot_pos).circle(r) \
|
.pushPoints(dot_pos).circle(r)
|
||||||
.extrude(r)
|
.extrude(r))
|
||||||
# Make a fillet almost the same radius to get a pseudo spherical cap.
|
# Make a fillet almost the same radius to get a pseudo spherical cap.
|
||||||
base = base.faces('>Z').edges() \
|
base = (base.faces('>Z').edges()
|
||||||
.fillet(r - 0.001)
|
.fillet(r - 0.001))
|
||||||
hidding_box = cq.Workplane('XY').box(
|
hidding_box = cq.Workplane('XY').box(
|
||||||
base_width, base_height, base_thickness, centered=(False, False, False))
|
base_width, base_height, base_thickness, centered=(False, False, False))
|
||||||
result = hidding_box.union(base)
|
result = hidding_box.union(base)
|
||||||
@ -1119,7 +1195,7 @@ This specific examples generates a helical cycloidal gear.
|
|||||||
return hypocycloid(t,r1,r2)
|
return hypocycloid(t,r1,r2)
|
||||||
|
|
||||||
# create the gear profile and extrude it
|
# create the gear profile and extrude it
|
||||||
result = cq.Workplane('XY').parametricCurve(lambda t: gear(t*2*pi,6,1))\
|
result = (cq.Workplane('XY').parametricCurve(lambda t: gear(t*2*pi,6,1))
|
||||||
.twistExtrude(15,90).faces('>Z').workplane().circle(2).cutThruAll()
|
.twistExtrude(15,90).faces('>Z').workplane().circle(2).cutThruAll())
|
||||||
|
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
@ -111,12 +111,19 @@ backwards in the stack to get the face as well::
|
|||||||
You can browse stack access methods here: :ref:`stackMethods`.
|
You can browse stack access methods here: :ref:`stackMethods`.
|
||||||
|
|
||||||
|
|
||||||
|
.. _chaining:
|
||||||
|
|
||||||
Chaining
|
Chaining
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
All CadQuery methods return another CadQuery object, so that you can chain the methods together fluently. Use
|
All CadQuery methods return another CadQuery object, so that you can chain the methods together fluently. Use
|
||||||
the core CQ methods to get at the objects that were created.
|
the core CQ methods to get at the objects that were created.
|
||||||
|
|
||||||
|
Each time a new CadQuery object is produced during these chained calls, it has a ``parent`` attribute that points
|
||||||
|
to the CadQuery object that created it. Several CadQuery methods search this parent chain, for example when searching
|
||||||
|
for the context solid. You can also give a CadQuery object a tag, and further down your chain of CadQuery calls you
|
||||||
|
can refer back to this particular object using it's tag.
|
||||||
|
|
||||||
|
|
||||||
The Context Solid
|
The Context Solid
|
||||||
---------------------------
|
---------------------------
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
name: cadquery
|
name: cadquery
|
||||||
channels:
|
channels:
|
||||||
- conda-forge
|
|
||||||
- cadquery
|
- cadquery
|
||||||
|
- conda-forge
|
||||||
|
- defaults
|
||||||
dependencies:
|
dependencies:
|
||||||
- python=3.6
|
- python>=3.6
|
||||||
- pythonocc-core=0.18.2
|
- cadquery::pythonocc-core
|
||||||
- oce=0.18.2
|
|
||||||
- pyparsing
|
- pyparsing
|
||||||
- pip:
|
|
||||||
- "--editable=."
|
|
||||||
# Documentation
|
|
||||||
- sphinx
|
- sphinx
|
||||||
- sphinx_rtd_theme
|
- sphinx_rtd_theme
|
||||||
|
- black
|
||||||
|
- codecov
|
||||||
|
- pytest
|
||||||
|
- pytest-cov
|
||||||
|
- pip
|
||||||
|
- pip:
|
||||||
|
- "--editable=."
|
||||||
|
@ -13,8 +13,13 @@ center_hole_dia = 22.0 # Diameter of center hole in block
|
|||||||
# 2. The highest (max) Z face is selected and a new workplane is created on it.
|
# 2. The highest (max) Z face is selected and a new workplane is created on it.
|
||||||
# 3. The new workplane is used to drill a hole through the block.
|
# 3. The new workplane is used to drill a hole through the block.
|
||||||
# 3a. The hole is automatically centered in the workplane.
|
# 3a. The hole is automatically centered in the workplane.
|
||||||
result = cq.Workplane("XY").box(length, height, thickness) \
|
result = (
|
||||||
.faces(">Z").workplane().hole(center_hole_dia)
|
cq.Workplane("XY")
|
||||||
|
.box(length, height, thickness)
|
||||||
|
.faces(">Z")
|
||||||
|
.workplane()
|
||||||
|
.hole(center_hole_dia)
|
||||||
|
)
|
||||||
|
|
||||||
# Displays the result of this script
|
# Displays the result of this script
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
@ -26,12 +26,20 @@ cbore_depth = 2.1 # Bolt head pocket hole depth
|
|||||||
# do not show up in the final displayed geometry.
|
# do not show up in the final displayed geometry.
|
||||||
# 6. The vertices of the rectangle (corners) are selected, and a counter-bored
|
# 6. The vertices of the rectangle (corners) are selected, and a counter-bored
|
||||||
# hole is placed at each of the vertices (all 4 of them at once).
|
# hole is placed at each of the vertices (all 4 of them at once).
|
||||||
result = cq.Workplane("XY").box(length, height, thickness) \
|
result = (
|
||||||
.faces(">Z").workplane().hole(center_hole_dia) \
|
cq.Workplane("XY")
|
||||||
.faces(">Z").workplane() \
|
.box(length, height, thickness)
|
||||||
.rect(length - cbore_inset, height - cbore_inset, forConstruction=True) \
|
.faces(">Z")
|
||||||
.vertices().cboreHole(cbore_hole_diameter, cbore_diameter, cbore_depth) \
|
.workplane()
|
||||||
.edges("|Z").fillet(2.0)
|
.hole(center_hole_dia)
|
||||||
|
.faces(">Z")
|
||||||
|
.workplane()
|
||||||
|
.rect(length - cbore_inset, height - cbore_inset, forConstruction=True)
|
||||||
|
.vertices()
|
||||||
|
.cboreHole(cbore_hole_diameter, cbore_diameter, cbore_depth)
|
||||||
|
.edges("|Z")
|
||||||
|
.fillet(2.0)
|
||||||
|
)
|
||||||
|
|
||||||
# Displays the result of this script
|
# Displays the result of this script
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
@ -21,9 +21,12 @@ rectangle_length = 19.0 # Length of rectangular hole in cylindrical plate
|
|||||||
# plate with a rectangular hole in the center.
|
# plate with a rectangular hole in the center.
|
||||||
# 3a. circle() and rect() could be changed to any other shape to completely
|
# 3a. circle() and rect() could be changed to any other shape to completely
|
||||||
# change the resulting plate and/or the hole in it.
|
# change the resulting plate and/or the hole in it.
|
||||||
result = cq.Workplane("front").circle(circle_radius) \
|
result = (
|
||||||
.rect(rectangle_width, rectangle_length) \
|
cq.Workplane("front")
|
||||||
|
.circle(circle_radius)
|
||||||
|
.rect(rectangle_width, rectangle_length)
|
||||||
.extrude(thickness)
|
.extrude(thickness)
|
||||||
|
)
|
||||||
|
|
||||||
# Displays the result of this script
|
# Displays the result of this script
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
@ -34,12 +34,16 @@ thickness = 0.25 # Thickness of the plate
|
|||||||
# 7a. Without the close(), the 2D sketch will be left open and the extrude
|
# 7a. Without the close(), the 2D sketch will be left open and the extrude
|
||||||
# operation will provide unpredictable results.
|
# operation will provide unpredictable results.
|
||||||
# 8. The 2D sketch is extruded into a solid object of the specified thickness.
|
# 8. The 2D sketch is extruded into a solid object of the specified thickness.
|
||||||
result = cq.Workplane("front").lineTo(width, 0) \
|
result = (
|
||||||
.lineTo(width, 1.0) \
|
cq.Workplane("front")
|
||||||
.threePointArc((1.0, 1.5), (0.0, 1.0)) \
|
.lineTo(width, 0)
|
||||||
.sagittaArc((-0.5, 1.0), 0.2) \
|
.lineTo(width, 1.0)
|
||||||
.radiusArc((-0.7, -0.2), -1.5) \
|
.threePointArc((1.0, 1.5), (0.0, 1.0))
|
||||||
.close().extrude(thickness)
|
.sagittaArc((-0.5, 1.0), 0.2)
|
||||||
|
.radiusArc((-0.7, -0.2), -1.5)
|
||||||
|
.close()
|
||||||
|
.extrude(thickness)
|
||||||
|
)
|
||||||
|
|
||||||
# Displays the result of this script
|
# Displays the result of this script
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
@ -30,10 +30,13 @@ polygon_dia = 1.0 # The diameter of the circle enclosing the polygon points
|
|||||||
# like cutBlind() assume a positive cut direction, but cutThruAll() assumes
|
# like cutBlind() assume a positive cut direction, but cutThruAll() assumes
|
||||||
# instead that the cut is made from a max direction and cuts downward from
|
# instead that the cut is made from a max direction and cuts downward from
|
||||||
# that max through all objects.
|
# that max through all objects.
|
||||||
result = cq.Workplane("front").box(width, height, thickness) \
|
result = (
|
||||||
.pushPoints([(0, 0.75), (0, -0.75)]) \
|
cq.Workplane("front")
|
||||||
.polygon(polygon_sides, polygon_dia) \
|
.box(width, height, thickness)
|
||||||
|
.pushPoints([(0, 0.75), (0, -0.75)])
|
||||||
|
.polygon(polygon_sides, polygon_dia)
|
||||||
.cutThruAll()
|
.cutThruAll()
|
||||||
|
)
|
||||||
|
|
||||||
# Displays the result of this script
|
# Displays the result of this script
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
@ -12,7 +12,7 @@ pts = [
|
|||||||
(t / 2.0, (t - H / 2.0)),
|
(t / 2.0, (t - H / 2.0)),
|
||||||
(W / 2.0, (t - H / 2.0)),
|
(W / 2.0, (t - H / 2.0)),
|
||||||
(W / 2.0, H / -2.0),
|
(W / 2.0, H / -2.0),
|
||||||
(0, H/-2.0)
|
(0, H / -2.0),
|
||||||
]
|
]
|
||||||
|
|
||||||
# We generate half of the I-beam outline and then mirror it to create the full
|
# We generate half of the I-beam outline and then mirror it to create the full
|
||||||
@ -30,10 +30,7 @@ pts = [
|
|||||||
# 3. Only half of the I-beam profile has been drawn so far. That half is
|
# 3. Only half of the I-beam profile has been drawn so far. That half is
|
||||||
# mirrored around the Y-axis to create the complete I-beam profile.
|
# mirrored around the Y-axis to create the complete I-beam profile.
|
||||||
# 4. The I-beam profile is extruded to the final length of the beam.
|
# 4. The I-beam profile is extruded to the final length of the beam.
|
||||||
result = cq.Workplane("front").moveTo(0, H/2.0) \
|
result = cq.Workplane("front").moveTo(0, H / 2.0).polyline(pts).mirrorY().extrude(L)
|
||||||
.polyline(pts) \
|
|
||||||
.mirrorY() \
|
|
||||||
.extrude(L)
|
|
||||||
|
|
||||||
# Displays the result of this script
|
# Displays the result of this script
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
@ -13,7 +13,7 @@ sPnts = [
|
|||||||
(1.5, 1.0),
|
(1.5, 1.0),
|
||||||
(1.0, 1.25),
|
(1.0, 1.25),
|
||||||
(0.5, 1.0),
|
(0.5, 1.0),
|
||||||
(0, 1.0)
|
(0, 1.0),
|
||||||
]
|
]
|
||||||
|
|
||||||
# 2. Generate our plate with the spline feature and make sure it is a
|
# 2. Generate our plate with the spline feature and make sure it is a
|
||||||
|
@ -13,10 +13,16 @@ import cadquery as cq
|
|||||||
# 6. Selects the vertices of the for-construction rectangle.
|
# 6. Selects the vertices of the for-construction rectangle.
|
||||||
# 7. Places holes at the center of each selected vertex.
|
# 7. Places holes at the center of each selected vertex.
|
||||||
# 7a. Since the workplane is rotated, this results in angled holes in the face.
|
# 7a. Since the workplane is rotated, this results in angled holes in the face.
|
||||||
result = cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z") \
|
result = (
|
||||||
.workplane() \
|
cq.Workplane("front")
|
||||||
.transformed(offset=(0, -1.5, 1.0), rotate=(60, 0, 0)) \
|
.box(4.0, 4.0, 0.25)
|
||||||
.rect(1.5, 1.5, forConstruction=True).vertices().hole(0.25)
|
.faces(">Z")
|
||||||
|
.workplane()
|
||||||
|
.transformed(offset=(0, -1.5, 1.0), rotate=(60, 0, 0))
|
||||||
|
.rect(1.5, 1.5, forConstruction=True)
|
||||||
|
.vertices()
|
||||||
|
.hole(0.25)
|
||||||
|
)
|
||||||
|
|
||||||
# Displays the result of this script
|
# Displays the result of this script
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
@ -12,10 +12,15 @@ import cadquery as cq
|
|||||||
# other geometry.
|
# other geometry.
|
||||||
# 6. Selects the vertices of the for-construction rectangle.
|
# 6. Selects the vertices of the for-construction rectangle.
|
||||||
# 7. Places holes at the center of each selected vertex.
|
# 7. Places holes at the center of each selected vertex.
|
||||||
result = cq.Workplane("front").box(2, 2, 0.5)\
|
result = (
|
||||||
.faces(">Z").workplane() \
|
cq.Workplane("front")
|
||||||
.rect(1.5, 1.5, forConstruction=True).vertices() \
|
.box(2, 2, 0.5)
|
||||||
|
.faces(">Z")
|
||||||
|
.workplane()
|
||||||
|
.rect(1.5, 1.5, forConstruction=True)
|
||||||
|
.vertices()
|
||||||
.hole(0.125)
|
.hole(0.125)
|
||||||
|
)
|
||||||
|
|
||||||
# Displays the result of this script
|
# Displays the result of this script
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
@ -11,10 +11,15 @@ import cadquery as cq
|
|||||||
# 5. Creates a workplane 3 mm above the face the circle was drawn on.
|
# 5. Creates a workplane 3 mm above the face the circle was drawn on.
|
||||||
# 6. Draws a 2D circle on the new, offset workplane.
|
# 6. Draws a 2D circle on the new, offset workplane.
|
||||||
# 7. Creates a loft between the circle and the rectangle.
|
# 7. Creates a loft between the circle and the rectangle.
|
||||||
result = cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z") \
|
result = (
|
||||||
.circle(1.5).workplane(offset=3.0) \
|
cq.Workplane("front")
|
||||||
.rect(0.75, 0.5) \
|
.box(4.0, 4.0, 0.25)
|
||||||
|
.faces(">Z")
|
||||||
|
.circle(1.5)
|
||||||
|
.workplane(offset=3.0)
|
||||||
|
.rect(0.75, 0.5)
|
||||||
.loft(combine=True)
|
.loft(combine=True)
|
||||||
|
)
|
||||||
|
|
||||||
# Displays the result of this script
|
# Displays the result of this script
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
@ -11,9 +11,15 @@ import cadquery as cq
|
|||||||
# function.
|
# function.
|
||||||
# 5a. When the depth of the counter-sink hole is set to None, the hole will be
|
# 5a. When the depth of the counter-sink hole is set to None, the hole will be
|
||||||
# cut through.
|
# cut through.
|
||||||
result = cq.Workplane(cq.Plane.XY()).box(4, 2, 0.5).faces(">Z") \
|
result = (
|
||||||
.workplane().rect(3.5, 1.5, forConstruction=True) \
|
cq.Workplane(cq.Plane.XY())
|
||||||
.vertices().cskHole(0.125, 0.25, 82.0, depth=None)
|
.box(4, 2, 0.5)
|
||||||
|
.faces(">Z")
|
||||||
|
.workplane()
|
||||||
|
.rect(3.5, 1.5, forConstruction=True)
|
||||||
|
.vertices()
|
||||||
|
.cskHole(0.125, 0.25, 82.0, depth=None)
|
||||||
|
)
|
||||||
|
|
||||||
# Displays the result of this script
|
# Displays the result of this script
|
||||||
show_object(result)
|
show_object(result)
|
||||||
|
@ -9,8 +9,7 @@ import cadquery as cq
|
|||||||
# that new geometry can be built on.
|
# that new geometry can be built on.
|
||||||
# 4. Draws a 2D circle on the new workplane and then uses it to cut a hole
|
# 4. Draws a 2D circle on the new workplane and then uses it to cut a hole
|
||||||
# all the way through the box.
|
# all the way through the box.
|
||||||
c = cq.Workplane("XY").box(1, 1, 1).faces(">Z").workplane() \
|
c = cq.Workplane("XY").box(1, 1, 1).faces(">Z").workplane().circle(0.25).cutThruAll()
|
||||||
.circle(0.25).cutThruAll()
|
|
||||||
|
|
||||||
# 5. Selects the face furthest away from the origin in the +Y axis direction.
|
# 5. Selects the face furthest away from the origin in the +Y axis direction.
|
||||||
# 6. Creates an offset workplane that is set in the center of the object.
|
# 6. Creates an offset workplane that is set in the center of the object.
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
|
|
||||||
# Points we will use to create spline and polyline paths to sweep over
|
# Points we will use to create spline and polyline paths to sweep over
|
||||||
pts = [
|
pts = [(0, 1), (1, 2), (2, 4)]
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 4)
|
|
||||||
]
|
|
||||||
|
|
||||||
# Spline path generated from our list of points (tuples)
|
# Spline path generated from our list of points (tuples)
|
||||||
path = cq.Workplane("XZ").spline(pts)
|
path = cq.Workplane("XZ").spline(pts)
|
||||||
|
@ -4,37 +4,80 @@ import cadquery as cq
|
|||||||
path = cq.Workplane("XZ").moveTo(-10, 0).lineTo(10, 0)
|
path = cq.Workplane("XZ").moveTo(-10, 0).lineTo(10, 0)
|
||||||
|
|
||||||
# Sweep a circle from diameter 2.0 to diameter 1.0 to diameter 2.0 along X axis length 10.0 + 10.0
|
# Sweep a circle from diameter 2.0 to diameter 1.0 to diameter 2.0 along X axis length 10.0 + 10.0
|
||||||
defaultSweep = cq.Workplane("YZ").workplane(offset=-10.0).circle(2.0). \
|
defaultSweep = (
|
||||||
workplane(offset=10.0).circle(1.0). \
|
cq.Workplane("YZ")
|
||||||
workplane(offset=10.0).circle(2.0).sweep(path, multisection=True)
|
.workplane(offset=-10.0)
|
||||||
|
.circle(2.0)
|
||||||
|
.workplane(offset=10.0)
|
||||||
|
.circle(1.0)
|
||||||
|
.workplane(offset=10.0)
|
||||||
|
.circle(2.0)
|
||||||
|
.sweep(path, multisection=True)
|
||||||
|
)
|
||||||
|
|
||||||
# We can sweep thrue different shapes
|
# We can sweep thrue different shapes
|
||||||
recttocircleSweep = cq.Workplane("YZ").workplane(offset=-10.0).rect(2.0, 2.0). \
|
recttocircleSweep = (
|
||||||
workplane(offset=8.0).circle(1.0).workplane(offset=4.0).circle(1.0). \
|
cq.Workplane("YZ")
|
||||||
workplane(offset=8.0).rect(2.0, 2.0).sweep(path, multisection=True)
|
.workplane(offset=-10.0)
|
||||||
|
.rect(2.0, 2.0)
|
||||||
|
.workplane(offset=8.0)
|
||||||
|
.circle(1.0)
|
||||||
|
.workplane(offset=4.0)
|
||||||
|
.circle(1.0)
|
||||||
|
.workplane(offset=8.0)
|
||||||
|
.rect(2.0, 2.0)
|
||||||
|
.sweep(path, multisection=True)
|
||||||
|
)
|
||||||
|
|
||||||
circletorectSweep = cq.Workplane("YZ").workplane(offset=-10.0).circle(1.0). \
|
circletorectSweep = (
|
||||||
workplane(offset=7.0).rect(2.0, 2.0).workplane(offset=6.0).rect(2.0, 2.0). \
|
cq.Workplane("YZ")
|
||||||
workplane(offset=7.0).circle(1.0).sweep(path, multisection=True)
|
.workplane(offset=-10.0)
|
||||||
|
.circle(1.0)
|
||||||
|
.workplane(offset=7.0)
|
||||||
|
.rect(2.0, 2.0)
|
||||||
|
.workplane(offset=6.0)
|
||||||
|
.rect(2.0, 2.0)
|
||||||
|
.workplane(offset=7.0)
|
||||||
|
.circle(1.0)
|
||||||
|
.sweep(path, multisection=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Placement of the Shape is important otherwise could produce unexpected shape
|
# Placement of the Shape is important otherwise could produce unexpected shape
|
||||||
specialSweep = cq.Workplane("YZ").circle(1.0).workplane(offset=10.0).rect(2.0, 2.0). \
|
specialSweep = (
|
||||||
sweep(path, multisection=True)
|
cq.Workplane("YZ")
|
||||||
|
.circle(1.0)
|
||||||
|
.workplane(offset=10.0)
|
||||||
|
.rect(2.0, 2.0)
|
||||||
|
.sweep(path, multisection=True)
|
||||||
|
)
|
||||||
|
|
||||||
# Switch to an arc for the path : line l=5.0 then half circle r=4.0 then line l=5.0
|
# Switch to an arc for the path : line l=5.0 then half circle r=4.0 then line l=5.0
|
||||||
path = cq.Workplane("XZ").moveTo(-5, 4).lineTo(0, 4). \
|
path = (
|
||||||
threePointArc((4, 0), (0, -4)).lineTo(-5, -4)
|
cq.Workplane("XZ")
|
||||||
|
.moveTo(-5, 4)
|
||||||
|
.lineTo(0, 4)
|
||||||
|
.threePointArc((4, 0), (0, -4))
|
||||||
|
.lineTo(-5, -4)
|
||||||
|
)
|
||||||
|
|
||||||
# Placement of different shapes should follow the path
|
# Placement of different shapes should follow the path
|
||||||
# cylinder r=1.5 along first line
|
# cylinder r=1.5 along first line
|
||||||
# then sweep allong arc from r=1.5 to r=1.0
|
# then sweep allong arc from r=1.5 to r=1.0
|
||||||
# then cylinder r=1.0 along last line
|
# then cylinder r=1.0 along last line
|
||||||
arcSweep = cq.Workplane("YZ").workplane(offset=-5).moveTo(0, 4).circle(1.5). \
|
arcSweep = (
|
||||||
workplane(offset=5).circle(1.5). \
|
cq.Workplane("YZ")
|
||||||
moveTo(0, -8).circle(1.0). \
|
.workplane(offset=-5)
|
||||||
workplane(offset=-5).circle(1.0). \
|
.moveTo(0, 4)
|
||||||
sweep(path, multisection=True)
|
.circle(1.5)
|
||||||
|
.workplane(offset=5)
|
||||||
|
.circle(1.5)
|
||||||
|
.moveTo(0, -8)
|
||||||
|
.circle(1.0)
|
||||||
|
.workplane(offset=-5)
|
||||||
|
.circle(1.0)
|
||||||
|
.sweep(path, multisection=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Translate the resulting solids so that they do not overlap and display them left to right
|
# Translate the resulting solids so that they do not overlap and display them left to right
|
||||||
@ -43,5 +86,3 @@ show_object(circletorectSweep.translate((0, 5, 0)))
|
|||||||
show_object(recttocircleSweep.translate((0, 10, 0)))
|
show_object(recttocircleSweep.translate((0, 10, 0)))
|
||||||
show_object(specialSweep.translate((0, 15, 0)))
|
show_object(specialSweep.translate((0, 15, 0)))
|
||||||
show_object(arcSweep.translate((0, -5, 0)))
|
show_object(arcSweep.translate((0, -5, 0)))
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,23 +32,37 @@ s = cq.Workplane("XY").box(total_length, total_width, height)
|
|||||||
s = s.faces("<Z").shell(-1.0 * t)
|
s = s.faces("<Z").shell(-1.0 * t)
|
||||||
|
|
||||||
# make the bumps on the top
|
# make the bumps on the top
|
||||||
s = s.faces(">Z").workplane(). \
|
s = (
|
||||||
rarray(pitch, pitch, lbumps, wbumps, True).circle(bumpDiam / 2.0) \
|
s.faces(">Z")
|
||||||
|
.workplane()
|
||||||
|
.rarray(pitch, pitch, lbumps, wbumps, True)
|
||||||
|
.circle(bumpDiam / 2.0)
|
||||||
.extrude(bumpHeight)
|
.extrude(bumpHeight)
|
||||||
|
)
|
||||||
|
|
||||||
# add posts on the bottom. posts are different diameter depending on geometry
|
# add posts on the bottom. posts are different diameter depending on geometry
|
||||||
# solid studs for 1 bump, tubes for multiple, none for 1x1
|
# solid studs for 1 bump, tubes for multiple, none for 1x1
|
||||||
tmp = s.faces("<Z").workplane(invert=True)
|
tmp = s.faces("<Z").workplane(invert=True)
|
||||||
|
|
||||||
if lbumps > 1 and wbumps > 1:
|
if lbumps > 1 and wbumps > 1:
|
||||||
tmp = tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True). \
|
tmp = (
|
||||||
circle(postDiam / 2.0).circle(bumpDiam / 2.0).extrude(height - t)
|
tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True)
|
||||||
|
.circle(postDiam / 2.0)
|
||||||
|
.circle(bumpDiam / 2.0)
|
||||||
|
.extrude(height - t)
|
||||||
|
)
|
||||||
elif lbumps > 1:
|
elif lbumps > 1:
|
||||||
tmp = tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True). \
|
tmp = (
|
||||||
circle(t).extrude(height - t)
|
tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True)
|
||||||
|
.circle(t)
|
||||||
|
.extrude(height - t)
|
||||||
|
)
|
||||||
elif wbumps > 1:
|
elif wbumps > 1:
|
||||||
tmp = tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True). \
|
tmp = (
|
||||||
circle(t).extrude(height - t)
|
tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True)
|
||||||
|
.circle(t)
|
||||||
|
.extrude(height - t)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
tmp = s
|
tmp = s
|
||||||
|
|
||||||
|
175
examples/Ex101_InterpPlate.py
Normal file
175
examples/Ex101_InterpPlate.py
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
from math import sin, cos, pi, sqrt
|
||||||
|
import cadquery as cq
|
||||||
|
|
||||||
|
# TEST_1
|
||||||
|
# example from PythonOCC core_geometry_geomplate.py, use of thickness = 0 returns 2D surface.
|
||||||
|
thickness = 0
|
||||||
|
edge_points = [[0.0, 0.0, 0.0], [0.0, 10.0, 0.0], [0.0, 10.0, 10.0], [0.0, 0.0, 10.0]]
|
||||||
|
surface_points = [[5.0, 5.0, 5.0]]
|
||||||
|
plate_0 = cq.Workplane("XY").interpPlate(edge_points, surface_points, thickness)
|
||||||
|
print("plate_0.val().Volume() = ", plate_0.val().Volume())
|
||||||
|
plate_0 = plate_0.translate((0, 6 * 12, 0))
|
||||||
|
show_object(plate_0)
|
||||||
|
|
||||||
|
# EXAMPLE 1
|
||||||
|
# Plate with 5 sides and 2 bumps, one side is not co-planar with the other sides
|
||||||
|
thickness = 0.1
|
||||||
|
edge_points = [
|
||||||
|
[-7.0, -7.0, 0.0],
|
||||||
|
[-3.0, -10.0, 3.0],
|
||||||
|
[7.0, -7.0, 0.0],
|
||||||
|
[7.0, 7.0, 0.0],
|
||||||
|
[-7.0, 7.0, 0.0],
|
||||||
|
]
|
||||||
|
edge_wire = cq.Workplane("XY").polyline(
|
||||||
|
[(-7.0, -7.0), (7.0, -7.0), (7.0, 7.0), (-7.0, 7.0)]
|
||||||
|
)
|
||||||
|
# edge_wire = edge_wire.add(cq.Workplane("YZ").workplane().transformed(offset=cq.Vector(0, 0, -7), rotate=cq.Vector(45, 0, 0)).polyline([(-7.,0.), (3,-3), (7.,0.)]))
|
||||||
|
# In CadQuery Sept-2019 it worked with rotate=cq.Vector(0, 45, 0). In CadQuery Dec-2019 rotate=cq.Vector(45, 0, 0) only closes the wire.
|
||||||
|
edge_wire = edge_wire.add(
|
||||||
|
cq.Workplane("YZ")
|
||||||
|
.workplane()
|
||||||
|
.transformed(offset=cq.Vector(0, 0, -7), rotate=cq.Vector(45, 0, 0))
|
||||||
|
.spline([(-7.0, 0.0), (3, -3), (7.0, 0.0)])
|
||||||
|
)
|
||||||
|
surface_points = [[-3.0, -3.0, -3.0], [3.0, 3.0, 3.0]]
|
||||||
|
plate_1 = cq.Workplane("XY").interpPlate(edge_wire, surface_points, thickness)
|
||||||
|
# plate_1 = cq.Workplane("XY").interpPlate(edge_points, surface_points, thickness) # list of (x,y,z) points instead of wires for edges
|
||||||
|
print("plate_1.val().Volume() = ", plate_1.val().Volume())
|
||||||
|
show_object(plate_1)
|
||||||
|
|
||||||
|
# EXAMPLE 2
|
||||||
|
# Embossed star, need to change optional parameters to obtain nice looking result.
|
||||||
|
r1 = 3.0
|
||||||
|
r2 = 10.0
|
||||||
|
fn = 6
|
||||||
|
thickness = 0.1
|
||||||
|
edge_points = [
|
||||||
|
[r1 * cos(i * pi / fn), r1 * sin(i * pi / fn)]
|
||||||
|
if i % 2 == 0
|
||||||
|
else [r2 * cos(i * pi / fn), r2 * sin(i * pi / fn)]
|
||||||
|
for i in range(2 * fn + 1)
|
||||||
|
]
|
||||||
|
edge_wire = cq.Workplane("XY").polyline(edge_points)
|
||||||
|
r2 = 4.5
|
||||||
|
surface_points = [
|
||||||
|
[r2 * cos(i * pi / fn), r2 * sin(i * pi / fn), 1.0] for i in range(2 * fn)
|
||||||
|
] + [[0.0, 0.0, -2.0]]
|
||||||
|
plate_2 = cq.Workplane("XY").interpPlate(
|
||||||
|
edge_wire,
|
||||||
|
surface_points,
|
||||||
|
thickness,
|
||||||
|
combine=True,
|
||||||
|
clean=True,
|
||||||
|
degree=3,
|
||||||
|
nbPtsOnCur=15,
|
||||||
|
nbIter=2,
|
||||||
|
anisotropy=False,
|
||||||
|
tol2d=0.00001,
|
||||||
|
tol3d=0.0001,
|
||||||
|
tolAng=0.01,
|
||||||
|
tolCurv=0.1,
|
||||||
|
maxDeg=8,
|
||||||
|
maxSegments=49,
|
||||||
|
)
|
||||||
|
# plate_2 = cq.Workplane("XY").interpPlate(edge_points, surface_points, thickness, combine=True, clean=True, Degree=3, NbPtsOnCur=15, NbIter=2, Anisotropie=False, Tol2d=0.00001, Tol3d=0.0001, TolAng=0.01, TolCurv=0.1, MaxDeg=8, MaxSegments=49) # list of (x,y,z) points instead of wires for edges
|
||||||
|
print("plate_2.val().Volume() = ", plate_2.val().Volume())
|
||||||
|
plate_2 = plate_2.translate((0, 2 * 12, 0))
|
||||||
|
show_object(plate_2)
|
||||||
|
|
||||||
|
# EXAMPLE 3
|
||||||
|
# Points on hexagonal pattern coordinates, use of pushpoints.
|
||||||
|
r1 = 1.0
|
||||||
|
N = 3
|
||||||
|
ca = cos(30.0 * pi / 180.0)
|
||||||
|
sa = sin(30.0 * pi / 180.0)
|
||||||
|
# EVEN ROWS
|
||||||
|
pts = [
|
||||||
|
(-3.0, -3.0),
|
||||||
|
(-1.267949, -3.0),
|
||||||
|
(0.464102, -3.0),
|
||||||
|
(2.196152, -3.0),
|
||||||
|
(-3.0, 0.0),
|
||||||
|
(-1.267949, 0.0),
|
||||||
|
(0.464102, 0.0),
|
||||||
|
(2.196152, 0.0),
|
||||||
|
(-2.133974, -1.5),
|
||||||
|
(-0.401923, -1.5),
|
||||||
|
(1.330127, -1.5),
|
||||||
|
(3.062178, -1.5),
|
||||||
|
(-2.133975, 1.5),
|
||||||
|
(-0.401924, 1.5),
|
||||||
|
(1.330127, 1.5),
|
||||||
|
(3.062178, 1.5),
|
||||||
|
]
|
||||||
|
# Spike surface
|
||||||
|
thickness = 0.1
|
||||||
|
fn = 6
|
||||||
|
edge_points = [
|
||||||
|
[
|
||||||
|
r1 * cos(i * 2 * pi / fn + 30 * pi / 180),
|
||||||
|
r1 * sin(i * 2 * pi / fn + 30 * pi / 180),
|
||||||
|
]
|
||||||
|
for i in range(fn + 1)
|
||||||
|
]
|
||||||
|
surface_points = [
|
||||||
|
[
|
||||||
|
r1 / 4 * cos(i * 2 * pi / fn + 30 * pi / 180),
|
||||||
|
r1 / 4 * sin(i * 2 * pi / fn + 30 * pi / 180),
|
||||||
|
0.75,
|
||||||
|
]
|
||||||
|
for i in range(fn + 1)
|
||||||
|
] + [[0, 0, 2]]
|
||||||
|
edge_wire = cq.Workplane("XY").polyline(edge_points)
|
||||||
|
plate_3 = (
|
||||||
|
cq.Workplane("XY")
|
||||||
|
.pushPoints(pts)
|
||||||
|
.interpPlate(
|
||||||
|
edge_wire,
|
||||||
|
surface_points,
|
||||||
|
thickness,
|
||||||
|
combine=False,
|
||||||
|
clean=False,
|
||||||
|
degree=2,
|
||||||
|
nbPtsOnCur=20,
|
||||||
|
nbIter=2,
|
||||||
|
anisotropy=False,
|
||||||
|
tol2d=0.00001,
|
||||||
|
tol3d=0.0001,
|
||||||
|
tolAng=0.01,
|
||||||
|
tolCurv=0.1,
|
||||||
|
maxDeg=8,
|
||||||
|
maxSegments=9,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
print("plate_3.val().Volume() = ", plate_3.val().Volume())
|
||||||
|
plate_3 = plate_3.translate((0, 4 * 11, 0))
|
||||||
|
show_object(plate_3)
|
||||||
|
|
||||||
|
# EXAMPLE 4
|
||||||
|
# Gyroïd, all edges are splines on different workplanes.
|
||||||
|
thickness = 0.1
|
||||||
|
edge_points = [
|
||||||
|
[[3.54, 3.54], [1.77, 0.0], [3.54, -3.54]],
|
||||||
|
[[-3.54, -3.54], [0.0, -1.77], [3.54, -3.54]],
|
||||||
|
[[-3.54, -3.54], [0.0, -1.77], [3.54, -3.54]],
|
||||||
|
[[-3.54, -3.54], [-1.77, 0.0], [-3.54, 3.54]],
|
||||||
|
[[3.54, 3.54], [0.0, 1.77], [-3.54, 3.54]],
|
||||||
|
[[3.54, 3.54], [0.0, 1.77], [-3.54, 3.54]],
|
||||||
|
]
|
||||||
|
plane_list = ["XZ", "XY", "YZ", "XZ", "YZ", "XY"]
|
||||||
|
offset_list = [-3.54, 3.54, 3.54, 3.54, -3.54, -3.54]
|
||||||
|
edge_wire = (
|
||||||
|
cq.Workplane(plane_list[0]).workplane(offset=-offset_list[0]).spline(edge_points[0])
|
||||||
|
)
|
||||||
|
for i in range(len(edge_points) - 1):
|
||||||
|
edge_wire = edge_wire.add(
|
||||||
|
cq.Workplane(plane_list[i + 1])
|
||||||
|
.workplane(offset=-offset_list[i + 1])
|
||||||
|
.spline(edge_points[i + 1])
|
||||||
|
)
|
||||||
|
surface_points = [[0, 0, 0]]
|
||||||
|
plate_4 = cq.Workplane("XY").interpPlate(edge_wire, surface_points, thickness)
|
||||||
|
print("plate_4.val().Volume() = ", plate_4.val().Volume())
|
||||||
|
plate_4 = plate_4.translate((0, 5 * 12, 0))
|
||||||
|
show_object(plate_4)
|
@ -1,3 +0,0 @@
|
|||||||
sphinx-rtd-theme==0.1.9
|
|
||||||
travis-sphinx==1.1.0
|
|
||||||
Sphinx==1.3.1
|
|
22
runtests.py
22
runtests.py
@ -1,22 +0,0 @@
|
|||||||
import sys
|
|
||||||
from tests import *
|
|
||||||
import cadquery
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
#if you are on python 2.7, you can use -m uniitest discover.
|
|
||||||
#but this is required for python 2.6.6 on windows. FreeCAD0.12 will not load
|
|
||||||
#on py 2.7.x on win
|
|
||||||
suite = unittest.TestSuite()
|
|
||||||
|
|
||||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadObjects.TestCadObjects))
|
|
||||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadQuery.TestCadQuery))
|
|
||||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCQGI.TestCQGI))
|
|
||||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCQSelectors.TestCQSelectors))
|
|
||||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestExporters.TestExporters))
|
|
||||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestImporters.TestImporters))
|
|
||||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestJupyter.TestJupyter))
|
|
||||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestWorkplanes.TestWorkplanes))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
result = unittest.TextTestRunner(verbosity=2).run(suite)
|
|
||||||
sys.exit(not result.wasSuccessful())
|
|
64
setup.py
64
setup.py
@ -16,43 +16,47 @@ from setuptools import setup
|
|||||||
|
|
||||||
|
|
||||||
# if we are building in travis, use the build number as the sub-minor version
|
# if we are building in travis, use the build number as the sub-minor version
|
||||||
version = '0.5-SNAPSHOT'
|
version = "0.5-SNAPSHOT"
|
||||||
if 'TRAVIS_TAG' in os.environ.keys():
|
if "TRAVIS_TAG" in os.environ.keys():
|
||||||
version= os.environ['TRAVIS_TAG']
|
version = os.environ["TRAVIS_TAG"]
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='cadquery',
|
name="cadquery",
|
||||||
version=version,
|
version=version,
|
||||||
url='https://github.com/dcowden/cadquery',
|
url="https://github.com/dcowden/cadquery",
|
||||||
license='Apache Public License 2.0',
|
license="Apache Public License 2.0",
|
||||||
author='David Cowden',
|
author="David Cowden",
|
||||||
author_email='dave.cowden@gmail.com',
|
author_email="dave.cowden@gmail.com",
|
||||||
description='CadQuery is a parametric scripting language for creating and traversing CAD models',
|
description="CadQuery is a parametric scripting language for creating and traversing CAD models",
|
||||||
long_description=open('README.md').read(),
|
long_description=open("README.md").read(),
|
||||||
packages=['cadquery','cadquery.contrib','cadquery.occ_impl','cadquery.plugins','tests'],
|
packages=[
|
||||||
install_requires=['pyparsing'],
|
"cadquery",
|
||||||
|
"cadquery.contrib",
|
||||||
|
"cadquery.occ_impl",
|
||||||
|
"cadquery.plugins",
|
||||||
|
"tests",
|
||||||
|
],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
platforms='any',
|
platforms="any",
|
||||||
test_suite='tests',
|
test_suite="tests",
|
||||||
|
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 5 - Production/Stable',
|
"Development Status :: 5 - Production/Stable",
|
||||||
#'Development Status :: 6 - Mature',
|
#'Development Status :: 6 - Mature',
|
||||||
#'Development Status :: 7 - Inactive',
|
#'Development Status :: 7 - Inactive',
|
||||||
'Intended Audience :: Developers',
|
"Intended Audience :: Developers",
|
||||||
'Intended Audience :: End Users/Desktop',
|
"Intended Audience :: End Users/Desktop",
|
||||||
'Intended Audience :: Information Technology',
|
"Intended Audience :: Information Technology",
|
||||||
'Intended Audience :: Science/Research',
|
"Intended Audience :: Science/Research",
|
||||||
'Intended Audience :: System Administrators',
|
"Intended Audience :: System Administrators",
|
||||||
'License :: OSI Approved :: Apache Software License',
|
"License :: OSI Approved :: Apache Software License",
|
||||||
'Operating System :: POSIX',
|
"Operating System :: POSIX",
|
||||||
'Operating System :: MacOS',
|
"Operating System :: MacOS",
|
||||||
'Operating System :: Unix',
|
"Operating System :: Unix",
|
||||||
'Programming Language :: Python',
|
"Programming Language :: Python",
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
'Topic :: Internet',
|
"Topic :: Internet",
|
||||||
'Topic :: Scientific/Engineering'
|
"Topic :: Scientific/Engineering",
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
|
@ -1,284 +0,0 @@
|
|||||||
# system modules
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
from tests import BaseTest
|
|
||||||
from OCP.gp import gp, gp_Vec, gp_Pnt, gp_Ax2, gp_Circ, gp_XYZ
|
|
||||||
from OCP.BRepBuilderAPI import (BRepBuilderAPI_MakeVertex,
|
|
||||||
BRepBuilderAPI_MakeEdge,
|
|
||||||
BRepBuilderAPI_MakeFace)
|
|
||||||
|
|
||||||
from OCP.GC import GC_MakeCircle
|
|
||||||
|
|
||||||
from cadquery import *
|
|
||||||
|
|
||||||
|
|
||||||
class TestCadObjects(BaseTest):
|
|
||||||
|
|
||||||
def _make_circle(self):
|
|
||||||
|
|
||||||
circle = gp_Circ(gp_Ax2(gp_Pnt(1, 2, 3), gp.DZ_s()),
|
|
||||||
2.)
|
|
||||||
return Shape.cast(BRepBuilderAPI_MakeEdge(circle).Edge())
|
|
||||||
|
|
||||||
def testVectorConstructors(self):
|
|
||||||
v1 = Vector(1, 2, 3)
|
|
||||||
v2 = Vector((1, 2, 3))
|
|
||||||
v3 = Vector(gp_Vec(1, 2, 3))
|
|
||||||
v4 = Vector([1,2,3])
|
|
||||||
v5 = Vector(gp_XYZ(1,2,3))
|
|
||||||
|
|
||||||
for v in [v1, v2, v3, v4, v5]:
|
|
||||||
self.assertTupleAlmostEquals((1, 2, 3), v.toTuple(), 4)
|
|
||||||
|
|
||||||
v6 = Vector((1,2))
|
|
||||||
v7 = Vector([1,2])
|
|
||||||
v8 = Vector(1,2)
|
|
||||||
|
|
||||||
for v in [v6, v7, v8]:
|
|
||||||
self.assertTupleAlmostEquals((1, 2, 0), v.toTuple(), 4)
|
|
||||||
|
|
||||||
v9 = Vector()
|
|
||||||
self.assertTupleAlmostEquals((0, 0, 0), v9.toTuple(), 4)
|
|
||||||
|
|
||||||
v9.x = 1.
|
|
||||||
v9.y = 2.
|
|
||||||
v9.z = 3.
|
|
||||||
self.assertTupleAlmostEquals((1, 2, 3), (v9.x, v9.y, v9.z), 4)
|
|
||||||
|
|
||||||
def testVertex(self):
|
|
||||||
"""
|
|
||||||
Tests basic vertex functions
|
|
||||||
"""
|
|
||||||
v = Vertex.makeVertex(1, 1, 1)
|
|
||||||
self.assertEqual(1, v.X)
|
|
||||||
self.assertEqual(Vector, type(v.Center()))
|
|
||||||
|
|
||||||
def testBasicBoundingBox(self):
|
|
||||||
v = Vertex.makeVertex(1, 1, 1)
|
|
||||||
v2 = Vertex.makeVertex(2, 2, 2)
|
|
||||||
self.assertEqual(BoundBox, type(v.BoundingBox()))
|
|
||||||
self.assertEqual(BoundBox, type(v2.BoundingBox()))
|
|
||||||
|
|
||||||
bb1 = v.BoundingBox().add(v2.BoundingBox())
|
|
||||||
|
|
||||||
# OCC uses some approximations
|
|
||||||
self.assertAlmostEqual(bb1.xlen, 1.0, 1)
|
|
||||||
|
|
||||||
def testEdgeWrapperCenter(self):
|
|
||||||
e = self._make_circle()
|
|
||||||
|
|
||||||
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), e.Center().toTuple(), 3)
|
|
||||||
|
|
||||||
def testEdgeWrapperMakeCircle(self):
|
|
||||||
halfCircleEdge = Edge.makeCircle(radius=10, pnt=(
|
|
||||||
0, 0, 0), dir=(0, 0, 1), angle1=0, angle2=180)
|
|
||||||
|
|
||||||
#self.assertTupleAlmostEquals((0.0, 5.0, 0.0), halfCircleEdge.CenterOfBoundBox(0.0001).toTuple(),3)
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
(10.0, 0.0, 0.0), halfCircleEdge.startPoint().toTuple(), 3)
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
(-10.0, 0.0, 0.0), halfCircleEdge.endPoint().toTuple(), 3)
|
|
||||||
|
|
||||||
def testFaceWrapperMakePlane(self):
|
|
||||||
mplane = Face.makePlane(10, 10)
|
|
||||||
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
(0.0, 0.0, 1.0), mplane.normalAt().toTuple(), 3)
|
|
||||||
|
|
||||||
def testCenterOfBoundBox(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def testCombinedCenterOfBoundBox(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def testCompoundCenter(self):
|
|
||||||
"""
|
|
||||||
Tests whether or not a proper weighted center can be found for a compound
|
|
||||||
"""
|
|
||||||
|
|
||||||
def cylinders(self, radius, height):
|
|
||||||
def _cyl(pnt):
|
|
||||||
# Inner function to build a cylinder
|
|
||||||
return Solid.makeCylinder(radius, height, pnt)
|
|
||||||
|
|
||||||
# Combine all the cylinders into a single compound
|
|
||||||
r = self.eachpoint(_cyl, True).combineSolids()
|
|
||||||
|
|
||||||
return r
|
|
||||||
|
|
||||||
Workplane.cyl = cylinders
|
|
||||||
|
|
||||||
# Now test. here we want weird workplane to see if the objects are transformed right
|
|
||||||
s = Workplane("XY").rect(
|
|
||||||
2.0, 3.0, forConstruction=True).vertices().cyl(0.25, 0.5)
|
|
||||||
|
|
||||||
self.assertEqual(4, len(s.val().Solids()))
|
|
||||||
self.assertTupleAlmostEquals(
|
|
||||||
(0.0, 0.0, 0.25), s.val().Center().toTuple(), 3)
|
|
||||||
|
|
||||||
def testDot(self):
|
|
||||||
v1 = Vector(2, 2, 2)
|
|
||||||
v2 = Vector(1, -1, 1)
|
|
||||||
self.assertEqual(2.0, v1.dot(v2))
|
|
||||||
|
|
||||||
def testVectorAdd(self):
|
|
||||||
result = Vector(1, 2, 0) + Vector(0, 0, 3)
|
|
||||||
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), result.toTuple(), 3)
|
|
||||||
|
|
||||||
def testVectorOperators(self):
|
|
||||||
result = Vector(1, 1, 1) + Vector(2, 2, 2)
|
|
||||||
self.assertEqual(Vector(3, 3, 3), result)
|
|
||||||
|
|
||||||
result = Vector(1, 2, 3) - Vector(3, 2, 1)
|
|
||||||
self.assertEqual(Vector(-2, 0, 2), result)
|
|
||||||
|
|
||||||
result = Vector(1, 2, 3) * 2
|
|
||||||
self.assertEqual(Vector(2, 4, 6), result)
|
|
||||||
|
|
||||||
result = Vector(2, 4, 6) / 2
|
|
||||||
self.assertEqual(Vector(1, 2, 3), result)
|
|
||||||
|
|
||||||
self.assertEqual(Vector(-1, -1, -1), -Vector(1, 1, 1))
|
|
||||||
|
|
||||||
self.assertEqual(0, abs(Vector(0, 0, 0)))
|
|
||||||
self.assertEqual(1, abs(Vector(1, 0, 0)))
|
|
||||||
self.assertEqual((1+4+9)**0.5, abs(Vector(1, 2, 3)))
|
|
||||||
|
|
||||||
def testVectorEquals(self):
|
|
||||||
a = Vector(1, 2, 3)
|
|
||||||
b = Vector(1, 2, 3)
|
|
||||||
c = Vector(1, 2, 3.000001)
|
|
||||||
self.assertEqual(a, b)
|
|
||||||
self.assertEqual(a, c)
|
|
||||||
|
|
||||||
def testVectorProject(self):
|
|
||||||
"""
|
|
||||||
Test method to project vector to plane.
|
|
||||||
"""
|
|
||||||
decimal_places = 9
|
|
||||||
|
|
||||||
normal = Vector(1, 2, 3)
|
|
||||||
base = Vector(5, 7, 9)
|
|
||||||
x_dir = Vector(1, 0, 0)
|
|
||||||
|
|
||||||
# test passing Plane object
|
|
||||||
point = Vector(10, 11, 12).projectToPlane(Plane(base, x_dir, normal))
|
|
||||||
self.assertTupleAlmostEquals(point.toTuple(), (59/7, 55/7, 51/7),
|
|
||||||
decimal_places)
|
|
||||||
|
|
||||||
def testMatrixCreationAndAccess(self):
|
|
||||||
def matrix_vals(m):
|
|
||||||
return [[m[r,c] for c in range(4)] for r in range(4)]
|
|
||||||
# default constructor creates a 4x4 identity matrix
|
|
||||||
m = Matrix()
|
|
||||||
identity = [[1., 0., 0., 0.],
|
|
||||||
[0., 1., 0., 0.],
|
|
||||||
[0., 0., 1., 0.],
|
|
||||||
[0., 0., 0., 1.]]
|
|
||||||
self.assertEqual(identity, matrix_vals(m))
|
|
||||||
|
|
||||||
vals4x4 = [[1., 0., 0., 1.],
|
|
||||||
[0., 1., 0., 2.],
|
|
||||||
[0., 0., 1., 3.],
|
|
||||||
[0., 0., 0., 1.]]
|
|
||||||
vals4x4_tuple = tuple(tuple(r) for r in vals4x4)
|
|
||||||
|
|
||||||
# test constructor with 16-value input
|
|
||||||
m = Matrix(vals4x4)
|
|
||||||
self.assertEqual(vals4x4, matrix_vals(m))
|
|
||||||
m = Matrix(vals4x4_tuple)
|
|
||||||
self.assertEqual(vals4x4, matrix_vals(m))
|
|
||||||
|
|
||||||
# test constructor with 12-value input (the last 4 are an implied
|
|
||||||
# [0,0,0,1])
|
|
||||||
m = Matrix(vals4x4[:3])
|
|
||||||
self.assertEqual(vals4x4, matrix_vals(m))
|
|
||||||
m = Matrix(vals4x4_tuple[:3])
|
|
||||||
self.assertEqual(vals4x4, matrix_vals(m))
|
|
||||||
|
|
||||||
# Test 16-value input with invalid values for the last 4
|
|
||||||
invalid = [[1., 0., 0., 1.],
|
|
||||||
[0., 1., 0., 2.],
|
|
||||||
[0., 0., 1., 3.],
|
|
||||||
[1., 2., 3., 4.]]
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
Matrix(invalid)
|
|
||||||
|
|
||||||
# Test input with invalid size / nested types
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
Matrix([[1, 2, 3, 4], [1, 2, 3], [1, 2, 3, 4]])
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
Matrix([1,2,3])
|
|
||||||
|
|
||||||
# Invalid sub-type
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
Matrix([[1, 2, 3, 4], 'abc', [1, 2, 3, 4]])
|
|
||||||
|
|
||||||
# test out-of-bounds access
|
|
||||||
m = Matrix()
|
|
||||||
with self.assertRaises(IndexError):
|
|
||||||
m[0, 4]
|
|
||||||
with self.assertRaises(IndexError):
|
|
||||||
m[4, 0]
|
|
||||||
with self.assertRaises(IndexError):
|
|
||||||
m['ab']
|
|
||||||
|
|
||||||
|
|
||||||
def testTranslate(self):
|
|
||||||
e = Edge.makeCircle(2, (1, 2, 3))
|
|
||||||
e2 = e.translate(Vector(0, 0, 1))
|
|
||||||
|
|
||||||
self.assertTupleAlmostEquals((1.0, 2.0, 4.0), e2.Center().toTuple(), 3)
|
|
||||||
|
|
||||||
def testVertices(self):
|
|
||||||
e = Shape.cast(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 0),
|
|
||||||
gp_Pnt(1, 1, 0)).Edge())
|
|
||||||
self.assertEqual(2, len(e.Vertices()))
|
|
||||||
|
|
||||||
def testPlaneEqual(self):
|
|
||||||
# default orientation
|
|
||||||
self.assertEqual(
|
|
||||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
|
||||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1))
|
|
||||||
)
|
|
||||||
# moved origin
|
|
||||||
self.assertEqual(
|
|
||||||
Plane(origin=(2,1,-1), xDir=(1,0,0), normal=(0,0,1)),
|
|
||||||
Plane(origin=(2,1,-1), xDir=(1,0,0), normal=(0,0,1))
|
|
||||||
)
|
|
||||||
# moved x-axis
|
|
||||||
self.assertEqual(
|
|
||||||
Plane(origin=(0,0,0), xDir=(1,1,0), normal=(0,0,1)),
|
|
||||||
Plane(origin=(0,0,0), xDir=(1,1,0), normal=(0,0,1))
|
|
||||||
)
|
|
||||||
# moved z-axis
|
|
||||||
self.assertEqual(
|
|
||||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,1,1)),
|
|
||||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,1,1))
|
|
||||||
)
|
|
||||||
|
|
||||||
def testPlaneNotEqual(self):
|
|
||||||
# type difference
|
|
||||||
for value in [None, 0, 1, 'abc']:
|
|
||||||
self.assertNotEqual(
|
|
||||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
|
||||||
value
|
|
||||||
)
|
|
||||||
# origin difference
|
|
||||||
self.assertNotEqual(
|
|
||||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
|
||||||
Plane(origin=(0,0,1), xDir=(1,0,0), normal=(0,0,1))
|
|
||||||
)
|
|
||||||
# x-axis difference
|
|
||||||
self.assertNotEqual(
|
|
||||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
|
||||||
Plane(origin=(0,0,0), xDir=(1,1,0), normal=(0,0,1))
|
|
||||||
)
|
|
||||||
# z-axis difference
|
|
||||||
self.assertNotEqual(
|
|
||||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
|
|
||||||
Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,1,1))
|
|
||||||
)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
File diff suppressed because it is too large
Load Diff
@ -6,21 +6,23 @@ import os
|
|||||||
|
|
||||||
|
|
||||||
def readFileAsString(fileName):
|
def readFileAsString(fileName):
|
||||||
f = open(fileName, 'r')
|
f = open(fileName, "r")
|
||||||
s = f.read()
|
s = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
def writeStringToFile(strToWrite, fileName):
|
def writeStringToFile(strToWrite, fileName):
|
||||||
f = open(fileName, 'w')
|
f = open(fileName, "w")
|
||||||
f.write(strToWrite)
|
f.write(strToWrite)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
def makeUnitSquareWire():
|
def makeUnitSquareWire():
|
||||||
V = Vector
|
V = Vector
|
||||||
return Wire.makePolygon([V(0, 0, 0), V(1, 0, 0), V(1, 1, 0), V(0, 1, 0), V(0, 0, 0)])
|
return Wire.makePolygon(
|
||||||
|
[V(0, 0, 0), V(1, 0, 0), V(1, 1, 0), V(0, 1, 0), V(0, 0, 0)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def makeUnitCube():
|
def makeUnitCube():
|
||||||
@ -38,25 +40,23 @@ def toTuple(v):
|
|||||||
elif type(v) == Vector:
|
elif type(v) == Vector:
|
||||||
return v.toTuple()
|
return v.toTuple()
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(
|
raise RuntimeError("dont know how to convert type %s to tuple" % str(type(v)))
|
||||||
"dont know how to convert type %s to tuple" % str(type(v)))
|
|
||||||
|
|
||||||
|
|
||||||
class BaseTest(unittest.TestCase):
|
class BaseTest(unittest.TestCase):
|
||||||
|
|
||||||
def assertTupleAlmostEquals(self, expected, actual, places):
|
def assertTupleAlmostEquals(self, expected, actual, places):
|
||||||
for i, j in zip(actual, expected):
|
for i, j in zip(actual, expected):
|
||||||
self.assertAlmostEqual(i, j, places)
|
self.assertAlmostEqual(i, j, places)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'TestCadObjects',
|
"TestCadObjects",
|
||||||
'TestCadQuery',
|
"TestCadQuery",
|
||||||
'TestCQGI',
|
"TestCQGI",
|
||||||
'TestCQSelectors',
|
"TestCQSelectors",
|
||||||
'TestCQSelectors',
|
"TestCQSelectors",
|
||||||
'TestExporters',
|
"TestExporters",
|
||||||
'TestImporters',
|
"TestImporters",
|
||||||
'TestJupyter',
|
"TestJupyter",
|
||||||
'TestWorkplanes',
|
"TestWorkplanes",
|
||||||
]
|
]
|
||||||
|
406
tests/test_cad_objects.py
Normal file
406
tests/test_cad_objects.py
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
# system modules
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from tests import BaseTest
|
||||||
|
from OCP.gp import gp_Vec, gp_Pnt, gp_Ax2, gp_Circ, gp_Elips, gp, gp_XYZ
|
||||||
|
from OCP.BRepBuilderAPI import (
|
||||||
|
BRepBuilderAPI_MakeVertex,
|
||||||
|
BRepBuilderAPI_MakeEdge,
|
||||||
|
BRepBuilderAPI_MakeFace,
|
||||||
|
)
|
||||||
|
|
||||||
|
from OCP.GC import GC_MakeCircle
|
||||||
|
|
||||||
|
from cadquery import *
|
||||||
|
|
||||||
|
DEG2RAD = 2 * math.pi / 360
|
||||||
|
|
||||||
|
|
||||||
|
class TestCadObjects(BaseTest):
|
||||||
|
def _make_circle(self):
|
||||||
|
|
||||||
|
circle = gp_Circ(gp_Ax2(gp_Pnt(1, 2, 3), gp.DZ_s()), 2.0)
|
||||||
|
return Shape.cast(BRepBuilderAPI_MakeEdge(circle).Edge())
|
||||||
|
|
||||||
|
def _make_ellipse(self):
|
||||||
|
|
||||||
|
ellipse = gp_Elips(gp_Ax2(gp_Pnt(1, 2, 3), gp.DZ_s()), 4.0, 2.0)
|
||||||
|
return Shape.cast(BRepBuilderAPI_MakeEdge(ellipse).Edge())
|
||||||
|
|
||||||
|
def testVectorConstructors(self):
|
||||||
|
v1 = Vector(1, 2, 3)
|
||||||
|
v2 = Vector((1, 2, 3))
|
||||||
|
v3 = Vector(gp_Vec(1, 2, 3))
|
||||||
|
v4 = Vector([1, 2, 3])
|
||||||
|
v5 = Vector(gp_XYZ(1, 2, 3))
|
||||||
|
|
||||||
|
for v in [v1, v2, v3, v4, v5]:
|
||||||
|
self.assertTupleAlmostEquals((1, 2, 3), v.toTuple(), 4)
|
||||||
|
|
||||||
|
v6 = Vector((1, 2))
|
||||||
|
v7 = Vector([1, 2])
|
||||||
|
v8 = Vector(1, 2)
|
||||||
|
|
||||||
|
for v in [v6, v7, v8]:
|
||||||
|
self.assertTupleAlmostEquals((1, 2, 0), v.toTuple(), 4)
|
||||||
|
|
||||||
|
v9 = Vector()
|
||||||
|
self.assertTupleAlmostEquals((0, 0, 0), v9.toTuple(), 4)
|
||||||
|
|
||||||
|
v9.x = 1.0
|
||||||
|
v9.y = 2.0
|
||||||
|
v9.z = 3.0
|
||||||
|
self.assertTupleAlmostEquals((1, 2, 3), (v9.x, v9.y, v9.z), 4)
|
||||||
|
|
||||||
|
def testVertex(self):
|
||||||
|
"""
|
||||||
|
Tests basic vertex functions
|
||||||
|
"""
|
||||||
|
v = Vertex.makeVertex(1, 1, 1)
|
||||||
|
self.assertEqual(1, v.X)
|
||||||
|
self.assertEqual(Vector, type(v.Center()))
|
||||||
|
|
||||||
|
def testBasicBoundingBox(self):
|
||||||
|
v = Vertex.makeVertex(1, 1, 1)
|
||||||
|
v2 = Vertex.makeVertex(2, 2, 2)
|
||||||
|
self.assertEqual(BoundBox, type(v.BoundingBox()))
|
||||||
|
self.assertEqual(BoundBox, type(v2.BoundingBox()))
|
||||||
|
|
||||||
|
bb1 = v.BoundingBox().add(v2.BoundingBox())
|
||||||
|
|
||||||
|
# OCC uses some approximations
|
||||||
|
self.assertAlmostEqual(bb1.xlen, 1.0, 1)
|
||||||
|
|
||||||
|
def testEdgeWrapperCenter(self):
|
||||||
|
e = self._make_circle()
|
||||||
|
|
||||||
|
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), e.Center().toTuple(), 3)
|
||||||
|
|
||||||
|
def testEdgeWrapperEllipseCenter(self):
|
||||||
|
e = self._make_ellipse()
|
||||||
|
w = Wire.assembleEdges([e])
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
(1.0, 2.0, 3.0), Face.makeFromWires(w).Center().toTuple(), 3
|
||||||
|
)
|
||||||
|
|
||||||
|
def testEdgeWrapperMakeCircle(self):
|
||||||
|
halfCircleEdge = Edge.makeCircle(
|
||||||
|
radius=10, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=0, angle2=180
|
||||||
|
)
|
||||||
|
|
||||||
|
# self.assertTupleAlmostEquals((0.0, 5.0, 0.0), halfCircleEdge.CenterOfBoundBox(0.0001).toTuple(),3)
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
(10.0, 0.0, 0.0), halfCircleEdge.startPoint().toTuple(), 3
|
||||||
|
)
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
(-10.0, 0.0, 0.0), halfCircleEdge.endPoint().toTuple(), 3
|
||||||
|
)
|
||||||
|
|
||||||
|
def testEdgeWrapperMakeTangentArc(self):
|
||||||
|
tangent_arc = Edge.makeTangentArc(
|
||||||
|
Vector(1, 1), # starts at 1, 1
|
||||||
|
Vector(0, 1), # tangent at start of arc is in the +y direction
|
||||||
|
Vector(2, 1), # arc curves 180 degrees and ends at 2, 1
|
||||||
|
)
|
||||||
|
self.assertTupleAlmostEquals((1, 1, 0), tangent_arc.startPoint().toTuple(), 3)
|
||||||
|
self.assertTupleAlmostEquals((2, 1, 0), tangent_arc.endPoint().toTuple(), 3)
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
(0, 1, 0), tangent_arc.tangentAt(locationParam=0).toTuple(), 3
|
||||||
|
)
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
(1, 0, 0), tangent_arc.tangentAt(locationParam=0.5).toTuple(), 3
|
||||||
|
)
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
(0, -1, 0), tangent_arc.tangentAt(locationParam=1).toTuple(), 3
|
||||||
|
)
|
||||||
|
|
||||||
|
def testEdgeWrapperMakeEllipse1(self):
|
||||||
|
# Check x_radius > y_radius
|
||||||
|
x_radius, y_radius = 20, 10
|
||||||
|
angle1, angle2 = -75.0, 90.0
|
||||||
|
arcEllipseEdge = Edge.makeEllipse(
|
||||||
|
x_radius=x_radius,
|
||||||
|
y_radius=y_radius,
|
||||||
|
pnt=(0, 0, 0),
|
||||||
|
dir=(0, 0, 1),
|
||||||
|
angle1=angle1,
|
||||||
|
angle2=angle2,
|
||||||
|
)
|
||||||
|
|
||||||
|
start = (
|
||||||
|
x_radius * math.cos(angle1 * DEG2RAD),
|
||||||
|
y_radius * math.sin(angle1 * DEG2RAD),
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
end = (
|
||||||
|
x_radius * math.cos(angle2 * DEG2RAD),
|
||||||
|
y_radius * math.sin(angle2 * DEG2RAD),
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3)
|
||||||
|
self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3)
|
||||||
|
|
||||||
|
def testEdgeWrapperMakeEllipse2(self):
|
||||||
|
# Check x_radius < y_radius
|
||||||
|
x_radius, y_radius = 10, 20
|
||||||
|
angle1, angle2 = 0.0, 45.0
|
||||||
|
arcEllipseEdge = Edge.makeEllipse(
|
||||||
|
x_radius=x_radius,
|
||||||
|
y_radius=y_radius,
|
||||||
|
pnt=(0, 0, 0),
|
||||||
|
dir=(0, 0, 1),
|
||||||
|
angle1=angle1,
|
||||||
|
angle2=angle2,
|
||||||
|
)
|
||||||
|
|
||||||
|
start = (
|
||||||
|
x_radius * math.cos(angle1 * DEG2RAD),
|
||||||
|
y_radius * math.sin(angle1 * DEG2RAD),
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
end = (
|
||||||
|
x_radius * math.cos(angle2 * DEG2RAD),
|
||||||
|
y_radius * math.sin(angle2 * DEG2RAD),
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3)
|
||||||
|
self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3)
|
||||||
|
|
||||||
|
def testEdgeWrapperMakeCircleWithEllipse(self):
|
||||||
|
# Check x_radius == y_radius
|
||||||
|
x_radius, y_radius = 20, 20
|
||||||
|
angle1, angle2 = 15.0, 60.0
|
||||||
|
arcEllipseEdge = Edge.makeEllipse(
|
||||||
|
x_radius=x_radius,
|
||||||
|
y_radius=y_radius,
|
||||||
|
pnt=(0, 0, 0),
|
||||||
|
dir=(0, 0, 1),
|
||||||
|
angle1=angle1,
|
||||||
|
angle2=angle2,
|
||||||
|
)
|
||||||
|
|
||||||
|
start = (
|
||||||
|
x_radius * math.cos(angle1 * DEG2RAD),
|
||||||
|
y_radius * math.sin(angle1 * DEG2RAD),
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
end = (
|
||||||
|
x_radius * math.cos(angle2 * DEG2RAD),
|
||||||
|
y_radius * math.sin(angle2 * DEG2RAD),
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
self.assertTupleAlmostEquals(start, arcEllipseEdge.startPoint().toTuple(), 3)
|
||||||
|
self.assertTupleAlmostEquals(end, arcEllipseEdge.endPoint().toTuple(), 3)
|
||||||
|
|
||||||
|
def testFaceWrapperMakePlane(self):
|
||||||
|
mplane = Face.makePlane(10, 10)
|
||||||
|
|
||||||
|
self.assertTupleAlmostEquals((0.0, 0.0, 1.0), mplane.normalAt().toTuple(), 3)
|
||||||
|
|
||||||
|
def testCenterOfBoundBox(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def testCombinedCenterOfBoundBox(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def testCompoundCenter(self):
|
||||||
|
"""
|
||||||
|
Tests whether or not a proper weighted center can be found for a compound
|
||||||
|
"""
|
||||||
|
|
||||||
|
def cylinders(self, radius, height):
|
||||||
|
def _cyl(pnt):
|
||||||
|
# Inner function to build a cylinder
|
||||||
|
return Solid.makeCylinder(radius, height, pnt)
|
||||||
|
|
||||||
|
# Combine all the cylinders into a single compound
|
||||||
|
r = self.eachpoint(_cyl, True).combineSolids()
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
Workplane.cyl = cylinders
|
||||||
|
|
||||||
|
# Now test. here we want weird workplane to see if the objects are transformed right
|
||||||
|
s = (
|
||||||
|
Workplane("XY")
|
||||||
|
.rect(2.0, 3.0, forConstruction=True)
|
||||||
|
.vertices()
|
||||||
|
.cyl(0.25, 0.5)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(4, len(s.val().Solids()))
|
||||||
|
self.assertTupleAlmostEquals((0.0, 0.0, 0.25), s.val().Center().toTuple(), 3)
|
||||||
|
|
||||||
|
def testDot(self):
|
||||||
|
v1 = Vector(2, 2, 2)
|
||||||
|
v2 = Vector(1, -1, 1)
|
||||||
|
self.assertEqual(2.0, v1.dot(v2))
|
||||||
|
|
||||||
|
def testVectorAdd(self):
|
||||||
|
result = Vector(1, 2, 0) + Vector(0, 0, 3)
|
||||||
|
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), result.toTuple(), 3)
|
||||||
|
|
||||||
|
def testVectorOperators(self):
|
||||||
|
result = Vector(1, 1, 1) + Vector(2, 2, 2)
|
||||||
|
self.assertEqual(Vector(3, 3, 3), result)
|
||||||
|
|
||||||
|
result = Vector(1, 2, 3) - Vector(3, 2, 1)
|
||||||
|
self.assertEqual(Vector(-2, 0, 2), result)
|
||||||
|
|
||||||
|
result = Vector(1, 2, 3) * 2
|
||||||
|
self.assertEqual(Vector(2, 4, 6), result)
|
||||||
|
|
||||||
|
result = Vector(2, 4, 6) / 2
|
||||||
|
self.assertEqual(Vector(1, 2, 3), result)
|
||||||
|
|
||||||
|
self.assertEqual(Vector(-1, -1, -1), -Vector(1, 1, 1))
|
||||||
|
|
||||||
|
self.assertEqual(0, abs(Vector(0, 0, 0)))
|
||||||
|
self.assertEqual(1, abs(Vector(1, 0, 0)))
|
||||||
|
self.assertEqual((1 + 4 + 9) ** 0.5, abs(Vector(1, 2, 3)))
|
||||||
|
|
||||||
|
def testVectorEquals(self):
|
||||||
|
a = Vector(1, 2, 3)
|
||||||
|
b = Vector(1, 2, 3)
|
||||||
|
c = Vector(1, 2, 3.000001)
|
||||||
|
self.assertEqual(a, b)
|
||||||
|
self.assertEqual(a, c)
|
||||||
|
|
||||||
|
def testVectorProject(self):
|
||||||
|
"""
|
||||||
|
Test method to project vector to plane.
|
||||||
|
"""
|
||||||
|
decimal_places = 9
|
||||||
|
|
||||||
|
normal = Vector(1, 2, 3)
|
||||||
|
base = Vector(5, 7, 9)
|
||||||
|
x_dir = Vector(1, 0, 0)
|
||||||
|
|
||||||
|
# test passing Plane object
|
||||||
|
point = Vector(10, 11, 12).projectToPlane(Plane(base, x_dir, normal))
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
point.toTuple(), (59 / 7, 55 / 7, 51 / 7), decimal_places
|
||||||
|
)
|
||||||
|
|
||||||
|
def testMatrixCreationAndAccess(self):
|
||||||
|
def matrix_vals(m):
|
||||||
|
return [[m[r, c] for c in range(4)] for r in range(4)]
|
||||||
|
|
||||||
|
# default constructor creates a 4x4 identity matrix
|
||||||
|
m = Matrix()
|
||||||
|
identity = [
|
||||||
|
[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],
|
||||||
|
]
|
||||||
|
self.assertEqual(identity, matrix_vals(m))
|
||||||
|
|
||||||
|
vals4x4 = [
|
||||||
|
[1.0, 0.0, 0.0, 1.0],
|
||||||
|
[0.0, 1.0, 0.0, 2.0],
|
||||||
|
[0.0, 0.0, 1.0, 3.0],
|
||||||
|
[0.0, 0.0, 0.0, 1.0],
|
||||||
|
]
|
||||||
|
vals4x4_tuple = tuple(tuple(r) for r in vals4x4)
|
||||||
|
|
||||||
|
# test constructor with 16-value input
|
||||||
|
m = Matrix(vals4x4)
|
||||||
|
self.assertEqual(vals4x4, matrix_vals(m))
|
||||||
|
m = Matrix(vals4x4_tuple)
|
||||||
|
self.assertEqual(vals4x4, matrix_vals(m))
|
||||||
|
|
||||||
|
# test constructor with 12-value input (the last 4 are an implied
|
||||||
|
# [0,0,0,1])
|
||||||
|
m = Matrix(vals4x4[:3])
|
||||||
|
self.assertEqual(vals4x4, matrix_vals(m))
|
||||||
|
m = Matrix(vals4x4_tuple[:3])
|
||||||
|
self.assertEqual(vals4x4, matrix_vals(m))
|
||||||
|
|
||||||
|
# Test 16-value input with invalid values for the last 4
|
||||||
|
invalid = [
|
||||||
|
[1.0, 0.0, 0.0, 1.0],
|
||||||
|
[0.0, 1.0, 0.0, 2.0],
|
||||||
|
[0.0, 0.0, 1.0, 3.0],
|
||||||
|
[1.0, 2.0, 3.0, 4.0],
|
||||||
|
]
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
Matrix(invalid)
|
||||||
|
|
||||||
|
# Test input with invalid size / nested types
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Matrix([[1, 2, 3, 4], [1, 2, 3], [1, 2, 3, 4]])
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Matrix([1, 2, 3])
|
||||||
|
|
||||||
|
# Invalid sub-type
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Matrix([[1, 2, 3, 4], "abc", [1, 2, 3, 4]])
|
||||||
|
|
||||||
|
# test out-of-bounds access
|
||||||
|
m = Matrix()
|
||||||
|
with self.assertRaises(IndexError):
|
||||||
|
m[0, 4]
|
||||||
|
with self.assertRaises(IndexError):
|
||||||
|
m[4, 0]
|
||||||
|
with self.assertRaises(IndexError):
|
||||||
|
m["ab"]
|
||||||
|
|
||||||
|
def testTranslate(self):
|
||||||
|
e = Edge.makeCircle(2, (1, 2, 3))
|
||||||
|
e2 = e.translate(Vector(0, 0, 1))
|
||||||
|
|
||||||
|
self.assertTupleAlmostEquals((1.0, 2.0, 4.0), e2.Center().toTuple(), 3)
|
||||||
|
|
||||||
|
def testVertices(self):
|
||||||
|
e = Shape.cast(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(1, 1, 0)).Edge())
|
||||||
|
self.assertEqual(2, len(e.Vertices()))
|
||||||
|
|
||||||
|
def testPlaneEqual(self):
|
||||||
|
# default orientation
|
||||||
|
self.assertEqual(
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||||
|
)
|
||||||
|
# moved origin
|
||||||
|
self.assertEqual(
|
||||||
|
Plane(origin=(2, 1, -1), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||||
|
Plane(origin=(2, 1, -1), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||||
|
)
|
||||||
|
# moved x-axis
|
||||||
|
self.assertEqual(
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(1, 1, 0), normal=(0, 0, 1)),
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(1, 1, 0), normal=(0, 0, 1)),
|
||||||
|
)
|
||||||
|
# moved z-axis
|
||||||
|
self.assertEqual(
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
|
||||||
|
)
|
||||||
|
|
||||||
|
def testPlaneNotEqual(self):
|
||||||
|
# type difference
|
||||||
|
for value in [None, 0, 1, "abc"]:
|
||||||
|
self.assertNotEqual(
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)), value
|
||||||
|
)
|
||||||
|
# origin difference
|
||||||
|
self.assertNotEqual(
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||||
|
Plane(origin=(0, 0, 1), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||||
|
)
|
||||||
|
# x-axis difference
|
||||||
|
self.assertNotEqual(
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(1, 1, 0), normal=(0, 0, 1)),
|
||||||
|
)
|
||||||
|
# z-axis difference
|
||||||
|
self.assertNotEqual(
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1)),
|
||||||
|
Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 1)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
3350
tests/test_cadquery.py
Normal file
3350
tests/test_cadquery.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -42,8 +42,9 @@ class TestCQGI(BaseTest):
|
|||||||
model = cqgi.CQModel(TESTSCRIPT)
|
model = cqgi.CQModel(TESTSCRIPT)
|
||||||
metadata = model.metadata
|
metadata = model.metadata
|
||||||
|
|
||||||
self.assertEqual(set(metadata.parameters.keys()), {
|
self.assertEqual(
|
||||||
'height', 'width', 'a', 'b', 'foo'})
|
set(metadata.parameters.keys()), {"height", "width", "a", "b", "foo"}
|
||||||
|
)
|
||||||
|
|
||||||
def test_build_with_debug(self):
|
def test_build_with_debug(self):
|
||||||
model = cqgi.CQModel(TEST_DEBUG_SCRIPT)
|
model = cqgi.CQModel(TEST_DEBUG_SCRIPT)
|
||||||
@ -51,7 +52,7 @@ class TestCQGI(BaseTest):
|
|||||||
debugItems = result.debugObjects
|
debugItems = result.debugObjects
|
||||||
self.assertTrue(len(debugItems) == 2)
|
self.assertTrue(len(debugItems) == 2)
|
||||||
self.assertTrue(debugItems[0].shape == "bar")
|
self.assertTrue(debugItems[0].shape == "bar")
|
||||||
self.assertTrue(debugItems[0].options == {"color": 'yellow'})
|
self.assertTrue(debugItems[0].options == {"color": "yellow"})
|
||||||
self.assertTrue(debugItems[1].shape == 2.0)
|
self.assertTrue(debugItems[1].shape == 2.0)
|
||||||
self.assertTrue(debugItems[1].options == {})
|
self.assertTrue(debugItems[1].options == {})
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ class TestCQGI(BaseTest):
|
|||||||
|
|
||||||
def test_build_with_different_params(self):
|
def test_build_with_different_params(self):
|
||||||
model = cqgi.CQModel(TESTSCRIPT)
|
model = cqgi.CQModel(TESTSCRIPT)
|
||||||
result = model.build({'height': 3.0})
|
result = model.build({"height": 3.0})
|
||||||
self.assertTrue(result.results[0].shape == "3.0|3.0|bar|1.0")
|
self.assertTrue(result.results[0].shape == "3.0|3.0|bar|1.0")
|
||||||
|
|
||||||
def test_describe_parameters(self):
|
def test_describe_parameters(self):
|
||||||
@ -76,9 +77,9 @@ class TestCQGI(BaseTest):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
model = cqgi.CQModel(script)
|
model = cqgi.CQModel(script)
|
||||||
a_param = model.metadata.parameters['a']
|
a_param = model.metadata.parameters["a"]
|
||||||
self.assertTrue(a_param.default_value == 2.0)
|
self.assertTrue(a_param.default_value == 2.0)
|
||||||
self.assertTrue(a_param.desc == 'FirstLetter')
|
self.assertTrue(a_param.desc == "FirstLetter")
|
||||||
self.assertTrue(a_param.varType == cqgi.NumberParameterType)
|
self.assertTrue(a_param.varType == cqgi.NumberParameterType)
|
||||||
|
|
||||||
def test_describe_parameter_invalid_doesnt_fail_script(self):
|
def test_describe_parameter_invalid_doesnt_fail_script(self):
|
||||||
@ -89,8 +90,8 @@ class TestCQGI(BaseTest):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
model = cqgi.CQModel(script)
|
model = cqgi.CQModel(script)
|
||||||
a_param = model.metadata.parameters['a']
|
a_param = model.metadata.parameters["a"]
|
||||||
self.assertTrue(a_param.name == 'a')
|
self.assertTrue(a_param.name == "a")
|
||||||
|
|
||||||
def test_build_with_exception(self):
|
def test_build_with_exception(self):
|
||||||
badscript = textwrap.dedent(
|
badscript = textwrap.dedent(
|
||||||
@ -115,7 +116,7 @@ class TestCQGI(BaseTest):
|
|||||||
with self.assertRaises(Exception) as context:
|
with self.assertRaises(Exception) as context:
|
||||||
model = cqgi.CQModel(badscript)
|
model = cqgi.CQModel(badscript)
|
||||||
|
|
||||||
self.assertTrue('invalid syntax' in context.exception.args)
|
self.assertTrue("invalid syntax" in context.exception.args)
|
||||||
|
|
||||||
def test_that_two_results_are_returned(self):
|
def test_that_two_results_are_returned(self):
|
||||||
script = textwrap.dedent(
|
script = textwrap.dedent(
|
||||||
@ -140,7 +141,7 @@ class TestCQGI(BaseTest):
|
|||||||
show_object(h)
|
show_object(h)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = cqgi.parse(script).build({'h': 33.33})
|
result = cqgi.parse(script).build({"h": 33.33})
|
||||||
self.assertEqual(result.results[0].shape, "33.33")
|
self.assertEqual(result.results[0].shape, "33.33")
|
||||||
|
|
||||||
def test_that_assigning_string_to_number_fails(self):
|
def test_that_assigning_string_to_number_fails(self):
|
||||||
@ -150,9 +151,8 @@ class TestCQGI(BaseTest):
|
|||||||
show_object(h)
|
show_object(h)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = cqgi.parse(script).build({'h': "a string"})
|
result = cqgi.parse(script).build({"h": "a string"})
|
||||||
self.assertTrue(isinstance(result.exception,
|
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
|
||||||
cqgi.InvalidParameterError))
|
|
||||||
|
|
||||||
def test_that_assigning_unknown_var_fails(self):
|
def test_that_assigning_unknown_var_fails(self):
|
||||||
script = textwrap.dedent(
|
script = textwrap.dedent(
|
||||||
@ -162,9 +162,8 @@ class TestCQGI(BaseTest):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
result = cqgi.parse(script).build({'w': "var is not there"})
|
result = cqgi.parse(script).build({"w": "var is not there"})
|
||||||
self.assertTrue(isinstance(result.exception,
|
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
|
||||||
cqgi.InvalidParameterError))
|
|
||||||
|
|
||||||
def test_that_cq_objects_are_visible(self):
|
def test_that_cq_objects_are_visible(self):
|
||||||
script = textwrap.dedent(
|
script = textwrap.dedent(
|
||||||
@ -198,10 +197,10 @@ class TestCQGI(BaseTest):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
result = cqgi.parse(script).build({'h': False})
|
result = cqgi.parse(script).build({"h": False})
|
||||||
|
|
||||||
self.assertTrue(result.success)
|
self.assertTrue(result.success)
|
||||||
self.assertEqual(result.first_result.shape, '*False*')
|
self.assertEqual(result.first_result.shape, "*False*")
|
||||||
|
|
||||||
def test_that_only_top_level_vars_are_detected(self):
|
def test_that_only_top_level_vars_are_detected(self):
|
||||||
script = textwrap.dedent(
|
script = textwrap.dedent(
|
@ -12,7 +12,6 @@ from tests import BaseTest
|
|||||||
|
|
||||||
|
|
||||||
class TestExporters(BaseTest):
|
class TestExporters(BaseTest):
|
||||||
|
|
||||||
def _exportBox(self, eType, stringsToFind):
|
def _exportBox(self, eType, stringsToFind):
|
||||||
"""
|
"""
|
||||||
Exports a test object, and then looks for
|
Exports a test object, and then looks for
|
||||||
@ -28,24 +27,25 @@ class TestExporters(BaseTest):
|
|||||||
|
|
||||||
exporters.exportShape(p, eType, s, 0.1)
|
exporters.exportShape(p, eType, s, 0.1)
|
||||||
|
|
||||||
result = '{}'.format(s.getvalue())
|
result = "{}".format(s.getvalue())
|
||||||
|
|
||||||
for q in stringsToFind:
|
for q in stringsToFind:
|
||||||
self.assertTrue(result.find(q) > -1)
|
self.assertTrue(result.find(q) > -1)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def testSTL(self):
|
def testSTL(self):
|
||||||
self._exportBox(exporters.ExportTypes.STL, ['facet normal'])
|
self._exportBox(exporters.ExportTypes.STL, ["facet normal"])
|
||||||
|
|
||||||
def testSVG(self):
|
def testSVG(self):
|
||||||
self._exportBox(exporters.ExportTypes.SVG, ['<svg', '<g transform'])
|
self._exportBox(exporters.ExportTypes.SVG, ["<svg", "<g transform"])
|
||||||
|
|
||||||
def testAMF(self):
|
def testAMF(self):
|
||||||
self._exportBox(exporters.ExportTypes.AMF, ['<amf units', '</object>'])
|
self._exportBox(exporters.ExportTypes.AMF, ["<amf units", "</object>"])
|
||||||
|
|
||||||
def testSTEP(self):
|
def testSTEP(self):
|
||||||
self._exportBox(exporters.ExportTypes.STEP, ['FILE_SCHEMA'])
|
self._exportBox(exporters.ExportTypes.STEP, ["FILE_SCHEMA"])
|
||||||
|
|
||||||
def testTJS(self):
|
def testTJS(self):
|
||||||
self._exportBox(exporters.ExportTypes.TJS, [
|
self._exportBox(
|
||||||
'vertices', 'formatVersion', 'faces'])
|
exporters.ExportTypes.TJS, ["vertices", "formatVersion", "faces"]
|
||||||
|
)
|
@ -36,12 +36,18 @@ class TestImporters(BaseTest):
|
|||||||
self.assertTrue(importedShape.val().ShapeType() == "Solid")
|
self.assertTrue(importedShape.val().ShapeType() == "Solid")
|
||||||
|
|
||||||
# Check the number of faces and vertices per face to make sure we have a box shape
|
# Check the number of faces and vertices per face to make sure we have a box shape
|
||||||
self.assertTrue(importedShape.faces("+X").size() ==
|
self.assertTrue(
|
||||||
1 and importedShape.faces("+X").vertices().size() == 4)
|
importedShape.faces("+X").size() == 1
|
||||||
self.assertTrue(importedShape.faces("+Y").size() ==
|
and importedShape.faces("+X").vertices().size() == 4
|
||||||
1 and importedShape.faces("+Y").vertices().size() == 4)
|
)
|
||||||
self.assertTrue(importedShape.faces("+Z").size() ==
|
self.assertTrue(
|
||||||
1 and importedShape.faces("+Z").vertices().size() == 4)
|
importedShape.faces("+Y").size() == 1
|
||||||
|
and importedShape.faces("+Y").vertices().size() == 4
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
importedShape.faces("+Z").size() == 1
|
||||||
|
and importedShape.faces("+Z").vertices().size() == 4
|
||||||
|
)
|
||||||
|
|
||||||
def testSTEP(self):
|
def testSTEP(self):
|
||||||
"""
|
"""
|
||||||
@ -55,7 +61,8 @@ class TestImporters(BaseTest):
|
|||||||
not segfault.
|
not segfault.
|
||||||
"""
|
"""
|
||||||
tmpfile = OUTDIR + "/badSTEP.step"
|
tmpfile = OUTDIR + "/badSTEP.step"
|
||||||
with open(tmpfile, 'w') as f: f.write("invalid STEP file")
|
with open(tmpfile, "w") as f:
|
||||||
|
f.write("invalid STEP file")
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
importers.importShape(importers.ImportTypes.STEP, tmpfile)
|
importers.importShape(importers.ImportTypes.STEP, tmpfile)
|
||||||
|
|
||||||
@ -69,6 +76,8 @@ class TestImporters(BaseTest):
|
|||||||
objs = importers.importShape(importers.ImportTypes.STEP, filename)
|
objs = importers.importShape(importers.ImportTypes.STEP, filename)
|
||||||
self.assertEqual(2, len(objs.all()))
|
self.assertEqual(2, len(objs.all()))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
unittest.main()
|
unittest.main()
|
@ -2,9 +2,10 @@ from tests import BaseTest
|
|||||||
|
|
||||||
import cadquery
|
import cadquery
|
||||||
|
|
||||||
|
|
||||||
class TestJupyter(BaseTest):
|
class TestJupyter(BaseTest):
|
||||||
def test_repr_html(self):
|
def test_repr_html(self):
|
||||||
cube = cadquery.Workplane('XY').box(1, 1, 1)
|
cube = cadquery.Workplane("XY").box(1, 1, 1)
|
||||||
shape = cube.val()
|
shape = cube.val()
|
||||||
self.assertIsInstance(shape, cadquery.occ_impl.shapes.Solid)
|
self.assertIsInstance(shape, cadquery.occ_impl.shapes.Solid)
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
__author__ = 'dcowden'
|
__author__ = "dcowden"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Tests for CadQuery Selectors
|
Tests for CadQuery Selectors
|
||||||
@ -20,22 +20,19 @@ from cadquery import selectors
|
|||||||
|
|
||||||
|
|
||||||
class TestCQSelectors(BaseTest):
|
class TestCQSelectors(BaseTest):
|
||||||
|
|
||||||
def testWorkplaneCenter(self):
|
def testWorkplaneCenter(self):
|
||||||
"Test Moving workplane center"
|
"Test Moving workplane center"
|
||||||
s = Workplane(Plane.XY())
|
s = Workplane(Plane.XY())
|
||||||
|
|
||||||
# current point and world point should be equal
|
# current point and world point should be equal
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals((0.0, 0.0, 0.0), s.plane.origin.toTuple(), 3)
|
||||||
(0.0, 0.0, 0.0), s.plane.origin.toTuple(), 3)
|
|
||||||
|
|
||||||
# move origin and confirm center moves
|
# move origin and confirm center moves
|
||||||
s.center(-2.0, -2.0)
|
s = s.center(-2.0, -2.0)
|
||||||
|
|
||||||
# current point should be 0,0, but
|
# current point should be 0,0, but
|
||||||
|
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals((-2.0, -2.0, 0.0), s.plane.origin.toTuple(), 3)
|
||||||
(-2.0, -2.0, 0.0), s.plane.origin.toTuple(), 3)
|
|
||||||
|
|
||||||
def testVertices(self):
|
def testVertices(self):
|
||||||
t = makeUnitSquareWire() # square box
|
t = makeUnitSquareWire() # square box
|
||||||
@ -43,8 +40,7 @@ class TestCQSelectors(BaseTest):
|
|||||||
|
|
||||||
self.assertEqual(4, c.vertices().size())
|
self.assertEqual(4, c.vertices().size())
|
||||||
self.assertEqual(4, c.edges().size())
|
self.assertEqual(4, c.edges().size())
|
||||||
self.assertEqual(0, c.vertices().edges().size()
|
self.assertEqual(0, c.vertices().edges().size()) # no edges on any vertices
|
||||||
) # no edges on any vertices
|
|
||||||
# but selecting all edges still yields all vertices
|
# but selecting all edges still yields all vertices
|
||||||
self.assertEqual(4, c.edges().vertices().size())
|
self.assertEqual(4, c.edges().vertices().size())
|
||||||
self.assertEqual(1, c.wires().size()) # just one wire
|
self.assertEqual(1, c.wires().size()) # just one wire
|
||||||
@ -71,8 +67,7 @@ class TestCQSelectors(BaseTest):
|
|||||||
def testFirst(self):
|
def testFirst(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
self.assertEqual(type(c.vertices().first().val()), Vertex)
|
self.assertEqual(type(c.vertices().first().val()), Vertex)
|
||||||
self.assertEqual(
|
self.assertEqual(type(c.vertices().first().first().first().val()), Vertex)
|
||||||
type(c.vertices().first().first().first().val()), Vertex)
|
|
||||||
|
|
||||||
def testCompounds(self):
|
def testCompounds(self):
|
||||||
c = CQ(makeUnitSquareWire())
|
c = CQ(makeUnitSquareWire())
|
||||||
@ -99,11 +94,11 @@ class TestCQSelectors(BaseTest):
|
|||||||
def testFaceTypesFilter(self):
|
def testFaceTypesFilter(self):
|
||||||
"Filters by face type"
|
"Filters by face type"
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
self.assertEqual(c.faces().size(), c.faces('%PLANE').size())
|
self.assertEqual(c.faces().size(), c.faces("%PLANE").size())
|
||||||
self.assertEqual(c.faces().size(), c.faces('%plane').size())
|
self.assertEqual(c.faces().size(), c.faces("%plane").size())
|
||||||
self.assertEqual(0, c.faces('%sphere').size())
|
self.assertEqual(0, c.faces("%sphere").size())
|
||||||
self.assertEqual(0, c.faces('%cone').size())
|
self.assertEqual(0, c.faces("%cone").size())
|
||||||
self.assertEqual(0, c.faces('%SPHERE').size())
|
self.assertEqual(0, c.faces("%SPHERE").size())
|
||||||
|
|
||||||
def testPerpendicularDirFilter(self):
|
def testPerpendicularDirFilter(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
@ -131,10 +126,12 @@ class TestCQSelectors(BaseTest):
|
|||||||
# faces parallel to Z axis
|
# faces parallel to Z axis
|
||||||
self.assertEqual(2, c.faces("|Z").size())
|
self.assertEqual(2, c.faces("|Z").size())
|
||||||
# TODO: provide short names for ParallelDirSelector
|
# TODO: provide short names for ParallelDirSelector
|
||||||
self.assertEqual(2, c.faces(selectors.ParallelDirSelector(
|
self.assertEqual(
|
||||||
Vector((0, 0, 1)))).size()) # same thing as above
|
2, c.faces(selectors.ParallelDirSelector(Vector((0, 0, 1)))).size()
|
||||||
self.assertEqual(2, c.faces(selectors.ParallelDirSelector(
|
) # same thing as above
|
||||||
Vector((0, 0, -1)))).size()) # same thing as above
|
self.assertEqual(
|
||||||
|
2, c.faces(selectors.ParallelDirSelector(Vector((0, 0, -1)))).size()
|
||||||
|
) # same thing as above
|
||||||
|
|
||||||
# just for fun, vertices on faces parallel to z
|
# just for fun, vertices on faces parallel to z
|
||||||
self.assertEqual(8, c.faces("|Z").vertices().size())
|
self.assertEqual(8, c.faces("|Z").vertices().size())
|
||||||
@ -178,97 +175,96 @@ class TestCQSelectors(BaseTest):
|
|||||||
self.assertEqual(4, len(el))
|
self.assertEqual(4, len(el))
|
||||||
|
|
||||||
def testNthDistance(self):
|
def testNthDistance(self):
|
||||||
c = Workplane('XY').pushPoints([(-2, 0), (2, 0)]).box(1, 1, 1)
|
c = Workplane("XY").pushPoints([(-2, 0), (2, 0)]).box(1, 1, 1)
|
||||||
|
|
||||||
# 2nd face
|
# 2nd face
|
||||||
val = c.faces(selectors.DirectionNthSelector(Vector(1, 0, 0), 1)).val()
|
val = c.faces(selectors.DirectionNthSelector(Vector(1, 0, 0), 1)).val()
|
||||||
self.assertAlmostEqual(val.Center().x, -1.5)
|
self.assertAlmostEqual(val.Center().x, -1.5)
|
||||||
|
|
||||||
# 2nd face with inversed selection vector
|
# 2nd face with inversed selection vector
|
||||||
val = c.faces(selectors.DirectionNthSelector(
|
val = c.faces(selectors.DirectionNthSelector(Vector(-1, 0, 0), 1)).val()
|
||||||
Vector(-1, 0, 0), 1)).val()
|
|
||||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||||
|
|
||||||
# 2nd last face
|
# 2nd last face
|
||||||
val = c.faces(selectors.DirectionNthSelector(
|
val = c.faces(selectors.DirectionNthSelector(Vector(1, 0, 0), -2)).val()
|
||||||
Vector(1, 0, 0), -2)).val()
|
|
||||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||||
|
|
||||||
# Last face
|
# Last face
|
||||||
val = c.faces(selectors.DirectionNthSelector(
|
val = c.faces(selectors.DirectionNthSelector(Vector(1, 0, 0), -1)).val()
|
||||||
Vector(1, 0, 0), -1)).val()
|
|
||||||
self.assertAlmostEqual(val.Center().x, 2.5)
|
self.assertAlmostEqual(val.Center().x, 2.5)
|
||||||
|
|
||||||
# check if the selected face if normal to the specified Vector
|
# check if the selected face if normal to the specified Vector
|
||||||
self.assertAlmostEqual(
|
self.assertAlmostEqual(val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
||||||
val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
|
||||||
|
|
||||||
# repeat the test using string based selector
|
# repeat the test using string based selector
|
||||||
|
|
||||||
# 2nd face
|
# 2nd face
|
||||||
val = c.faces('>(1,0,0)[1]').val()
|
val = c.faces(">(1,0,0)[1]").val()
|
||||||
self.assertAlmostEqual(val.Center().x, -1.5)
|
self.assertAlmostEqual(val.Center().x, -1.5)
|
||||||
val = c.faces('>X[1]').val()
|
val = c.faces(">X[1]").val()
|
||||||
self.assertAlmostEqual(val.Center().x, -1.5)
|
self.assertAlmostEqual(val.Center().x, -1.5)
|
||||||
|
|
||||||
# 2nd face with inversed selection vector
|
# 2nd face with inversed selection vector
|
||||||
val = c.faces('>(-1,0,0)[1]').val()
|
val = c.faces(">(-1,0,0)[1]").val()
|
||||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||||
val = c.faces('<X[1]').val()
|
val = c.faces("<X[1]").val()
|
||||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||||
|
|
||||||
# 2nd last face
|
# 2nd last face
|
||||||
val = c.faces('>X[-2]').val()
|
val = c.faces(">X[-2]").val()
|
||||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||||
|
|
||||||
# Last face
|
# Last face
|
||||||
val = c.faces('>X[-1]').val()
|
val = c.faces(">X[-1]").val()
|
||||||
self.assertAlmostEqual(val.Center().x, 2.5)
|
self.assertAlmostEqual(val.Center().x, 2.5)
|
||||||
|
|
||||||
# check if the selected face if normal to the specified Vector
|
# check if the selected face if normal to the specified Vector
|
||||||
self.assertAlmostEqual(
|
self.assertAlmostEqual(val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
||||||
val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
|
||||||
|
|
||||||
# test selection of multiple faces with the same distance
|
# test selection of multiple faces with the same distance
|
||||||
c = Workplane('XY')\
|
c = (
|
||||||
.box(1, 4, 1, centered=(False, True, False)).faces('<Z')\
|
Workplane("XY")
|
||||||
.box(2, 2, 2, centered=(True, True, False)).faces('>Z')\
|
.box(1, 4, 1, centered=(False, True, False))
|
||||||
|
.faces("<Z")
|
||||||
|
.box(2, 2, 2, centered=(True, True, False))
|
||||||
|
.faces(">Z")
|
||||||
.box(1, 1, 1, centered=(True, True, False))
|
.box(1, 1, 1, centered=(True, True, False))
|
||||||
|
)
|
||||||
|
|
||||||
# select 2nd from the bottom (NB python indexing is 0-based)
|
# select 2nd from the bottom (NB python indexing is 0-based)
|
||||||
vals = c.faces('>Z[1]').vals()
|
vals = c.faces(">Z[1]").vals()
|
||||||
self.assertEqual(len(vals), 2)
|
self.assertEqual(len(vals), 2)
|
||||||
|
|
||||||
val = c.faces('>Z[1]').val()
|
val = c.faces(">Z[1]").val()
|
||||||
self.assertAlmostEqual(val.Center().z, 1)
|
self.assertAlmostEqual(val.Center().z, 1)
|
||||||
|
|
||||||
# do the same but by selecting 3rd from the top
|
# do the same but by selecting 3rd from the top
|
||||||
vals = c.faces('<Z[2]').vals()
|
vals = c.faces("<Z[2]").vals()
|
||||||
self.assertEqual(len(vals), 2)
|
self.assertEqual(len(vals), 2)
|
||||||
|
|
||||||
val = c.faces('<Z[2]').val()
|
val = c.faces("<Z[2]").val()
|
||||||
self.assertAlmostEqual(val.Center().z, 1)
|
self.assertAlmostEqual(val.Center().z, 1)
|
||||||
|
|
||||||
# do the same but by selecting 2nd last from the bottom
|
# do the same but by selecting 2nd last from the bottom
|
||||||
vals = c.faces('<Z[-2]').vals()
|
vals = c.faces("<Z[-2]").vals()
|
||||||
self.assertEqual(len(vals), 2)
|
self.assertEqual(len(vals), 2)
|
||||||
|
|
||||||
val = c.faces('<Z[-2]').val()
|
val = c.faces("<Z[-2]").val()
|
||||||
self.assertAlmostEqual(val.Center().z, 1)
|
self.assertAlmostEqual(val.Center().z, 1)
|
||||||
|
|
||||||
# verify that <Z[-1] is equivalent to <Z
|
# verify that <Z[-1] is equivalent to <Z
|
||||||
val1 = c.faces('<Z[-1]').val()
|
val1 = c.faces("<Z[-1]").val()
|
||||||
val2 = c.faces('<Z').val()
|
val2 = c.faces("<Z").val()
|
||||||
self.assertTupleAlmostEquals(val1.Center().toTuple(),
|
self.assertTupleAlmostEquals(
|
||||||
val2.Center().toTuple(),
|
val1.Center().toTuple(), val2.Center().toTuple(), 3
|
||||||
3)
|
)
|
||||||
|
|
||||||
# verify that >Z[-1] is equivalent to >Z
|
# verify that >Z[-1] is equivalent to >Z
|
||||||
val1 = c.faces('>Z[-1]').val()
|
val1 = c.faces(">Z[-1]").val()
|
||||||
val2 = c.faces('>Z').val()
|
val2 = c.faces(">Z").val()
|
||||||
self.assertTupleAlmostEquals(val1.Center().toTuple(),
|
self.assertTupleAlmostEquals(
|
||||||
val2.Center().toTuple(),
|
val1.Center().toTuple(), val2.Center().toTuple(), 3
|
||||||
3)
|
)
|
||||||
|
|
||||||
def testNearestTo(self):
|
def testNearestTo(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
@ -302,7 +298,7 @@ class TestCQSelectors(BaseTest):
|
|||||||
((0.9, -0.1, -0.1), (1.1, 0.1, 0.1), (1.0, 0.0, 0.0)),
|
((0.9, -0.1, -0.1), (1.1, 0.1, 0.1), (1.0, 0.0, 0.0)),
|
||||||
((0.9, 0.9, -0.1), (1.1, 1.1, 0.1), (1.0, 1.0, 0.0)),
|
((0.9, 0.9, -0.1), (1.1, 1.1, 0.1), (1.0, 1.0, 0.0)),
|
||||||
((-0.1, 0.9, -0.1), (0.1, 1.1, 0.1), (0.0, 1.0, 0.0)),
|
((-0.1, 0.9, -0.1), (0.1, 1.1, 0.1), (0.0, 1.0, 0.0)),
|
||||||
((0.9, -0.1, 0.9), (1.1, 0.1, 1.1), (1.0, 0.0, 1.0))
|
((0.9, -0.1, 0.9), (1.1, 0.1, 1.1), (1.0, 0.0, 1.0)),
|
||||||
]
|
]
|
||||||
|
|
||||||
for d in test_data_vertices:
|
for d in test_data_vertices:
|
||||||
@ -318,11 +314,13 @@ class TestCQSelectors(BaseTest):
|
|||||||
self.assertTupleAlmostEquals(d[2], (v.X, v.Y, v.Z), 3)
|
self.assertTupleAlmostEquals(d[2], (v.X, v.Y, v.Z), 3)
|
||||||
|
|
||||||
# test multiple vertices selection
|
# test multiple vertices selection
|
||||||
vl = c.vertices(selectors.BoxSelector(
|
vl = c.vertices(
|
||||||
(-0.1, -0.1, 0.9), (0.1, 1.1, 1.1))).vals()
|
selectors.BoxSelector((-0.1, -0.1, 0.9), (0.1, 1.1, 1.1))
|
||||||
|
).vals()
|
||||||
self.assertEqual(2, len(vl))
|
self.assertEqual(2, len(vl))
|
||||||
vl = c.vertices(selectors.BoxSelector(
|
vl = c.vertices(
|
||||||
(-0.1, -0.1, -0.1), (0.1, 1.1, 1.1))).vals()
|
selectors.BoxSelector((-0.1, -0.1, -0.1), (0.1, 1.1, 1.1))
|
||||||
|
).vals()
|
||||||
self.assertEqual(4, len(vl))
|
self.assertEqual(4, len(vl))
|
||||||
|
|
||||||
# test edge selection
|
# test edge selection
|
||||||
@ -331,7 +329,7 @@ class TestCQSelectors(BaseTest):
|
|||||||
((0.4, -0.1, -0.1), (0.6, 0.1, 0.1), (0.5, 0.0, 0.0)),
|
((0.4, -0.1, -0.1), (0.6, 0.1, 0.1), (0.5, 0.0, 0.0)),
|
||||||
((-0.1, -0.1, 0.4), (0.1, 0.1, 0.6), (0.0, 0.0, 0.5)),
|
((-0.1, -0.1, 0.4), (0.1, 0.1, 0.6), (0.0, 0.0, 0.5)),
|
||||||
((0.9, 0.9, 0.4), (1.1, 1.1, 0.6), (1.0, 1.0, 0.5)),
|
((0.9, 0.9, 0.4), (1.1, 1.1, 0.6), (1.0, 1.0, 0.5)),
|
||||||
((0.4, 0.9, 0.9), (0.6, 1.1, 1.1,), (0.5, 1.0, 1.0))
|
((0.4, 0.9, 0.9), (0.6, 1.1, 1.1,), (0.5, 1.0, 1.0)),
|
||||||
]
|
]
|
||||||
|
|
||||||
for d in test_data_edges:
|
for d in test_data_edges:
|
||||||
@ -347,11 +345,9 @@ class TestCQSelectors(BaseTest):
|
|||||||
self.assertTupleAlmostEquals(d[2], (ec.x, ec.y, ec.z), 3)
|
self.assertTupleAlmostEquals(d[2], (ec.x, ec.y, ec.z), 3)
|
||||||
|
|
||||||
# test multiple edge selection
|
# test multiple edge selection
|
||||||
el = c.edges(selectors.BoxSelector(
|
el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (0.6, 0.1, 0.6))).vals()
|
||||||
(-0.1, -0.1, -0.1), (0.6, 0.1, 0.6))).vals()
|
|
||||||
self.assertEqual(2, len(el))
|
self.assertEqual(2, len(el))
|
||||||
el = c.edges(selectors.BoxSelector(
|
el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6))).vals()
|
||||||
(-0.1, -0.1, -0.1), (1.1, 0.1, 0.6))).vals()
|
|
||||||
self.assertEqual(3, len(el))
|
self.assertEqual(3, len(el))
|
||||||
|
|
||||||
# test face selection
|
# test face selection
|
||||||
@ -360,7 +356,7 @@ class TestCQSelectors(BaseTest):
|
|||||||
((0.4, -0.1, 0.4), (0.6, 0.1, 0.6), (0.5, 0.0, 0.5)),
|
((0.4, -0.1, 0.4), (0.6, 0.1, 0.6), (0.5, 0.0, 0.5)),
|
||||||
((0.9, 0.4, 0.4), (1.1, 0.6, 0.6), (1.0, 0.5, 0.5)),
|
((0.9, 0.4, 0.4), (1.1, 0.6, 0.6), (1.0, 0.5, 0.5)),
|
||||||
((0.4, 0.4, 0.9), (0.6, 0.6, 1.1), (0.5, 0.5, 1.0)),
|
((0.4, 0.4, 0.9), (0.6, 0.6, 1.1), (0.5, 0.5, 1.0)),
|
||||||
((0.4, 0.4, -0.1), (0.6, 0.6, 0.1), (0.5, 0.5, 0.0))
|
((0.4, 0.4, -0.1), (0.6, 0.6, 0.1), (0.5, 0.5, 0.0)),
|
||||||
]
|
]
|
||||||
|
|
||||||
for d in test_data_faces:
|
for d in test_data_faces:
|
||||||
@ -376,22 +372,23 @@ class TestCQSelectors(BaseTest):
|
|||||||
self.assertTupleAlmostEquals(d[2], (fc.x, fc.y, fc.z), 3)
|
self.assertTupleAlmostEquals(d[2], (fc.x, fc.y, fc.z), 3)
|
||||||
|
|
||||||
# test multiple face selection
|
# test multiple face selection
|
||||||
fl = c.faces(selectors.BoxSelector(
|
fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (0.6, 1.1, 1.1))).vals()
|
||||||
(0.4, 0.4, 0.4), (0.6, 1.1, 1.1))).vals()
|
|
||||||
self.assertEqual(2, len(fl))
|
self.assertEqual(2, len(fl))
|
||||||
fl = c.faces(selectors.BoxSelector(
|
fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1))).vals()
|
||||||
(0.4, 0.4, 0.4), (1.1, 1.1, 1.1))).vals()
|
|
||||||
self.assertEqual(3, len(fl))
|
self.assertEqual(3, len(fl))
|
||||||
|
|
||||||
# test boundingbox option
|
# test boundingbox option
|
||||||
el = c.edges(selectors.BoxSelector(
|
el = c.edges(
|
||||||
(-0.1, -0.1, -0.1), (1.1, 0.1, 0.6), True)).vals()
|
selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6), True)
|
||||||
|
).vals()
|
||||||
self.assertEqual(1, len(el))
|
self.assertEqual(1, len(el))
|
||||||
fl = c.faces(selectors.BoxSelector(
|
fl = c.faces(
|
||||||
(0.4, 0.4, 0.4), (1.1, 1.1, 1.1), True)).vals()
|
selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1), True)
|
||||||
|
).vals()
|
||||||
self.assertEqual(0, len(fl))
|
self.assertEqual(0, len(fl))
|
||||||
fl = c.faces(selectors.BoxSelector(
|
fl = c.faces(
|
||||||
(-0.1, 0.4, -0.1), (1.1, 1.1, 1.1), True)).vals()
|
selectors.BoxSelector((-0.1, 0.4, -0.1), (1.1, 1.1, 1.1), True)
|
||||||
|
).vals()
|
||||||
self.assertEqual(1, len(fl))
|
self.assertEqual(1, len(fl))
|
||||||
|
|
||||||
def testAndSelector(self):
|
def testAndSelector(self):
|
||||||
@ -400,12 +397,13 @@ class TestCQSelectors(BaseTest):
|
|||||||
S = selectors.StringSyntaxSelector
|
S = selectors.StringSyntaxSelector
|
||||||
BS = selectors.BoxSelector
|
BS = selectors.BoxSelector
|
||||||
|
|
||||||
el = c.edges(selectors.AndSelector(
|
el = c.edges(
|
||||||
S('|X'), BS((-2, -2, 0.1), (2, 2, 2)))).vals()
|
selectors.AndSelector(S("|X"), BS((-2, -2, 0.1), (2, 2, 2)))
|
||||||
|
).vals()
|
||||||
self.assertEqual(2, len(el))
|
self.assertEqual(2, len(el))
|
||||||
|
|
||||||
# test 'and' (intersection) operator
|
# test 'and' (intersection) operator
|
||||||
el = c.edges(S('|X') & BS((-2, -2, 0.1), (2, 2, 2))).vals()
|
el = c.edges(S("|X") & BS((-2, -2, 0.1), (2, 2, 2))).vals()
|
||||||
self.assertEqual(2, len(el))
|
self.assertEqual(2, len(el))
|
||||||
|
|
||||||
# test using extended string syntax
|
# test using extended string syntax
|
||||||
@ -455,27 +453,27 @@ class TestCQSelectors(BaseTest):
|
|||||||
|
|
||||||
S = selectors.StringSyntaxSelector
|
S = selectors.StringSyntaxSelector
|
||||||
|
|
||||||
fl = c.faces(selectors.InverseSelector(S('>Z'))).vals()
|
fl = c.faces(selectors.InverseSelector(S(">Z"))).vals()
|
||||||
self.assertEqual(5, len(fl))
|
self.assertEqual(5, len(fl))
|
||||||
el = c.faces('>Z').edges(selectors.InverseSelector(S('>X'))).vals()
|
el = c.faces(">Z").edges(selectors.InverseSelector(S(">X"))).vals()
|
||||||
self.assertEqual(3, len(el))
|
self.assertEqual(3, len(el))
|
||||||
|
|
||||||
# test invert operator
|
# test invert operator
|
||||||
fl = c.faces(-S('>Z')).vals()
|
fl = c.faces(-S(">Z")).vals()
|
||||||
self.assertEqual(5, len(fl))
|
self.assertEqual(5, len(fl))
|
||||||
el = c.faces('>Z').edges(-S('>X')).vals()
|
el = c.faces(">Z").edges(-S(">X")).vals()
|
||||||
self.assertEqual(3, len(el))
|
self.assertEqual(3, len(el))
|
||||||
|
|
||||||
# test using extended string syntax
|
# test using extended string syntax
|
||||||
fl = c.faces('not >Z').vals()
|
fl = c.faces("not >Z").vals()
|
||||||
self.assertEqual(5, len(fl))
|
self.assertEqual(5, len(fl))
|
||||||
el = c.faces('>Z').edges('not >X').vals()
|
el = c.faces(">Z").edges("not >X").vals()
|
||||||
self.assertEqual(3, len(el))
|
self.assertEqual(3, len(el))
|
||||||
|
|
||||||
def testComplexStringSelector(self):
|
def testComplexStringSelector(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
|
|
||||||
v = c.vertices('(>X and >Y) or (<X and <Y)').vals()
|
v = c.vertices("(>X and >Y) or (<X and <Y)").vals()
|
||||||
self.assertEqual(4, len(v))
|
self.assertEqual(4, len(v))
|
||||||
|
|
||||||
def testFaceCount(self):
|
def testFaceCount(self):
|
||||||
@ -503,25 +501,27 @@ class TestCQSelectors(BaseTest):
|
|||||||
|
|
||||||
gram = selectors._expression_grammar
|
gram = selectors._expression_grammar
|
||||||
|
|
||||||
expressions = ['+X ',
|
expressions = [
|
||||||
'-Y',
|
"+X ",
|
||||||
'|(1,0,0)',
|
"-Y",
|
||||||
'#(1.,1.4114,-0.532)',
|
"|(1,0,0)",
|
||||||
'%Plane',
|
"#(1.,1.4114,-0.532)",
|
||||||
'>XZ',
|
"%Plane",
|
||||||
'<Z[-2]',
|
">XZ",
|
||||||
'>(1,4,55.)[20]',
|
"<Z[-2]",
|
||||||
'|XY',
|
">(1,4,55.)[20]",
|
||||||
'<YZ[0]',
|
"|XY",
|
||||||
'front',
|
"<YZ[0]",
|
||||||
'back',
|
"front",
|
||||||
'left',
|
"back",
|
||||||
'right',
|
"left",
|
||||||
'top',
|
"right",
|
||||||
'bottom',
|
"top",
|
||||||
'not |(1,1,0) and >(0,0,1) or XY except >(1,1,1)[-1]',
|
"bottom",
|
||||||
'(not |(1,1,0) and >(0,0,1)) exc XY and (Z or X)',
|
"not |(1,1,0) and >(0,0,1) or XY except >(1,1,1)[-1]",
|
||||||
'not ( <X or >X or <Y or >Y )']
|
"(not |(1,1,0) and >(0,0,1)) exc XY and (Z or X)",
|
||||||
|
"not ( <X or >X or <Y or >Y )",
|
||||||
|
]
|
||||||
|
|
||||||
for e in expressions:
|
for e in expressions:
|
||||||
gram.parseString(e, parseAll=True)
|
gram.parseString(e, parseAll=True)
|
@ -16,31 +16,30 @@ zInvAxis_ = Vector(0, 0, -1)
|
|||||||
|
|
||||||
|
|
||||||
class TestWorkplanes(BaseTest):
|
class TestWorkplanes(BaseTest):
|
||||||
|
|
||||||
def testYZPlaneOrigins(self):
|
def testYZPlaneOrigins(self):
|
||||||
# xy plane-- with origin at x=0.25
|
# xy plane-- with origin at x=0.25
|
||||||
base = Vector(0.25, 0, 0)
|
base = Vector(0.25, 0, 0)
|
||||||
p = Plane(base, Vector(0, 1, 0), Vector(1, 0, 0))
|
p = Plane(base, Vector(0, 1, 0), Vector(1, 0, 0))
|
||||||
|
|
||||||
# origin is always (0,0,0) in local coordinates
|
# origin is always (0,0,0) in local coordinates
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals((0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||||
(0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
|
||||||
|
|
||||||
# (0,0,0) is always the original base in global coordinates
|
# (0,0,0) is always the original base in global coordinates
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
base.toTuple(), p.toWorldCoords((0, 0)).toTuple(), 2)
|
base.toTuple(), p.toWorldCoords((0, 0)).toTuple(), 2
|
||||||
|
)
|
||||||
|
|
||||||
def testXYPlaneOrigins(self):
|
def testXYPlaneOrigins(self):
|
||||||
base = Vector(0, 0, 0.25)
|
base = Vector(0, 0, 0.25)
|
||||||
p = Plane(base, Vector(1, 0, 0), Vector(0, 0, 1))
|
p = Plane(base, Vector(1, 0, 0), Vector(0, 0, 1))
|
||||||
|
|
||||||
# origin is always (0,0,0) in local coordinates
|
# origin is always (0,0,0) in local coordinates
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals((0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||||
(0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
|
||||||
|
|
||||||
# (0,0,0) is always the original base in global coordinates
|
# (0,0,0) is always the original base in global coordinates
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2)
|
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2
|
||||||
|
)
|
||||||
|
|
||||||
def testXZPlaneOrigins(self):
|
def testXZPlaneOrigins(self):
|
||||||
base = Vector(0, 0.25, 0)
|
base = Vector(0, 0.25, 0)
|
||||||
@ -48,33 +47,39 @@ class TestWorkplanes(BaseTest):
|
|||||||
|
|
||||||
# (0,0,0) is always the original base in global coordinates
|
# (0,0,0) is always the original base in global coordinates
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2)
|
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2
|
||||||
|
)
|
||||||
|
|
||||||
# origin is always (0,0,0) in local coordinates
|
# origin is always (0,0,0) in local coordinates
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals((0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||||
(0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
|
||||||
|
|
||||||
def testPlaneBasics(self):
|
def testPlaneBasics(self):
|
||||||
p = Plane.XY()
|
p = Plane.XY()
|
||||||
# local to world
|
# local to world
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
(1.0, 1.0, 0), p.toWorldCoords((1, 1)).toTuple(), 2)
|
(1.0, 1.0, 0), p.toWorldCoords((1, 1)).toTuple(), 2
|
||||||
|
)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
(-1.0, -1.0, 0), p.toWorldCoords((-1, -1)).toTuple(), 2)
|
(-1.0, -1.0, 0), p.toWorldCoords((-1, -1)).toTuple(), 2
|
||||||
|
)
|
||||||
|
|
||||||
# world to local
|
# world to local
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
(-1.0, -1.0), p.toLocalCoords(Vector(-1, -1, 0)).toTuple(), 2)
|
(-1.0, -1.0), p.toLocalCoords(Vector(-1, -1, 0)).toTuple(), 2
|
||||||
|
)
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
(1.0, 1.0), p.toLocalCoords(Vector(1, 1, 0)).toTuple(), 2)
|
(1.0, 1.0), p.toLocalCoords(Vector(1, 1, 0)).toTuple(), 2
|
||||||
|
)
|
||||||
|
|
||||||
p = Plane.YZ()
|
p = Plane.YZ()
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
(0, 1.0, 1.0), p.toWorldCoords((1, 1)).toTuple(), 2)
|
(0, 1.0, 1.0), p.toWorldCoords((1, 1)).toTuple(), 2
|
||||||
|
)
|
||||||
|
|
||||||
# world to local
|
# world to local
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
(1.0, 1.0), p.toLocalCoords(Vector(0, 1, 1)).toTuple(), 2)
|
(1.0, 1.0), p.toLocalCoords(Vector(0, 1, 1)).toTuple(), 2
|
||||||
|
)
|
||||||
|
|
||||||
p = Plane.XZ()
|
p = Plane.XZ()
|
||||||
r = p.toWorldCoords((1, 1)).toTuple()
|
r = p.toWorldCoords((1, 1)).toTuple()
|
||||||
@ -82,62 +87,68 @@ class TestWorkplanes(BaseTest):
|
|||||||
|
|
||||||
# world to local
|
# world to local
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
(1.0, 1.0), p.toLocalCoords(Vector(1, 0, 1)).toTuple(), 2)
|
(1.0, 1.0), p.toLocalCoords(Vector(1, 0, 1)).toTuple(), 2
|
||||||
|
)
|
||||||
|
|
||||||
def testOffsetPlanes(self):
|
def testOffsetPlanes(self):
|
||||||
"Tests that a plane offset from the origin works ok too"
|
"Tests that a plane offset from the origin works ok too"
|
||||||
p = Plane.XY(origin=(10.0, 10.0, 0))
|
p = Plane.XY(origin=(10.0, 10.0, 0))
|
||||||
|
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
(11.0, 11.0, 0.0), p.toWorldCoords((1.0, 1.0)).toTuple(), 2)
|
(11.0, 11.0, 0.0), p.toWorldCoords((1.0, 1.0)).toTuple(), 2
|
||||||
self.assertTupleAlmostEquals((2.0, 2.0), p.toLocalCoords(
|
)
|
||||||
Vector(12.0, 12.0, 0)).toTuple(), 2)
|
self.assertTupleAlmostEquals(
|
||||||
|
(2.0, 2.0), p.toLocalCoords(Vector(12.0, 12.0, 0)).toTuple(), 2
|
||||||
|
)
|
||||||
|
|
||||||
# TODO test these offsets in the other dimensions too
|
# TODO test these offsets in the other dimensions too
|
||||||
p = Plane.YZ(origin=(0, 2, 2))
|
p = Plane.YZ(origin=(0, 2, 2))
|
||||||
self.assertTupleAlmostEquals(
|
self.assertTupleAlmostEquals(
|
||||||
(0.0, 5.0, 5.0), p.toWorldCoords((3.0, 3.0)).toTuple(), 2)
|
(0.0, 5.0, 5.0), p.toWorldCoords((3.0, 3.0)).toTuple(), 2
|
||||||
self.assertTupleAlmostEquals((10, 10.0, 0.0), p.toLocalCoords(
|
)
|
||||||
Vector(0.0, 12.0, 12.0)).toTuple(), 2)
|
self.assertTupleAlmostEquals(
|
||||||
|
(10, 10.0, 0.0), p.toLocalCoords(Vector(0.0, 12.0, 12.0)).toTuple(), 2
|
||||||
|
)
|
||||||
|
|
||||||
p = Plane.XZ(origin=(2, 0, 2))
|
p = Plane.XZ(origin=(2, 0, 2))
|
||||||
r = p.toWorldCoords((1.0, 1.0)).toTuple()
|
r = p.toWorldCoords((1.0, 1.0)).toTuple()
|
||||||
self.assertTupleAlmostEquals((3.0, 0.0, 3.0), r, 2)
|
self.assertTupleAlmostEquals((3.0, 0.0, 3.0), r, 2)
|
||||||
self.assertTupleAlmostEquals((10.0, 10.0), p.toLocalCoords(
|
self.assertTupleAlmostEquals(
|
||||||
Vector(12.0, 0.0, 12.0)).toTuple(), 2)
|
(10.0, 10.0), p.toLocalCoords(Vector(12.0, 0.0, 12.0)).toTuple(), 2
|
||||||
|
)
|
||||||
|
|
||||||
def testXYPlaneBasics(self):
|
def testXYPlaneBasics(self):
|
||||||
p = Plane.named('XY')
|
p = Plane.named("XY")
|
||||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), zAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.zDir.toTuple(), zAxis_.toTuple(), 4)
|
||||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4)
|
||||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4)
|
||||||
|
|
||||||
def testYZPlaneBasics(self):
|
def testYZPlaneBasics(self):
|
||||||
p = Plane.named('YZ')
|
p = Plane.named("YZ")
|
||||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), xAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.zDir.toTuple(), xAxis_.toTuple(), 4)
|
||||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4)
|
||||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4)
|
||||||
|
|
||||||
def testZXPlaneBasics(self):
|
def testZXPlaneBasics(self):
|
||||||
p = Plane.named('ZX')
|
p = Plane.named("ZX")
|
||||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), yAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.zDir.toTuple(), yAxis_.toTuple(), 4)
|
||||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4)
|
||||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4)
|
||||||
|
|
||||||
def testXZPlaneBasics(self):
|
def testXZPlaneBasics(self):
|
||||||
p = Plane.named('XZ')
|
p = Plane.named("XZ")
|
||||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), yInvAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.zDir.toTuple(), yInvAxis_.toTuple(), 4)
|
||||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4)
|
||||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4)
|
||||||
|
|
||||||
def testYXPlaneBasics(self):
|
def testYXPlaneBasics(self):
|
||||||
p = Plane.named('YX')
|
p = Plane.named("YX")
|
||||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), zInvAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.zDir.toTuple(), zInvAxis_.toTuple(), 4)
|
||||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4)
|
||||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4)
|
||||||
|
|
||||||
def testZYPlaneBasics(self):
|
def testZYPlaneBasics(self):
|
||||||
p = Plane.named('ZY')
|
p = Plane.named("ZY")
|
||||||
self.assertTupleAlmostEquals(p.zDir.toTuple(), xInvAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.zDir.toTuple(), xInvAxis_.toTuple(), 4)
|
||||||
self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4)
|
||||||
self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4)
|
self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4)
|
Reference in New Issue
Block a user