mirror of
https://github.com/Ultimaker/Cura.git
synced 2026-02-15 08:59:32 -07:00
Have the projection of the paint-stroke involve the camera after all.
This fixes the issue that it wouldn't flood over the 'rim' whenever the endpoints of a stroke was wholly within one plane, but the stroke itself would overlapp another. Plus also handle click on single point and use transformed mesh consistantly. part of CURA-12662
This commit is contained in:
parent
392e614887
commit
b091cc23a1
1 changed files with 36 additions and 35 deletions
|
|
@ -12,6 +12,7 @@ from UM.Event import Event, MouseEvent
|
|||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.Polygon import Polygon
|
||||
from UM.Mesh.MeshData import MeshData
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Tool import Tool
|
||||
|
|
@ -242,7 +243,7 @@ class PaintTool(Tool):
|
|||
def _nodeTransformChanged(self, *args) -> None:
|
||||
self._cache_dirty = True
|
||||
|
||||
def _getCoordsFromClick(self, node: SceneNode, x: float, y: float) -> Tuple[int, Optional[numpy.ndarray], Optional[numpy.ndarray]]:
|
||||
def _getCoordsFromClick(self, mesh: MeshData, x: float, y: float) -> Tuple[int, Optional[numpy.ndarray], Optional[numpy.ndarray]]:
|
||||
""" Retrieves coordinates based on a user's click on a 3D scene node.
|
||||
|
||||
This function calculates and returns the face identifier, texture coordinates, and real-world coordinates
|
||||
|
|
@ -256,14 +257,14 @@ class PaintTool(Tool):
|
|||
|
||||
face_id = self._faces_selection_pass.getFaceIdAtPosition(x, y)
|
||||
|
||||
if face_id < 0 or face_id >= node.getMeshData().getFaceCount():
|
||||
if face_id < 0 or face_id >= mesh.getFaceCount():
|
||||
return face_id, None, None
|
||||
|
||||
pt = self._picking_pass.getPickedPosition(x, y).getData()
|
||||
|
||||
va, vb, vc = self._mesh_transformed_cache.getFaceNodes(face_id)
|
||||
|
||||
face_uv_coordinates = node.getMeshData().getFaceUvCoords(face_id)
|
||||
face_uv_coordinates = mesh.getFaceUvCoords(face_id)
|
||||
if face_uv_coordinates is None:
|
||||
return face_id, None, None
|
||||
ta, tb, tc = face_uv_coordinates
|
||||
|
|
@ -307,12 +308,12 @@ class PaintTool(Tool):
|
|||
return shape.translate(stroke_a[0], stroke_a[1]).unionConvexHulls(shape.translate(stroke_b[0], stroke_b[1]))
|
||||
|
||||
# NOTE: Currently, it's unclear how well this would work for non-convex brush-shapes.
|
||||
def _getUvAreasForStroke(self, node: SceneNode, face_id_a: int, face_id_b: int, world_coords_a: numpy.ndarray, world_coords_b: numpy.ndarray) -> List[Polygon]:
|
||||
""" Fetches all texture-coordinate areas within the provided stroke on the mesh (of the given node).
|
||||
def _getUvAreasForStroke(self, mesh: MeshData, face_id_a: int, face_id_b: int, world_coords_a: numpy.ndarray, world_coords_b: numpy.ndarray) -> List[Polygon]:
|
||||
""" Fetches all texture-coordinate areas within the provided stroke on the mesh.
|
||||
|
||||
Calculates intersections of the stroke with the surface of the geometry and maps them to UV-space polygons.
|
||||
|
||||
:param node: The 3D scene node containing mesh data to evaluate.
|
||||
:param mesh: The 3D mesh data, pre-transformed.
|
||||
:param face_id_a: ID of the face where the stroke starts.
|
||||
:param face_id_b: ID of the face where the stroke ends.
|
||||
:param world_coords_a: 3D ('world') coordinates corresponding to the starting stroke point.
|
||||
|
|
@ -320,26 +321,25 @@ class PaintTool(Tool):
|
|||
:return: A list of UV-mapped polygons representing areas intersected by the stroke on the node's mesh surface.
|
||||
"""
|
||||
|
||||
if (face_id_a == face_id_b) and (world_coords_a == world_coords_b).all():
|
||||
# TODO: this doesn't work yet...
|
||||
mid, norm = self._mesh_transformed_cache.getFacePlane(face_id_a)
|
||||
norm /= numpy.linalg.norm(norm)
|
||||
perp = mid.cross(world_coords_a - mid)
|
||||
perp /= numpy.linalg.norm(perp)
|
||||
vec_ab = norm.cross(perp)
|
||||
vec_ab /= numpy.linalg.norm(vec_ab)
|
||||
else:
|
||||
vec_ab = world_coords_b - world_coords_a
|
||||
_, norm_a = self._mesh_transformed_cache.getFacePlane(face_id_a)
|
||||
_, norm_b = self._mesh_transformed_cache.getFacePlane(face_id_b)
|
||||
norm = (norm_a + norm_b) * 0.5
|
||||
norm /= numpy.linalg.norm(norm)
|
||||
perp = numpy.cross(norm, vec_ab)
|
||||
camera = Application.getInstance().getController().getScene().getActiveCamera()
|
||||
cam_ray = camera.getRay(0, 0)
|
||||
cam_norm = cam_ray.direction.getData()
|
||||
cam_norm /= numpy.linalg.norm(cam_norm)
|
||||
|
||||
if (world_coords_a == world_coords_b).all():
|
||||
world_coords_b = world_coords_a + numpy.array([0.01, -0.01, 0.01])
|
||||
|
||||
vec_ab = world_coords_b - world_coords_a
|
||||
stroke_dir = vec_ab / numpy.linalg.norm(vec_ab)
|
||||
norm = -cam_norm
|
||||
norm /= numpy.linalg.norm(norm)
|
||||
perp = numpy.cross(norm, stroke_dir)
|
||||
perp /= numpy.linalg.norm(perp)
|
||||
|
||||
def get_projected_on_plane(pt: numpy.ndarray) -> numpy.ndarray:
|
||||
proj_pt = (pt - numpy.dot(norm, pt - world_coords_a) * norm) - world_coords_a
|
||||
x_coord = numpy.dot(vec_ab, proj_pt)
|
||||
y_coord = numpy.dot(perp, proj_pt)
|
||||
y_coord = numpy.dot(stroke_dir, proj_pt)
|
||||
x_coord = numpy.dot(perp, proj_pt)
|
||||
return numpy.array([x_coord, y_coord])
|
||||
|
||||
def get_tri_in_stroke(a: numpy.ndarray, b: numpy.ndarray, c: numpy.ndarray) -> Polygon:
|
||||
|
|
@ -349,27 +349,24 @@ class PaintTool(Tool):
|
|||
get_projected_on_plane(c)])
|
||||
|
||||
def remap_polygon_to_uv(original_tri: Polygon, poly: Polygon, face_id: int) -> Polygon:
|
||||
face_uv_coordinates = node.getMeshData().getFaceUvCoords(face_id)
|
||||
face_uv_coordinates = mesh.getFaceUvCoords(face_id)
|
||||
if face_uv_coordinates is None:
|
||||
return Polygon()
|
||||
ta, tb, tc = face_uv_coordinates
|
||||
original_uv_poly = Polygon([ta, tb, tc])
|
||||
return poly.map(lambda pt: PaintTool._remapBarycentric(original_tri, pt, original_uv_poly))
|
||||
|
||||
stroke_len = numpy.linalg.norm(vec_ab)
|
||||
|
||||
uv_a0, uv_a1, _ = node.getMeshData().getFaceUvCoords(face_id_a)
|
||||
w_a0, w_a1, _ = node.getMeshData().getFaceNodes(face_id_a)
|
||||
w_scale = node.getScale().getData()
|
||||
world_to_uv_size_factor = numpy.linalg.norm(uv_a1 - uv_a0) / numpy.linalg.norm(w_a1 * w_scale - w_a0 * w_scale)
|
||||
uv_a0, uv_a1, _ = mesh.getFaceUvCoords(face_id_a)
|
||||
w_a0, w_a1, _ = mesh.getFaceNodes(face_id_a)
|
||||
world_to_uv_size_factor = numpy.linalg.norm(uv_a1 - uv_a0) / numpy.linalg.norm(w_a1 - w_a0)
|
||||
|
||||
stroke_poly = self._getStrokePolygon(
|
||||
stroke_len * world_to_uv_size_factor,
|
||||
world_to_uv_size_factor,
|
||||
get_projected_on_plane(world_coords_a),
|
||||
get_projected_on_plane(world_coords_b))
|
||||
|
||||
def get_stroke_intersect_with_tri(face_id: int) -> Polygon:
|
||||
va, vb, vc = self._mesh_transformed_cache.getFaceNodes(face_id)
|
||||
va, vb, vc = mesh.getFaceNodes(face_id)
|
||||
stroke_tri = get_tri_in_stroke(va, vb, vc)
|
||||
return remap_polygon_to_uv(stroke_tri, stroke_poly.intersection(stroke_tri), face_id)
|
||||
|
||||
|
|
@ -380,11 +377,15 @@ class PaintTool(Tool):
|
|||
def add_adjacent_candidates(face_id: int) -> None:
|
||||
[candidates.add(x) for x in self._mesh_transformed_cache.getFaceNeighbourIDs(face_id)]
|
||||
|
||||
def wrong_face_normal(face_id: int) -> bool:
|
||||
_, fnorm = mesh.getFacePlane(face_id)
|
||||
return numpy.dot(fnorm, norm) < 0
|
||||
|
||||
res = []
|
||||
seen = set()
|
||||
while candidates:
|
||||
candidate = candidates.pop()
|
||||
if candidate in seen or candidate < 0:
|
||||
if candidate in seen or candidate < 0 or wrong_face_normal(candidate):
|
||||
continue
|
||||
uv_area = get_stroke_intersect_with_tri(candidate)
|
||||
if not uv_area.isValid():
|
||||
|
|
@ -469,14 +470,14 @@ class PaintTool(Tool):
|
|||
if not self._mesh_transformed_cache:
|
||||
return False
|
||||
|
||||
face_id, _, world_coords = self._getCoordsFromClick(node, mouse_evt.x, mouse_evt.y)
|
||||
face_id, _, world_coords = self._getCoordsFromClick(self._mesh_transformed_cache, mouse_evt.x, mouse_evt.y)
|
||||
if face_id < 0:
|
||||
return False
|
||||
if self._last_world_coords is None:
|
||||
self._last_world_coords = world_coords
|
||||
self._last_face_id = face_id
|
||||
|
||||
uv_areas = self._getUvAreasForStroke(node, self._last_face_id, face_id, self._last_world_coords, world_coords)
|
||||
uv_areas = self._getUvAreasForStroke(self._mesh_transformed_cache, self._last_face_id, face_id, self._last_world_coords, world_coords)
|
||||
if len(uv_areas) == 0:
|
||||
return False
|
||||
stroke_img, (start_x, start_y) = self._createStrokeImage(uv_areas)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue