From 14762356659dfc39cd9386c2dc4b8db169b06b84 Mon Sep 17 00:00:00 2001 From: TimStrauven <96440176+TimStrauven@users.noreply.github.com> Date: Thu, 25 Apr 2024 08:51:18 +0200 Subject: [PATCH] Checks for duplicate constraints. (#461) * Checks for duplicate constraints. On constraint creation checks are added to see if it exists on the selected entity or entitiies. The exists method was added to GenericConstraintOp for this. Line distances have been replaced for point to point distances. * Flake8 cleanups * Simplify exists method and version changes Simplified the exists method in base_constraint not to do specific checks and moved this logic into the add_distance operator. Added version check for older drawings. This updates all distance constraints on a line to the endpoints of that line. Changed addon version from 0.27.3 to 0.27.4 * Removed nested if statements Added extra check on length of line_entities to avoid errors if it would somehow be out of bounds --- __init__.py | 2 +- blender_manifest.toml | 2 +- operators/add_angle.py | 17 ++-- operators/add_diameter.py | 15 ++-- operators/add_distance.py | 43 ++++++++-- operators/add_geometric_constraints.py | 112 ++++++++++++++----------- operators/base_constraint.py | 19 ++++- versioning.py | 24 ++++++ 8 files changed, 162 insertions(+), 72 deletions(-) diff --git a/__init__.py b/__init__.py index 0d1f6d53..7a3d8693 100644 --- a/__init__.py +++ b/__init__.py @@ -7,7 +7,7 @@ bl_info = { "name": "CAD Sketcher", "author": "hlorus", - "version": (0, 27, 3), + "version": (0, 27, 4), "blender": (3, 3, 0), "location": "View3D > Toolbar", "description": "Parametric, constraint-based geometry sketcher", diff --git a/blender_manifest.toml b/blender_manifest.toml index 97b8e9f4..0f62ef68 100644 --- a/blender_manifest.toml +++ b/blender_manifest.toml @@ -1,7 +1,7 @@ schema_version = "1.0.0" id = "CAD_Sketcher" -version = "0.27.3" +version = "0.27.4" name = "CAD Sketcher" tagline = "Parametric, constraint-based geometry sketcher" maintainer = "hlorus " diff --git a/operators/add_angle.py b/operators/add_angle.py index 13760182..6740a3b9 100644 --- a/operators/add_angle.py +++ b/operators/add_angle.py @@ -9,6 +9,8 @@ from ..stateful_operator.utilities.register import register_stateops_factory from .base_constraint import GenericConstraintOp +from ..model.angle import SlvsAngle + logger = logging.getLogger(__name__) @@ -45,13 +47,14 @@ class VIEW3D_OT_slvs_add_angle(Operator, GenericConstraintOp): property_keys = ("value", "setting") def main(self, context): - self.target = context.scene.sketcher.constraints.add_angle( - self.entity1, - self.entity2, - sketch=self.sketch, - init=not self.initialized, - **self.get_settings() - ) + if not self.exists(context, SlvsAngle): + self.target = context.scene.sketcher.constraints.add_angle( + self.entity1, + self.entity2, + sketch=self.sketch, + init=not self.initialized, + **self.get_settings() + ) return super().main(context) diff --git a/operators/add_diameter.py b/operators/add_diameter.py index 3a0b18d9..3393a386 100644 --- a/operators/add_diameter.py +++ b/operators/add_diameter.py @@ -7,6 +7,8 @@ from ..stateful_operator.utilities.register import register_stateops_factory from .base_constraint import GenericConstraintOp +from ..model.diameter import SlvsDiameter + logger = logging.getLogger(__name__) @@ -30,12 +32,13 @@ class VIEW3D_OT_slvs_add_diameter(Operator, GenericConstraintOp): property_keys = ("value", "setting") def main(self, context): - self.target = context.scene.sketcher.constraints.add_diameter( - self.entity1, - sketch=self.sketch, - init=not self.initialized, - **self.get_settings(), - ) + if not self.exists(context, SlvsDiameter): + self.target = context.scene.sketcher.constraints.add_diameter( + self.entity1, + sketch=self.sketch, + init=not self.initialized, + **self.get_settings(), + ) return super().main(context) diff --git a/operators/add_distance.py b/operators/add_distance.py index 6aa7df99..b426dd65 100644 --- a/operators/add_distance.py +++ b/operators/add_distance.py @@ -8,6 +8,11 @@ from ..declarations import Operators from ..stateful_operator.utilities.register import register_stateops_factory +from ..model.distance import SlvsDistance +from ..model.line_2d import SlvsLine2D +from ..model.point_2d import SlvsPoint2D +from ..model.types import SlvsPoint3D +from ..model.types import SlvsLine3D logger = logging.getLogger(__name__) @@ -33,13 +38,37 @@ class VIEW3D_OT_slvs_add_distance(Operator, GenericConstraintOp): property_keys = ("value", "align", "flip") def main(self, context): - self.target = context.scene.sketcher.constraints.add_distance( - self.entity1, - self.entity2, - sketch=self.sketch, - init=not self.initialized, - **self.get_settings(), - ) + if isinstance(self.entity1, SlvsLine2D) and self.entity2 is None: + dependencies = self.entity1.dependencies() + if (isinstance(dependencies[0], SlvsPoint2D) and + isinstance(dependencies[1], SlvsPoint2D)): + # for loop changes the values of self.entity1 and self.entity2 + # from a line entity to its endpoints + for i in range(0, 2): + state_data = self.get_state_data(i) + state_data["hovered"] = -1 + state_data["type"] = type(dependencies[i]) + state_data["is_existing_entity"] = True + state_data["entity_index"] = dependencies[i].slvs_index + self.next_state(context) # end user selection, no need for second entity + + if (isinstance(self.entity1, (SlvsPoint3D, SlvsLine3D)) or + isinstance(self.entity2, (SlvsPoint3D, SlvsLine3D))): + max_constraints = 3 + elif ((isinstance(self.entity1, SlvsLine2D) and self.entity2 is None) or + isinstance(self.entity1, SlvsPoint2D) and isinstance(self.entity2, SlvsPoint2D)): + max_constraints = 2 + else: + max_constraints = 1 + + if not self.exists(context, SlvsDistance, max_constraints): + self.target = context.scene.sketcher.constraints.add_distance( + self.entity1, + self.entity2, + sketch=self.sketch, + init=not self.initialized, + **self.get_settings(), + ) return super().main(context) def fini(self, context: Context, succeede: bool): diff --git a/operators/add_geometric_constraints.py b/operators/add_geometric_constraints.py index 3306878c..d3487e21 100644 --- a/operators/add_geometric_constraints.py +++ b/operators/add_geometric_constraints.py @@ -12,6 +12,15 @@ from ..utilities.view import refresh from ..solver import solve_system +from ..model.coincident import SlvsCoincident +from ..model.equal import SlvsEqual +from ..model.vertical import SlvsVertical +from ..model.horizontal import SlvsHorizontal +from ..model.parallel import SlvsParallel +from ..model.perpendicular import SlvsPerpendicular +from ..model.tangent import SlvsTangent +from ..model.midpoint import SlvsMidpoint +from ..model.ratio import SlvsRatio logger = logging.getLogger(__name__) @@ -52,11 +61,12 @@ def main(self, context: Context): if self.handle_merge(context): return True - self.target = context.scene.sketcher.constraints.add_coincident( - self.entity1, - self.entity2, - sketch=self.sketch, - ) + if not self.exists(context, SlvsCoincident): + self.target = context.scene.sketcher.constraints.add_coincident( + self.entity1, + self.entity2, + sketch=self.sketch, + ) return super().main(context) @@ -70,11 +80,12 @@ class VIEW3D_OT_slvs_add_equal(Operator, GenericConstraintOp): type = "EQUAL" def main(self, context): - self.target = context.scene.sketcher.constraints.add_equal( - self.entity1, - self.entity2, - sketch=self.sketch, - ) + if not self.exists(context, SlvsEqual): + self.target = context.scene.sketcher.constraints.add_equal( + self.entity1, + self.entity2, + sketch=self.sketch, + ) return super().main(context) @@ -89,11 +100,12 @@ class VIEW3D_OT_slvs_add_vertical(Operator, GenericConstraintOp): type = "VERTICAL" def main(self, context): - self.target = context.scene.sketcher.constraints.add_vertical( - self.entity1, - entity2=self.entity2, - sketch=self.sketch, - ) + if not self.exists(context, SlvsVertical): + self.target = context.scene.sketcher.constraints.add_vertical( + self.entity1, + entity2=self.entity2, + sketch=self.sketch, + ) return super().main(context) @@ -108,11 +120,12 @@ class VIEW3D_OT_slvs_add_horizontal(Operator, GenericConstraintOp): type = "HORIZONTAL" def main(self, context): - self.target = context.scene.sketcher.constraints.add_horizontal( - self.entity1, - entity2=self.entity2, - sketch=self.sketch, - ) + if not self.exists(context, SlvsHorizontal): + self.target = context.scene.sketcher.constraints.add_horizontal( + self.entity1, + entity2=self.entity2, + sketch=self.sketch, + ) return super().main(context) @@ -127,11 +140,12 @@ class VIEW3D_OT_slvs_add_parallel(Operator, GenericConstraintOp): type = "PARALLEL" def main(self, context): - self.target = context.scene.sketcher.constraints.add_parallel( - self.entity1, - self.entity2, - sketch=self.sketch, - ) + if not self.exists(context, SlvsParallel): + self.target = context.scene.sketcher.constraints.add_parallel( + self.entity1, + self.entity2, + sketch=self.sketch, + ) return super().main(context) @@ -146,11 +160,12 @@ class VIEW3D_OT_slvs_add_perpendicular(Operator, GenericConstraintOp): type = "PERPENDICULAR" def main(self, context): - self.target = context.scene.sketcher.constraints.add_perpendicular( - self.entity1, - self.entity2, - sketch=self.sketch, - ) + if not self.exists(context, SlvsPerpendicular): + self.target = context.scene.sketcher.constraints.add_perpendicular( + self.entity1, + self.entity2, + sketch=self.sketch, + ) return super().main(context) @@ -165,11 +180,12 @@ class VIEW3D_OT_slvs_add_tangent(Operator, GenericConstraintOp): type = "TANGENT" def main(self, context): - self.target = context.scene.sketcher.constraints.add_tangent( - self.entity1, - self.entity2, - sketch=self.sketch, - ) + if not self.exists(context, SlvsTangent): + self.target = context.scene.sketcher.constraints.add_tangent( + self.entity1, + self.entity2, + sketch=self.sketch, + ) return super().main(context) @@ -184,11 +200,12 @@ class VIEW3D_OT_slvs_add_midpoint(Operator, GenericConstraintOp): type = "MIDPOINT" def main(self, context): - self.target = context.scene.sketcher.constraints.add_midpoint( - self.entity1, - self.entity2, - sketch=self.sketch, - ) + if not self.exists(context, SlvsMidpoint): + self.target = context.scene.sketcher.constraints.add_midpoint( + self.entity1, + self.entity2, + sketch=self.sketch, + ) return super().main(context) @@ -211,13 +228,14 @@ class VIEW3D_OT_slvs_add_ratio(Operator, GenericConstraintOp): property_keys = ("value",) def main(self, context): - self.target = context.scene.sketcher.constraints.add_ratio( - self.entity1, - self.entity2, - sketch=self.sketch, - init=not self.initialized, - **self.get_settings(), - ) + if not self.exists(context, SlvsRatio): + self.target = context.scene.sketcher.constraints.add_ratio( + self.entity1, + self.entity2, + sketch=self.sketch, + init=not self.initialized, + **self.get_settings(), + ) return super().main(context) diff --git a/operators/base_constraint.py b/operators/base_constraint.py index 49ee73bf..05044257 100644 --- a/operators/base_constraint.py +++ b/operators/base_constraint.py @@ -10,9 +10,6 @@ from ..utilities.select import deselect_all from ..utilities.view import refresh from .base_2d import Operator2d -from ..utilities.select import deselect_all -from ..utilities.view import refresh -from ..solver import solve_system logger = logging.getLogger(__name__) @@ -124,3 +121,19 @@ def draw(self, context: Context): for key in self.property_keys: layout.prop(self, key) + + def exists(self, context, constraint_type=None, max_constraints=1) -> bool: + if hasattr(self, "entity2"): + new_dependencies = [i for i in [self.entity1, self.entity2, self.sketch] if i is not None] + else: + new_dependencies = [i for i in [self.entity1, self.sketch] if i is not None] + + constraint_counter = 0 + for c in context.scene.sketcher.constraints.all: + if isinstance(c, constraint_type): + if set(c.dependencies()) == set(new_dependencies): + constraint_counter += 1 + if constraint_counter >= max_constraints: + return True + + return False diff --git a/versioning.py b/versioning.py index f3e3a2af..8b857362 100644 --- a/versioning.py +++ b/versioning.py @@ -101,4 +101,28 @@ def do_versioning(self): ) c.is_reference = True + if version < (0, 27, 4): + # update distance constraints on only a line + # to distance constraints on the endpoints of that line. + from .model.line_2d import SlvsLine2D + from .model.distance import SlvsDistance + from .model.point_2d import SlvsPoint2D + from .model.sketch import SlvsSketch + + for c in context.scene.sketcher.constraints.dimensional: + if len(c.dependencies()) != 2: + continue + if not (isinstance(c, SlvsDistance) and + isinstance(c.dependencies()[0], SlvsLine2D) and + isinstance(c.dependencies()[1], SlvsSketch)): + continue + + line_dependencies = c.dependencies()[0].dependencies() + if len(line_dependencies) != 3: + continue + if (isinstance(line_dependencies[0], SlvsPoint2D) and + isinstance(line_dependencies[1], SlvsPoint2D)): + setattr(c, "entity1", line_dependencies[0]) + setattr(c, "entity2", line_dependencies[1]) + logger.debug(msg)